<commit_before>package db

import (
	"bytes"
	"database/sql"
	"fmt"
	"io/ioutil"
	"log"
	"strings"

	"github.com/remeh/mehtadata/model"

	_ "github.com/mattn/go-sqlite3"
)

func InitSchema(database, filename string) (bool, error) {

	// database
	// ----------------------

	var err error

	db, err := sql.Open("sqlite3", database)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// read the schema
	// ----------------------

	var data []byte

	if data, err = ioutil.ReadFile(filename); err != nil {
		return false, err
	}

	// remove all line return

	data = bytes.Replace(data, []byte("\n"), []byte(" "), -1)
	data = bytes.Replace(data, []byte("\r"), []byte(" "), -1) // windows user having edited the file

	queries := strings.Split(string(data), ";")

	// prepares the transaction
	// ----------------------

	tx, err := db.Begin()
	if err != nil {
		return false, err
	}

	// execute each line
	// ----------------------

	for _, query := range queries {
		if _, err := tx.Exec(query); err != nil {
			return false, err
		}
	}

	// commit
	// ----------------------

	err = tx.Commit()

	return err == nil, err
}

// CreatePlatform creates an empty platform in the given sqlite database.
// Returns the ID of the newly created platform.
func CreatePlatform(database string, platform model.Platform) (int64, bool, error) {

	// database
	// ----------------------

	db, err := sql.Open("sqlite3", database)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// ensure that no platform with the same name
	// is already existing
	// ----------------------

	var i int
	if err := db.QueryRow(`
		SELECT count(*)
		FROM "platform"
		WHERE
			name = ?
	`, platform.Name).Scan(&i); err != nil {
		return -1, false, err
	}

	exists := false
	if i >= 1 {
		exists = true
	}

	// executes the query
	// ----------------------

	var result sql.Result

	if !exists {
		result, err = db.Exec(`insert into "platform" ("name", "command", "icon", "background", "type", "discover_dir", "discover_ext") values(?, ?, ?, ?, ?, ?, ?)`, platform.Name, platform.Command, platform.Icon, platform.Background, platform.Type, platform.DiscoverDir, platform.DiscoverExts)
	} else {
		result, err = db.Exec(`
			UPDATE "platform"
			SET
				"command" = ?,
				"icon" = ?,
				"background" = ?,
				"type" = ?,
				"discover_dir" = ?,
				"discover_ext" = ?
			WHERE
				"name" = ?
		`, platform.Command, platform.Icon, platform.Background, platform.Type, platform.DiscoverDir, platform.DiscoverExts, platform.Name)
	}

	if err != nil {
		return -1, false, fmt.Errorf("[err] Can't create a new platform %s in the DB: %s", platform.Name, err.Error())
	}

	id, err := result.LastInsertId()
	if err != nil {
		return -1, false, fmt.Errorf("[err] Can't create a new platform %s in the DB (id retrieving): %s", platform.Name, err.Error())
	}

	return id, exists, nil
}

// DeletePlatform deletes the given platform by its name and
// all its executables.
func DeletePlatform(database, platformName string) (bool, error) {

	// database
	// ----------------------

	var err error

	db, err := sql.Open("sqlite3", database)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// ensure that the platform with the given name exists
	// and retrieve its ID.
	// ----------------------

	var platformId int
	if err := db.QueryRow(`
		SELECT "id"
		FROM "platform"
		WHERE
			name = ?
	`, platformName).Scan(&platformId); err == sql.ErrNoRows {
		return false, fmt.Errorf("Unknown platform")
	} else if err != nil {
		return false, err
	}

	// delete all its executables
	// ----------------------

	var result sql.Result

	if result, err = db.Exec(`
		DELETE FROM "executable"
		WHERE
			"platform_id" = ?
	`, platformId); err != nil {
		return false, err
	}

	// delete the platform
	// ----------------------

	if result, err = db.Exec(`
		DELETE FROM "platform"
		WHERE
			"id" = ?
	`, platformId); err != nil {
		return false, err
	}

	// checks the deletion
	// ----------------------

	c, err := result.RowsAffected()
	if err != nil {
		return false, fmt.Errorf("while checking for delection: %v", err.Error())
	}

	return c == 1, nil
}

// DeleteExecutable deletes the executable from the database.
func DeleteExecutable(database, platformName, filepath string) (bool, error) {

	// database
	// ----------------------

	var err error

	db, err := sql.Open("sqlite3", database)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// ensure that the platform with the given name exists
	// and retrieve its ID.
	// ----------------------

	var platformId int
	if err := db.QueryRow(`
		SELECT "id"
		FROM "platform"
		WHERE
			name = ?
	`, platformName).Scan(&platformId); err == sql.ErrNoRows {
		return false, fmt.Errorf("Unknown platform")
	} else if err != nil {
		return false, err
	}

	// delete the executable
	// ----------------------

	var result sql.Result

	if result, err = db.Exec(`
		DELETE FROM "executable"
		WHERE
			"filepath" = ?
			AND
			"platform_id" = ?
	`, filepath, platformId); err != nil {
		return false, err
	}

	// checks the deletion
	// ----------------------

	c, err := result.RowsAffected()
	if err != nil {
		return false, fmt.Errorf("while checking for delection: %v", err.Error())
	}

	return c >= 1, nil
}

// CreateExecutable creates the executable in the database.
// Returns the ID of the created executable.
func CreateExecutable(database string, platformName string, executable model.Executable) (int64, bool, error) {

	// database
	// ----------------------

	db, err := sql.Open("sqlite3", database)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// ensure that the platform with the given name exists
	// and retrieve its ID.
	// ----------------------

	var platformId int
	if err := db.QueryRow(`
		SELECT "id"
		FROM "platform"
		WHERE
			name = ?
	`, platformName).Scan(&platformId); err == sql.ErrNoRows {
		return -1, false, fmt.Errorf("Unknown platform")
	} else if err != nil {
		return -1, false, err
	}

	// checks whether this executable already exists
	// ----------------------

	var c int
	if err := db.QueryRow(`
		SELECT COUNT(*)
		FROM "executable"
		JOIN "platform"
			ON "platform"."id" = "executable"."platform_id"
		WHERE
			"platform"."name" = ?
			AND
			"executable"."filepath" = ?
	`, platformName, executable.Filepath).Scan(&c); err != nil {
		return -1, false, err
	}

	exists := false
	if c != 0 {
		exists = true
	}

	// executes the query
	// ----------------------

	var result sql.Result

	if !exists {
		result, err = db.Exec(`
		INSERT INTO "executable"
		("display_name", "filepath", "platform_id", "description", "genres", "publisher", "developer", "release_date", "players", "rating")
		VALUES
		(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, executable.Name, executable.Filepath, platformId, executable.Description, executable.Genres, executable.Publisher, executable.Developer, executable.ReleaseDate, executable.Players, executable.Rating)
	} else {
		result, err = db.Exec(`
		UPDATE "executable"
		SET
			"display_name" = ?,
			"description" = ?,
			"genres" = ?,
			"publisher" = ?,
			"developer" = ?,
			"release_date" = ?,
			"players" = ?,
			"rating" = ?
		WHERE
			"executable"."platform_id" = ?
			AND
			"executable"."filepath" = ?
	`, executable.Name, executable.Description, executable.Genres, executable.Publisher, executable.Developer, executable.ReleaseDate, executable.Players, executable.Rating, platformId, executable.Filepath)
	}

	if err != nil {
		return -1, false, fmt.Errorf("[err] Can't create/update a new executable %s in the DB: %s", executable.Name, err.Error())
	}

	id, err := result.LastInsertId()
	if err != nil {
		return -1, false, fmt.Errorf("[err] During id retrieving of '%s': %s", executable.Name, err.Error())
	}

	return id, exists, nil

}

// writeDatabase writes the result of the scraping into the given database.
func WriteDatabase(database string, platform int, gamesInfo *model.Gamesinfo) {
	db, err := sql.Open("sqlite3", database)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Prepares the transaction
	tx, err := db.Begin()
	if err != nil {
		log.Fatal(err)
	}
	execStmt, err := tx.Prepare("insert into executable (display_name, filepath, platform_id, description, genres, developer, publisher, release_date, players, rating) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
	execResStmt, err := tx.Prepare("insert into executable_resource (executable_id, type, filepath) values(?, ?, ?)")
	if err != nil {
		log.Fatal(err)
	}

	for idx := range gamesInfo.Games {
		gameInfo := gamesInfo.Games[idx]
		writeGame(db, platform, execStmt, execResStmt, gameInfo)
	}

	// Ends the transaction
	tx.Commit()
}

// writeGame writes one game in the DB.
func writeGame(db *sql.DB, platform int, execStmt *sql.Stmt, execResStmt *sql.Stmt, gameInfo model.Gameinfo) {
	// Entry in executable
	rating := fmt.Sprintf("%2.1f", gameInfo.Rating)
	result, err := execStmt.Exec(gameInfo.Title, gameInfo.Filepath, platform, gameInfo.Description, gameInfo.Genres, gameInfo.Developer, gameInfo.Publisher, gameInfo.ReleaseDate, gameInfo.Players, rating)
	if err != nil {
		log.Printf("[err] Can't write the info of %s in the DB: %s", gameInfo.Title, err.Error())
		return
	}
	id, err := result.LastInsertId()
	if err != nil {
		log.Printf("[err] Can't retrieve the last insert id for %s : %s", gameInfo.Title, err.Error())
		return
	}

	// Entries in executable_resource
	// Cover
	if len(gameInfo.CoverPath) != 0 {
		result, err = execResStmt.Exec(id, "cover", gameInfo.CoverPath)
		if err != nil {
			log.Printf("[err] Can't write the cover of %s in the DB: %s", gameInfo.Title, err.Error())
		}
	}
	// Logos
	if len(gameInfo.LogoPaths) != 0 {
		for idx := range gameInfo.LogoPaths {
			logoPath := gameInfo.LogoPaths[idx]
			result, err = execResStmt.Exec(id, "logo", logoPath)
			if err != nil {
				log.Printf("[err] Can't write a logo of %s in the DB: %s", gameInfo.Title, err.Error())
			}
		}
	}
	// Screenshots
	if len(gameInfo.ScreenshotPaths) != 0 {
		for idx := range gameInfo.ScreenshotPaths {
			screenshotPath := gameInfo.ScreenshotPaths[idx]
			result, err = execResStmt.Exec(id, "screenshot", screenshotPath)
			if err != nil {
				log.Printf("[err] Can't write a screenshot of %s in the DB: %s", gameInfo.Title, err.Error())
			}
		}
	}
	// Fanarts
	if len(gameInfo.FanartPaths) != 0 {
		for idx := range gameInfo.FanartPaths {
			fanartPath := gameInfo.FanartPaths[idx]
			result, err = execResStmt.Exec(id, "fanart", fanartPath)
			if err != nil {
				log.Printf("[err] Can't write a fanart of %s in the DB: %s", gameInfo.Title, err.Error())
			}
		}
	}
}
<commit_msg>db: delete cascade of executable resources.<commit_after>package db

import (
	"bytes"
	"database/sql"
	"fmt"
	"io/ioutil"
	"log"
	"strings"

	"github.com/remeh/mehtadata/model"

	_ "github.com/mattn/go-sqlite3"
)

func InitSchema(database, filename string) (bool, error) {

	// database
	// ----------------------

	var err error

	db, err := sql.Open("sqlite3", database)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// read the schema
	// ----------------------

	var data []byte

	if data, err = ioutil.ReadFile(filename); err != nil {
		return false, err
	}

	// remove all line return

	data = bytes.Replace(data, []byte("\n"), []byte(" "), -1)
	data = bytes.Replace(data, []byte("\r"), []byte(" "), -1) // windows user having edited the file

	queries := strings.Split(string(data), ";")

	// prepares the transaction
	// ----------------------

	tx, err := db.Begin()
	if err != nil {
		return false, err
	}

	// execute each line
	// ----------------------

	for _, query := range queries {
		if _, err := tx.Exec(query); err != nil {
			return false, err
		}
	}

	// commit
	// ----------------------

	err = tx.Commit()

	return err == nil, err
}

// CreatePlatform creates an empty platform in the given sqlite database.
// Returns the ID of the newly created platform.
func CreatePlatform(database string, platform model.Platform) (int64, bool, error) {

	// database
	// ----------------------

	db, err := sql.Open("sqlite3", database)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// ensure that no platform with the same name
	// is already existing
	// ----------------------

	var i int
	if err := db.QueryRow(`
		SELECT count(*)
		FROM "platform"
		WHERE
			name = ?
	`, platform.Name).Scan(&i); err != nil {
		return -1, false, err
	}

	exists := false
	if i >= 1 {
		exists = true
	}

	// executes the query
	// ----------------------

	var result sql.Result

	if !exists {
		result, err = db.Exec(`insert into "platform" ("name", "command", "icon", "background", "type", "discover_dir", "discover_ext") values(?, ?, ?, ?, ?, ?, ?)`, platform.Name, platform.Command, platform.Icon, platform.Background, platform.Type, platform.DiscoverDir, platform.DiscoverExts)
	} else {
		result, err = db.Exec(`
			UPDATE "platform"
			SET
				"command" = ?,
				"icon" = ?,
				"background" = ?,
				"type" = ?,
				"discover_dir" = ?,
				"discover_ext" = ?
			WHERE
				"name" = ?
		`, platform.Command, platform.Icon, platform.Background, platform.Type, platform.DiscoverDir, platform.DiscoverExts, platform.Name)
	}

	if err != nil {
		return -1, false, fmt.Errorf("[err] Can't create a new platform %s in the DB: %s", platform.Name, err.Error())
	}

	id, err := result.LastInsertId()
	if err != nil {
		return -1, false, fmt.Errorf("[err] Can't create a new platform %s in the DB (id retrieving): %s", platform.Name, err.Error())
	}

	return id, exists, nil
}

// DeletePlatform deletes the given platform by its name and
// all its executables.
func DeletePlatform(database, platformName string) (bool, error) {

	// database
	// ----------------------

	var err error

	db, err := sql.Open("sqlite3", database)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// ensure that the platform with the given name exists
	// and retrieve its ID.
	// ----------------------

	var platformId int
	if err := db.QueryRow(`
		SELECT "id"
		FROM "platform"
		WHERE
			name = ?
	`, platformName).Scan(&platformId); err == sql.ErrNoRows {
		return false, fmt.Errorf("Unknown platform")
	} else if err != nil {
		return false, err
	}

	// delete all executables resources
	// ----------------------

	var result sql.Result

	if result, err = db.Exec(`
		DELETE FROM "executable_resource"
		WHERE "executable_id" IN (
			SELECT "id" FROM "executable"
			WHERE
				"platform_id" = ?
		)
	`, platformId); err != nil {
		return false, err
	}

	// delete all its executables
	// ----------------------

	if result, err = db.Exec(`
		DELETE FROM "executable"
		WHERE
			"platform_id" = ?
	`, platformId); err != nil {
		return false, err
	}

	// delete the platform
	// ----------------------

	if result, err = db.Exec(`
		DELETE FROM "platform"
		WHERE
			"id" = ?
	`, platformId); err != nil {
		return false, err
	}

	// checks the deletion
	// ----------------------

	c, err := result.RowsAffected()
	if err != nil {
		return false, fmt.Errorf("while checking for delection: %v", err.Error())
	}

	return c == 1, nil
}

// DeleteExecutable deletes the executable from the database.
func DeleteExecutable(database, platformName, filepath string) (bool, error) {

	// database
	// ----------------------

	var err error

	db, err := sql.Open("sqlite3", database)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// ensure that the platform with the given name exists
	// and retrieve its ID.
	// ----------------------

	var platformId int
	if err := db.QueryRow(`
		SELECT "id"
		FROM "platform"
		WHERE
			name = ?
	`, platformName).Scan(&platformId); err == sql.ErrNoRows {
		return false, fmt.Errorf("Unknown platform")
	} else if err != nil {
		return false, err
	}

	// delete all executables resources
	// ----------------------

	var result sql.Result

	if result, err = db.Exec(`
		DELETE FROM "executable_resource"
		WHERE "executable_id" IN (
			SELECT "id" FROM "executable"
			WHERE
				"executable"."filepath" = ?
				AND
				"platform_id" = ?
		)
	`, filepath, platformId); err != nil {
		return false, err
	}

	// delete the executable
	// ----------------------

	var result sql.Result

	if result, err = db.Exec(`
		DELETE FROM "executable"
		WHERE
			"filepath" = ?
			AND
			"platform_id" = ?
	`, filepath, platformId); err != nil {
		return false, err
	}

	// checks the deletion
	// ----------------------

	c, err := result.RowsAffected()
	if err != nil {
		return false, fmt.Errorf("while checking for delection: %v", err.Error())
	}

	return c >= 1, nil
}

// CreateExecutable creates the executable in the database.
// Returns the ID of the created executable.
func CreateExecutable(database string, platformName string, executable model.Executable) (int64, bool, error) {

	// database
	// ----------------------

	db, err := sql.Open("sqlite3", database)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// ensure that the platform with the given name exists
	// and retrieve its ID.
	// ----------------------

	var platformId int
	if err := db.QueryRow(`
		SELECT "id"
		FROM "platform"
		WHERE
			name = ?
	`, platformName).Scan(&platformId); err == sql.ErrNoRows {
		return -1, false, fmt.Errorf("Unknown platform")
	} else if err != nil {
		return -1, false, err
	}

	// checks whether this executable already exists
	// ----------------------

	var c int
	if err := db.QueryRow(`
		SELECT COUNT(*)
		FROM "executable"
		JOIN "platform"
			ON "platform"."id" = "executable"."platform_id"
		WHERE
			"platform"."name" = ?
			AND
			"executable"."filepath" = ?
	`, platformName, executable.Filepath).Scan(&c); err != nil {
		return -1, false, err
	}

	exists := false
	if c != 0 {
		exists = true
	}

	// executes the query
	// ----------------------

	var result sql.Result

	if !exists {
		result, err = db.Exec(`
		INSERT INTO "executable"
		("display_name", "filepath", "platform_id", "description", "genres", "publisher", "developer", "release_date", "players", "rating")
		VALUES
		(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
	`, executable.Name, executable.Filepath, platformId, executable.Description, executable.Genres, executable.Publisher, executable.Developer, executable.ReleaseDate, executable.Players, executable.Rating)
	} else {
		result, err = db.Exec(`
		UPDATE "executable"
		SET
			"display_name" = ?,
			"description" = ?,
			"genres" = ?,
			"publisher" = ?,
			"developer" = ?,
			"release_date" = ?,
			"players" = ?,
			"rating" = ?
		WHERE
			"executable"."platform_id" = ?
			AND
			"executable"."filepath" = ?
	`, executable.Name, executable.Description, executable.Genres, executable.Publisher, executable.Developer, executable.ReleaseDate, executable.Players, executable.Rating, platformId, executable.Filepath)
	}

	if err != nil {
		return -1, false, fmt.Errorf("[err] Can't create/update a new executable %s in the DB: %s", executable.Name, err.Error())
	}

	id, err := result.LastInsertId()
	if err != nil {
		return -1, false, fmt.Errorf("[err] During id retrieving of '%s': %s", executable.Name, err.Error())
	}

	return id, exists, nil

}

// writeDatabase writes the result of the scraping into the given database.
func WriteDatabase(database string, platform int, gamesInfo *model.Gamesinfo) {
	db, err := sql.Open("sqlite3", database)
	if err != nil {
		log.Fatal(err)
	}
	defer db.Close()

	// Prepares the transaction
	tx, err := db.Begin()
	if err != nil {
		log.Fatal(err)
	}
	execStmt, err := tx.Prepare("insert into executable (display_name, filepath, platform_id, description, genres, developer, publisher, release_date, players, rating) values(?, ?, ?, ?, ?, ?, ?, ?, ?, ?)")
	execResStmt, err := tx.Prepare("insert into executable_resource (executable_id, type, filepath) values(?, ?, ?)")
	if err != nil {
		log.Fatal(err)
	}

	for idx := range gamesInfo.Games {
		gameInfo := gamesInfo.Games[idx]
		writeGame(db, platform, execStmt, execResStmt, gameInfo)
	}

	// Ends the transaction
	tx.Commit()
}

// writeGame writes one game in the DB.
func writeGame(db *sql.DB, platform int, execStmt *sql.Stmt, execResStmt *sql.Stmt, gameInfo model.Gameinfo) {
	// Entry in executable
	rating := fmt.Sprintf("%2.1f", gameInfo.Rating)
	result, err := execStmt.Exec(gameInfo.Title, gameInfo.Filepath, platform, gameInfo.Description, gameInfo.Genres, gameInfo.Developer, gameInfo.Publisher, gameInfo.ReleaseDate, gameInfo.Players, rating)
	if err != nil {
		log.Printf("[err] Can't write the info of %s in the DB: %s", gameInfo.Title, err.Error())
		return
	}
	id, err := result.LastInsertId()
	if err != nil {
		log.Printf("[err] Can't retrieve the last insert id for %s : %s", gameInfo.Title, err.Error())
		return
	}

	// Entries in executable_resource
	// Cover
	if len(gameInfo.CoverPath) != 0 {
		result, err = execResStmt.Exec(id, "cover", gameInfo.CoverPath)
		if err != nil {
			log.Printf("[err] Can't write the cover of %s in the DB: %s", gameInfo.Title, err.Error())
		}
	}
	// Logos
	if len(gameInfo.LogoPaths) != 0 {
		for idx := range gameInfo.LogoPaths {
			logoPath := gameInfo.LogoPaths[idx]
			result, err = execResStmt.Exec(id, "logo", logoPath)
			if err != nil {
				log.Printf("[err] Can't write a logo of %s in the DB: %s", gameInfo.Title, err.Error())
			}
		}
	}
	// Screenshots
	if len(gameInfo.ScreenshotPaths) != 0 {
		for idx := range gameInfo.ScreenshotPaths {
			screenshotPath := gameInfo.ScreenshotPaths[idx]
			result, err = execResStmt.Exec(id, "screenshot", screenshotPath)
			if err != nil {
				log.Printf("[err] Can't write a screenshot of %s in the DB: %s", gameInfo.Title, err.Error())
			}
		}
	}
	// Fanarts
	if len(gameInfo.FanartPaths) != 0 {
		for idx := range gameInfo.FanartPaths {
			fanartPath := gameInfo.FanartPaths[idx]
			result, err = execResStmt.Exec(id, "fanart", fanartPath)
			if err != nil {
				log.Printf("[err] Can't write a fanart of %s in the DB: %s", gameInfo.Title, err.Error())
			}
		}
	}
}
<|endoftext|>


<commit_before>package core

import (
	"bytes"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	ipnspath "github.com/ipfs/go-ipfs/path"
	"gx/ipfs/QmcZfnkapfECQGcLZaf9B79NRg7cRa9EnZh4LSbkCzwNvY/go-cid"
	"io/ioutil"
	"os"
	"path"
	"strings"
	"time"

	"github.com/OpenBazaar/jsonpb"
	"github.com/OpenBazaar/openbazaar-go/pb"
	"github.com/golang/protobuf/ptypes"
	"github.com/imdario/mergo"
)

const KeyCachePrefix = "/pubkey/"

var ErrorProfileNotFound error = errors.New("Profile not found")

func (n *OpenBazaarNode) GetProfile() (pb.Profile, error) {
	var profile pb.Profile
	f, err := os.Open(path.Join(n.RepoPath, "root", "profile.json"))
	if err != nil {
		return profile, ErrorProfileNotFound
	}
	defer f.Close()
	err = jsonpb.Unmarshal(f, &profile)
	if err != nil {
		return profile, err
	}
	return profile, nil
}

func (n *OpenBazaarNode) FetchProfile(peerId string, useCache bool) (pb.Profile, error) {
	var pro pb.Profile
	b, err := n.IPNSResolveThenCat(ipnspath.FromString(path.Join(peerId, "profile.json")), time.Minute, useCache)
	if err != nil || len(b) == 0 {
		return pro, err
	}
	err = jsonpb.UnmarshalString(string(b), &pro)
	if err != nil {
		return pro, err
	}
	return pro, nil
}

func (n *OpenBazaarNode) UpdateProfile(profile *pb.Profile) error {
	mPubkey, err := n.Wallet.MasterPublicKey().ECPubKey()
	if err != nil {
		return err
	}

	if err := ValidateProfile(profile); err != nil {
		return err
	}

	profile.BitcoinPubkey = hex.EncodeToString(mPubkey.SerializeCompressed())
	m := jsonpb.Marshaler{
		EnumsAsInts:  false,
		EmitDefaults: true,
		Indent:       "    ",
		OrigName:     false,
	}

	if profile.Currencies == nil {
		profile.Currencies = []string{NormalizeCurrencyCode(n.Wallet.CurrencyCode())}
	}

	if profile.ModeratorInfo != nil {
		profile.ModeratorInfo.AcceptedCurrencies = []string{NormalizeCurrencyCode(n.Wallet.CurrencyCode())}
	}
	profile.PeerID = n.IpfsNode.Identity.Pretty()
	ts, err := ptypes.TimestampProto(time.Now())
	if err != nil {
		return err
	}
	profile.LastModified = ts
	out, err := m.MarshalToString(profile)
	if err != nil {
		return err
	}

	profilePath := path.Join(n.RepoPath, "root", "profile.json")
	f, err := os.Create(profilePath)
	defer f.Close()
	if err != nil {
		return err
	}
	if _, err := f.WriteString(out); err != nil {
		return err
	}
	return nil
}

func (n *OpenBazaarNode) PatchProfile(patch map[string]interface{}) error {
	profilePath := path.Join(n.RepoPath, "root", "profile.json")

	// Read stored profile data
	file, err := os.Open(profilePath)
	if err != nil {
		return err
	}
	d := json.NewDecoder(file)
	d.UseNumber()

	var i interface{}
	err = d.Decode(&i)
	if err != nil {
		return err
	}
	profile := i.(map[string]interface{})

	patchMod, pok := patch["moderator"]
	storedMod, sok := profile["moderator"]
	if pok && sok {
		patchBool, ok := patchMod.(bool)
		if !ok {
			return errors.New("Invalid moderator type")
		}
		storedBool, ok := storedMod.(bool)
		if !ok {
			return errors.New("Invalid moderator type")
		}
		if patchBool && patchBool != storedBool {
			if err := n.SetSelfAsModerator(nil); err != nil {
				return err
			}
		} else if !patchBool && patchBool != storedBool {
			if err := n.RemoveSelfAsModerator(); err != nil {
				return err
			}
		}
	}

	// Assuming that `profile` map contains complete data, as it is read
	// from storage, and `patch` map is possibly incomplete, merge first
	// into second recursively, preserving new fields and adding missing
	// old ones
	if err := mergo.Map(&patch, &profile); err != nil {
		return err
	}

	// Execute UpdateProfile with new profile
	newProfile, err := json.Marshal(patch)
	p := new(pb.Profile)
	if err := jsonpb.Unmarshal(bytes.NewReader(newProfile), p); err != nil {
		return err
	}
	return n.UpdateProfile(p)
}

func (n *OpenBazaarNode) appendCountsToProfile(profile *pb.Profile) (*pb.Profile, bool, error) {
	if profile.Stats == nil {
		profile.Stats = new(pb.Profile_Stats)
	}
	var changed bool
	listingCount := uint32(n.GetListingCount())
	if listingCount != profile.Stats.ListingCount {
		profile.Stats.ListingCount = listingCount
		changed = true
	}
	postCount := uint32(n.GetPostCount())
	if postCount != profile.Stats.PostCount {
		profile.Stats.PostCount = postCount
		changed = true
	}
	followerCount := uint32(n.Datastore.Followers().Count())
	if followerCount != profile.Stats.FollowerCount {
		profile.Stats.FollowerCount = followerCount
		changed = true
	}
	followingCount := uint32(n.Datastore.Following().Count())
	if followingCount != profile.Stats.FollowingCount {
		profile.Stats.FollowingCount = followingCount
		changed = true
	}
	ratingCount, averageRating, err := n.GetRatingCounts()
	if err == nil && (ratingCount != profile.Stats.RatingCount || averageRating != profile.Stats.AverageRating) {
		profile.Stats.RatingCount = ratingCount
		profile.Stats.AverageRating = averageRating
		changed = true
	}
	return profile, changed, nil
}

func (n *OpenBazaarNode) updateProfileCounts() error {
	profilePath := path.Join(n.RepoPath, "root", "profile.json")
	profile := new(pb.Profile)
	_, ferr := os.Stat(profilePath)
	if !os.IsNotExist(ferr) {
		// Read existing file
		file, err := os.Open(profilePath)
		defer file.Close()
		if err != nil {
			return err
		}
		err = jsonpb.Unmarshal(file, profile)
		if err != nil {
			return err
		}
	} else {
		return nil
	}
	newPro, changed, err := n.appendCountsToProfile(profile)
	if err != nil {
		return err
	}
	if changed {
		return n.UpdateProfile(newPro)
	}
	return nil
}

func (n *OpenBazaarNode) updateProfileRatings(newRating *pb.Rating) error {
	profilePath := path.Join(n.RepoPath, "root", "profile.json")
	profile := new(pb.Profile)
	_, ferr := os.Stat(profilePath)
	if !os.IsNotExist(ferr) {
		// Read existing file
		file, err := ioutil.ReadFile(profilePath)
		if err != nil {
			return err
		}
		err = jsonpb.UnmarshalString(string(file), profile)
		if err != nil {
			return err
		}
	} else {
		return nil
	}
	if profile.Stats != nil && newRating.RatingData != nil {
		total := profile.Stats.AverageRating * float32(profile.Stats.RatingCount)
		total += float32(newRating.RatingData.Overall)
		profile.Stats.RatingCount += 1
		profile.Stats.AverageRating = total / float32(profile.Stats.RatingCount)
	}
	newPro, _, err := n.appendCountsToProfile(profile)
	if err != nil {
		return err
	}

	return n.UpdateProfile(newPro)
}

func ValidateProfile(profile *pb.Profile) error {
	if strings.Contains(profile.Handle, "@") {
		return errors.New("Handle should not contain @")
	}
	if len(profile.Handle) > WordMaxCharacters {
		return fmt.Errorf("Handle character length is greater than the max of %d", WordMaxCharacters)
	}
	if len(profile.Name) == 0 {
		return errors.New("Profile name not set")
	}
	if len(profile.Name) > WordMaxCharacters {
		return fmt.Errorf("Name character length is greater than the max of %d", WordMaxCharacters)
	}
	if len(profile.Location) > WordMaxCharacters {
		return fmt.Errorf("Location character length is greater than the max of %d", WordMaxCharacters)
	}
	if len(profile.About) > AboutMaxCharacters {
		return fmt.Errorf("About character length is greater than the max of %d", AboutMaxCharacters)
	}
	if len(profile.ShortDescription) > ShortDescriptionLength {
		return fmt.Errorf("Short description character length is greater than the max of %d", ShortDescriptionLength)
	}
	if profile.ContactInfo != nil {
		if len(profile.ContactInfo.Website) > URLMaxCharacters {
			return fmt.Errorf("Website character length is greater than the max of %d", URLMaxCharacters)
		}
		if len(profile.ContactInfo.Email) > SentenceMaxCharacters {
			return fmt.Errorf("Email character length is greater than the max of %d", SentenceMaxCharacters)
		}
		if len(profile.ContactInfo.PhoneNumber) > WordMaxCharacters {
			return fmt.Errorf("Phone number character length is greater than the max of %d", WordMaxCharacters)
		}
		if len(profile.ContactInfo.Social) > MaxListItems {
			return fmt.Errorf("Number of social accounts is greater than the max of %d", MaxListItems)
		}
		for _, s := range profile.ContactInfo.Social {
			if len(s.Username) > WordMaxCharacters {
				return fmt.Errorf("Social username character length is greater than the max of %d", WordMaxCharacters)
			}
			if len(s.Type) > WordMaxCharacters {
				return fmt.Errorf("Social account type character length is greater than the max of %d", WordMaxCharacters)
			}
			if len(s.Proof) > URLMaxCharacters {
				return fmt.Errorf("Social proof character length is greater than the max of %d", WordMaxCharacters)
			}
		}
	}
	if profile.ModeratorInfo != nil {
		if len(profile.ModeratorInfo.Description) > AboutMaxCharacters {
			return fmt.Errorf("Moderator description character length is greater than the max of %d", AboutMaxCharacters)
		}
		if len(profile.ModeratorInfo.TermsAndConditions) > PolicyMaxCharacters {
			return fmt.Errorf("Moderator terms and conditions character length is greater than the max of %d", PolicyMaxCharacters)
		}
		if len(profile.ModeratorInfo.Languages) > MaxListItems {
			return fmt.Errorf("Moderator number of languages greater than the max of %d", MaxListItems)
		}
		for _, l := range profile.ModeratorInfo.Languages {
			if len(l) > WordMaxCharacters {
				return fmt.Errorf("Moderator language character length is greater than the max of %d", WordMaxCharacters)
			}
		}
		if profile.ModeratorInfo.Fee != nil {
			if profile.ModeratorInfo.Fee.FixedFee != nil {
				if len(profile.ModeratorInfo.Fee.FixedFee.CurrencyCode) > WordMaxCharacters {
					return fmt.Errorf("Moderator fee currency code character length is greater than the max of %d", WordMaxCharacters)
				}
			}
		}
	}
	if profile.AvatarHashes != nil && (profile.AvatarHashes.Large != "" || profile.AvatarHashes.Medium != "" ||
		profile.AvatarHashes.Small != "" || profile.AvatarHashes.Tiny != "" || profile.AvatarHashes.Original != "") {
		_, err := cid.Decode(profile.AvatarHashes.Tiny)
		if err != nil {
			return errors.New("Tiny image hashes must be properly formatted CID")
		}
		_, err = cid.Decode(profile.AvatarHashes.Small)
		if err != nil {
			return errors.New("Small image hashes must be properly formatted CID")
		}
		_, err = cid.Decode(profile.AvatarHashes.Medium)
		if err != nil {
			return errors.New("Medium image hashes must be properly formatted CID")
		}
		_, err = cid.Decode(profile.AvatarHashes.Large)
		if err != nil {
			return errors.New("Large image hashes must be properly formatted CID")
		}
		_, err = cid.Decode(profile.AvatarHashes.Original)
		if err != nil {
			return errors.New("Original image hashes must be properly formatted CID")
		}
	}
	if profile.HeaderHashes != nil && (profile.HeaderHashes.Large != "" || profile.HeaderHashes.Medium != "" ||
		profile.HeaderHashes.Small != "" || profile.HeaderHashes.Tiny != "" || profile.HeaderHashes.Original != "") {
		_, err := cid.Decode(profile.HeaderHashes.Tiny)
		if err != nil {
			return errors.New("Tiny image hashes must be properly formatted CID")
		}
		_, err = cid.Decode(profile.HeaderHashes.Small)
		if err != nil {
			return errors.New("Small image hashes must be properly formatted CID")
		}
		_, err = cid.Decode(profile.HeaderHashes.Medium)
		if err != nil {
			return errors.New("Medium image hashes must be properly formatted CID")
		}
		_, err = cid.Decode(profile.HeaderHashes.Large)
		if err != nil {
			return errors.New("Large image hashes must be properly formatted CID")
		}
		_, err = cid.Decode(profile.HeaderHashes.Original)
		if err != nil {
			return errors.New("Original image hashes must be properly formatted CID")
		}
	}
	if len(profile.BitcoinPubkey) > 66 {
		return fmt.Errorf("Bitcoin public key character length is greater than the max of %d", 66)
	}
	if profile.Stats != nil {
		if profile.Stats.AverageRating > 5 {
			return fmt.Errorf("Average rating cannot be greater than %d", 5)
		}
	}
	return nil
}
<commit_msg>lint core/profile<commit_after>package core

import (
	"bytes"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"strings"
	"time"

	"github.com/OpenBazaar/jsonpb"
	"github.com/golang/protobuf/ptypes"
	"github.com/imdario/mergo"
	ipnspath "github.com/ipfs/go-ipfs/path"

	"gx/ipfs/QmcZfnkapfECQGcLZaf9B79NRg7cRa9EnZh4LSbkCzwNvY/go-cid"

	"github.com/OpenBazaar/openbazaar-go/pb"
)

// KeyCachePrefix - cache prefix for public key
const KeyCachePrefix = "/pubkey/"

// ErrorProfileNotFound - profile not found error
var ErrorProfileNotFound = errors.New("profile not found")

// GetProfile - fetch user profile
func (n *OpenBazaarNode) GetProfile() (pb.Profile, error) {
	var profile pb.Profile
	f, err := os.Open(path.Join(n.RepoPath, "root", "profile.json"))
	if err != nil {
		return profile, ErrorProfileNotFound
	}
	defer f.Close()
	err = jsonpb.Unmarshal(f, &profile)
	if err != nil {
		return profile, err
	}
	return profile, nil
}

// FetchProfile - fetch peer's profile
func (n *OpenBazaarNode) FetchProfile(peerID string, useCache bool) (pb.Profile, error) {
	var pro pb.Profile
	b, err := n.IPNSResolveThenCat(ipnspath.FromString(path.Join(peerID, "profile.json")), time.Minute, useCache)
	if err != nil || len(b) == 0 {
		return pro, err
	}
	err = jsonpb.UnmarshalString(string(b), &pro)
	if err != nil {
		return pro, err
	}
	return pro, nil
}

// UpdateProfile - update user profile
func (n *OpenBazaarNode) UpdateProfile(profile *pb.Profile) error {
	mPubkey, err := n.Wallet.MasterPublicKey().ECPubKey()
	if err != nil {
		return err
	}

	if err := ValidateProfile(profile); err != nil {
		return err
	}

	profile.BitcoinPubkey = hex.EncodeToString(mPubkey.SerializeCompressed())
	m := jsonpb.Marshaler{
		EnumsAsInts:  false,
		EmitDefaults: true,
		Indent:       "    ",
		OrigName:     false,
	}

	if profile.Currencies == nil {
		profile.Currencies = []string{NormalizeCurrencyCode(n.Wallet.CurrencyCode())}
	}

	if profile.ModeratorInfo != nil {
		profile.ModeratorInfo.AcceptedCurrencies = []string{NormalizeCurrencyCode(n.Wallet.CurrencyCode())}
	}
	profile.PeerID = n.IpfsNode.Identity.Pretty()
	ts, err := ptypes.TimestampProto(time.Now())
	if err != nil {
		return err
	}
	profile.LastModified = ts
	out, err := m.MarshalToString(profile)
	if err != nil {
		return err
	}

	profilePath := path.Join(n.RepoPath, "root", "profile.json")
	f, err := os.Create(profilePath)
	defer f.Close()
	if err != nil {
		return err
	}
	if _, err := f.WriteString(out); err != nil {
		return err
	}
	return nil
}

// PatchProfile - patch user profile
func (n *OpenBazaarNode) PatchProfile(patch map[string]interface{}) error {
	profilePath := path.Join(n.RepoPath, "root", "profile.json")

	// Read stored profile data
	file, err := os.Open(profilePath)
	if err != nil {
		return err
	}
	d := json.NewDecoder(file)
	d.UseNumber()

	var i interface{}
	err = d.Decode(&i)
	if err != nil {
		return err
	}
	profile := i.(map[string]interface{})

	patchMod, pok := patch["moderator"]
	storedMod, sok := profile["moderator"]
	if pok && sok {
		patchBool, ok := patchMod.(bool)
		if !ok {
			return errors.New("Invalid moderator type")
		}
		storedBool, ok := storedMod.(bool)
		if !ok {
			return errors.New("Invalid moderator type")
		}
		if patchBool && patchBool != storedBool {
			if err := n.SetSelfAsModerator(nil); err != nil {
				return err
			}
		} else if !patchBool && patchBool != storedBool {
			if err := n.RemoveSelfAsModerator(); err != nil {
				return err
			}
		}
	}

	// Assuming that `profile` map contains complete data, as it is read
	// from storage, and `patch` map is possibly incomplete, merge first
	// into second recursively, preserving new fields and adding missing
	// old ones
	if err := mergo.Map(&patch, &profile); err != nil {
		return err
	}

	// Execute UpdateProfile with new profile
	newProfile, err := json.Marshal(patch)
	p := new(pb.Profile)
	if err := jsonpb.Unmarshal(bytes.NewReader(newProfile), p); err != nil {
		return err
	}
	return n.UpdateProfile(p)
}

func (n *OpenBazaarNode) appendCountsToProfile(profile *pb.Profile) (*pb.Profile, bool, error) {
	if profile.Stats == nil {
		profile.Stats = new(pb.Profile_Stats)
	}
	var changed bool
	listingCount := uint32(n.GetListingCount())
	if listingCount != profile.Stats.ListingCount {
		profile.Stats.ListingCount = listingCount
		changed = true
	}
	postCount := uint32(n.GetPostCount())
	if postCount != profile.Stats.PostCount {
		profile.Stats.PostCount = postCount
		changed = true
	}
	followerCount := uint32(n.Datastore.Followers().Count())
	if followerCount != profile.Stats.FollowerCount {
		profile.Stats.FollowerCount = followerCount
		changed = true
	}
	followingCount := uint32(n.Datastore.Following().Count())
	if followingCount != profile.Stats.FollowingCount {
		profile.Stats.FollowingCount = followingCount
		changed = true
	}
	ratingCount, averageRating, err := n.GetRatingCounts()
	if err == nil && (ratingCount != profile.Stats.RatingCount || averageRating != profile.Stats.AverageRating) {
		profile.Stats.RatingCount = ratingCount
		profile.Stats.AverageRating = averageRating
		changed = true
	}
	return profile, changed, nil
}

func (n *OpenBazaarNode) updateProfileCounts() error {
	profilePath := path.Join(n.RepoPath, "root", "profile.json")
	profile := new(pb.Profile)
	_, ferr := os.Stat(profilePath)
	if !os.IsNotExist(ferr) {
		// Read existing file
		file, err := os.Open(profilePath)
		defer file.Close()
		if err != nil {
			return err
		}
		err = jsonpb.Unmarshal(file, profile)
		if err != nil {
			return err
		}
	} else {
		return nil
	}
	newPro, changed, err := n.appendCountsToProfile(profile)
	if err != nil {
		return err
	}
	if changed {
		return n.UpdateProfile(newPro)
	}
	return nil
}

func (n *OpenBazaarNode) updateProfileRatings(newRating *pb.Rating) error {
	profilePath := path.Join(n.RepoPath, "root", "profile.json")
	profile := new(pb.Profile)
	_, ferr := os.Stat(profilePath)
	if !os.IsNotExist(ferr) {
		// Read existing file
		file, err := ioutil.ReadFile(profilePath)
		if err != nil {
			return err
		}
		err = jsonpb.UnmarshalString(string(file), profile)
		if err != nil {
			return err
		}
	} else {
		return nil
	}
	if profile.Stats != nil && newRating.RatingData != nil {
		total := profile.Stats.AverageRating * float32(profile.Stats.RatingCount)
		total += float32(newRating.RatingData.Overall)
		profile.Stats.RatingCount++ // += 1
		profile.Stats.AverageRating = total / float32(profile.Stats.RatingCount)
	}
	newPro, _, err := n.appendCountsToProfile(profile)
	if err != nil {
		return err
	}

	return n.UpdateProfile(newPro)
}

// ValidateProfile - validate fetched profile
func ValidateProfile(profile *pb.Profile) error {
	if strings.Contains(profile.Handle, "@") {
		return errors.New("Handle should not contain @")
	}
	if len(profile.Handle) > WordMaxCharacters {
		return fmt.Errorf("Handle character length is greater than the max of %d", WordMaxCharacters)
	}
	if len(profile.Name) == 0 {
		return errors.New("Profile name not set")
	}
	if len(profile.Name) > WordMaxCharacters {
		return fmt.Errorf("Name character length is greater than the max of %d", WordMaxCharacters)
	}
	if len(profile.Location) > WordMaxCharacters {
		return fmt.Errorf("Location character length is greater than the max of %d", WordMaxCharacters)
	}
	if len(profile.About) > AboutMaxCharacters {
		return fmt.Errorf("About character length is greater than the max of %d", AboutMaxCharacters)
	}
	if len(profile.ShortDescription) > ShortDescriptionLength {
		return fmt.Errorf("Short description character length is greater than the max of %d", ShortDescriptionLength)
	}
	if profile.ContactInfo != nil {
		if len(profile.ContactInfo.Website) > URLMaxCharacters {
			return fmt.Errorf("Website character length is greater than the max of %d", URLMaxCharacters)
		}
		if len(profile.ContactInfo.Email) > SentenceMaxCharacters {
			return fmt.Errorf("Email character length is greater than the max of %d", SentenceMaxCharacters)
		}
		if len(profile.ContactInfo.PhoneNumber) > WordMaxCharacters {
			return fmt.Errorf("Phone number character length is greater than the max of %d", WordMaxCharacters)
		}
		if len(profile.ContactInfo.Social) > MaxListItems {
			return fmt.Errorf("Number of social accounts is greater than the max of %d", MaxListItems)
		}
		for _, s := range profile.ContactInfo.Social {
			if len(s.Username) > WordMaxCharacters {
				return fmt.Errorf("Social username character length is greater than the max of %d", WordMaxCharacters)
			}
			if len(s.Type) > WordMaxCharacters {
				return fmt.Errorf("Social account type character length is greater than the max of %d", WordMaxCharacters)
			}
			if len(s.Proof) > URLMaxCharacters {
				return fmt.Errorf("Social proof character length is greater than the max of %d", WordMaxCharacters)
			}
		}
	}
	if profile.ModeratorInfo != nil {
		if len(profile.ModeratorInfo.Description) > AboutMaxCharacters {
			return fmt.Errorf("Moderator description character length is greater than the max of %d", AboutMaxCharacters)
		}
		if len(profile.ModeratorInfo.TermsAndConditions) > PolicyMaxCharacters {
			return fmt.Errorf("Moderator terms and conditions character length is greater than the max of %d", PolicyMaxCharacters)
		}
		if len(profile.ModeratorInfo.Languages) > MaxListItems {
			return fmt.Errorf("Moderator number of languages greater than the max of %d", MaxListItems)
		}
		for _, l := range profile.ModeratorInfo.Languages {
			if len(l) > WordMaxCharacters {
				return fmt.Errorf("Moderator language character length is greater than the max of %d", WordMaxCharacters)
			}
		}
		if profile.ModeratorInfo.Fee != nil {
			if profile.ModeratorInfo.Fee.FixedFee != nil {
				if len(profile.ModeratorInfo.Fee.FixedFee.CurrencyCode) > WordMaxCharacters {
					return fmt.Errorf("Moderator fee currency code character length is greater than the max of %d", WordMaxCharacters)
				}
			}
		}
	}
	if profile.AvatarHashes != nil && (profile.AvatarHashes.Large != "" || profile.AvatarHashes.Medium != "" ||
		profile.AvatarHashes.Small != "" || profile.AvatarHashes.Tiny != "" || profile.AvatarHashes.Original != "") {
		_, err := cid.Decode(profile.AvatarHashes.Tiny)
		if err != nil {
			return errors.New("Tiny image hashes must be properly formatted CID")
		}
		_, err = cid.Decode(profile.AvatarHashes.Small)
		if err != nil {
			return errors.New("Small image hashes must be properly formatted CID")
		}
		_, err = cid.Decode(profile.AvatarHashes.Medium)
		if err != nil {
			return errors.New("Medium image hashes must be properly formatted CID")
		}
		_, err = cid.Decode(profile.AvatarHashes.Large)
		if err != nil {
			return errors.New("Large image hashes must be properly formatted CID")
		}
		_, err = cid.Decode(profile.AvatarHashes.Original)
		if err != nil {
			return errors.New("Original image hashes must be properly formatted CID")
		}
	}
	if profile.HeaderHashes != nil && (profile.HeaderHashes.Large != "" || profile.HeaderHashes.Medium != "" ||
		profile.HeaderHashes.Small != "" || profile.HeaderHashes.Tiny != "" || profile.HeaderHashes.Original != "") {
		_, err := cid.Decode(profile.HeaderHashes.Tiny)
		if err != nil {
			return errors.New("Tiny image hashes must be properly formatted CID")
		}
		_, err = cid.Decode(profile.HeaderHashes.Small)
		if err != nil {
			return errors.New("Small image hashes must be properly formatted CID")
		}
		_, err = cid.Decode(profile.HeaderHashes.Medium)
		if err != nil {
			return errors.New("Medium image hashes must be properly formatted CID")
		}
		_, err = cid.Decode(profile.HeaderHashes.Large)
		if err != nil {
			return errors.New("Large image hashes must be properly formatted CID")
		}
		_, err = cid.Decode(profile.HeaderHashes.Original)
		if err != nil {
			return errors.New("Original image hashes must be properly formatted CID")
		}
	}
	if len(profile.BitcoinPubkey) > 66 {
		return fmt.Errorf("Bitcoin public key character length is greater than the max of %d", 66)
	}
	if profile.Stats != nil {
		if profile.Stats.AverageRating > 5 {
			return fmt.Errorf("Average rating cannot be greater than %d", 5)
		}
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2013-2015 The btcsuite developers
// Copyright (c) 2015-2017 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package main

import (
	"bufio"
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	_ "net/http/pprof"
	"os"
	"runtime"
	"runtime/pprof"
	"time"

	"github.com/decred/dcrd/chaincfg"
	"github.com/decred/dcrwallet/chain"
	"github.com/decred/dcrwallet/internal/prompt"
	"github.com/decred/dcrwallet/internal/zero"
	ldr "github.com/decred/dcrwallet/loader"
	"github.com/decred/dcrwallet/rpc/legacyrpc"
	"github.com/decred/dcrwallet/rpc/rpcserver"
	"github.com/decred/dcrwallet/wallet"
)

var (
	cfg *config
)

func main() {
	// Create a context that is cancelled when a shutdown request is received
	// through an interrupt signal or an RPC request.
	ctx := withShutdownCancel(context.Background())
	go shutdownListener()

	// Run the wallet until permanent failure or shutdown is requested.
	if err := run(ctx); err != nil && err != context.Canceled {
		os.Exit(1)
	}
}

// done returns whether the context's Done channel was closed due to
// cancellation or exceeded deadline.
func done(ctx context.Context) bool {
	select {
	case <-ctx.Done():
		return true
	default:
		return false
	}
}

// run is the main startup and teardown logic performed by the main package.  It
// is responsible for parsing the config, starting RPC servers, loading and
// syncing the wallet (if necessary), and stopping all started services when the
// context is cancelled.
func run(ctx context.Context) error {
	// Load configuration and parse command line.  This function also
	// initializes logging and configures it accordingly.
	tcfg, _, err := loadConfig()
	if err != nil {
		return err
	}
	cfg = tcfg
	defer func() {
		if logRotator != nil {
			logRotator.Close()
		}
	}()

	// Show version at startup.
	log.Infof("Version %s (Go version %s)", version(), runtime.Version())

	// Read IPC messages from the read end of a pipe created and passed by the
	// parent process, if any.  When this pipe is closed, shutdown is
	// initialized.
	if cfg.PipeRx != nil {
		go serviceControlPipeRx(uintptr(*cfg.PipeRx))
	}

	// Run the pprof profiler if enabled.
	if len(cfg.Profile) > 0 {
		if done(ctx) {
			return ctx.Err()
		}

		profileRedirect := http.RedirectHandler("/debug/pprof", http.StatusSeeOther)
		http.Handle("/", profileRedirect)
		for _, listenAddr := range cfg.Profile {
			listenAddr := listenAddr // copy for closure
			go func() {
				log.Infof("Starting profile server on %s", listenAddr)
				err := http.ListenAndServe(listenAddr, nil)
				if err != nil {
					fatalf("Unable to run profiler: %v", err)
				}
			}()
		}
	}

	// Write mem profile if requested.
	if cfg.MemProfile != "" {
		if done(ctx) {
			return ctx.Err()
		}

		f, err := os.Create(cfg.MemProfile)
		if err != nil {
			log.Errorf("Unable to create cpu profile: %v", err)
			return err
		}
		timer := time.NewTimer(time.Minute * 5) // 5 minutes
		go func() {
			<-timer.C
			pprof.WriteHeapProfile(f)
			f.Close()
		}()
	}

	if done(ctx) {
		return ctx.Err()
	}

	// Create the loader which is used to load and unload the wallet.  If
	// --noinitialload is not set, this function is responsible for loading the
	// wallet.  Otherwise, loading is deferred so it can be performed over RPC.
	dbDir := networkDir(cfg.AppDataDir.Value, activeNet.Params)
	stakeOptions := &ldr.StakeOptions{
		VotingEnabled:       cfg.EnableVoting,
		AddressReuse:        cfg.ReuseAddresses,
		VotingAddress:       cfg.TBOpts.VotingAddress.Address,
		PoolAddress:         cfg.PoolAddress.Address,
		PoolFees:            cfg.PoolFees,
		StakePoolColdExtKey: cfg.StakePoolColdExtKey,
		TicketFee:           cfg.TicketFee.ToCoin(),
	}
	loader := ldr.NewLoader(activeNet.Params, dbDir, stakeOptions,
		cfg.AddrIdxScanLen, cfg.AllowHighFees, cfg.RelayFee.ToCoin())

	// Stop any services started by the loader after the shutdown procedure is
	// initialized and this function returns.
	defer func() {
		err := loader.UnloadWallet()
		if err != nil && err != ldr.ErrWalletNotLoaded {
			log.Errorf("Failed to close wallet: %v", err)
		} else if err == nil {
			log.Infof("Closed wallet")
		}
	}()

	// Open the wallet when --noinitialload was not set.
	passphrase := []byte{}
	if !cfg.NoInitialLoad {
		walletPass := []byte(cfg.WalletPass)
		defer zero.Bytes(walletPass)

		if cfg.PromptPublicPass {
			os.Stdout.Sync()
			for {
				reader := bufio.NewReader(os.Stdin)
				walletPass, err = prompt.PassPrompt(reader, "Enter public wallet passphrase", false)
				if err != nil {
					fmt.Println("Failed to input password. Please try again.")
					continue
				}
				break
			}
		}

		if done(ctx) {
			return ctx.Err()
		}

		// Load the wallet.  It must have been created already or this will
		// return an appropriate error.
		w, err := loader.OpenExistingWallet(walletPass)
		if err != nil {
			log.Errorf("Failed to open wallet: %v", err)
			return err
		}

		if done(ctx) {
			return ctx.Err()
		}

		// TODO(jrick): I think that this prompt should be removed
		// entirely instead of enabling it when --noinitialload is
		// unset.  It can be replaced with an RPC request (either
		// providing the private passphrase as a parameter, or require
		// unlocking the wallet first) to trigger a full accounts
		// rescan.
		//
		// Until then, since --noinitialload users are expecting to use
		// the wallet only over RPC, disable this feature for them.
		if cfg.Pass != "" {
			err = w.Unlock([]byte(cfg.Pass), nil)
			if err != nil {
				log.Errorf("Incorrect passphrase in pass config setting.")
				return err
			}
			w.SetInitiallyUnlocked(true)
		} else {
			passphrase = startPromptPass(w)
		}
	}

	if done(ctx) {
		return ctx.Err()
	}

	// Create and start the RPC servers to serve wallet client connections.  If
	// any of the servers can not be started, it will be nil.  If none of them
	// can be started, this errors since at least one server must run for the
	// wallet to be useful.
	//
	// Servers will be associated with a loaded wallet if it has already been
	// loaded, or after it is loaded later on.
	gRPCServer, jsonRPCServer, err := startRPCServers(loader)
	if err != nil {
		log.Errorf("Unable to create RPC servers: %v", err)
		return err
	}
	if gRPCServer != nil {
		// Start wallet and voting gRPC services after a wallet is loaded.
		loader.RunAfterLoad(func(w *wallet.Wallet) {
			rpcserver.StartWalletService(gRPCServer, w)
			rpcserver.StartVotingService(gRPCServer, w)
		})
		defer func() {
			log.Warn("Stopping gRPC server...")
			gRPCServer.Stop()
			log.Info("gRPC server shutdown")
		}()
	}
	if jsonRPCServer != nil {
		go func() {
			for range jsonRPCServer.RequestProcessShutdown() {
				requestShutdown()
			}
		}()
		defer func() {
			log.Warn("Stopping JSON-RPC server...")
			jsonRPCServer.Stop()
			log.Info("JSON-RPC server shutdown")
		}()
	}

	// Stop the ticket buyer (if running) on shutdown.  This returns an error
	// that can be ignored when the ticket buyer was never started.
	defer loader.StopTicketPurchase()

	// When not running with --noinitialload, it is the main package's
	// responsibility to connect the loaded wallet to the dcrd RPC server for
	// wallet synchronization.  This function blocks until cancelled.
	if !cfg.NoInitialLoad {
		if done(ctx) {
			return ctx.Err()
		}

		rpcClientConnectLoop(ctx, passphrase, jsonRPCServer, loader)
	}

	// Wait until shutdown is signaled before returning and running deferred
	// shutdown tasks.
	<-ctx.Done()
	return ctx.Err()
}

// startPromptPass prompts the user for a password to unlock their wallet in
// the event that it was restored from seed or --promptpass flag is set.
func startPromptPass(w *wallet.Wallet) []byte {
	promptPass := cfg.PromptPass

	// Watching only wallets never require a password.
	if w.Manager.WatchingOnly() {
		return nil
	}

	// The wallet is totally desynced, so we need to resync accounts.
	// Prompt for the password. Then, set the flag it wallet so it
	// knows which address functions to call when resyncing.
	needSync, err := w.NeedsAccountsSync()
	if err != nil {
		log.Errorf("Error determining whether an accounts sync is necessary: %v", err)
	}
	if err == nil && needSync {
		fmt.Println("*** ATTENTION ***")
		fmt.Println("Since this is your first time running we need to sync accounts. Please enter")
		fmt.Println("the private wallet passphrase. This will complete syncing of the wallet")
		fmt.Println("accounts and then leave your wallet unlocked. You may relock wallet after by")
		fmt.Println("calling 'walletlock' through the RPC.")
		fmt.Println("*****************")
		promptPass = true
	}
	if cfg.EnableTicketBuyer {
		promptPass = true
	}

	if !promptPass {
		return nil
	}
	w.SetInitiallyUnlocked(true)
	os.Stdout.Sync()

	// We need to rescan accounts for the initial sync. Unlock the
	// wallet after prompting for the passphrase. The special case
	// of a --createtemp simnet wallet is handled by first
	// attempting to automatically open it with the default
	// passphrase. The wallet should also request to be unlocked
	// if stake mining is currently on, so users with this flag
	// are prompted here as well.
	for {
		if w.ChainParams() == &chaincfg.SimNetParams {
			var unlockAfter <-chan time.Time
			err := w.Unlock(wallet.SimulationPassphrase, unlockAfter)
			if err == nil {
				// Unlock success with the default password.
				return wallet.SimulationPassphrase
			}
		}
		os.Stdout.Sync()
		reader := bufio.NewReader(os.Stdin)
		passphrase, err := prompt.PassPrompt(reader, "Enter private passphrase", false)
		if err != nil {
			fmt.Println("Failed to input password. Please try again.")
			continue
		}

		var unlockAfter <-chan time.Time
		err = w.Unlock(passphrase, unlockAfter)
		if err != nil {
			fmt.Println("Incorrect password entered. Please " +
				"try again.")
			continue
		}
		return passphrase
	}
}

// rpcClientConnectLoop loops forever, attempting to create a connection to the
// consensus RPC server.  If this connection succeeds, the RPC client is used as
// the loaded wallet's network backend and used to keep the wallet synchronized
// to the network.  If/when the RPC connection is lost, the wallet is
// disassociated from the client and a new connection is attempmted.
//
// The JSON-RPC server is optional.  If set, the connected RPC client will be
// associated with the server for RPC passthrough and to enable additional
// methods.
//
// This function panics if the wallet has not already been loaded.
func rpcClientConnectLoop(ctx context.Context, passphrase []byte, jsonRPCServer *legacyrpc.Server, loader *ldr.Loader) {
	w, ok := loader.LoadedWallet()
	if !ok {
		panic("rpcClientConnectLoop: called without loaded wallet")
	}

	certs := readCAFile()

	for {
		chainClient, err := startChainRPC(ctx, certs)
		if err != nil {
			return
		}

		n := chain.BackendFromRPCClient(chainClient.Client)
		w.SetNetworkBackend(n)
		if jsonRPCServer != nil {
			jsonRPCServer.SetChainServer(chainClient)
		}
		loader.SetChainClient(chainClient.Client)

		if cfg.EnableTicketBuyer {
			err = loader.StartTicketPurchase(passphrase, &cfg.tbCfg)
			if err != nil {
				log.Errorf("Unable to start ticket buyer: %v", err)
			}
		}

		// Run wallet synchronization until it is cancelled or errors.  If the
		// context was cancelled, return immediately instead of trying to
		// reconnect.
		syncer := chain.NewRPCSyncer(w, chainClient)
		err = syncer.Run(ctx, true)
		if err == context.Canceled {
			return
		}
		if err != nil {
			syncLog.Errorf("Wallet synchronization stopped: %v", err)
		}

		// Disassociate the RPC client from all subsystems until reconnection
		// occurs.
		w.SetNetworkBackend(nil)
		if jsonRPCServer != nil {
			jsonRPCServer.SetChainServer(nil)
		}
		loader.SetChainClient(nil)
		loader.StopTicketPurchase()
	}
}

func readCAFile() []byte {
	// Read certificate file if TLS is not disabled.
	var certs []byte
	if !cfg.DisableClientTLS {
		var err error
		certs, err = ioutil.ReadFile(cfg.CAFile.Value)
		if err != nil {
			log.Warnf("Cannot open CA file: %v", err)
			// If there's an error reading the CA file, continue
			// with nil certs and without the client connection.
			certs = nil
		}
	} else {
		log.Info("Chain server RPC TLS is disabled")
	}

	return certs
}

// startChainRPC opens a RPC client connection to a dtcd server for blockchain
// services.  This function uses the RPC options from the global config and
// there is no recovery in case the server is not available or if there is an
// authentication error.  Instead, all requests to the client will simply error.
func startChainRPC(ctx context.Context, certs []byte) (*chain.RPCClient, error) {
	log.Infof("Attempting RPC client connection to %v", cfg.RPCConnect)
	rpcc, err := chain.NewRPCClient(activeNet.Params, cfg.RPCConnect,
		cfg.DcrdUsername, cfg.DcrdPassword, certs, cfg.DisableClientTLS)
	if err != nil {
		return nil, err
	}
	err = rpcc.Start(ctx, true)
	return rpcc, err
}
<commit_msg>set passphrase for ticketbuyer config<commit_after>// Copyright (c) 2013-2015 The btcsuite developers
// Copyright (c) 2015-2017 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.

package main

import (
	"bufio"
	"context"
	"fmt"
	"io/ioutil"
	"net/http"
	_ "net/http/pprof"
	"os"
	"runtime"
	"runtime/pprof"
	"time"

	"github.com/decred/dcrd/chaincfg"
	"github.com/decred/dcrwallet/chain"
	"github.com/decred/dcrwallet/internal/prompt"
	"github.com/decred/dcrwallet/internal/zero"
	ldr "github.com/decred/dcrwallet/loader"
	"github.com/decred/dcrwallet/rpc/legacyrpc"
	"github.com/decred/dcrwallet/rpc/rpcserver"
	"github.com/decred/dcrwallet/wallet"
)

var (
	cfg *config
)

func main() {
	// Create a context that is cancelled when a shutdown request is received
	// through an interrupt signal or an RPC request.
	ctx := withShutdownCancel(context.Background())
	go shutdownListener()

	// Run the wallet until permanent failure or shutdown is requested.
	if err := run(ctx); err != nil && err != context.Canceled {
		os.Exit(1)
	}
}

// done returns whether the context's Done channel was closed due to
// cancellation or exceeded deadline.
func done(ctx context.Context) bool {
	select {
	case <-ctx.Done():
		return true
	default:
		return false
	}
}

// run is the main startup and teardown logic performed by the main package.  It
// is responsible for parsing the config, starting RPC servers, loading and
// syncing the wallet (if necessary), and stopping all started services when the
// context is cancelled.
func run(ctx context.Context) error {
	// Load configuration and parse command line.  This function also
	// initializes logging and configures it accordingly.
	tcfg, _, err := loadConfig()
	if err != nil {
		return err
	}
	cfg = tcfg
	defer func() {
		if logRotator != nil {
			logRotator.Close()
		}
	}()

	// Show version at startup.
	log.Infof("Version %s (Go version %s)", version(), runtime.Version())

	// Read IPC messages from the read end of a pipe created and passed by the
	// parent process, if any.  When this pipe is closed, shutdown is
	// initialized.
	if cfg.PipeRx != nil {
		go serviceControlPipeRx(uintptr(*cfg.PipeRx))
	}

	// Run the pprof profiler if enabled.
	if len(cfg.Profile) > 0 {
		if done(ctx) {
			return ctx.Err()
		}

		profileRedirect := http.RedirectHandler("/debug/pprof", http.StatusSeeOther)
		http.Handle("/", profileRedirect)
		for _, listenAddr := range cfg.Profile {
			listenAddr := listenAddr // copy for closure
			go func() {
				log.Infof("Starting profile server on %s", listenAddr)
				err := http.ListenAndServe(listenAddr, nil)
				if err != nil {
					fatalf("Unable to run profiler: %v", err)
				}
			}()
		}
	}

	// Write mem profile if requested.
	if cfg.MemProfile != "" {
		if done(ctx) {
			return ctx.Err()
		}

		f, err := os.Create(cfg.MemProfile)
		if err != nil {
			log.Errorf("Unable to create cpu profile: %v", err)
			return err
		}
		timer := time.NewTimer(time.Minute * 5) // 5 minutes
		go func() {
			<-timer.C
			pprof.WriteHeapProfile(f)
			f.Close()
		}()
	}

	if done(ctx) {
		return ctx.Err()
	}

	// Create the loader which is used to load and unload the wallet.  If
	// --noinitialload is not set, this function is responsible for loading the
	// wallet.  Otherwise, loading is deferred so it can be performed over RPC.
	dbDir := networkDir(cfg.AppDataDir.Value, activeNet.Params)
	stakeOptions := &ldr.StakeOptions{
		VotingEnabled:       cfg.EnableVoting,
		AddressReuse:        cfg.ReuseAddresses,
		VotingAddress:       cfg.TBOpts.VotingAddress.Address,
		PoolAddress:         cfg.PoolAddress.Address,
		PoolFees:            cfg.PoolFees,
		StakePoolColdExtKey: cfg.StakePoolColdExtKey,
		TicketFee:           cfg.TicketFee.ToCoin(),
	}
	loader := ldr.NewLoader(activeNet.Params, dbDir, stakeOptions,
		cfg.AddrIdxScanLen, cfg.AllowHighFees, cfg.RelayFee.ToCoin())

	// Stop any services started by the loader after the shutdown procedure is
	// initialized and this function returns.
	defer func() {
		err := loader.UnloadWallet()
		if err != nil && err != ldr.ErrWalletNotLoaded {
			log.Errorf("Failed to close wallet: %v", err)
		} else if err == nil {
			log.Infof("Closed wallet")
		}
	}()

	// Open the wallet when --noinitialload was not set.
	passphrase := []byte{}
	if !cfg.NoInitialLoad {
		walletPass := []byte(cfg.WalletPass)
		defer zero.Bytes(walletPass)

		if cfg.PromptPublicPass {
			os.Stdout.Sync()
			for {
				reader := bufio.NewReader(os.Stdin)
				walletPass, err = prompt.PassPrompt(reader, "Enter public wallet passphrase", false)
				if err != nil {
					fmt.Println("Failed to input password. Please try again.")
					continue
				}
				break
			}
		}

		if done(ctx) {
			return ctx.Err()
		}

		// Load the wallet.  It must have been created already or this will
		// return an appropriate error.
		w, err := loader.OpenExistingWallet(walletPass)
		if err != nil {
			log.Errorf("Failed to open wallet: %v", err)
			return err
		}

		if done(ctx) {
			return ctx.Err()
		}

		// TODO(jrick): I think that this prompt should be removed
		// entirely instead of enabling it when --noinitialload is
		// unset.  It can be replaced with an RPC request (either
		// providing the private passphrase as a parameter, or require
		// unlocking the wallet first) to trigger a full accounts
		// rescan.
		//
		// Until then, since --noinitialload users are expecting to use
		// the wallet only over RPC, disable this feature for them.
		if cfg.Pass != "" {
			passphrase = []byte(cfg.Pass)
			err = w.Unlock(passphrase, nil)
			if err != nil {
				log.Errorf("Incorrect passphrase in pass config setting.")
				return err
			}
			w.SetInitiallyUnlocked(true)
		} else {
			passphrase = startPromptPass(w)
		}
	}

	if done(ctx) {
		return ctx.Err()
	}

	// Create and start the RPC servers to serve wallet client connections.  If
	// any of the servers can not be started, it will be nil.  If none of them
	// can be started, this errors since at least one server must run for the
	// wallet to be useful.
	//
	// Servers will be associated with a loaded wallet if it has already been
	// loaded, or after it is loaded later on.
	gRPCServer, jsonRPCServer, err := startRPCServers(loader)
	if err != nil {
		log.Errorf("Unable to create RPC servers: %v", err)
		return err
	}
	if gRPCServer != nil {
		// Start wallet and voting gRPC services after a wallet is loaded.
		loader.RunAfterLoad(func(w *wallet.Wallet) {
			rpcserver.StartWalletService(gRPCServer, w)
			rpcserver.StartVotingService(gRPCServer, w)
		})
		defer func() {
			log.Warn("Stopping gRPC server...")
			gRPCServer.Stop()
			log.Info("gRPC server shutdown")
		}()
	}
	if jsonRPCServer != nil {
		go func() {
			for range jsonRPCServer.RequestProcessShutdown() {
				requestShutdown()
			}
		}()
		defer func() {
			log.Warn("Stopping JSON-RPC server...")
			jsonRPCServer.Stop()
			log.Info("JSON-RPC server shutdown")
		}()
	}

	// Stop the ticket buyer (if running) on shutdown.  This returns an error
	// that can be ignored when the ticket buyer was never started.
	defer loader.StopTicketPurchase()

	// When not running with --noinitialload, it is the main package's
	// responsibility to connect the loaded wallet to the dcrd RPC server for
	// wallet synchronization.  This function blocks until cancelled.
	if !cfg.NoInitialLoad {
		if done(ctx) {
			return ctx.Err()
		}

		rpcClientConnectLoop(ctx, passphrase, jsonRPCServer, loader)
	}

	// Wait until shutdown is signaled before returning and running deferred
	// shutdown tasks.
	<-ctx.Done()
	return ctx.Err()
}

// startPromptPass prompts the user for a password to unlock their wallet in
// the event that it was restored from seed or --promptpass flag is set.
func startPromptPass(w *wallet.Wallet) []byte {
	promptPass := cfg.PromptPass

	// Watching only wallets never require a password.
	if w.Manager.WatchingOnly() {
		return nil
	}

	// The wallet is totally desynced, so we need to resync accounts.
	// Prompt for the password. Then, set the flag it wallet so it
	// knows which address functions to call when resyncing.
	needSync, err := w.NeedsAccountsSync()
	if err != nil {
		log.Errorf("Error determining whether an accounts sync is necessary: %v", err)
	}
	if err == nil && needSync {
		fmt.Println("*** ATTENTION ***")
		fmt.Println("Since this is your first time running we need to sync accounts. Please enter")
		fmt.Println("the private wallet passphrase. This will complete syncing of the wallet")
		fmt.Println("accounts and then leave your wallet unlocked. You may relock wallet after by")
		fmt.Println("calling 'walletlock' through the RPC.")
		fmt.Println("*****************")
		promptPass = true
	}
	if cfg.EnableTicketBuyer {
		promptPass = true
	}

	if !promptPass {
		return nil
	}
	w.SetInitiallyUnlocked(true)
	os.Stdout.Sync()

	// We need to rescan accounts for the initial sync. Unlock the
	// wallet after prompting for the passphrase. The special case
	// of a --createtemp simnet wallet is handled by first
	// attempting to automatically open it with the default
	// passphrase. The wallet should also request to be unlocked
	// if stake mining is currently on, so users with this flag
	// are prompted here as well.
	for {
		if w.ChainParams() == &chaincfg.SimNetParams {
			var unlockAfter <-chan time.Time
			err := w.Unlock(wallet.SimulationPassphrase, unlockAfter)
			if err == nil {
				// Unlock success with the default password.
				return wallet.SimulationPassphrase
			}
		}
		os.Stdout.Sync()
		reader := bufio.NewReader(os.Stdin)
		passphrase, err := prompt.PassPrompt(reader, "Enter private passphrase", false)
		if err != nil {
			fmt.Println("Failed to input password. Please try again.")
			continue
		}

		var unlockAfter <-chan time.Time
		err = w.Unlock(passphrase, unlockAfter)
		if err != nil {
			fmt.Println("Incorrect password entered. Please " +
				"try again.")
			continue
		}
		return passphrase
	}
}

// rpcClientConnectLoop loops forever, attempting to create a connection to the
// consensus RPC server.  If this connection succeeds, the RPC client is used as
// the loaded wallet's network backend and used to keep the wallet synchronized
// to the network.  If/when the RPC connection is lost, the wallet is
// disassociated from the client and a new connection is attempmted.
//
// The JSON-RPC server is optional.  If set, the connected RPC client will be
// associated with the server for RPC passthrough and to enable additional
// methods.
//
// This function panics if the wallet has not already been loaded.
func rpcClientConnectLoop(ctx context.Context, passphrase []byte, jsonRPCServer *legacyrpc.Server, loader *ldr.Loader) {
	w, ok := loader.LoadedWallet()
	if !ok {
		panic("rpcClientConnectLoop: called without loaded wallet")
	}

	certs := readCAFile()

	for {
		chainClient, err := startChainRPC(ctx, certs)
		if err != nil {
			return
		}

		n := chain.BackendFromRPCClient(chainClient.Client)
		w.SetNetworkBackend(n)
		if jsonRPCServer != nil {
			jsonRPCServer.SetChainServer(chainClient)
		}
		loader.SetChainClient(chainClient.Client)

		if cfg.EnableTicketBuyer {
			err = loader.StartTicketPurchase(passphrase, &cfg.tbCfg)
			if err != nil {
				log.Errorf("Unable to start ticket buyer: %v", err)
			}
		}

		// Run wallet synchronization until it is cancelled or errors.  If the
		// context was cancelled, return immediately instead of trying to
		// reconnect.
		syncer := chain.NewRPCSyncer(w, chainClient)
		err = syncer.Run(ctx, true)
		if err == context.Canceled {
			return
		}
		if err != nil {
			syncLog.Errorf("Wallet synchronization stopped: %v", err)
		}

		// Disassociate the RPC client from all subsystems until reconnection
		// occurs.
		w.SetNetworkBackend(nil)
		if jsonRPCServer != nil {
			jsonRPCServer.SetChainServer(nil)
		}
		loader.SetChainClient(nil)
		loader.StopTicketPurchase()
	}
}

func readCAFile() []byte {
	// Read certificate file if TLS is not disabled.
	var certs []byte
	if !cfg.DisableClientTLS {
		var err error
		certs, err = ioutil.ReadFile(cfg.CAFile.Value)
		if err != nil {
			log.Warnf("Cannot open CA file: %v", err)
			// If there's an error reading the CA file, continue
			// with nil certs and without the client connection.
			certs = nil
		}
	} else {
		log.Info("Chain server RPC TLS is disabled")
	}

	return certs
}

// startChainRPC opens a RPC client connection to a dtcd server for blockchain
// services.  This function uses the RPC options from the global config and
// there is no recovery in case the server is not available or if there is an
// authentication error.  Instead, all requests to the client will simply error.
func startChainRPC(ctx context.Context, certs []byte) (*chain.RPCClient, error) {
	log.Infof("Attempting RPC client connection to %v", cfg.RPCConnect)
	rpcc, err := chain.NewRPCClient(activeNet.Params, cfg.RPCConnect,
		cfg.DcrdUsername, cfg.DcrdPassword, certs, cfg.DisableClientTLS)
	if err != nil {
		return nil, err
	}
	err = rpcc.Start(ctx, true)
	return rpcc, err
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/Terry-Mao/goim/define"
	"sync"
)

// Bucket is a channel holder.
type Bucket struct {
	cLock       sync.RWMutex                    // protect the channels for chs
	chs         map[string]*Channel             // map sub key to a channel
	rooms       map[int32]map[*Channel]struct{} // map room id with channels
	roomChannel int
}

// NewBucket new a bucket struct. store the subkey with im channel.
func NewBucket(channel, room, roomChannel, cliProto, svrProto int) *Bucket {
	b := new(Bucket)
	b.chs = make(map[string]*Channel, channel)
	b.rooms = make(map[int32]map[*Channel]struct{}, room)
	b.roomChannel = roomChannel
	return b
}

// Put put a channel according with sub key.
func (b *Bucket) Put(subKey string, ch *Channel) {
	var (
		room map[*Channel]struct{}
		ok   bool
	)
	b.cLock.Lock()
	b.chs[subKey] = ch
	if ch.RoomId != define.NoRoom {
		if room, ok = b.rooms[ch.RoomId]; !ok {
			room = make(map[*Channel]struct{}, b.roomChannel)
			b.rooms[ch.RoomId] = room
		}
		room[ch] = struct{}{}
	}
	b.cLock.Unlock()
}

// Get get a channel by sub key.
func (b *Bucket) Get(subKey string) *Channel {
	var ch *Channel
	b.cLock.RLock()
	ch = b.chs[subKey]
	b.cLock.RUnlock()
	return ch
}

// Del delete the channel by sub key.
func (b *Bucket) Del(subKey string) {
	var (
		ok   bool
		ch   *Channel
		room map[*Channel]struct{}
	)
	b.cLock.Lock()
	if ch, ok = b.chs[subKey]; ok {
		delete(b.chs, subKey)
		if ch.RoomId != define.NoRoom {
			if room, ok = b.rooms[ch.RoomId]; ok {
				delete(room, ch)
			}
		}
	}
	b.cLock.Unlock()
}

// Broadcast push msgs to all channels in the bucket.
func (b *Bucket) Broadcast(ver int16, operation int32, msg []byte) {
	var (
		i   = 0
		ch  *Channel
		chs []*Channel
	)
	b.cLock.RLock()
	chs = make([]*Channel, len(b.chs))
	for _, ch = range b.chs {
		chs[i] = ch
		i++
	}
	b.cLock.RUnlock()
	for _, ch = range chs {
		// ignore error
		ch.PushMsg(ver, operation, msg)
	}
}

// Broadcast push msgs to all channels in the bucket's room.
func (b *Bucket) BroadcastRoom(rid int32, ver int16, operation int32, msg []byte) {
	var (
		i    = 0
		ok   bool
		ch   *Channel
		chs  []*Channel
		room map[*Channel]struct{}
	)
	b.cLock.RLock()
	if room, ok = b.rooms[rid]; ok && len(room) > 0 {
		chs = make([]*Channel, len(room))
		for ch, _ = range room {
			chs[i] = ch
			i++
		}
	}
	b.cLock.RUnlock()
	for _, ch = range chs {
		// ignore error
		ch.PushMsg(ver, operation, msg)
	}
}

// Rooms get all room id where online number > 0.
func (b *Bucket) Rooms() (res map[int32]bool) {
	var (
		roomId int32
		room   map[*Channel]struct{}
	)
	b.cLock.RLock()
	res = make(map[int32]bool, len(b.rooms))
	for roomId, room = range b.rooms {
		if len(room) > 0 {
			res[roomId] = true
		}
	}
	b.cLock.RUnlock()
	return
}
<commit_msg>fix bug<commit_after>package main

import (
	"github.com/Terry-Mao/goim/define"
	"sync"
)

// Bucket is a channel holder.
type Bucket struct {
	cLock       sync.RWMutex                    // protect the channels for chs
	chs         map[string]*Channel             // map sub key to a channel
	rooms       map[int32]map[*Channel]struct{} // map room id with channels
	roomChannel int
}

// NewBucket new a bucket struct. store the subkey with im channel.
func NewBucket(channel, room, roomChannel, cliProto, svrProto int) *Bucket {
	b := new(Bucket)
	b.chs = make(map[string]*Channel, channel)
	b.rooms = make(map[int32]map[*Channel]struct{}, room)
	b.roomChannel = roomChannel
	return b
}

// Put put a channel according with sub key.
func (b *Bucket) Put(subKey string, ch *Channel) {
	var (
		room map[*Channel]struct{}
		ok   bool
	)
	b.cLock.Lock()
	b.chs[subKey] = ch
	if ch.RoomId != define.NoRoom {
		if room, ok = b.rooms[ch.RoomId]; !ok {
			room = make(map[*Channel]struct{}, b.roomChannel)
			b.rooms[ch.RoomId] = room
		}
		room[ch] = struct{}{}
	}
	b.cLock.Unlock()
}

// Get get a channel by sub key.
func (b *Bucket) Get(subKey string) *Channel {
	var ch *Channel
	b.cLock.RLock()
	ch = b.chs[subKey]
	b.cLock.RUnlock()
	return ch
}

// Del delete the channel by sub key.
func (b *Bucket) Del(subKey string) {
	var (
		ok   bool
		ch   *Channel
		room map[*Channel]struct{}
	)
	b.cLock.Lock()
	if ch, ok = b.chs[subKey]; ok {
		delete(b.chs, subKey)
		if ch.RoomId != define.NoRoom {
			if room, ok = b.rooms[ch.RoomId]; ok {
				delete(room, ch)
			}
		}
	}
	b.cLock.Unlock()
}

// Broadcast push msgs to all channels in the bucket.
func (b *Bucket) Broadcast(ver int16, operation int32, msg []byte) {
	var (
		i   = 0
		ch  *Channel
		chs []*Channel
	)
	b.cLock.RLock()
	chs = make([]*Channel, len(b.chs))
	for _, ch = range b.chs {
		chs[i] = ch
		i++
	}
	b.cLock.RUnlock()
	for _, ch = range chs {
		// ignore error
		ch.PushMsg(ver, operation, msg)
	}
}

// Broadcast push msgs to all channels in the bucket's room.
func (b *Bucket) BroadcastRoom(rid int32, ver int16, operation int32, msg []byte) {
	var (
		i    = 0
		ok   bool
		ch   *Channel
		chs  []*Channel
		room map[*Channel]struct{}
	)
	b.cLock.RLock()
	if room, ok = b.rooms[rid]; ok && len(room) > 0 {
		chs = make([]*Channel, len(room))
		for ch, _ = range room {
			chs[i] = ch
			i++
		}
	}
	b.cLock.RUnlock()
	for _, ch = range chs {
		// ignore error
		ch.PushMsg(ver, operation, msg)
	}
}

// Rooms get all room id where online number > 0.
func (b *Bucket) Rooms() (res map[int32]struct{}) {
	var (
		roomId int32
		room   map[*Channel]struct{}
	)
	b.cLock.RLock()
	res = make(map[int32]struct{}, len(b.rooms))
	for roomId, room = range b.rooms {
		if len(room) > 0 {
			res[roomId] = struct{}{}
		}
	}
	b.cLock.RUnlock()
	return
}
<|endoftext|>


<commit_before>package command

import (
	"fmt"
	"reflect"

	"github.com/briandowns/wasteband/config"
	"github.com/briandowns/wasteband/utils"
	"github.com/fatih/flags"
	"github.com/mitchellh/cli"
)

// Show holds in the passed in configuration
type Show struct {
	config *config.Configuration
}

// NewShow creates a new instance of Delete
func NewShow(conf *config.Configuration) cli.CommandFactory {
	return func() (cli.Command, error) {
		return &Show{
			config: conf,
		}, nil
	}
}

// Run shows a given resource.
func (s *Show) Run(args []string) int {
	if flags.Has("help", args) || len(args) < 1 {
		fmt.Print(s.Help())
		return 1
	}

	// process the subcommand and it's options
	switch args[0] {
	case "config":
		fmt.Print("\nwasteband config:\n")
		w := utils.NewTabWriter()

		v := reflect.ValueOf(*s.config)

		fmt.Fprint(w, "\n")

		// iterate through the values of the struct and write to the tabwriter
		for i := 0; i < v.NumField(); i++ {
			fmt.Fprintf(w, "%s\t%v\n", v.Type().Field(i).Name, v.Field(i).Interface())
		}

		fmt.Fprintf(w, "\n")
		w.Flush()
	case "indices":
		// most of this functionaity will have to wait on PR 209 in the elastigo
		// project to be merged.
		result := utils.ESConn(s.config.Endpoint).GetCatIndexInfo("")

		w := utils.NewTabWriter()

		fmt.Fprintf(w, "\nHealth\tName\tShards\tReplicas\tDocuments\tSize")
		fmt.Fprintf(w, "\n----------\t----------\t----------\t----------\t----------\t----------\n")

		for _, i := range result {
			fmt.Fprintf(w, "%s\t%s\t%d\t%d\t%d\t%d\n",
				i.Health, i.Name, i.Shards, i.Replicas, i.Docs.Deleted, i.Store.Size)
		}

		fmt.Fprintf(w, "\n")
		w.Flush()
	case "cluster-health":
		result, err := utils.ESConn(s.config.Endpoint).Health()
		if err != nil {
			fmt.Printf("ERROR: %v\n", err.Error())
			return 1
		}

		fmt.Printf("\nCluster Health: %s\n", s.config.Name)
		w := utils.NewTabWriter()

		v := reflect.ValueOf(result)

		fmt.Fprint(w, "\n")

		// iterate through the values of the struct and write to the tabwriter
		for i := 0; i < v.NumField(); i++ {
			// colorize the value of Status field based on it's content
			switch v.Field(i).Interface() {
			case "green":
				fmt.Fprintf(w, "%s\t%v\n",
					v.Type().Field(i).Name, green(v.Field(i).Interface().(string)))
				continue
			case "yellow":
				fmt.Fprintf(w, "%s\t%v\n",
					v.Type().Field(i).Name, yellow(v.Field(i).Interface().(string)))
				continue
			case "red":
				fmt.Fprintf(w, "%s\t%v\n",
					v.Type().Field(i).Name, red(v.Field(i).Interface().(string)))
				continue
			}
			fmt.Fprintf(w, "%s\t%v\n",
				v.Type().Field(i).Name, v.Field(i).Interface())
		}

		fmt.Fprintf(w, "\n")
		w.Flush() // dump out of writing to the tabwriter / os.Stdout
	case "cluster-stats":
		//
	default:
		fmt.Println("ERROR: invalid option for show\n")
	}

	return 1
}

// Help provides full help inforamation for the subcommand
func (s *Show) Help() string {
	return `Usage: wasteband show <option> <arguments>

  Show a resource

Options:

  config            Display the current wasteband configuration
  indexes           Display all indexes
  cluster-health    Display the health of the cluster
  cluster-state     Display the state of the cluster
  cluster-stats     Display the stats from the cluster

`
}

// Synopsis provides a brief description of the command
func (s *Show) Synopsis() string {
	return "Show an Elasticsearch resource"
}
<commit_msg>marker for later work<commit_after>package command

import (
	"fmt"
	"reflect"

	"github.com/briandowns/wasteband/config"
	"github.com/briandowns/wasteband/utils"
	"github.com/fatih/flags"
	"github.com/mitchellh/cli"
)

// Show holds in the passed in configuration
type Show struct {
	config *config.Configuration
}

// NewShow creates a new instance of Delete
func NewShow(conf *config.Configuration) cli.CommandFactory {
	return func() (cli.Command, error) {
		return &Show{
			config: conf,
		}, nil
	}
}

// Run shows a given resource.
func (s *Show) Run(args []string) int {
	if flags.Has("help", args) || len(args) < 1 {
		fmt.Print(s.Help())
		return 1
	}

	// process the subcommand and it's options
	switch args[0] {
	case "config":
		/*

			Fix all of the code below.  THere's no reason for reflection here!!!

		*/
		fmt.Print("\nwasteband config:\n")
		w := utils.NewTabWriter()

		v := reflect.ValueOf(*s.config)

		fmt.Fprint(w, "\n")

		// iterate through the values of the struct and write to the tabwriter
		for i := 0; i < v.NumField(); i++ {
			fmt.Fprintf(w, "%s\t%v\n", v.Type().Field(i).Name, v.Field(i).Interface())
		}

		fmt.Fprintf(w, "\n")
		w.Flush()
	case "indices":
		// most of this functionaity will have to wait on PR 209 in the elastigo
		// project to be merged.
		result := utils.ESConn(s.config.Endpoint).GetCatIndexInfo("")

		w := utils.NewTabWriter()

		fmt.Fprintf(w, "\nHealth\tName\tShards\tReplicas\tDocuments\tSize")
		fmt.Fprintf(w, "\n----------\t----------\t----------\t----------\t----------\t----------\n")

		for _, i := range result {
			fmt.Fprintf(w, "%s\t%s\t%d\t%d\t%d\t%d\n",
				i.Health, i.Name, i.Shards, i.Replicas, i.Docs.Deleted, i.Store.Size)
		}

		fmt.Fprintf(w, "\n")
		w.Flush()
	case "cluster-health":
		result, err := utils.ESConn(s.config.Endpoint).Health()
		if err != nil {
			fmt.Printf("ERROR: %v\n", err.Error())
			return 1
		}

		fmt.Printf("\nCluster Health: %s\n", s.config.Name)
		w := utils.NewTabWriter()

		v := reflect.ValueOf(result)

		fmt.Fprint(w, "\n")

		// iterate through the values of the struct and write to the tabwriter
		for i := 0; i < v.NumField(); i++ {
			// colorize the value of Status field based on it's content
			switch v.Field(i).Interface() {
			case "green":
				fmt.Fprintf(w, "%s\t%v\n",
					v.Type().Field(i).Name, green(v.Field(i).Interface().(string)))
				continue
			case "yellow":
				fmt.Fprintf(w, "%s\t%v\n",
					v.Type().Field(i).Name, yellow(v.Field(i).Interface().(string)))
				continue
			case "red":
				fmt.Fprintf(w, "%s\t%v\n",
					v.Type().Field(i).Name, red(v.Field(i).Interface().(string)))
				continue
			}
			fmt.Fprintf(w, "%s\t%v\n",
				v.Type().Field(i).Name, v.Field(i).Interface())
		}

		fmt.Fprintf(w, "\n")
		w.Flush() // dump out of writing to the tabwriter / os.Stdout
	case "cluster-stats":
		//
	default:
		fmt.Println("ERROR: invalid option for show\n")
	}

	return 1
}

// Help provides full help inforamation for the subcommand
func (s *Show) Help() string {
	return `Usage: wasteband show <option> <arguments>

  Show a resource

Options:

  config            Display the current wasteband configuration
  indexes           Display all indexes
  cluster-health    Display the health of the cluster
  cluster-state     Display the state of the cluster
  cluster-stats     Display the stats from the cluster

`
}

// Synopsis provides a brief description of the command
func (s *Show) Synopsis() string {
	return "Show an Elasticsearch resource"
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"os"
	"testing"

	"github.com/jmhodges/levigo"
	. "launchpad.net/gocheck"
)

// Hook gocheck into the gotest runner.
func Test(t *testing.T) { TestingT(t) }

type CommandSuite struct{}

var _ = Suite(&CommandSuite{})

func (s CommandSuite) SetUpSuite(c *C) {
	os.RemoveAll("db")
	openDB()
}

func MaybeFail(c *C, err error) {
	if err != nil {
		c.Log(err)
		c.FailNow()
	}
}

var tests = []struct {
	command  string
	args     string
	response interface{}
}{
	{"ping", "", "PONG"},
	{"echo", "foo", []byte("foo")},
	{"zadd", "foo 1 bar", uint32(1)},
	{"zadd", "foo 1 bar", uint32(0)},
	{"zadd", "foo 2 bar", uint32(0)},
	{"zadd", "foo 1 baz", uint32(1)},
	{"zadd", "foo 1 baz 2 bar", uint32(0)},
	{"zadd", "foo 5.1 asdf 2 buzz 1 baz 2 bar", uint32(2)},
	{"zadd", "asdf 0.1 bar", uint32(1)},
	{"zadd", "fooz 4e29 bar 0.2 baz", uint32(2)},
	{"zscore", "foo bar", []byte("2")},
	{"zscore", "foo baz", []byte("1")},
	{"zscore", "asdf bar", []byte("0.1")},
	{"zscore", "fooz bar", []byte("4e+29")},
	{"zscore", "fooz bag", nil},
	{"zincrby", "foo 0.1 bar", []byte("2.1")},
	{"zincrby", "foo 1.1 bazz", []byte("1.1")},
	{"zcard", "foo", uint32(5)},
	{"zcard", "fooz", uint32(2)},
	{"zcard", "asdf", uint32(1)},
	{"zcard", "asdfa", uint32(0)},
	{"zrem", "foo bar baz", uint32(2)},
	{"zrem", "foo bar", uint32(0)},
	{"zrem", "asdfa bar", 0},
	{"zcard", "foo", uint32(3)},
	{"zrem", "asdf bar", uint32(1)},
	{"zcard", "asdf", uint32(0)},
	{"zrange", "foo 0 1", []cmdReply{[]byte("bazz"), []byte("buzz")}},
	{"zrange", "foo 0 -1 withscores", []cmdReply{[]byte("bazz"), []byte("1.1"), []byte("buzz"), []byte("2"), []byte("asdf"), []byte("5.1")}},
	{"zrange", "foo 0 -2 WITHSCORES", []cmdReply{[]byte("bazz"), []byte("1.1"), []byte("buzz"), []byte("2")}},
	{"zrange", "foo -1 -1", []cmdReply{[]byte("asdf")}},
	{"zrange", "foo 10 12", []cmdReply{}},
	{"zrange", "foo 2 1", []cmdReply{}},
	{"zrange", "foo -10 -1 withscores", []cmdReply{[]byte("bazz"), []byte("1.1"), []byte("buzz"), []byte("2"), []byte("asdf"), []byte("5.1")}},
	{"zrevrange", "foo 0 1", []cmdReply{[]byte("asdf"), []byte("buzz")}},
	{"zrevrange", "foo 0 -1 withscores", []cmdReply{[]byte("asdf"), []byte("5.1"), []byte("buzz"), []byte("2"), []byte("bazz"), []byte("1.1")}},
	{"zrevrange", "foo 0 -2 WITHSCORES", []cmdReply{[]byte("asdf"), []byte("5.1"), []byte("buzz"), []byte("2")}},
	{"zrevrange", "foo -1 -1", []cmdReply{[]byte("bazz")}},
	{"zrevrange", "foo 10 12", []cmdReply{}},
	{"zrevrange", "foo 2 1", []cmdReply{}},
	{"zrevrange", "foo -10 -1 withscores", []cmdReply{[]byte("asdf"), []byte("5.1"), []byte("buzz"), []byte("2"), []byte("bazz"), []byte("1.1")}},
	{"zadd", "asdf 1 bar", uint32(1)},
	{"del", "foo asdf", 2},
	{"del", "foo asdf", 0},
	{"zcard", "foo", uint32(0)},
	{"zcard", "asdf", uint32(0)},
	{"set", "foo bar", "OK"},
	{"get", "foo", []byte("bar")},
	{"set", "foo baz", "OK"},
	{"get", "foo", []byte("baz")},
	{"del", "foo", 1},
	{"zadd", "asdf 1 bar", uint32(1)},
	{"set", "asdf foo", "OK"},
	{"get", "asdf", []byte("foo")},
	{"sadd", "aset 1 2 3 4 5", uint32(5)},
	{"sadd", "set2 1 a 3", uint32(3)},
	{"sadd", "set3 2 b 4", uint32(3)},
	{"sunion", "aset set2 set3", map[string]bool{"1": true, "2": true, "3": true, "4": true, "5": true, "a": true, "b": true}},
	{"sadd", "aset 1", uint32(0)},
	{"scard", "aset", uint32(5)},
	{"sadd", "aset 6", uint32(1)},
	{"scard", "aset", uint32(6)},
	{"srem", "aset 4 5", uint32(2)},
	{"srem", "aset 4 5", uint32(0)},
	{"scard", "aset", uint32(4)},
	{"sismember", "aset 6", 1},
	{"sismember", "aset 7", 0},
	{"smembers", "aset", []cmdReply{[]byte("1"), []byte("2"), []byte("3"), []byte("6")}},
	{"smembers", "bset", []cmdReply{}},
	{"smove", "aset newset 1", 1},
	{"sismember", "aset 1", 0},
	{"sismember", "newset 1", 1},
	{"smove", "aset newset 1", 0},
	{"del", "aset", 1},
	{"scard", "aset", uint32(0)},
	{"sadd", "bset a", uint32(1)},
	{"spop", "bset", []byte("a")},
	{"scard", "bset", uint32(0)},
	{"hset", "hash foo bar", 1},
	{"hget", "hash foo", []byte("bar")},
	{"hget", "hash0 baz", []byte(nil)},
	{"hset", "hash foo baz", 0},
	{"hget", "hash foo", []byte("baz")},
	{"hlen", "hash", uint32(1)},
	{"hlen", "haz", uint32(0)},
	{"hset", "hash bar baz", 1},
	{"hlen", "hash", uint32(2)},
	{"hmset", "hash2 foo 1 bar 2 baz 3", "OK"},
	{"hmget", "hash2 foo bar test", []cmdReply{[]byte("1"), []byte("2"), []byte(nil)}},
	{"hgetall", "hash2", []cmdReply{[]byte("bar"), []byte("2"), []byte("baz"), []byte("3"), []byte("foo"), []byte("1")}},
	{"hgetall", "hash3", []cmdReply{}},
	{"hkeys", "hash2", []cmdReply{[]byte("bar"), []byte("baz"), []byte("foo")}},
	{"hkeys", "hash3", []cmdReply{}},
	{"hvals", "hash2", []cmdReply{[]byte("2"), []byte("3"), []byte("1")}},
	{"hvals", "hash3", []cmdReply{}},
	{"hexists", "hash2 bar", 1},
	{"hexists", "hash2 bax", 0},
	{"hsetnx", "hash2 foox 3", 1},
	{"hget", "hash2 foox", []byte("3")},
	{"hsetnx", "hash2 foox 4", 0},
	{"hget", "hash2 foox", []byte("3")},
	{"hincrby", "hash2 fooz 3", []byte("3")},
	{"hget", "hash2 fooz", []byte("3")},
	{"hincrby", "hash2 fooz -20", []byte("-17")},
	{"hget", "hash2 fooz", []byte("-17")},
	{"hincrbyfloat", "hash2 fooa 10.50", []byte("10.5")},
	{"hget", "hash2 fooa", []byte("10.5")},
	{"hset", "hash2 fooa 5.0e3", 0},
	{"hincrbyfloat", "hash2 fooa 2.0e2", []byte("5200")},
	{"hget", "hash2 fooa", []byte("5200")},
	{"keys", "hash*", []cmdReply{[]byte("hash"), []byte("hash2")}},
}

func (s CommandSuite) TestCommands(c *C) {
	for _, t := range tests {
		cmd := commands[t.command]
		var wb *levigo.WriteBatch
		if cmd.writes {
			wb = levigo.NewWriteBatch()
		}
		var args [][]byte
		if t.args != "" {
			args = bytes.Split([]byte(t.args), []byte(" "))
		}
		cmd.lockKeys(args)
		res := cmd.function(args, wb)
		if cmd.writes {
			err := DB.Write(DefaultWriteOptions, wb)
			MaybeFail(c, err)
			wb.Close()
		}
		cmd.unlockKeys(args)
		if stream, ok := res.(*cmdReplyStream); ok {
			items := make([]cmdReply, 0, int(stream.size))
			for item := range stream.items {
				items = append(items, item)
			}
			res = items
		}
		c.Assert(res, DeepEquals, t.response, Commentf("%s %s, obtained=%s expected=%s", t.command, t.args, res, t.response))
	}
}
<commit_msg>Add missing DEL hash test case<commit_after>package main

import (
	"bytes"
	"os"
	"testing"

	"github.com/jmhodges/levigo"
	. "launchpad.net/gocheck"
)

// Hook gocheck into the gotest runner.
func Test(t *testing.T) { TestingT(t) }

type CommandSuite struct{}

var _ = Suite(&CommandSuite{})

func (s CommandSuite) SetUpSuite(c *C) {
	os.RemoveAll("db")
	openDB()
}

func MaybeFail(c *C, err error) {
	if err != nil {
		c.Log(err)
		c.FailNow()
	}
}

var tests = []struct {
	command  string
	args     string
	response interface{}
}{
	{"ping", "", "PONG"},
	{"echo", "foo", []byte("foo")},
	{"zadd", "foo 1 bar", uint32(1)},
	{"zadd", "foo 1 bar", uint32(0)},
	{"zadd", "foo 2 bar", uint32(0)},
	{"zadd", "foo 1 baz", uint32(1)},
	{"zadd", "foo 1 baz 2 bar", uint32(0)},
	{"zadd", "foo 5.1 asdf 2 buzz 1 baz 2 bar", uint32(2)},
	{"zadd", "asdf 0.1 bar", uint32(1)},
	{"zadd", "fooz 4e29 bar 0.2 baz", uint32(2)},
	{"zscore", "foo bar", []byte("2")},
	{"zscore", "foo baz", []byte("1")},
	{"zscore", "asdf bar", []byte("0.1")},
	{"zscore", "fooz bar", []byte("4e+29")},
	{"zscore", "fooz bag", nil},
	{"zincrby", "foo 0.1 bar", []byte("2.1")},
	{"zincrby", "foo 1.1 bazz", []byte("1.1")},
	{"zcard", "foo", uint32(5)},
	{"zcard", "fooz", uint32(2)},
	{"zcard", "asdf", uint32(1)},
	{"zcard", "asdfa", uint32(0)},
	{"zrem", "foo bar baz", uint32(2)},
	{"zrem", "foo bar", uint32(0)},
	{"zrem", "asdfa bar", 0},
	{"zcard", "foo", uint32(3)},
	{"zrem", "asdf bar", uint32(1)},
	{"zcard", "asdf", uint32(0)},
	{"zrange", "foo 0 1", []cmdReply{[]byte("bazz"), []byte("buzz")}},
	{"zrange", "foo 0 -1 withscores", []cmdReply{[]byte("bazz"), []byte("1.1"), []byte("buzz"), []byte("2"), []byte("asdf"), []byte("5.1")}},
	{"zrange", "foo 0 -2 WITHSCORES", []cmdReply{[]byte("bazz"), []byte("1.1"), []byte("buzz"), []byte("2")}},
	{"zrange", "foo -1 -1", []cmdReply{[]byte("asdf")}},
	{"zrange", "foo 10 12", []cmdReply{}},
	{"zrange", "foo 2 1", []cmdReply{}},
	{"zrange", "foo -10 -1 withscores", []cmdReply{[]byte("bazz"), []byte("1.1"), []byte("buzz"), []byte("2"), []byte("asdf"), []byte("5.1")}},
	{"zrevrange", "foo 0 1", []cmdReply{[]byte("asdf"), []byte("buzz")}},
	{"zrevrange", "foo 0 -1 withscores", []cmdReply{[]byte("asdf"), []byte("5.1"), []byte("buzz"), []byte("2"), []byte("bazz"), []byte("1.1")}},
	{"zrevrange", "foo 0 -2 WITHSCORES", []cmdReply{[]byte("asdf"), []byte("5.1"), []byte("buzz"), []byte("2")}},
	{"zrevrange", "foo -1 -1", []cmdReply{[]byte("bazz")}},
	{"zrevrange", "foo 10 12", []cmdReply{}},
	{"zrevrange", "foo 2 1", []cmdReply{}},
	{"zrevrange", "foo -10 -1 withscores", []cmdReply{[]byte("asdf"), []byte("5.1"), []byte("buzz"), []byte("2"), []byte("bazz"), []byte("1.1")}},
	{"zadd", "asdf 1 bar", uint32(1)},
	{"del", "foo asdf", 2},
	{"del", "foo asdf", 0},
	{"zcard", "foo", uint32(0)},
	{"zcard", "asdf", uint32(0)},
	{"set", "foo bar", "OK"},
	{"get", "foo", []byte("bar")},
	{"set", "foo baz", "OK"},
	{"get", "foo", []byte("baz")},
	{"del", "foo", 1},
	{"zadd", "asdf 1 bar", uint32(1)},
	{"set", "asdf foo", "OK"},
	{"get", "asdf", []byte("foo")},
	{"sadd", "aset 1 2 3 4 5", uint32(5)},
	{"sadd", "set2 1 a 3", uint32(3)},
	{"sadd", "set3 2 b 4", uint32(3)},
	{"sunion", "aset set2 set3", map[string]bool{"1": true, "2": true, "3": true, "4": true, "5": true, "a": true, "b": true}},
	{"sadd", "aset 1", uint32(0)},
	{"scard", "aset", uint32(5)},
	{"sadd", "aset 6", uint32(1)},
	{"scard", "aset", uint32(6)},
	{"srem", "aset 4 5", uint32(2)},
	{"srem", "aset 4 5", uint32(0)},
	{"scard", "aset", uint32(4)},
	{"sismember", "aset 6", 1},
	{"sismember", "aset 7", 0},
	{"smembers", "aset", []cmdReply{[]byte("1"), []byte("2"), []byte("3"), []byte("6")}},
	{"smembers", "bset", []cmdReply{}},
	{"smove", "aset newset 1", 1},
	{"sismember", "aset 1", 0},
	{"sismember", "newset 1", 1},
	{"smove", "aset newset 1", 0},
	{"del", "aset", 1},
	{"scard", "aset", uint32(0)},
	{"sadd", "bset a", uint32(1)},
	{"spop", "bset", []byte("a")},
	{"scard", "bset", uint32(0)},
	{"hset", "hash foo bar", 1},
	{"hget", "hash foo", []byte("bar")},
	{"hget", "hash0 baz", []byte(nil)},
	{"hset", "hash foo baz", 0},
	{"hget", "hash foo", []byte("baz")},
	{"hlen", "hash", uint32(1)},
	{"hlen", "haz", uint32(0)},
	{"hset", "hash bar baz", 1},
	{"hlen", "hash", uint32(2)},
	{"hmset", "hash2 foo 1 bar 2 baz 3", "OK"},
	{"hmget", "hash2 foo bar test", []cmdReply{[]byte("1"), []byte("2"), []byte(nil)}},
	{"hgetall", "hash2", []cmdReply{[]byte("bar"), []byte("2"), []byte("baz"), []byte("3"), []byte("foo"), []byte("1")}},
	{"hgetall", "hash3", []cmdReply{}},
	{"hkeys", "hash2", []cmdReply{[]byte("bar"), []byte("baz"), []byte("foo")}},
	{"hkeys", "hash3", []cmdReply{}},
	{"hvals", "hash2", []cmdReply{[]byte("2"), []byte("3"), []byte("1")}},
	{"hvals", "hash3", []cmdReply{}},
	{"hexists", "hash2 bar", 1},
	{"hexists", "hash2 bax", 0},
	{"hsetnx", "hash2 foox 3", 1},
	{"hget", "hash2 foox", []byte("3")},
	{"hsetnx", "hash2 foox 4", 0},
	{"hget", "hash2 foox", []byte("3")},
	{"hincrby", "hash2 fooz 3", []byte("3")},
	{"hget", "hash2 fooz", []byte("3")},
	{"hincrby", "hash2 fooz -20", []byte("-17")},
	{"hget", "hash2 fooz", []byte("-17")},
	{"hincrbyfloat", "hash2 fooa 10.50", []byte("10.5")},
	{"hget", "hash2 fooa", []byte("10.5")},
	{"hset", "hash2 fooa 5.0e3", 0},
	{"hincrbyfloat", "hash2 fooa 2.0e2", []byte("5200")},
	{"hget", "hash2 fooa", []byte("5200")},
	{"keys", "hash*", []cmdReply{[]byte("hash"), []byte("hash2")}},
	{"del", "hash2", 1},
	{"hlen", "hash2", uint32(0)},
}

func (s CommandSuite) TestCommands(c *C) {
	for _, t := range tests {
		cmd := commands[t.command]
		var wb *levigo.WriteBatch
		if cmd.writes {
			wb = levigo.NewWriteBatch()
		}
		var args [][]byte
		if t.args != "" {
			args = bytes.Split([]byte(t.args), []byte(" "))
		}
		cmd.lockKeys(args)
		res := cmd.function(args, wb)
		if cmd.writes {
			err := DB.Write(DefaultWriteOptions, wb)
			MaybeFail(c, err)
			wb.Close()
		}
		cmd.unlockKeys(args)
		if stream, ok := res.(*cmdReplyStream); ok {
			items := make([]cmdReply, 0, int(stream.size))
			for item := range stream.items {
				items = append(items, item)
			}
			res = items
		}
		c.Assert(res, DeepEquals, t.response, Commentf("%s %s, obtained=%s expected=%s", t.command, t.args, res, t.response))
	}
}
<|endoftext|>