<commit_before>package trousseau

import (
	"time"

	"github.com/oleiade/trousseau/dsn"
	"os"
	"io"
	"io/ioutil"
	"encoding/json"
	"fmt"
	"strings"
)

func CreateAction(recipients []string) {
	meta := Meta{
		CreatedAt:        time.Now().String(),
		LastModifiedAt:   time.Now().String(),
		Recipients:       recipients,
		TrousseauVersion: TROUSSEAU_VERSION,
	}
	store := NewStore(meta)

	tr := Trousseau{
		CryptoType:      ASYMMETRIC_ENCRYPTION,
		CryptoAlgorithm: GPG_ENCRYPTION,
	}
	tr.Encrypt(store)

	err := tr.Write(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	InfoLogger.Println("Trousseau data store succesfully created")
}

func PushAction(destination string, sshPrivateKey string, askPassword bool) {
	endpointDsn, err := dsn.Parse(destination)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	switch endpointDsn.Scheme {
	case "s3":
		err := endpointDsn.SetDefaults(S3Defaults)
		if err != nil {
			ErrorLogger.Fatal(err)
		}

		err = UploadUsingS3(endpointDsn)
		if err != nil {
			ErrorLogger.Fatal(err)
		}
		InfoLogger.Println("Trousseau data store succesfully pushed to s3")
	case "scp":
		err := endpointDsn.SetDefaults(ScpDefaults)
		if err != nil {
			ErrorLogger.Fatal(err)
		}

		if askPassword == true {
			password := PromptForPassword()
			endpointDsn.Secret = password
		}

		err = UploadUsingScp(endpointDsn, sshPrivateKey)
		if err != nil {
			ErrorLogger.Fatal(err)
		}
		InfoLogger.Println("Trousseau data store succesfully pushed to ssh remote storage")
	case "gist":
		err = UploadUsingGist(endpointDsn)
		if err != nil {
			ErrorLogger.Fatal(err)
		}
		InfoLogger.Println("Trousseau data store succesfully pushed to gist")
	}
}

func PullAction(source string, sshPrivateKey string, askPassword bool) {
	endpointDsn, err := dsn.Parse(source)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	switch endpointDsn.Scheme {
	case "s3":
		err := endpointDsn.SetDefaults(S3Defaults)
		if err != nil {
			ErrorLogger.Fatal(err)
		}

		err = DownloadUsingS3(endpointDsn)
		if err != nil {
			ErrorLogger.Fatal(err)
		}
		InfoLogger.Println("Trousseau data store succesfully pulled from S3")
	case "scp":
		err := endpointDsn.SetDefaults(ScpDefaults)
		if err != nil {
			ErrorLogger.Fatal(err)
		}

		if askPassword == true {
			password := PromptForPassword()
			endpointDsn.Secret = password
		}

		err = DownloadUsingScp(endpointDsn, sshPrivateKey)
		if err != nil {
			ErrorLogger.Fatal(err)
		}
		InfoLogger.Println("Trousseau data store succesfully pulled from ssh remote storage")
	case "gist":
		err = DownloadUsingGist(endpointDsn)
		if err != nil {
			ErrorLogger.Fatal(err)
		}
		InfoLogger.Println("Trousseau data store succesfully pulled from gist")
	default:
		if endpointDsn.Scheme == "" {
			ErrorLogger.Fatalf("No dsn scheme supplied")
		} else {
			ErrorLogger.Fatalf("Invalid dsn scheme supplied: %s", endpointDsn.Scheme)
		}
	}

	InfoLogger.Println("Trousseau data store succesfully pulled from remote storage")
}

func ExportAction(to string, plain bool) {
	outputFile, err := os.Create(to)
	if err != nil {
		ErrorLogger.Fatal(err)
	}
	defer outputFile.Close()

	// Make sure the file is readble/writable only
	// by its owner
	err = os.Chmod(outputFile.Name(), os.FileMode(0600))
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	if plain == true {
		tr, err := OpenTrousseau(InferStorePath())
		if err != nil {
			ErrorLogger.Fatal(err)
		}

		store, err := tr.Decrypt()
		if err != nil {
			ErrorLogger.Fatal(err)
		}

		storeBytes, err := json.Marshal(store)
		if err != nil {
			ErrorLogger.Fatal(err)
		}

		err = ioutil.WriteFile(to, storeBytes, os.FileMode(0600))
		if err != nil {
			ErrorLogger.Fatal(err)
		}
	} else {
		inputFile, err := os.Open(InferStorePath())
		defer inputFile.Close()
		if err != nil {
			ErrorLogger.Fatal(err)
		}

		_, err = io.Copy(outputFile, inputFile)
		if err != nil {
			ErrorLogger.Fatal(err)
		}
	}

	InfoLogger.Println(fmt.Sprintf("Trousseau data store exported to: %s", to))
}

func ImportAction(from string, strategy ImportStrategy, plain bool) {
	var importedStore *Store = &Store{}
	var localFilePath string = InferStorePath()

	localTr, err := OpenTrousseau(localFilePath)
	if err != nil {
	ErrorLogger.Fatal(err)
	}

	localStore, err := localTr.Decrypt()
	if err != nil {
	ErrorLogger.Fatal(err)
	}

	if plain == true {
	importedData, err := ioutil.ReadFile(from)
	if err != nil {
	ErrorLogger.Fatal(err)
	}

	err = json.Unmarshal(importedData, importedStore)
	if err != nil {
	ErrorLogger.Fatal(err)
	}
	} else {
	importedTr, err := OpenTrousseau(from)
	if err != nil {
	ErrorLogger.Fatal(err)
	}

	importedStore, err = importedTr.Decrypt()
	if err != nil {
	ErrorLogger.Fatal(err)
	}
	}

	err = ImportStore(importedStore, localStore, strategy)
	if err != nil {
	ErrorLogger.Fatal(err)
	}

	err = localTr.Encrypt(localStore)
	if err != nil {
	ErrorLogger.Fatal(err)
	}

	err = localTr.Write(localFilePath)
	if err != nil {
	ErrorLogger.Fatal(err)
	}

	InfoLogger.Println(fmt.Sprintf("Trousseau data store imported: %s", from))
}

func ListRecipientsAction() {
	tr, err := OpenTrousseau(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store, err := tr.Decrypt()
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	recipients := store.Meta.ListRecipients()
	for _, r := range recipients {
		InfoLogger.Println(r)
	}
}

func AddRecipientAction(recipient string) {
	tr, err := OpenTrousseau(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store, err := tr.Decrypt()
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = store.Meta.AddRecipient(recipient)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = tr.Encrypt(store)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = tr.Write(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}
}

func RemoveRecipientAction(recipient string) {
	tr, err := OpenTrousseau(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store, err := tr.Decrypt()
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = store.Meta.RemoveRecipient(recipient)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = tr.Encrypt(store)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = tr.Write(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}
}

func GetAction(key string, filepath string) {
	tr, err := OpenTrousseau(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store, err := tr.Decrypt()
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	value, err := store.Data.Get(key)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	// If the --file flag is provided
	if filepath != "" {
		valueBytes, ok := value.(string)
		if !ok {
			ErrorLogger.Fatal(fmt.Sprintf("unable to write %s value to file", key))
		}

		err := ioutil.WriteFile(filepath, []byte(valueBytes), os.FileMode(0644))
		if err != nil {
			ErrorLogger.Fatal(err)
		}
	} else {
		// Use fmt.Print to support patch processes,
		// to get the value "as is" without any appended newlines
		if isPipe(os.Stdout) {
			fmt.Print(value)
		} else {
			InfoLogger.Println(value)
		}
	}
}

func SetAction(key, value, file string) {
	// If the --file flag is provided
	if file != "" {
		// And the file actually exists on file system
		if PathExists(file) {
			// Then load it's content
			fileContent, err := ioutil.ReadFile(file)
			if err != nil {
				ErrorLogger.Fatal(err)
			}

			value = string(fileContent)
		} else {
			ErrorLogger.Fatalf("Cannot open %s because it doesn't exist", file)
		}
	}

	tr, err := OpenTrousseau(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store, err := tr.Decrypt()
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store.Data.Set(key, value)

	err = tr.Encrypt(store)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = tr.Write(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}
}

func RenameAction(src, dest string, overwrite bool) {
	tr, err := OpenTrousseau(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store, err := tr.Decrypt()
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = store.Data.Rename(src, dest, overwrite)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = tr.Encrypt(store)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = tr.Write(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

}

func DelAction(key string) {
	tr, err := OpenTrousseau(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store, err := tr.Decrypt()
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store.Data.Del(key)

	tr.Encrypt(store)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = tr.Write(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}
}

func KeysAction() {
	tr, err := OpenTrousseau(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store, err := tr.Decrypt()
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	keys := store.Data.Keys()
	for _, k := range keys {
		InfoLogger.Println(k)
	}
}

func ShowAction() {
	tr, err := OpenTrousseau(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store, err := tr.Decrypt()
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	items := store.Data.Items()
	for k, v := range items {
		InfoLogger.Println(fmt.Sprintf("%s : %s", k, v.(string)))
	}
}

func MetaAction() {
	tr, err := OpenTrousseau(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store, err := tr.Decrypt()
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	InfoLogger.Println(store.Meta)
}

func UpgradeAction(yes, noBackup bool) {
	var proceed string = "n"

	data, err := ioutil.ReadFile(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	version := DiscoverVersion(data, VersionDiscoverClosures)
	if version == "" {
		fmt.Errorf("Initial store version could not be detected")
	}

	newStoreFile, err := UpgradeFrom(version, data, UpgradeClosures)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	if yes == false {
		fmt.Printf("You are about to upgrade trousseau data "+
					"store %s (version %s) up to version %s. Proceed? [Y/n] ",
			InferStorePath(), version, TROUSSEAU_VERSION)
		_, err = fmt.Scanf("%s", &proceed)
		if err != nil {
			ErrorLogger.Fatal(err)
		}
	}

	if strings.ToLower(proceed) == "y" || yes {
		// Write a backup of the old store file inplace
		if noBackup == false {
			err = ioutil.WriteFile(InferStorePath()+".bkp", data, os.FileMode(0700))
			if err != nil {
				ErrorLogger.Fatal(err)
			}
		}

		// Overwrite source legacy store with the new version content
		err = ioutil.WriteFile(InferStorePath(), newStoreFile, os.FileMode(0700))
		if err != nil {
			ErrorLogger.Fatal(err)
		}
	} else {
		fmt.Println("upgrade cancelled")
	}
}

// Thanks, mrnugget!
// https://github.com/mrnugget/fzz/blob/master/utils.go#L14-L21
func isPipe(f *os.File) bool {
	s, err := f.Stat()
	if err != nil {
		return false
	}

	return s.Mode()&os.ModeNamedPipe != 0
}

<commit_msg>Throw error when recipient does not exist on create command [fix #89]<commit_after>package trousseau

import (
	"time"

	"github.com/oleiade/trousseau/dsn"
	"os"
	"io"
	"io/ioutil"
	"encoding/json"
	"fmt"
	"strings"
)

func CreateAction(recipients []string) {
	meta := Meta{
		CreatedAt:        time.Now().String(),
		LastModifiedAt:   time.Now().String(),
		Recipients:       recipients,
		TrousseauVersion: TROUSSEAU_VERSION,
	}
	store := NewStore(meta)

	tr := Trousseau{
		CryptoType:      ASYMMETRIC_ENCRYPTION,
		CryptoAlgorithm: GPG_ENCRYPTION,
	}

	err := tr.Encrypt(store)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = tr.Write(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	InfoLogger.Println("Trousseau data store succesfully created")
}

func PushAction(destination string, sshPrivateKey string, askPassword bool) {
	endpointDsn, err := dsn.Parse(destination)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	switch endpointDsn.Scheme {
	case "s3":
		err := endpointDsn.SetDefaults(S3Defaults)
		if err != nil {
			ErrorLogger.Fatal(err)
		}

		err = UploadUsingS3(endpointDsn)
		if err != nil {
			ErrorLogger.Fatal(err)
		}
		InfoLogger.Println("Trousseau data store succesfully pushed to s3")
	case "scp":
		err := endpointDsn.SetDefaults(ScpDefaults)
		if err != nil {
			ErrorLogger.Fatal(err)
		}

		if askPassword == true {
			password := PromptForPassword()
			endpointDsn.Secret = password
		}

		err = UploadUsingScp(endpointDsn, sshPrivateKey)
		if err != nil {
			ErrorLogger.Fatal(err)
		}
		InfoLogger.Println("Trousseau data store succesfully pushed to ssh remote storage")
	case "gist":
		err = UploadUsingGist(endpointDsn)
		if err != nil {
			ErrorLogger.Fatal(err)
		}
		InfoLogger.Println("Trousseau data store succesfully pushed to gist")
	}
}

func PullAction(source string, sshPrivateKey string, askPassword bool) {
	endpointDsn, err := dsn.Parse(source)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	switch endpointDsn.Scheme {
	case "s3":
		err := endpointDsn.SetDefaults(S3Defaults)
		if err != nil {
			ErrorLogger.Fatal(err)
		}

		err = DownloadUsingS3(endpointDsn)
		if err != nil {
			ErrorLogger.Fatal(err)
		}
		InfoLogger.Println("Trousseau data store succesfully pulled from S3")
	case "scp":
		err := endpointDsn.SetDefaults(ScpDefaults)
		if err != nil {
			ErrorLogger.Fatal(err)
		}

		if askPassword == true {
			password := PromptForPassword()
			endpointDsn.Secret = password
		}

		err = DownloadUsingScp(endpointDsn, sshPrivateKey)
		if err != nil {
			ErrorLogger.Fatal(err)
		}
		InfoLogger.Println("Trousseau data store succesfully pulled from ssh remote storage")
	case "gist":
		err = DownloadUsingGist(endpointDsn)
		if err != nil {
			ErrorLogger.Fatal(err)
		}
		InfoLogger.Println("Trousseau data store succesfully pulled from gist")
	default:
		if endpointDsn.Scheme == "" {
			ErrorLogger.Fatalf("No dsn scheme supplied")
		} else {
			ErrorLogger.Fatalf("Invalid dsn scheme supplied: %s", endpointDsn.Scheme)
		}
	}

	InfoLogger.Println("Trousseau data store succesfully pulled from remote storage")
}

func ExportAction(to string, plain bool) {
	outputFile, err := os.Create(to)
	if err != nil {
		ErrorLogger.Fatal(err)
	}
	defer outputFile.Close()

	// Make sure the file is readble/writable only
	// by its owner
	err = os.Chmod(outputFile.Name(), os.FileMode(0600))
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	if plain == true {
		tr, err := OpenTrousseau(InferStorePath())
		if err != nil {
			ErrorLogger.Fatal(err)
		}

		store, err := tr.Decrypt()
		if err != nil {
			ErrorLogger.Fatal(err)
		}

		storeBytes, err := json.Marshal(store)
		if err != nil {
			ErrorLogger.Fatal(err)
		}

		err = ioutil.WriteFile(to, storeBytes, os.FileMode(0600))
		if err != nil {
			ErrorLogger.Fatal(err)
		}
	} else {
		inputFile, err := os.Open(InferStorePath())
		defer inputFile.Close()
		if err != nil {
			ErrorLogger.Fatal(err)
		}

		_, err = io.Copy(outputFile, inputFile)
		if err != nil {
			ErrorLogger.Fatal(err)
		}
	}

	InfoLogger.Println(fmt.Sprintf("Trousseau data store exported to: %s", to))
}

func ImportAction(from string, strategy ImportStrategy, plain bool) {
	var importedStore *Store = &Store{}
	var localFilePath string = InferStorePath()

	localTr, err := OpenTrousseau(localFilePath)
	if err != nil {
	ErrorLogger.Fatal(err)
	}

	localStore, err := localTr.Decrypt()
	if err != nil {
	ErrorLogger.Fatal(err)
	}

	if plain == true {
	importedData, err := ioutil.ReadFile(from)
	if err != nil {
	ErrorLogger.Fatal(err)
	}

	err = json.Unmarshal(importedData, importedStore)
	if err != nil {
	ErrorLogger.Fatal(err)
	}
	} else {
	importedTr, err := OpenTrousseau(from)
	if err != nil {
	ErrorLogger.Fatal(err)
	}

	importedStore, err = importedTr.Decrypt()
	if err != nil {
	ErrorLogger.Fatal(err)
	}
	}

	err = ImportStore(importedStore, localStore, strategy)
	if err != nil {
	ErrorLogger.Fatal(err)
	}

	err = localTr.Encrypt(localStore)
	if err != nil {
	ErrorLogger.Fatal(err)
	}

	err = localTr.Write(localFilePath)
	if err != nil {
	ErrorLogger.Fatal(err)
	}

	InfoLogger.Println(fmt.Sprintf("Trousseau data store imported: %s", from))
}

func ListRecipientsAction() {
	tr, err := OpenTrousseau(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store, err := tr.Decrypt()
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	recipients := store.Meta.ListRecipients()
	for _, r := range recipients {
		InfoLogger.Println(r)
	}
}

func AddRecipientAction(recipient string) {
	tr, err := OpenTrousseau(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store, err := tr.Decrypt()
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = store.Meta.AddRecipient(recipient)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = tr.Encrypt(store)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = tr.Write(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}
}

func RemoveRecipientAction(recipient string) {
	tr, err := OpenTrousseau(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store, err := tr.Decrypt()
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = store.Meta.RemoveRecipient(recipient)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = tr.Encrypt(store)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = tr.Write(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}
}

func GetAction(key string, filepath string) {
	tr, err := OpenTrousseau(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store, err := tr.Decrypt()
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	value, err := store.Data.Get(key)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	// If the --file flag is provided
	if filepath != "" {
		valueBytes, ok := value.(string)
		if !ok {
			ErrorLogger.Fatal(fmt.Sprintf("unable to write %s value to file", key))
		}

		err := ioutil.WriteFile(filepath, []byte(valueBytes), os.FileMode(0644))
		if err != nil {
			ErrorLogger.Fatal(err)
		}
	} else {
		// Use fmt.Print to support patch processes,
		// to get the value "as is" without any appended newlines
		if isPipe(os.Stdout) {
			fmt.Print(value)
		} else {
			InfoLogger.Println(value)
		}
	}
}

func SetAction(key, value, file string) {
	// If the --file flag is provided
	if file != "" {
		// And the file actually exists on file system
		if PathExists(file) {
			// Then load it's content
			fileContent, err := ioutil.ReadFile(file)
			if err != nil {
				ErrorLogger.Fatal(err)
			}

			value = string(fileContent)
		} else {
			ErrorLogger.Fatalf("Cannot open %s because it doesn't exist", file)
		}
	}

	tr, err := OpenTrousseau(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store, err := tr.Decrypt()
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store.Data.Set(key, value)

	err = tr.Encrypt(store)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = tr.Write(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}
}

func RenameAction(src, dest string, overwrite bool) {
	tr, err := OpenTrousseau(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store, err := tr.Decrypt()
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = store.Data.Rename(src, dest, overwrite)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = tr.Encrypt(store)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = tr.Write(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

}

func DelAction(key string) {
	tr, err := OpenTrousseau(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store, err := tr.Decrypt()
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store.Data.Del(key)

	tr.Encrypt(store)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	err = tr.Write(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}
}

func KeysAction() {
	tr, err := OpenTrousseau(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store, err := tr.Decrypt()
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	keys := store.Data.Keys()
	for _, k := range keys {
		InfoLogger.Println(k)
	}
}

func ShowAction() {
	tr, err := OpenTrousseau(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store, err := tr.Decrypt()
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	items := store.Data.Items()
	for k, v := range items {
		InfoLogger.Println(fmt.Sprintf("%s : %s", k, v.(string)))
	}
}

func MetaAction() {
	tr, err := OpenTrousseau(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	store, err := tr.Decrypt()
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	InfoLogger.Println(store.Meta)
}

func UpgradeAction(yes, noBackup bool) {
	var proceed string = "n"

	data, err := ioutil.ReadFile(InferStorePath())
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	version := DiscoverVersion(data, VersionDiscoverClosures)
	if version == "" {
		fmt.Errorf("Initial store version could not be detected")
	}

	newStoreFile, err := UpgradeFrom(version, data, UpgradeClosures)
	if err != nil {
		ErrorLogger.Fatal(err)
	}

	if yes == false {
		fmt.Printf("You are about to upgrade trousseau data "+
					"store %s (version %s) up to version %s. Proceed? [Y/n] ",
			InferStorePath(), version, TROUSSEAU_VERSION)
		_, err = fmt.Scanf("%s", &proceed)
		if err != nil {
			ErrorLogger.Fatal(err)
		}
	}

	if strings.ToLower(proceed) == "y" || yes {
		// Write a backup of the old store file inplace
		if noBackup == false {
			err = ioutil.WriteFile(InferStorePath()+".bkp", data, os.FileMode(0700))
			if err != nil {
				ErrorLogger.Fatal(err)
			}
		}

		// Overwrite source legacy store with the new version content
		err = ioutil.WriteFile(InferStorePath(), newStoreFile, os.FileMode(0700))
		if err != nil {
			ErrorLogger.Fatal(err)
		}
	} else {
		fmt.Println("upgrade cancelled")
	}
}

// Thanks, mrnugget!
// https://github.com/mrnugget/fzz/blob/master/utils.go#L14-L21
func isPipe(f *os.File) bool {
	s, err := f.Stat()
	if err != nil {
		return false
	}

	return s.Mode()&os.ModeNamedPipe != 0
}

<|endoftext|>


<commit_before>// Copyright (c) 2015, Ben Morgan. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.

package repoctl

import (
	"os"
	"path"

	"github.com/goulash/errs"
	"github.com/goulash/osutil"
	pu "github.com/goulash/pacman/pkgutil"
)

// Link tries to hard link the file, and failing that, copies it over.
func (r *Repo) Link(h errs.Handler, pkgfiles ...string) error {
	return r.add(h, pkgfiles, linkFile, "linking")
}

// TODO: Get this in the osutil package.
func linkFile(src, dst string) error {
	// Before we delete dst, make sure src exists!
	if ex, err := osutil.FileExists(src); !ex {
		if err != nil {
			return err
		}
		return &NotExistsError{src}
	}

	// Check if dst exists, so we can delete prior to linking.
	if ex, err := osutil.FileExists(dst); !ex {
		if err != nil {
			return err
		}
	} else {
		err = os.Remove(dst)
		if err != nil {
			return err
		}
	}

	err := os.Link(src, dst)
	if err != nil {
		// If we can't link it (not same filesystem, etc.), then try copying.
		return osutil.CopyFileLazy(src, dst)
	}
	return nil
}

// Copy copies the given files into the repository if they do not already
// exist there and adds them to the database.
func (r *Repo) Copy(h errs.Handler, pkgfiles ...string) error {
	return r.add(h, pkgfiles, osutil.CopyFileLazy, "copying")
}

// Move moves the given files into the repository if they do not already
// exist there and adds them to the database. If the files already exist
// there, then they are still deleted from where they were. Thus, the
// move always appears to have worked, even if no work was done.
//
// The exception is that when the source and destination files are the
// same; then no move or deletion is performed.
func (r *Repo) Move(h errs.Handler, pkgfiles ...string) error {
	return r.add(h, pkgfiles, osutil.MoveFileLazy, "moving")
}

// add does the hard work of Move and Copy.
func (r *Repo) add(h errs.Handler, pkgfiles []string, ar func(string, string) error, lbl string) error {
	errs.Init(&h)
	if len(pkgfiles) == 0 {
		return nil
	}

	added := make([]string, 0, len(pkgfiles))
	for _, f := range pkgfiles {
		pkg, err := NewSignedPkg(f)
		if err != nil {
			// This means that we are trying to add something that's
			// non-existant or corrupt.
			r.errorf("skipping %s: %s", f, err)
			continue
		}
		if r.RequireSignature && !pkg.HasSignature() {
			r.errorf("skipping %s: require signature but none available", f)
			continue
		}

		r.printf("%s and adding to repository: %s\n", lbl, pkg.PathSet())
		err = pkg.Apply(func(src string, _ bool) error {
			dst := path.Join(r.Directory, path.Base(src))
			return ar(src, dst)
		})
		if err != nil {
			err = h(err)
			if err != nil {
				return err
			}
			continue
		}
		added = append(added, path.Join(r.Directory, path.Base(f)))
	}

	err := r.DatabaseAdd(added...)
	if err != nil {
		return err
	}

	pkgs, err := r.FindSimilar(h, added...)
	return r.Dispatch(h, pu.Map(pkgs, pu.PkgFilename)...)
}

// Remove removes the given names from the database and dispatches
// the files.
func (r *Repo) Remove(h errs.Handler, pkgnames ...string) error {
	errs.Init(&h)
	if len(pkgnames) == 0 {
		return nil
	}

	pkgs, err := r.ReadNames(h, pkgnames...)
	if err != nil {
		return err
	}
	err = h(r.DatabaseRemove(pu.Map(pkgs, pu.PkgName)...))
	if err != nil {
		return err
	}
	return r.Dispatch(h, pu.Map(pkgs, pu.PkgFilename)...)
}

// Dispatch either removes the given files or it backs them up.
func (r *Repo) Dispatch(h errs.Handler, pkgfiles ...string) error {
	errs.Init(&h)
	if len(pkgfiles) == 0 {
		return nil
	}

	if r.Backup {
		return r.backup(h, pkgfiles)
	}
	return r.unlink(h, pkgfiles)
}

// IsObsoleteCached returns true when obsolete package files should
// be cached instead of backed-up or deleted.
//
// If the backup directory is the directory where all the
// packages are, then the idea is that we leave them in place.
func (r *Repo) IsObsoleteCached() bool {
	// TODO: this assumes that r.Directory is absolute and clean.
	return r.backupDirAbs() == r.Directory
}

// backupDirAbs returns the absolute path to the backup directory.
// If r.BackupDir is relative, then it is relative to the repository
// path, otherwise it is as is.
func (r *Repo) backupDirAbs() string {
	if path.IsAbs(r.BackupDir) {
		return path.Clean(r.BackupDir)
	}
	return path.Join(r.Directory, r.BackupDir)
}

func (r *Repo) backup(h errs.Handler, pkgfiles []string) error {
	if r.IsObsoleteCached() {
		for _, f := range pkgfiles {
			r.debugf("cached: %s\n", f)
		}
		return nil
	}

	backupDir := r.backupDirAbs()
	for _, f := range pkgfiles {
		pkg, err := NewSignedPkg(f)
		if err != nil {
			err = h(err)
			if err != nil {
				return err
			}
			if pkg == nil {
				continue
			}
		}

		r.printf("backing up: %s\n", pkg.NameSet())
		err = pkg.Apply(func(f string, _ bool) error {
			src := path.Base(f)
			dst := path.Join(backupDir, src)
			return osutil.MoveFileLazy(f, dst)
		})
		if err != nil {
			err = h(err)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

func (r *Repo) unlink(h errs.Handler, pkgfiles []string) error {
	for _, f := range pkgfiles {
		pkg, err := NewSignedPkg(f)
		if err != nil {
			err = h(err)
			if err != nil {
				return err
			}
			if pkg == nil {
				continue
			}
		}

		r.printf("deleting: %s\n", pkg.NameSet())

		err = pkg.Apply(func(f string, _ bool) error {
			return os.Remove(f)
		})
		if err != nil {
			err = h(err)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// Update adds the newest package found for the given name to the
// database and dispatches the obsolete packages. Any obsolete entries
// in the database are removed.
//
// If pkgnames is empty, the entire repository is scanned.
//
// TODO: What happens when there are multiple files, and you delete
// the most recent one. Which file is deleted?
func (r *Repo) Update(h errs.Handler, pkgnames ...string) error {
	errs.Init(&h)

	pkgs, err := r.ReadMeta(h, pkgnames...)
	if err != nil {
		return err
	}

	var updates []string
	var obsolete []string
	var missing []string
	for _, p := range pkgs {
		if !p.HasFiles() {
			missing = append(missing, p.Name)
			continue
		}
		if p.HasObsolete() {
			obsolete = append(obsolete, pu.Map(p.Obsolete(), pu.PkgFilename)...)
		}
		if p.HasUpdate() || len(pkgnames) > 0 {
			f := p.Pkg().Filename
			if r.RequireSignature {
				spkg, err := NewSignedPkg(f)
				if err != nil {
					r.errorf("skipping %s: %s", f, err)
					continue
				} else if spkg.HasSignature() {
					r.errorf("skipping %s: require signature but none found", f)
					continue
				}
			}
			updates = append(updates, f)
		}
	}

	err = r.DatabaseRemove(missing...)
	if err != nil {
		return err
	}

	err = r.DatabaseAdd(updates...)
	if err != nil {
		return err
	}

	return r.Dispatch(h, obsolete...)
}

// Reset deletes the repository database and reads all the packages.
// This is the same as unlinking the database and then running Update.
func (r *Repo) Reset(h errs.Handler) error {
	errs.Init(&h)

	err := r.DeleteDatabase()
	if err != nil {
		return err
	}

	return r.Update(h)
}

// DeleteDatabase deletes the repository database (but not the files).
func (r *Repo) DeleteDatabase() error {
	db := path.Join(r.Directory, r.Database)
	r.printf("deleting database: %s\n", db)
	return os.Remove(db)
}
<commit_msg>Do not delete destination file before hard-linking<commit_after>// Copyright (c) 2015, Ben Morgan. All rights reserved.
// Use of this source code is governed by an MIT license
// that can be found in the LICENSE file.

package repoctl

import (
	"os"
	"path"

	"github.com/goulash/errs"
	"github.com/goulash/osutil"
	pu "github.com/goulash/pacman/pkgutil"
)

// Link tries to hard link the file, and failing that, copies it over.
func (r *Repo) Link(h errs.Handler, pkgfiles ...string) error {
	return r.add(h, pkgfiles, linkFile, "linking")
}

// TODO: Get this in the osutil package.
func linkFile(src, dst string) error {
	err := os.Link(src, dst)
	if err != nil {
		// If we can't link it (not same filesystem, etc.), then try copying.
		return osutil.CopyFileLazy(src, dst)
	}
	return nil
}

// Copy copies the given files into the repository if they do not already
// exist there and adds them to the database.
func (r *Repo) Copy(h errs.Handler, pkgfiles ...string) error {
	return r.add(h, pkgfiles, osutil.CopyFileLazy, "copying")
}

// Move moves the given files into the repository if they do not already
// exist there and adds them to the database. If the files already exist
// there, then they are still deleted from where they were. Thus, the
// move always appears to have worked, even if no work was done.
//
// The exception is that when the source and destination files are the
// same; then no move or deletion is performed.
func (r *Repo) Move(h errs.Handler, pkgfiles ...string) error {
	return r.add(h, pkgfiles, osutil.MoveFileLazy, "moving")
}

// add does the hard work of Move and Copy.
func (r *Repo) add(h errs.Handler, pkgfiles []string, ar func(string, string) error, lbl string) error {
	errs.Init(&h)
	if len(pkgfiles) == 0 {
		return nil
	}

	added := make([]string, 0, len(pkgfiles))
	for _, f := range pkgfiles {
		pkg, err := NewSignedPkg(f)
		if err != nil {
			// This means that we are trying to add something that's
			// non-existant or corrupt.
			r.errorf("skipping %s: %s", f, err)
			continue
		}
		if r.RequireSignature && !pkg.HasSignature() {
			r.errorf("skipping %s: require signature but none available", f)
			continue
		}

		r.printf("%s and adding to repository: %s\n", lbl, pkg.PathSet())
		err = pkg.Apply(func(src string, _ bool) error {
			dst := path.Join(r.Directory, path.Base(src))
			return ar(src, dst)
		})
		if err != nil {
			err = h(err)
			if err != nil {
				return err
			}
			continue
		}
		added = append(added, path.Join(r.Directory, path.Base(f)))
	}

	err := r.DatabaseAdd(added...)
	if err != nil {
		return err
	}

	pkgs, err := r.FindSimilar(h, added...)
	return r.Dispatch(h, pu.Map(pkgs, pu.PkgFilename)...)
}

// Remove removes the given names from the database and dispatches
// the files.
func (r *Repo) Remove(h errs.Handler, pkgnames ...string) error {
	errs.Init(&h)
	if len(pkgnames) == 0 {
		return nil
	}

	pkgs, err := r.ReadNames(h, pkgnames...)
	if err != nil {
		return err
	}
	err = h(r.DatabaseRemove(pu.Map(pkgs, pu.PkgName)...))
	if err != nil {
		return err
	}
	return r.Dispatch(h, pu.Map(pkgs, pu.PkgFilename)...)
}

// Dispatch either removes the given files or it backs them up.
func (r *Repo) Dispatch(h errs.Handler, pkgfiles ...string) error {
	errs.Init(&h)
	if len(pkgfiles) == 0 {
		return nil
	}

	if r.Backup {
		return r.backup(h, pkgfiles)
	}
	return r.unlink(h, pkgfiles)
}

// IsObsoleteCached returns true when obsolete package files should
// be cached instead of backed-up or deleted.
//
// If the backup directory is the directory where all the
// packages are, then the idea is that we leave them in place.
func (r *Repo) IsObsoleteCached() bool {
	// TODO: this assumes that r.Directory is absolute and clean.
	return r.backupDirAbs() == r.Directory
}

// backupDirAbs returns the absolute path to the backup directory.
// If r.BackupDir is relative, then it is relative to the repository
// path, otherwise it is as is.
func (r *Repo) backupDirAbs() string {
	if path.IsAbs(r.BackupDir) {
		return path.Clean(r.BackupDir)
	}
	return path.Join(r.Directory, r.BackupDir)
}

func (r *Repo) backup(h errs.Handler, pkgfiles []string) error {
	if r.IsObsoleteCached() {
		for _, f := range pkgfiles {
			r.debugf("cached: %s\n", f)
		}
		return nil
	}

	backupDir := r.backupDirAbs()
	for _, f := range pkgfiles {
		pkg, err := NewSignedPkg(f)
		if err != nil {
			err = h(err)
			if err != nil {
				return err
			}
			if pkg == nil {
				continue
			}
		}

		r.printf("backing up: %s\n", pkg.NameSet())
		err = pkg.Apply(func(f string, _ bool) error {
			src := path.Base(f)
			dst := path.Join(backupDir, src)
			return osutil.MoveFileLazy(f, dst)
		})
		if err != nil {
			err = h(err)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

func (r *Repo) unlink(h errs.Handler, pkgfiles []string) error {
	for _, f := range pkgfiles {
		pkg, err := NewSignedPkg(f)
		if err != nil {
			err = h(err)
			if err != nil {
				return err
			}
			if pkg == nil {
				continue
			}
		}

		r.printf("deleting: %s\n", pkg.NameSet())

		err = pkg.Apply(func(f string, _ bool) error {
			return os.Remove(f)
		})
		if err != nil {
			err = h(err)
			if err != nil {
				return err
			}
		}
	}
	return nil
}

// Update adds the newest package found for the given name to the
// database and dispatches the obsolete packages. Any obsolete entries
// in the database are removed.
//
// If pkgnames is empty, the entire repository is scanned.
//
// TODO: What happens when there are multiple files, and you delete
// the most recent one. Which file is deleted?
func (r *Repo) Update(h errs.Handler, pkgnames ...string) error {
	errs.Init(&h)

	pkgs, err := r.ReadMeta(h, pkgnames...)
	if err != nil {
		return err
	}

	var updates []string
	var obsolete []string
	var missing []string
	for _, p := range pkgs {
		if !p.HasFiles() {
			missing = append(missing, p.Name)
			continue
		}
		if p.HasObsolete() {
			obsolete = append(obsolete, pu.Map(p.Obsolete(), pu.PkgFilename)...)
		}
		if p.HasUpdate() || len(pkgnames) > 0 {
			f := p.Pkg().Filename
			if r.RequireSignature {
				spkg, err := NewSignedPkg(f)
				if err != nil {
					r.errorf("skipping %s: %s", f, err)
					continue
				} else if spkg.HasSignature() {
					r.errorf("skipping %s: require signature but none found", f)
					continue
				}
			}
			updates = append(updates, f)
		}
	}

	err = r.DatabaseRemove(missing...)
	if err != nil {
		return err
	}

	err = r.DatabaseAdd(updates...)
	if err != nil {
		return err
	}

	return r.Dispatch(h, obsolete...)
}

// Reset deletes the repository database and reads all the packages.
// This is the same as unlinking the database and then running Update.
func (r *Repo) Reset(h errs.Handler) error {
	errs.Init(&h)

	err := r.DeleteDatabase()
	if err != nil {
		return err
	}

	return r.Update(h)
}

// DeleteDatabase deletes the repository database (but not the files).
func (r *Repo) DeleteDatabase() error {
	db := path.Join(r.Directory, r.Database)
	r.printf("deleting database: %s\n", db)
	return os.Remove(db)
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"github.com/boivie/lovebeat-go/alert"
	"github.com/boivie/lovebeat-go/backend"
	"github.com/boivie/lovebeat-go/config"
	"github.com/boivie/lovebeat-go/dashboard"
	"github.com/boivie/lovebeat-go/httpapi"
	"github.com/boivie/lovebeat-go/metrics"
	"github.com/boivie/lovebeat-go/service"
	"github.com/boivie/lovebeat-go/tcpapi"
	"github.com/boivie/lovebeat-go/udpapi"
	"github.com/gorilla/mux"
	"github.com/op/go-logging"
	"net/http"
	"os"
	"os/signal"
	"runtime"
	"strings"
	"syscall"
)

var log = logging.MustGetLogger("lovebeat")

const (
	VERSION = "0.8.0"
)

var (
	debug       = flag.Bool("debug", false, "Enable debug printouts")
	showVersion = flag.Bool("version", false, "Print version string")
	cfgFile     = flag.String("config", "/etc/lovebeat.cfg", "Configuration file")
)

var (
	signalchan = make(chan os.Signal, 1)
)

func signalHandler(be backend.Backend) {
	for {
		select {
		case sig := <-signalchan:
			fmt.Printf("!! Caught signal %d... shutting down\n", sig)
			be.Sync()
			return
		}
	}
}

func httpServer(cfg *config.ConfigBind, svcs *service.Services) {
	rtr := mux.NewRouter()
	httpapi.Register(rtr, svcs.GetClient())
	dashboard.Register(rtr, svcs.GetClient())
	http.Handle("/", rtr)
	log.Info("HTTP listening on %s\n", cfg.Listen)
	http.ListenAndServe(cfg.Listen, nil)
}

func getHostname() string {
	var hostname, err = os.Hostname()
	if err != nil {
		return fmt.Sprintf("unknown_%d", os.Getpid())
	}
	return strings.Split(hostname, ".")[0]
}

func main() {
	flag.Parse()

	var format = logging.MustStringFormatter("%{level} %{message}")
	logging.SetFormatter(format)
	if *debug {
		logging.SetLevel(logging.DEBUG, "lovebeat")
	} else {
		logging.SetLevel(logging.INFO, "lovebeat")
	}
	log.Debug("Debug logs enabled")

	if *showVersion {
		fmt.Printf("lovebeats v%s (built w/%s)\n", VERSION, runtime.Version())
		return
	}

	var cfg = config.ReadConfig(*cfgFile)

	var hostname = getHostname()
	log.Info("Lovebeat v%s started as host %s, PID %d", VERSION, hostname, os.Getpid())

	m := metrics.New(&cfg.Metrics)

	var be = backend.NewFileBackend(&cfg.Database, m)
	var alerters = []alert.Alerter{alert.NewMailAlerter(&cfg.Mail),
		alert.NewWebhooksAlerter()}
	var svcs = service.NewServices(be, alerters, m)

	signal.Notify(signalchan, syscall.SIGTERM)
	signal.Notify(signalchan, os.Interrupt)

	go svcs.Monitor()
	go httpServer(&cfg.Http, svcs)
	go udpapi.Listener(&cfg.Udp, svcs.GetClient())
	go tcpapi.Listener(&cfg.Tcp, svcs.GetClient())

	// Ensure that the 'all' view exists
	svcs.GetClient().CreateOrUpdateView("all", "", "", "")

	m.IncCounter("started.count")

	signalHandler(be)
}
<commit_msg>Add -syslog flag for sending log output to syslog.<commit_after>package main

import (
	"flag"
	"fmt"
	"github.com/boivie/lovebeat-go/alert"
	"github.com/boivie/lovebeat-go/backend"
	"github.com/boivie/lovebeat-go/config"
	"github.com/boivie/lovebeat-go/dashboard"
	"github.com/boivie/lovebeat-go/httpapi"
	"github.com/boivie/lovebeat-go/metrics"
	"github.com/boivie/lovebeat-go/service"
	"github.com/boivie/lovebeat-go/tcpapi"
	"github.com/boivie/lovebeat-go/udpapi"
	"github.com/gorilla/mux"
	"github.com/op/go-logging"
	"log/syslog"
	"net/http"
	"os"
	"os/signal"
	"runtime"
	"strings"
	"syscall"
)

var log = logging.MustGetLogger("lovebeat")

const (
	VERSION = "0.8.0"
)

var (
	debug       = flag.Bool("debug", false, "Enable debug logs")
	showVersion = flag.Bool("version", false, "Print version string")
	cfgFile     = flag.String("config", "/etc/lovebeat.cfg", "Configuration file")
	useSyslog   = flag.Bool("syslog", false, "Log to syslog instead of stderr")
)

var (
	signalchan = make(chan os.Signal, 1)
)

func signalHandler(be backend.Backend) {
	for {
		select {
		case sig := <-signalchan:
			fmt.Printf("!! Caught signal %d... shutting down\n", sig)
			be.Sync()
			return
		}
	}
}

func httpServer(cfg *config.ConfigBind, svcs *service.Services) {
	rtr := mux.NewRouter()
	httpapi.Register(rtr, svcs.GetClient())
	dashboard.Register(rtr, svcs.GetClient())
	http.Handle("/", rtr)
	log.Info("HTTP listening on %s\n", cfg.Listen)
	http.ListenAndServe(cfg.Listen, nil)
}

func getHostname() string {
	var hostname, err = os.Hostname()
	if err != nil {
		return fmt.Sprintf("unknown_%d", os.Getpid())
	}
	return strings.Split(hostname, ".")[0]
}

func main() {
	flag.Parse()

	if *debug {
		logging.SetLevel(logging.DEBUG, "lovebeat")
	} else {
		logging.SetLevel(logging.INFO, "lovebeat")
	}
	if *useSyslog {
		var backend, err = logging.NewSyslogBackendPriority("lovebeat", syslog.LOG_DAEMON)
		if err != nil {
			panic(err)
		}
		logging.SetBackend(logging.AddModuleLevel(backend))
	} else {
		var format = logging.MustStringFormatter("%{level} %{message}")
		logging.SetFormatter(format)
	}
	log.Debug("Debug logs enabled")

	if *showVersion {
		fmt.Printf("lovebeats v%s (built w/%s)\n", VERSION, runtime.Version())
		return
	}

	var cfg = config.ReadConfig(*cfgFile)

	var hostname = getHostname()
	log.Info("Lovebeat v%s started as host %s, PID %d", VERSION, hostname, os.Getpid())

	m := metrics.New(&cfg.Metrics)

	var be = backend.NewFileBackend(&cfg.Database, m)
	var alerters = []alert.Alerter{alert.NewMailAlerter(&cfg.Mail),
		alert.NewWebhooksAlerter()}
	var svcs = service.NewServices(be, alerters, m)

	signal.Notify(signalchan, syscall.SIGTERM)
	signal.Notify(signalchan, os.Interrupt)

	go svcs.Monitor()
	go httpServer(&cfg.Http, svcs)
	go udpapi.Listener(&cfg.Udp, svcs.GetClient())
	go tcpapi.Listener(&cfg.Tcp, svcs.GetClient())

	// Ensure that the 'all' view exists
	svcs.GetClient().CreateOrUpdateView("all", "", "", "")

	m.IncCounter("started.count")

	signalHandler(be)
}
<|endoftext|>


<commit_before>// Copyright 2018 The ACH Authors
// Use of this source code is governed by an Apache License
// license that can be found in the LICENSE file.

package ach

import "testing"

// mockBatchBOCHeader creates a BatchBOC BatchHeader
func mockBatchBOCHeader() *BatchHeader {
	bh := NewBatchHeader()
	bh.ServiceClassCode = 225
	bh.StandardEntryClassCode = "BOC"
	bh.CompanyName = "Company Name"
	bh.CompanyIdentification = "121042882"
	bh.CompanyEntryDescription = "BOC"
	bh.ODFIIdentification = "12104288"
	return bh
}

// mockBOCEntryDetail creates a BatchBOC EntryDetail
func mockBOCEntryDetail() *EntryDetail {
	entry := NewEntryDetail()
	entry.TransactionCode = 27
	entry.SetRDFI("231380104")
	entry.DFIAccountNumber = "744-5678-99"
	entry.Amount = 25000
	entry.SetCheckSerialNumber("123456789")
	entry.SetReceivingCompany("ABC Company")
	entry.SetTraceNumber(mockBatchBOCHeader().ODFIIdentification, 123)
	entry.Category = CategoryForward
	return entry
}

// mockBatchBOC creates a BatchBOC
func mockBatchBOC() *BatchBOC {
	mockBatch := NewBatchBOC(mockBatchBOCHeader())
	mockBatch.AddEntry(mockBOCEntryDetail())
	if err := mockBatch.Create(); err != nil {
		panic(err)
	}
	return mockBatch
}

// mockBatchBOCHeaderCredit creates a BatchBOC BatchHeader
func mockBatchBOCHeaderCredit() *BatchHeader {
	bh := NewBatchHeader()
	bh.ServiceClassCode = 225
	bh.StandardEntryClassCode = "BOC"
	bh.CompanyName = "Company Name"
	bh.CompanyIdentification = "121042882"
	bh.CompanyEntryDescription = "REDEPCHECK"
	bh.ODFIIdentification = "12104288"
	return bh
}

// mockBOCEntryDetailCredit creates a BatchBOC EntryDetail with a credit
func mockBOCEntryDetailCredit() *EntryDetail {
	entry := NewEntryDetail()
	entry.TransactionCode = 22
	entry.SetRDFI("231380104")
	entry.DFIAccountNumber = "744-5678-99"
	entry.Amount = 25000
	entry.SetCheckSerialNumber("123456789")
	entry.SetReceivingCompany("ABC Company")
	entry.SetTraceNumber(mockBatchBOCHeader().ODFIIdentification, 123)
	entry.Category = CategoryForward
	return entry
}

// mockBatchBOCCredit creates a BatchBOC with a Credit entry
func mockBatchBOCCredit() *BatchBOC {
	mockBatch := NewBatchBOC(mockBatchBOCHeaderCredit())
	mockBatch.AddEntry(mockBOCEntryDetailCredit())
	return mockBatch
}

// testBatchBOCHeader creates a BatchBOC BatchHeader
func testBatchBOCHeader(t testing.TB) {
	batch, _ := NewBatch(mockBatchBOCHeader())
	err, ok := batch.(*BatchBOC)
	if !ok {
		t.Errorf("Expecting BatchBOC got %T", err)
	}
}

// TestBatchBOCHeader tests validating BatchBOC BatchHeader
func TestBatchBOCHeader(t *testing.T) {
	testBatchBOCHeader(t)
}

// BenchmarkBatchBOCHeader benchmarks validating BatchBOC BatchHeader
func BenchmarkBatchBOCHeader(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBatchBOCHeader(b)
	}
}

// testBatchBOCCreate validates BatchBOC create
func testBatchBOCCreate(t testing.TB) {
	mockBatch := mockBatchBOC()
	mockBatch.Create()
	if err := mockBatch.Validate(); err != nil {
		t.Errorf("%T: %s", err, err)
	}
}

// TestBatchBOCCreate tests validating BatchBOC create
func TestBatchBOCCreate(t *testing.T) {
	testBatchBOCCreate(t)
}

// BenchmarkBatchBOCCreate benchmarks validating BatchBOC create
func BenchmarkBatchBOCCreate(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBatchBOCCreate(b)
	}
}

// testBatchBOCStandardEntryClassCode validates BatchBOC create for an invalid StandardEntryClassCode
func testBatchBOCStandardEntryClassCode(t testing.TB) {
	mockBatch := mockBatchBOC()
	mockBatch.Header.StandardEntryClassCode = "WEB"
	mockBatch.Create()
	if err := mockBatch.Validate(); err != nil {
		if e, ok := err.(*BatchError); ok {
			if e.FieldName != "StandardEntryClassCode" {
				t.Errorf("%T: %s", err, err)
			}
		} else {
			t.Errorf("%T: %s", err, err)
		}
	}
}

// TestBatchBOCStandardEntryClassCode tests validating BatchBOC create for an invalid StandardEntryClassCode
func TestBatchBOCStandardEntryClassCode(t *testing.T) {
	testBatchBOCStandardEntryClassCode(t)
}

// BenchmarkBatchBOCStandardEntryClassCode benchmarks validating BatchBOC create for an invalid StandardEntryClassCode
func BenchmarkBatchBOCStandardEntryClassCode(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBatchBOCStandardEntryClassCode(b)
	}
}

// testBatchBOCServiceClass200 validates BatchBOC create for an invalid ServiceClassCode 200
func testBatchBOCServiceClass200(t testing.TB) {
	mockBatch := mockBatchBOC()
	mockBatch.Header.ServiceClassCode = 200
	mockBatch.Create()
	if err := mockBatch.Validate(); err != nil {
		if e, ok := err.(*BatchError); ok {
			if e.FieldName != "ServiceClassCode" {
				t.Errorf("%T: %s", err, err)
			}
		} else {
			t.Errorf("%T: %s", err, err)
		}
	}
}

// TestBatchBOCServiceClass200 tests validating BatchBOC create for an invalid ServiceClassCode 200
func TestBatchBOCServiceClass200(t *testing.T) {
	testBatchBOCServiceClass200(t)
}

// BenchmarkBatchBOCServiceClass200 benchmarks validating BatchBOC create for an invalid ServiceClassCode 200
func BenchmarkBatchBOCServiceClass200(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBatchBOCServiceClass200(b)
	}
}

// testBatchBOCServiceClass220 validates BatchBOC create for an invalid ServiceClassCode 220
func testBatchBOCServiceClass220(t testing.TB) {
	mockBatch := mockBatchBOC()
	mockBatch.Header.ServiceClassCode = 220
	mockBatch.Create()
	if err := mockBatch.Validate(); err != nil {
		if e, ok := err.(*BatchError); ok {
			if e.FieldName != "ServiceClassCode" {
				t.Errorf("%T: %s", err, err)
			}
		} else {
			t.Errorf("%T: %s", err, err)
		}
	}
}

// TestBatchBOCServiceClass220 tests validating BatchBOC create for an invalid ServiceClassCode 220
func TestBatchBOCServiceClass220(t *testing.T) {
	testBatchBOCServiceClass220(t)
}

// BenchmarkBatchBOCServiceClass220 benchmarks validating BatchBOC create for an invalid ServiceClassCode 220
func BenchmarkBatchBOCServiceClass220(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBatchBOCServiceClass220(b)
	}
}

// testBatchBOCServiceClass280 validates BatchBOC create for an invalid ServiceClassCode 280
func testBatchBOCServiceClass280(t testing.TB) {
	mockBatch := mockBatchBOC()
	mockBatch.Header.ServiceClassCode = 280
	mockBatch.Create()
	if err := mockBatch.Validate(); err != nil {
		if e, ok := err.(*BatchError); ok {
			if e.FieldName != "ServiceClassCode" {
				t.Errorf("%T: %s", err, err)
			}
		} else {
			t.Errorf("%T: %s", err, err)
		}
	}
}

// TestBatchBOCServiceClass280 tests validating BatchBOC create for an invalid ServiceClassCode 280
func TestBatchBOCServiceClass280(t *testing.T) {
	testBatchBOCServiceClass280(t)
}

// BenchmarkBatchBOCServiceClass280 benchmarks validating BatchBOC create for an invalid ServiceClassCode 280
func BenchmarkBatchBOCServiceClass280(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBatchBOCServiceClass280(b)
	}
}

// testBatchBOCAmount validates BatchBOC create for an invalid Amount
func testBatchBOCAmount(t testing.TB) {
	mockBatch := mockBatchBOC()
	mockBatch.Entries[0].Amount = 2500001
	mockBatch.Create()
	if err := mockBatch.Validate(); err != nil {
		if e, ok := err.(*BatchError); ok {
			if e.FieldName != "Amount" {
				t.Errorf("%T: %s", err, err)
			}
		} else {
			t.Errorf("%T: %s", err, err)
		}
	}
}

// TestBatchBOCAmount validates BatchBOC create for an invalid Amount
func TestBatchBOCAmount(t *testing.T) {
	testBatchBOCAmount(t)
}

// BenchmarkBatchBOCAmount validates BatchBOC create for an invalid Amount
func BenchmarkBatchBOCAmount(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBatchBOCAmount(b)
	}
}

// testBatchBOCCheckSerialNumber validates BatchBOC CheckSerialNumber / IdentificationNumber is a mandatory field
func testBatchBOCCheckSerialNumber(t testing.TB) {
	mockBatch := mockBatchBOC()
	// modify CheckSerialNumber / IdentificationNumber to empty string
	mockBatch.GetEntries()[0].SetCheckSerialNumber("")
	if err := mockBatch.Validate(); err != nil {
		if e, ok := err.(*BatchError); ok {
			if e.FieldName != "CheckSerialNumber" {
				t.Errorf("%T: %s", err, err)
			}
		} else {
			t.Errorf("%T: %s", err, err)
		}
	}
}

// TestBatchBOCCheckSerialNumber  tests validating BatchBOC CheckSerialNumber / IdentificationNumber is a mandatory field
func TestBatchBOCCheckSerialNumber(t *testing.T) {
	testBatchBOCCheckSerialNumber(t)
}

// BenchmarkBatchBOCCheckSerialNumber benchmarks validating BatchBOC
// CheckSerialNumber / IdentificationNumber is a mandatory field
func BenchmarkBatchBOCCheckSerialNumber(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBatchBOCCheckSerialNumber(b)
	}
}

// testBatchBOCTransactionCode validates BatchBOC TransactionCode is not a credit
func testBatchBOCTransactionCode(t testing.TB) {
	mockBatch := mockBatchBOCCredit()
	if err := mockBatch.Create(); err != nil {
		if e, ok := err.(*BatchError); ok {
			if e.FieldName != "TransactionCode" {
				t.Errorf("%T: %s", err, err)
			}
		} else {
			t.Errorf("%T: %s", err, err)
		}
	}
}

// TestBatchBOCTransactionCode tests validating BatchBOC TransactionCode is not a credit
func TestBatchBOCTransactionCode(t *testing.T) {
	testBatchBOCTransactionCode(t)
}

// BenchmarkBatchBOCTransactionCode benchmarks validating BatchBOC TransactionCode is not a credit
func BenchmarkBatchBOCTransactionCode(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBatchBOCTransactionCode(b)
	}
}

// testBatchBOCAddendaCount validates BatchBOC Addenda count
func testBatchBOCAddendaCount(t testing.TB) {
	mockBatch := mockBatchBOC()
	mockBatch.GetEntries()[0].AddAddenda(mockAddenda05())
	mockBatch.Create()
	if err := mockBatch.Validate(); err != nil {
		if e, ok := err.(*BatchError); ok {
			if e.FieldName != "AddendaCount" {
				t.Errorf("%T: %s", err, err)
			}
		} else {
			t.Errorf("%T: %s", err, err)
		}
	}
}

// TestBatchBOCAddendaCount tests validating BatchBOC Addenda count
func TestBatchBOCAddendaCount(t *testing.T) {
	testBatchBOCAddendaCount(t)
}

// BenchmarkBatchBOCAddendaCount benchmarks validating BatchBOC Addenda count
func BenchmarkBatchBOCAddendaCount(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBatchBOCAddendaCount(b)
	}
}
<commit_msg>batchBOC code coverage<commit_after>// Copyright 2018 The ACH Authors
// Use of this source code is governed by an Apache License
// license that can be found in the LICENSE file.

package ach

import "testing"

// mockBatchBOCHeader creates a BatchBOC BatchHeader
func mockBatchBOCHeader() *BatchHeader {
	bh := NewBatchHeader()
	bh.ServiceClassCode = 225
	bh.StandardEntryClassCode = "BOC"
	bh.CompanyName = "Company Name"
	bh.CompanyIdentification = "121042882"
	bh.CompanyEntryDescription = "BOC"
	bh.ODFIIdentification = "12104288"
	return bh
}

// mockBOCEntryDetail creates a BatchBOC EntryDetail
func mockBOCEntryDetail() *EntryDetail {
	entry := NewEntryDetail()
	entry.TransactionCode = 27
	entry.SetRDFI("231380104")
	entry.DFIAccountNumber = "744-5678-99"
	entry.Amount = 25000
	entry.SetCheckSerialNumber("123456789")
	entry.SetReceivingCompany("ABC Company")
	entry.SetTraceNumber(mockBatchBOCHeader().ODFIIdentification, 123)
	entry.Category = CategoryForward
	return entry
}

// mockBatchBOC creates a BatchBOC
func mockBatchBOC() *BatchBOC {
	mockBatch := NewBatchBOC(mockBatchBOCHeader())
	mockBatch.AddEntry(mockBOCEntryDetail())
	if err := mockBatch.Create(); err != nil {
		panic(err)
	}
	return mockBatch
}

// mockBatchBOCHeaderCredit creates a BatchBOC BatchHeader
func mockBatchBOCHeaderCredit() *BatchHeader {
	bh := NewBatchHeader()
	bh.ServiceClassCode = 225
	bh.StandardEntryClassCode = "BOC"
	bh.CompanyName = "Company Name"
	bh.CompanyIdentification = "121042882"
	bh.CompanyEntryDescription = "REDEPCHECK"
	bh.ODFIIdentification = "12104288"
	return bh
}

// mockBOCEntryDetailCredit creates a BatchBOC EntryDetail with a credit
func mockBOCEntryDetailCredit() *EntryDetail {
	entry := NewEntryDetail()
	entry.TransactionCode = 22
	entry.SetRDFI("231380104")
	entry.DFIAccountNumber = "744-5678-99"
	entry.Amount = 25000
	entry.SetCheckSerialNumber("123456789")
	entry.SetReceivingCompany("ABC Company")
	entry.SetTraceNumber(mockBatchBOCHeader().ODFIIdentification, 123)
	entry.Category = CategoryForward
	return entry
}

// mockBatchBOCCredit creates a BatchBOC with a Credit entry
func mockBatchBOCCredit() *BatchBOC {
	mockBatch := NewBatchBOC(mockBatchBOCHeaderCredit())
	mockBatch.AddEntry(mockBOCEntryDetailCredit())
	return mockBatch
}

// testBatchBOCHeader creates a BatchBOC BatchHeader
func testBatchBOCHeader(t testing.TB) {
	batch, _ := NewBatch(mockBatchBOCHeader())
	err, ok := batch.(*BatchBOC)
	if !ok {
		t.Errorf("Expecting BatchBOC got %T", err)
	}
}

// TestBatchBOCHeader tests validating BatchBOC BatchHeader
func TestBatchBOCHeader(t *testing.T) {
	testBatchBOCHeader(t)
}

// BenchmarkBatchBOCHeader benchmarks validating BatchBOC BatchHeader
func BenchmarkBatchBOCHeader(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBatchBOCHeader(b)
	}
}

// testBatchBOCCreate validates BatchBOC create
func testBatchBOCCreate(t testing.TB) {
	mockBatch := mockBatchBOC()
	mockBatch.Create()
	if err := mockBatch.Validate(); err != nil {
		t.Errorf("%T: %s", err, err)
	}
}

// TestBatchBOCCreate tests validating BatchBOC create
func TestBatchBOCCreate(t *testing.T) {
	testBatchBOCCreate(t)
}

// BenchmarkBatchBOCCreate benchmarks validating BatchBOC create
func BenchmarkBatchBOCCreate(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBatchBOCCreate(b)
	}
}

// testBatchBOCStandardEntryClassCode validates BatchBOC create for an invalid StandardEntryClassCode
func testBatchBOCStandardEntryClassCode(t testing.TB) {
	mockBatch := mockBatchBOC()
	mockBatch.Header.StandardEntryClassCode = "WEB"
	mockBatch.Create()
	if err := mockBatch.Validate(); err != nil {
		if e, ok := err.(*BatchError); ok {
			if e.FieldName != "StandardEntryClassCode" {
				t.Errorf("%T: %s", err, err)
			}
		} else {
			t.Errorf("%T: %s", err, err)
		}
	}
}

// TestBatchBOCStandardEntryClassCode tests validating BatchBOC create for an invalid StandardEntryClassCode
func TestBatchBOCStandardEntryClassCode(t *testing.T) {
	testBatchBOCStandardEntryClassCode(t)
}

// BenchmarkBatchBOCStandardEntryClassCode benchmarks validating BatchBOC create for an invalid StandardEntryClassCode
func BenchmarkBatchBOCStandardEntryClassCode(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBatchBOCStandardEntryClassCode(b)
	}
}

// testBatchBOCServiceClass200 validates BatchBOC create for an invalid ServiceClassCode 200
func testBatchBOCServiceClass200(t testing.TB) {
	mockBatch := mockBatchBOC()
	mockBatch.Header.ServiceClassCode = 200
	mockBatch.Create()
	if err := mockBatch.Validate(); err != nil {
		if e, ok := err.(*BatchError); ok {
			if e.FieldName != "ServiceClassCode" {
				t.Errorf("%T: %s", err, err)
			}
		} else {
			t.Errorf("%T: %s", err, err)
		}
	}
}

// TestBatchBOCServiceClass200 tests validating BatchBOC create for an invalid ServiceClassCode 200
func TestBatchBOCServiceClass200(t *testing.T) {
	testBatchBOCServiceClass200(t)
}

// BenchmarkBatchBOCServiceClass200 benchmarks validating BatchBOC create for an invalid ServiceClassCode 200
func BenchmarkBatchBOCServiceClass200(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBatchBOCServiceClass200(b)
	}
}

// testBatchBOCServiceClass220 validates BatchBOC create for an invalid ServiceClassCode 220
func testBatchBOCServiceClass220(t testing.TB) {
	mockBatch := mockBatchBOC()
	mockBatch.Header.ServiceClassCode = 220
	mockBatch.Create()
	if err := mockBatch.Validate(); err != nil {
		if e, ok := err.(*BatchError); ok {
			if e.FieldName != "ServiceClassCode" {
				t.Errorf("%T: %s", err, err)
			}
		} else {
			t.Errorf("%T: %s", err, err)
		}
	}
}

// TestBatchBOCServiceClass220 tests validating BatchBOC create for an invalid ServiceClassCode 220
func TestBatchBOCServiceClass220(t *testing.T) {
	testBatchBOCServiceClass220(t)
}

// BenchmarkBatchBOCServiceClass220 benchmarks validating BatchBOC create for an invalid ServiceClassCode 220
func BenchmarkBatchBOCServiceClass220(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBatchBOCServiceClass220(b)
	}
}

// testBatchBOCServiceClass280 validates BatchBOC create for an invalid ServiceClassCode 280
func testBatchBOCServiceClass280(t testing.TB) {
	mockBatch := mockBatchBOC()
	mockBatch.Header.ServiceClassCode = 280
	mockBatch.Create()
	if err := mockBatch.Validate(); err != nil {
		if e, ok := err.(*BatchError); ok {
			if e.FieldName != "ServiceClassCode" {
				t.Errorf("%T: %s", err, err)
			}
		} else {
			t.Errorf("%T: %s", err, err)
		}
	}
}

// TestBatchBOCServiceClass280 tests validating BatchBOC create for an invalid ServiceClassCode 280
func TestBatchBOCServiceClass280(t *testing.T) {
	testBatchBOCServiceClass280(t)
}

// BenchmarkBatchBOCServiceClass280 benchmarks validating BatchBOC create for an invalid ServiceClassCode 280
func BenchmarkBatchBOCServiceClass280(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBatchBOCServiceClass280(b)
	}
}

// testBatchBOCAmount validates BatchBOC create for an invalid Amount
func testBatchBOCAmount(t testing.TB) {
	mockBatch := mockBatchBOC()
	mockBatch.Entries[0].Amount = 2500001
	mockBatch.Create()
	if err := mockBatch.Validate(); err != nil {
		if e, ok := err.(*BatchError); ok {
			if e.FieldName != "Amount" {
				t.Errorf("%T: %s", err, err)
			}
		} else {
			t.Errorf("%T: %s", err, err)
		}
	}
}

// TestBatchBOCAmount validates BatchBOC create for an invalid Amount
func TestBatchBOCAmount(t *testing.T) {
	testBatchBOCAmount(t)
}

// BenchmarkBatchBOCAmount validates BatchBOC create for an invalid Amount
func BenchmarkBatchBOCAmount(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBatchBOCAmount(b)
	}
}

// testBatchBOCCheckSerialNumber validates BatchBOC CheckSerialNumber / IdentificationNumber is a mandatory field
func testBatchBOCCheckSerialNumber(t testing.TB) {
	mockBatch := mockBatchBOC()
	// modify CheckSerialNumber / IdentificationNumber to empty string
	mockBatch.GetEntries()[0].SetCheckSerialNumber("")
	if err := mockBatch.Validate(); err != nil {
		if e, ok := err.(*BatchError); ok {
			if e.FieldName != "CheckSerialNumber" {
				t.Errorf("%T: %s", err, err)
			}
		} else {
			t.Errorf("%T: %s", err, err)
		}
	}
}

// TestBatchBOCCheckSerialNumber  tests validating BatchBOC CheckSerialNumber / IdentificationNumber is a mandatory field
func TestBatchBOCCheckSerialNumber(t *testing.T) {
	testBatchBOCCheckSerialNumber(t)
}

// BenchmarkBatchBOCCheckSerialNumber benchmarks validating BatchBOC
// CheckSerialNumber / IdentificationNumber is a mandatory field
func BenchmarkBatchBOCCheckSerialNumber(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBatchBOCCheckSerialNumber(b)
	}
}

// testBatchBOCTransactionCode validates BatchBOC TransactionCode is not a credit
func testBatchBOCTransactionCode(t testing.TB) {
	mockBatch := mockBatchBOCCredit()
	if err := mockBatch.Create(); err != nil {
		if e, ok := err.(*BatchError); ok {
			if e.FieldName != "TransactionCode" {
				t.Errorf("%T: %s", err, err)
			}
		} else {
			t.Errorf("%T: %s", err, err)
		}
	}
}

// TestBatchBOCTransactionCode tests validating BatchBOC TransactionCode is not a credit
func TestBatchBOCTransactionCode(t *testing.T) {
	testBatchBOCTransactionCode(t)
}

// BenchmarkBatchBOCTransactionCode benchmarks validating BatchBOC TransactionCode is not a credit
func BenchmarkBatchBOCTransactionCode(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBatchBOCTransactionCode(b)
	}
}

// testBatchBOCAddendaCount validates BatchBOC Addenda count
func testBatchBOCAddendaCount(t testing.TB) {
	mockBatch := mockBatchBOC()
	mockBatch.GetEntries()[0].AddAddenda(mockAddenda05())
	mockBatch.Create()
	if err := mockBatch.Validate(); err != nil {
		if e, ok := err.(*BatchError); ok {
			if e.FieldName != "AddendaCount" {
				t.Errorf("%T: %s", err, err)
			}
		} else {
			t.Errorf("%T: %s", err, err)
		}
	}
}

// TestBatchBOCAddendaCount tests validating BatchBOC Addenda count
func TestBatchBOCAddendaCount(t *testing.T) {
	testBatchBOCAddendaCount(t)
}

// BenchmarkBatchBOCAddendaCount benchmarks validating BatchBOC Addenda count
func BenchmarkBatchBOCAddendaCount(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBatchBOCAddendaCount(b)
	}
}

// testBatchBOCInvalidBuild validates an invalid batch build
func testBatchBOCInvalidBuild(t testing.TB) {
	mockBatch := mockBatchBOC()
	mockBatch.GetHeader().recordType = "3"
	if err := mockBatch.Create(); err != nil {
		if e, ok := err.(*FieldError); ok {
			if e.FieldName != "recordType" {
				t.Errorf("%T: %s", err, err)
			}
		} else {
			t.Errorf("%T: %s", err, err)
		}
	}
}

// TestBatchBOCInvalidBuild tests validating an invalid batch build
func TestBatchBOCInvalidBuild(t *testing.T) {
	testBatchBOCInvalidBuild(t)
}

// BenchmarkBatchBOCInvalidBuild benchmarks validating an invalid batch build
func BenchmarkBatchBOCInvalidBuild(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		testBatchBOCInvalidBuild(b)
	}
}
<|endoftext|>


<commit_before>package contractor

// TODO: We are in the middle of migrating the contractor to a new concurrency
// model. The contractor should never call out to another package while under a
// lock (except for the proto package). This is because the renter is going to
// start calling contractor methods while holding the renter lock, so we need to
// be absolutely confident that no contractor thread will attempt to grab a
// renter lock.

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"sync"

	"github.com/NebulousLabs/Sia/modules"
	"github.com/NebulousLabs/Sia/modules/renter/proto"
	"github.com/NebulousLabs/Sia/persist"
	siasync "github.com/NebulousLabs/Sia/sync"
	"github.com/NebulousLabs/Sia/types"
)

var (
	errNilCS     = errors.New("cannot create contractor with nil consensus set")
	errNilTpool  = errors.New("cannot create contractor with nil transaction pool")
	errNilWallet = errors.New("cannot create contractor with nil wallet")

	// COMPATv1.0.4-lts
	// metricsContractID identifies a special contract that contains aggregate
	// financial metrics from older contractors
	metricsContractID = types.FileContractID{'m', 'e', 't', 'r', 'i', 'c', 's'}
)

// A cachedRevision contains changes that would be applied to a RenterContract
// if a contract revision succeeded. The contractor must cache these changes
// as a safeguard against desynchronizing with the host.
// TODO: save a diff of the Merkle roots instead of all of them.
type cachedRevision struct {
	Revision    types.FileContractRevision `json:"revision"`
	MerkleRoots modules.MerkleRootSet      `json:"merkleroots"`
}

// A Contractor negotiates, revises, renews, and provides access to file
// contracts.
type Contractor struct {
	// dependencies
	cs      consensusSet
	hdb     hostDB
	log     *persist.Logger
	persist persister
	mu      sync.RWMutex
	tg      siasync.ThreadGroup
	tpool   transactionPool
	wallet  wallet

	// Only one thread should be performing contract maintenance at a time.
	interruptMaintenance chan struct{}
	maintenanceLock      siasync.TryMutex

	allowance     modules.Allowance
	blockHeight   types.BlockHeight
	currentPeriod types.BlockHeight
	lastChange    modules.ConsensusChangeID

	downloaders map[types.FileContractID]*hostDownloader
	editors     map[types.FileContractID]*hostEditor
	renewing    map[types.FileContractID]bool // prevent revising during renewal
	revising    map[types.FileContractID]bool // prevent overlapping revisions

	// The contract utility values are not persisted in any way, instead get
	// set based on the values in the hostdb at startup. During startup, the
	// 'managedMarkContractsUtility' needs to be called so that the utility is
	// set correctly.
	contracts         *proto.ContractSet
	contractUtilities map[types.FileContractID]modules.ContractUtility
	oldContracts      map[types.FileContractID]modules.RenterContract
	renewedIDs        map[types.FileContractID]types.FileContractID
}

// resolveID returns the ID of the most recent renewal of id.
func (c *Contractor) resolveID(id types.FileContractID) types.FileContractID {
	newID, exists := c.renewedIDs[id]
	for exists {
		id = newID
		newID, exists = c.renewedIDs[id]
	}
	return id
}

// Allowance returns the current allowance.
func (c *Contractor) Allowance() modules.Allowance {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.allowance
}

// PeriodSpending returns the amount spent on contracts during the current
// billing period.
func (c *Contractor) PeriodSpending() modules.ContractorSpending {
	c.mu.RLock()
	defer c.mu.RUnlock()

	var spending modules.ContractorSpending
	for _, contract := range c.contracts.ViewAll() {
		spending.ContractSpending = spending.ContractSpending.Add(contract.TotalCost)
		spending.DownloadSpending = spending.DownloadSpending.Add(contract.DownloadSpending)
		spending.UploadSpending = spending.UploadSpending.Add(contract.UploadSpending)
		spending.StorageSpending = spending.StorageSpending.Add(contract.StorageSpending)
		// TODO: fix PreviousContracts
		// for _, pre := range contract.PreviousContracts {
		// 	spending.ContractSpending = spending.ContractSpending.Add(pre.TotalCost)
		// 	spending.DownloadSpending = spending.DownloadSpending.Add(pre.DownloadSpending)
		// 	spending.UploadSpending = spending.UploadSpending.Add(pre.UploadSpending)
		// 	spending.StorageSpending = spending.StorageSpending.Add(pre.StorageSpending)
		// }
	}
	allSpending := spending.ContractSpending.Add(spending.DownloadSpending).Add(spending.UploadSpending).Add(spending.StorageSpending)
	spending.Unspent = c.allowance.Funds.Sub(allSpending)
	return spending
}

// ContractByID returns the contract with the id specified, if it exists. The
// contract will be resolved if possible to the most recent child contract.
func (c *Contractor) ContractByID(id types.FileContractID) (modules.RenterContract, bool) {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.contracts.View(c.resolveID(id))
}

// Contracts returns the contracts formed by the contractor in the current
// allowance period. Only contracts formed with currently online hosts are
// returned.
func (c *Contractor) Contracts() []modules.RenterContract {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.contracts.ViewAll()
}

// ContractUtility returns the utility fields for the given contract.
func (c *Contractor) ContractUtility(id types.FileContractID) (modules.ContractUtility, bool) {
	c.mu.RLock()
	utility, exists := c.contractUtilities[c.resolveID(id)]
	c.mu.RUnlock()
	return utility, exists
}

// CurrentPeriod returns the height at which the current allowance period
// began.
func (c *Contractor) CurrentPeriod() types.BlockHeight {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.currentPeriod
}

// ResolveID returns the ID of the most recent renewal of id.
func (c *Contractor) ResolveID(id types.FileContractID) types.FileContractID {
	c.mu.RLock()
	newID := c.resolveID(id)
	c.mu.RUnlock()
	return newID
}

// Close closes the Contractor.
func (c *Contractor) Close() error {
	return c.tg.Stop()
}

// New returns a new Contractor.
func New(cs consensusSet, wallet walletShim, tpool transactionPool, hdb hostDB, persistDir string) (*Contractor, error) {
	// Check for nil inputs.
	if cs == nil {
		return nil, errNilCS
	}
	if wallet == nil {
		return nil, errNilWallet
	}
	if tpool == nil {
		return nil, errNilTpool
	}

	// Create the persist directory if it does not yet exist.
	if err := os.MkdirAll(persistDir, 0700); err != nil {
		return nil, err
	}

	// Convert the old persist file(s), if necessary. This must occur before
	// loading the contract set.
	if err := convertPersist(persistDir); err != nil {
		return nil, err
	}

	// Create the contract set.
	contractSet, err := proto.NewContractSet(filepath.Join(persistDir, "contracts"))
	if err != nil {
		return nil, err
	}
	// Create the logger.
	logger, err := persist.NewFileLogger(filepath.Join(persistDir, "contractor.log"))
	if err != nil {
		return nil, err
	}

	// Create Contractor using production dependencies.
	return newContractor(cs, &walletBridge{w: wallet}, tpool, hdb, contractSet, newPersist(persistDir), logger)
}

// newContractor creates a Contractor using the provided dependencies.
func newContractor(cs consensusSet, w wallet, tp transactionPool, hdb hostDB, contractSet *proto.ContractSet, p persister, l *persist.Logger) (*Contractor, error) {
	// Create the Contractor object.
	c := &Contractor{
		cs:      cs,
		hdb:     hdb,
		log:     l,
		persist: p,
		tpool:   tp,
		wallet:  w,

		interruptMaintenance: make(chan struct{}),

		contracts:         contractSet,
		downloaders:       make(map[types.FileContractID]*hostDownloader),
		editors:           make(map[types.FileContractID]*hostEditor),
		contractUtilities: make(map[types.FileContractID]modules.ContractUtility),
		oldContracts:      make(map[types.FileContractID]modules.RenterContract),
		renewedIDs:        make(map[types.FileContractID]types.FileContractID),
		renewing:          make(map[types.FileContractID]bool),
		revising:          make(map[types.FileContractID]bool),
	}
	// Close the contract set and logger upon shutdown.
	c.tg.AfterStop(func() {
		if err := c.contracts.Close(); err != nil {
			c.log.Println("Failed to close contract set:", err)
		}
		if err := c.log.Close(); err != nil {
			fmt.Println("Failed to close the contractor logger:", err)
		}
	})

	// Load the prior persistence structures.
	err := c.load()
	if err != nil && !os.IsNotExist(err) {
		return nil, err
	}

	// Mark contract utility.
	c.managedMarkContractsUtility()

	// Subscribe to the consensus set.
	err = cs.ConsensusSetSubscribe(c, c.lastChange, c.tg.StopChan())
	if err == modules.ErrInvalidConsensusChangeID {
		// Reset the contractor consensus variables and try rescanning.
		c.blockHeight = 0
		c.lastChange = modules.ConsensusChangeBeginning
		err = cs.ConsensusSetSubscribe(c, c.lastChange, c.tg.StopChan())
	}
	if err != nil {
		return nil, errors.New("contractor subscription failed: " + err.Error())
	}
	// Unsubscribe from the consensus set upon shutdown.
	c.tg.OnStop(func() {
		cs.Unsubscribe(c)
	})

	// We may have upgraded persist or resubscribed. Save now so that we don't
	// lose our work.
	c.mu.Lock()
	err = c.save()
	c.mu.Unlock()
	if err != nil {
		return nil, err
	}

	return c, nil
}
<commit_msg>fix panic if allowance is set to 0 and PeriodSpending is called<commit_after>package contractor

// TODO: We are in the middle of migrating the contractor to a new concurrency
// model. The contractor should never call out to another package while under a
// lock (except for the proto package). This is because the renter is going to
// start calling contractor methods while holding the renter lock, so we need to
// be absolutely confident that no contractor thread will attempt to grab a
// renter lock.

import (
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"sync"

	"github.com/NebulousLabs/Sia/modules"
	"github.com/NebulousLabs/Sia/modules/renter/proto"
	"github.com/NebulousLabs/Sia/persist"
	siasync "github.com/NebulousLabs/Sia/sync"
	"github.com/NebulousLabs/Sia/types"
)

var (
	errNilCS     = errors.New("cannot create contractor with nil consensus set")
	errNilTpool  = errors.New("cannot create contractor with nil transaction pool")
	errNilWallet = errors.New("cannot create contractor with nil wallet")

	// COMPATv1.0.4-lts
	// metricsContractID identifies a special contract that contains aggregate
	// financial metrics from older contractors
	metricsContractID = types.FileContractID{'m', 'e', 't', 'r', 'i', 'c', 's'}
)

// A cachedRevision contains changes that would be applied to a RenterContract
// if a contract revision succeeded. The contractor must cache these changes
// as a safeguard against desynchronizing with the host.
// TODO: save a diff of the Merkle roots instead of all of them.
type cachedRevision struct {
	Revision    types.FileContractRevision `json:"revision"`
	MerkleRoots modules.MerkleRootSet      `json:"merkleroots"`
}

// A Contractor negotiates, revises, renews, and provides access to file
// contracts.
type Contractor struct {
	// dependencies
	cs      consensusSet
	hdb     hostDB
	log     *persist.Logger
	persist persister
	mu      sync.RWMutex
	tg      siasync.ThreadGroup
	tpool   transactionPool
	wallet  wallet

	// Only one thread should be performing contract maintenance at a time.
	interruptMaintenance chan struct{}
	maintenanceLock      siasync.TryMutex

	allowance     modules.Allowance
	blockHeight   types.BlockHeight
	currentPeriod types.BlockHeight
	lastChange    modules.ConsensusChangeID

	downloaders map[types.FileContractID]*hostDownloader
	editors     map[types.FileContractID]*hostEditor
	renewing    map[types.FileContractID]bool // prevent revising during renewal
	revising    map[types.FileContractID]bool // prevent overlapping revisions

	// The contract utility values are not persisted in any way, instead get
	// set based on the values in the hostdb at startup. During startup, the
	// 'managedMarkContractsUtility' needs to be called so that the utility is
	// set correctly.
	contracts         *proto.ContractSet
	contractUtilities map[types.FileContractID]modules.ContractUtility
	oldContracts      map[types.FileContractID]modules.RenterContract
	renewedIDs        map[types.FileContractID]types.FileContractID
}

// resolveID returns the ID of the most recent renewal of id.
func (c *Contractor) resolveID(id types.FileContractID) types.FileContractID {
	newID, exists := c.renewedIDs[id]
	for exists {
		id = newID
		newID, exists = c.renewedIDs[id]
	}
	return id
}

// Allowance returns the current allowance.
func (c *Contractor) Allowance() modules.Allowance {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.allowance
}

// PeriodSpending returns the amount spent on contracts during the current
// billing period.
func (c *Contractor) PeriodSpending() modules.ContractorSpending {
	c.mu.RLock()
	defer c.mu.RUnlock()

	var spending modules.ContractorSpending
	for _, contract := range c.contracts.ViewAll() {
		spending.ContractSpending = spending.ContractSpending.Add(contract.TotalCost)
		spending.DownloadSpending = spending.DownloadSpending.Add(contract.DownloadSpending)
		spending.UploadSpending = spending.UploadSpending.Add(contract.UploadSpending)
		spending.StorageSpending = spending.StorageSpending.Add(contract.StorageSpending)
		// TODO: fix PreviousContracts
		// for _, pre := range contract.PreviousContracts {
		// 	spending.ContractSpending = spending.ContractSpending.Add(pre.TotalCost)
		// 	spending.DownloadSpending = spending.DownloadSpending.Add(pre.DownloadSpending)
		// 	spending.UploadSpending = spending.UploadSpending.Add(pre.UploadSpending)
		// 	spending.StorageSpending = spending.StorageSpending.Add(pre.StorageSpending)
		// }
	}
	allSpending := spending.ContractSpending.Add(spending.DownloadSpending).Add(spending.UploadSpending).Add(spending.StorageSpending)

	// If there is no allowance, the unspent funds are 0
	if !c.allowance.Funds.IsZero() {
		spending.Unspent = c.allowance.Funds.Sub(allSpending)
	}
	return spending
}

// ContractByID returns the contract with the id specified, if it exists. The
// contract will be resolved if possible to the most recent child contract.
func (c *Contractor) ContractByID(id types.FileContractID) (modules.RenterContract, bool) {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.contracts.View(c.resolveID(id))
}

// Contracts returns the contracts formed by the contractor in the current
// allowance period. Only contracts formed with currently online hosts are
// returned.
func (c *Contractor) Contracts() []modules.RenterContract {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.contracts.ViewAll()
}

// ContractUtility returns the utility fields for the given contract.
func (c *Contractor) ContractUtility(id types.FileContractID) (modules.ContractUtility, bool) {
	c.mu.RLock()
	utility, exists := c.contractUtilities[c.resolveID(id)]
	c.mu.RUnlock()
	return utility, exists
}

// CurrentPeriod returns the height at which the current allowance period
// began.
func (c *Contractor) CurrentPeriod() types.BlockHeight {
	c.mu.RLock()
	defer c.mu.RUnlock()
	return c.currentPeriod
}

// ResolveID returns the ID of the most recent renewal of id.
func (c *Contractor) ResolveID(id types.FileContractID) types.FileContractID {
	c.mu.RLock()
	newID := c.resolveID(id)
	c.mu.RUnlock()
	return newID
}

// Close closes the Contractor.
func (c *Contractor) Close() error {
	return c.tg.Stop()
}

// New returns a new Contractor.
func New(cs consensusSet, wallet walletShim, tpool transactionPool, hdb hostDB, persistDir string) (*Contractor, error) {
	// Check for nil inputs.
	if cs == nil {
		return nil, errNilCS
	}
	if wallet == nil {
		return nil, errNilWallet
	}
	if tpool == nil {
		return nil, errNilTpool
	}

	// Create the persist directory if it does not yet exist.
	if err := os.MkdirAll(persistDir, 0700); err != nil {
		return nil, err
	}

	// Convert the old persist file(s), if necessary. This must occur before
	// loading the contract set.
	if err := convertPersist(persistDir); err != nil {
		return nil, err
	}

	// Create the contract set.
	contractSet, err := proto.NewContractSet(filepath.Join(persistDir, "contracts"))
	if err != nil {
		return nil, err
	}
	// Create the logger.
	logger, err := persist.NewFileLogger(filepath.Join(persistDir, "contractor.log"))
	if err != nil {
		return nil, err
	}

	// Create Contractor using production dependencies.
	return newContractor(cs, &walletBridge{w: wallet}, tpool, hdb, contractSet, newPersist(persistDir), logger)
}

// newContractor creates a Contractor using the provided dependencies.
func newContractor(cs consensusSet, w wallet, tp transactionPool, hdb hostDB, contractSet *proto.ContractSet, p persister, l *persist.Logger) (*Contractor, error) {
	// Create the Contractor object.
	c := &Contractor{
		cs:      cs,
		hdb:     hdb,
		log:     l,
		persist: p,
		tpool:   tp,
		wallet:  w,

		interruptMaintenance: make(chan struct{}),

		contracts:         contractSet,
		downloaders:       make(map[types.FileContractID]*hostDownloader),
		editors:           make(map[types.FileContractID]*hostEditor),
		contractUtilities: make(map[types.FileContractID]modules.ContractUtility),
		oldContracts:      make(map[types.FileContractID]modules.RenterContract),
		renewedIDs:        make(map[types.FileContractID]types.FileContractID),
		renewing:          make(map[types.FileContractID]bool),
		revising:          make(map[types.FileContractID]bool),
	}
	// Close the contract set and logger upon shutdown.
	c.tg.AfterStop(func() {
		if err := c.contracts.Close(); err != nil {
			c.log.Println("Failed to close contract set:", err)
		}
		if err := c.log.Close(); err != nil {
			fmt.Println("Failed to close the contractor logger:", err)
		}
	})

	// Load the prior persistence structures.
	err := c.load()
	if err != nil && !os.IsNotExist(err) {
		return nil, err
	}

	// Mark contract utility.
	c.managedMarkContractsUtility()

	// Subscribe to the consensus set.
	err = cs.ConsensusSetSubscribe(c, c.lastChange, c.tg.StopChan())
	if err == modules.ErrInvalidConsensusChangeID {
		// Reset the contractor consensus variables and try rescanning.
		c.blockHeight = 0
		c.lastChange = modules.ConsensusChangeBeginning
		err = cs.ConsensusSetSubscribe(c, c.lastChange, c.tg.StopChan())
	}
	if err != nil {
		return nil, errors.New("contractor subscription failed: " + err.Error())
	}
	// Unsubscribe from the consensus set upon shutdown.
	c.tg.OnStop(func() {
		cs.Unsubscribe(c)
	})

	// We may have upgraded persist or resubscribed. Save now so that we don't
	// lose our work.
	c.mu.Lock()
	err = c.save()
	c.mu.Unlock()
	if err != nil {
		return nil, err
	}

	return c, nil
}
<|endoftext|>


<commit_before>// Copyright © 2012 Hraban Luyat <hraban@0brg.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

// Light-weight in-memory LRU (object) cache library for Go.
//
// To use this library, first create a cache:
//
//      c := lrucache.New(1234)
//
// Then define a type that implements the Cacheable interface:
//
//      type cacheableInt int
//
//      func (i cacheableInt) OnPurge(deleted bool) {
//          fmt.Printf("Purging %d\n", i)
//      }
//
//      func (i cacheableInt) Size() int64 {
//          return 1
//      }
//
// Finally:
//
//     for i := 0; i < 2000; i++ {
//         c.Set(strconv.Itoa(i), cacheableInt(i))
//     }
//
// This will generate the following output:
//
//     Purging 0
//     Purging 1
//     ...
//     Purging 764
//     Purging 765
//
// Note:
//
// * The unit of item sizes is not defined; whatever it is, once the sum
// exceeds the maximum cache size, elements start getting purged until it
// drops below the threshold again.
//
// * The integers are passed by value. Caching pointers is, of course, Okay,
// but be careful when caching a memory location that holds two different
// values at different points in time; updating the value of a pointer after
// caching it will change the cached value.
//
package lrucache

type Cache struct {
	// Feel free to change this whenever. The units are not bytes but just
	// whatever unit it is that your cache entries return from Size(). If
	// (roughly) all cached items are going to be (roughly) the same size it
	// makes sense to return 1 from Size() and set MaxSize to the maximum number
	// of elements you want to allow in cache.
	MaxSize int64
	size    int64
	entries map[string]*cacheEntry
	// Cache operations are pushed down this channel to the main cache loop
	opChan           chan operation
	lruHead, lruTail *cacheEntry
	// If not nil, invoked for every cache miss.
	onMiss func(string) Cacheable
}

func (c *Cache) Size() int64 {
	return c.size
}

// Anything that implements this interface can be stored in a cache. Two
// different types can share the same cache, all that matters is that they
// implement this interface.
type Cacheable interface {
	// See Cache.MaxSize for an explanation
	Size() int64
}

type NotifyPurge interface {
	Cacheable
	// Called once when the element is purged from cache. The deleted boolean
	// indicates whether this call was the result of a call to Cache.Delete to
	// explicitly delete this item.  Possible reasons for this method to get
	// called:
	//
	// * Cache is growing too large and this is the least used item (deleted =
	// false)
	//
	// * This item was explicitly deleted using Cache.Delete(id) (deleted =
	// true)
	//
	// * A new element with the same key is stored (deleted = false)
	//
	// For most types of cached elements, this can just be a NOP. A real
	// example is a session cache where sessions are not stored in a database
	// until they are purged from the memory cache. As long as the memory cache
	// is large enough to hold all of them, they expire before the cache grows
	// too large and no database connection is ever needed. This OnPurge
	// implementation would store items to a database iff deleted == false.
	//
	// Called from within a private goroutine, but never called concurrently
	// with other elements' OnPurge().
	OnPurge(deleted bool)
}

// Requests that are passed to the cache managing goroutine
type operation interface{}

type reqSet struct {
	id      string
	payload Cacheable
}

type reqGet struct {
	id string
	// Cache goroutine pushes result down this channel (if any) and closes it
	reply chan Cacheable
}

type reqDelete struct {
	id string
}

// Used only for testing
type reqPing chan (bool)

type reqOnMissFunc func(string) Cacheable

type cacheEntry struct {
	payload Cacheable
	id      string
	// Pointers for LRU cache
	prev, next *cacheEntry
}

// Only call c.OnPurge() if c implements NotifyPurge.
func safeOnPurge(c Cacheable, deleted bool) {
	if t, ok := c.(NotifyPurge); ok {
		t.OnPurge(deleted)
	}
	return
}

func removeEntry(c *Cache, e *cacheEntry) {
	delete(c.entries, e.id)
	if e.prev == nil {
		c.lruTail = e.next
	} else {
		e.prev.next = e.next
	}
	if e.next == nil {
		c.lruHead = e.prev
	} else {
		e.next.prev = e.prev
	}
	c.size -= e.payload.Size()
	return
}

// Purge the least recently used from the cache
func purgeLRU(c *Cache) {
	safeOnPurge(c.lruTail.payload, false)
	removeEntry(c, c.lruTail)
	return
}

// Trim the cache until its size <= max size
func trimCache(c *Cache) {
	for c.size > c.MaxSize {
		purgeLRU(c)
	}
	return
}

// Not safe for use in concurrent goroutines
func directSet(c *Cache, req reqSet) {
	// Overwrite old entry
	if old, ok := c.entries[req.id]; ok {
		safeOnPurge(old.payload, false)
		removeEntry(c, old)
	}
	e := cacheEntry{payload: req.payload, id: req.id}
	if len(c.entries) == 0 {
		c.lruTail = &e
		c.lruHead = &e
		e.next = nil
		e.prev = nil
	} else {
		c.lruHead.next = &e
		e.prev = c.lruHead
		c.lruHead = &e
	}
	c.size += e.payload.Size()
	c.entries[req.id] = &e
	trimCache(c)
	return
}

// Not safe for use in concurrent goroutines
func directDelete(c *Cache, req reqDelete) {
	e, ok := c.entries[req.id]
	if ok {
		safeOnPurge(e.payload, true)
		removeEntry(c, e)
	}
	return
}

// Not safe for use in concurrent goroutines
func directGet(c *Cache, req reqGet) {
	e, ok := c.entries[req.id]
	if ok {
		req.reply <- e.payload
	} else {
		if c.onMiss != nil {
			p := c.onMiss(req.id)
			if p != nil {
				req.reply <- p
				close(req.reply)
				directSet(c, reqSet{req.id, p})
				return
			}
		}
	}
	close(req.reply)
	if !ok || e.next == nil {
		return
	}
	// Put element at the start of the LRU list
	if e.prev != nil {
		e.prev.next = e.next
	} else {
		c.lruTail = e.next
	}
	e.next.prev = e.prev
	e.prev = c.lruHead
	c.lruHead = e
	return
}

func (c *Cache) Init(maxsize int64) {
	c.MaxSize = maxsize
	c.opChan = make(chan operation)
	c.entries = map[string]*cacheEntry{}
	go func() {
		for op := range c.opChan {
			switch req := op.(type) {
			case reqSet:
				directSet(c, req)
			case reqDelete:
				directDelete(c, req)
			case reqGet:
				directGet(c, req)
			case reqPing:
				req <- true
			case reqOnMissFunc:
				c.onMiss = req
			default:
				panic("Illegal cache operation")
			}
		}
	}()
	return
}

// Store this item in cache. Panics if the cacheable is nil.
func (c *Cache) Set(id string, p Cacheable) {
	if p == nil {
		panic("Cacheable value must not be nil")
	}
	c.opChan <- reqSet{payload: p, id: id}
	return
}

func (c *Cache) Get(id string) (Cacheable, bool) {
	req := reqGet{id: id, reply: make(chan Cacheable)}
	c.opChan <- req
	e, ok := <-req.reply
	return e, ok
}

func (c *Cache) Delete(id string) {
	c.opChan <- reqDelete{id: id}
}

// Used to populate the cache if an entry is not found. If result is not nil,
// it is stored in cache and returned from Get. Call with f is nil to clear.
func (c *Cache) OnMiss(f func(string) Cacheable) {
	c.opChan <- reqOnMissFunc(f)
}

// Create and initialize a new cache, ready for use.
func New(maxsize int64) *Cache {
	var c Cache
	c.Init(maxsize)
	return &c
}

// Shared cache for configuration-less use

var sharedCache Cache

// Only necessary if you plan on using non-methods Get and Set.
func InitShared(maxsize int64) {
	sharedCache.Init(maxsize)
}

func Get(id string) (Cacheable, bool) {
	return sharedCache.Get(id)
}

func Set(id string, c Cacheable) {
	sharedCache.Set(id, c)
	return
}

func Delete(id string) {
	sharedCache.Delete(id)
	return
}
<commit_msg>Simpler reqDelete type (alias vs 1-element record)<commit_after>// Copyright © 2012 Hraban Luyat <hraban@0brg.net>
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

// Light-weight in-memory LRU (object) cache library for Go.
//
// To use this library, first create a cache:
//
//      c := lrucache.New(1234)
//
// Then define a type that implements the Cacheable interface:
//
//      type cacheableInt int
//
//      func (i cacheableInt) OnPurge(deleted bool) {
//          fmt.Printf("Purging %d\n", i)
//      }
//
//      func (i cacheableInt) Size() int64 {
//          return 1
//      }
//
// Finally:
//
//     for i := 0; i < 2000; i++ {
//         c.Set(strconv.Itoa(i), cacheableInt(i))
//     }
//
// This will generate the following output:
//
//     Purging 0
//     Purging 1
//     ...
//     Purging 764
//     Purging 765
//
// Note:
//
// * The unit of item sizes is not defined; whatever it is, once the sum
// exceeds the maximum cache size, elements start getting purged until it
// drops below the threshold again.
//
// * The integers are passed by value. Caching pointers is, of course, Okay,
// but be careful when caching a memory location that holds two different
// values at different points in time; updating the value of a pointer after
// caching it will change the cached value.
//
package lrucache

type Cache struct {
	// Feel free to change this whenever. The units are not bytes but just
	// whatever unit it is that your cache entries return from Size(). If
	// (roughly) all cached items are going to be (roughly) the same size it
	// makes sense to return 1 from Size() and set MaxSize to the maximum number
	// of elements you want to allow in cache.
	MaxSize int64
	size    int64
	entries map[string]*cacheEntry
	// Cache operations are pushed down this channel to the main cache loop
	opChan           chan operation
	lruHead, lruTail *cacheEntry
	// If not nil, invoked for every cache miss.
	onMiss func(string) Cacheable
}

func (c *Cache) Size() int64 {
	return c.size
}

// Anything that implements this interface can be stored in a cache. Two
// different types can share the same cache, all that matters is that they
// implement this interface.
type Cacheable interface {
	// See Cache.MaxSize for an explanation
	Size() int64
}

type NotifyPurge interface {
	Cacheable
	// Called once when the element is purged from cache. The deleted boolean
	// indicates whether this call was the result of a call to Cache.Delete to
	// explicitly delete this item.  Possible reasons for this method to get
	// called:
	//
	// * Cache is growing too large and this is the least used item (deleted =
	// false)
	//
	// * This item was explicitly deleted using Cache.Delete(id) (deleted =
	// true)
	//
	// * A new element with the same key is stored (deleted = false)
	//
	// For most types of cached elements, this can just be a NOP. A real
	// example is a session cache where sessions are not stored in a database
	// until they are purged from the memory cache. As long as the memory cache
	// is large enough to hold all of them, they expire before the cache grows
	// too large and no database connection is ever needed. This OnPurge
	// implementation would store items to a database iff deleted == false.
	//
	// Called from within a private goroutine, but never called concurrently
	// with other elements' OnPurge().
	OnPurge(deleted bool)
}

// Requests that are passed to the cache managing goroutine
type operation interface{}

type reqSet struct {
	id      string
	payload Cacheable
}

type reqGet struct {
	id string
	// Cache goroutine pushes result down this channel (if any) and closes it
	reply chan Cacheable
}

type reqDelete string

// Used only for testing
type reqPing chan (bool)

type reqOnMissFunc func(string) Cacheable

type cacheEntry struct {
	payload Cacheable
	id      string
	// Pointers for LRU cache
	prev, next *cacheEntry
}

// Only call c.OnPurge() if c implements NotifyPurge.
func safeOnPurge(c Cacheable, deleted bool) {
	if t, ok := c.(NotifyPurge); ok {
		t.OnPurge(deleted)
	}
	return
}

func removeEntry(c *Cache, e *cacheEntry) {
	delete(c.entries, e.id)
	if e.prev == nil {
		c.lruTail = e.next
	} else {
		e.prev.next = e.next
	}
	if e.next == nil {
		c.lruHead = e.prev
	} else {
		e.next.prev = e.prev
	}
	c.size -= e.payload.Size()
	return
}

// Purge the least recently used from the cache
func purgeLRU(c *Cache) {
	safeOnPurge(c.lruTail.payload, false)
	removeEntry(c, c.lruTail)
	return
}

// Trim the cache until its size <= max size
func trimCache(c *Cache) {
	for c.size > c.MaxSize {
		purgeLRU(c)
	}
	return
}

// Not safe for use in concurrent goroutines
func directSet(c *Cache, req reqSet) {
	// Overwrite old entry
	if old, ok := c.entries[req.id]; ok {
		safeOnPurge(old.payload, false)
		removeEntry(c, old)
	}
	e := cacheEntry{payload: req.payload, id: req.id}
	if len(c.entries) == 0 {
		c.lruTail = &e
		c.lruHead = &e
		e.next = nil
		e.prev = nil
	} else {
		c.lruHead.next = &e
		e.prev = c.lruHead
		c.lruHead = &e
	}
	c.size += e.payload.Size()
	c.entries[req.id] = &e
	trimCache(c)
	return
}

// Not safe for use in concurrent goroutines
func directDelete(c *Cache, req reqDelete) {
	id := string(req)
	e, ok := c.entries[id]
	if ok {
		safeOnPurge(e.payload, true)
		removeEntry(c, e)
	}
	return
}

// Not safe for use in concurrent goroutines
func directGet(c *Cache, req reqGet) {
	e, ok := c.entries[req.id]
	if ok {
		req.reply <- e.payload
	} else {
		if c.onMiss != nil {
			p := c.onMiss(req.id)
			if p != nil {
				req.reply <- p
				close(req.reply)
				directSet(c, reqSet{req.id, p})
				return
			}
		}
	}
	close(req.reply)
	if !ok || e.next == nil {
		return
	}
	// Put element at the start of the LRU list
	if e.prev != nil {
		e.prev.next = e.next
	} else {
		c.lruTail = e.next
	}
	e.next.prev = e.prev
	e.prev = c.lruHead
	c.lruHead = e
	return
}

func (c *Cache) Init(maxsize int64) {
	c.MaxSize = maxsize
	c.opChan = make(chan operation)
	c.entries = map[string]*cacheEntry{}
	go func() {
		for op := range c.opChan {
			switch req := op.(type) {
			case reqSet:
				directSet(c, req)
			case reqDelete:
				directDelete(c, req)
			case reqGet:
				directGet(c, req)
			case reqPing:
				req <- true
			case reqOnMissFunc:
				c.onMiss = req
			default:
				panic("Illegal cache operation")
			}
		}
	}()
	return
}

// Store this item in cache. Panics if the cacheable is nil.
func (c *Cache) Set(id string, p Cacheable) {
	if p == nil {
		panic("Cacheable value must not be nil")
	}
	c.opChan <- reqSet{payload: p, id: id}
	return
}

func (c *Cache) Get(id string) (Cacheable, bool) {
	req := reqGet{id: id, reply: make(chan Cacheable)}
	c.opChan <- req
	e, ok := <-req.reply
	return e, ok
}

func (c *Cache) Delete(id string) {
	c.opChan <- reqDelete(id)
}

// Used to populate the cache if an entry is not found. If result is not nil,
// it is stored in cache and returned from Get. Call with f is nil to clear.
func (c *Cache) OnMiss(f func(string) Cacheable) {
	c.opChan <- reqOnMissFunc(f)
}

// Create and initialize a new cache, ready for use.
func New(maxsize int64) *Cache {
	var c Cache
	c.Init(maxsize)
	return &c
}

// Shared cache for configuration-less use

var sharedCache Cache

// Only necessary if you plan on using non-methods Get and Set.
func InitShared(maxsize int64) {
	sharedCache.Init(maxsize)
}

func Get(id string) (Cacheable, bool) {
	return sharedCache.Get(id)
}

func Set(id string, c Cacheable) {
	sharedCache.Set(id, c)
	return
}

func Delete(id string) {
	sharedCache.Delete(id)
	return
}
<|endoftext|>