<commit_before>//  Package fileCache will provide simple file content caching tools for in-Memory access to files.
//  It uses golang/groupcache to cache your data into memory on multiple HTTP Pool servers.
package fileCache

import (
	"log"
	"os"
	"sync"

	"encoding/json"
	"io/ioutil"

	"github.com/DanielRenne/GoCore/core"
	"github.com/DanielRenne/GoCore/core/extensions"
	"github.com/DanielRenne/GoCore/core/serverSettings"
	"github.com/DanielRenne/GoCore/core/utils"
	"github.com/golang/groupcache"
)

const (
	CACHE_STORAGE_PATH = "/usr/local/goCore/caches"
)

const (
	CACHE_BOOTSTRAP_STORAGE_PATH = CACHE_STORAGE_PATH + "/bootstrap"
	CACHE_MANIFEST_STORAGE_PATH  = CACHE_STORAGE_PATH + "/manifests"
)

type model struct {
	sync.RWMutex
	BootstrapCache map[string][]string
}

type byteManifest struct {
	sync.RWMutex
	Cache map[string]map[string]int
}

var Model model
var ByteManifest byteManifest
var peers *groupcache.HTTPPool
var htmlFileCache *groupcache.Group
var stringCache *groupcache.Group

// contains the temporary string cache used to cache large strings.
var tempStringCacheSynced = struct {
	sync.RWMutex
	cache map[string]string
}{cache: make(map[string]string)}

func init() {
	os.MkdirAll(CACHE_BOOTSTRAP_STORAGE_PATH, 0777)
	os.MkdirAll(CACHE_MANIFEST_STORAGE_PATH, 0777)
	Model = model{
		BootstrapCache: make(map[string][]string, 0),
	}
	ByteManifest = byteManifest{
		Cache: make(map[string]map[string]int, 0),
	}
}

//Call Initialize in main before any calls to this package are performed.  serverSettings package must be initialized before fileCache.
func Initialize() {
	if serverSettings.WebConfig.Application.Domain != "" {
		initializeGroupCache(serverSettings.WebConfig.Application.Domain)
	}
}

func WriteBootStrapCacheFile(key string) (err error) {
	Model.RLock()
	caches, ok := Model.BootstrapCache[key]
	Model.RUnlock()
	if ok {
		strjson, err := json.Marshal(caches)
		if err != nil {
			return err
		}
		err = ioutil.WriteFile(CACHE_BOOTSTRAP_STORAGE_PATH+"/"+key+".json", []byte(strjson), 0777)
		if err != nil {
			return err
		}
	}
	return nil
}

func UpdateBootStrapMemoryCache(key string, value string) {
	Model.Lock()
	_, ok := Model.BootstrapCache[key]
	if !ok {
		Model.BootstrapCache[key] = utils.Array(value)
	} else {
		if !DoesHashExistInCache(key, value) {
			Model.BootstrapCache[key] = append(Model.BootstrapCache[key], value)
		}
	}
	Model.Unlock()
	return
}

func DeleteBootStrapFileCache(key string) (err error) {
	fname := CACHE_BOOTSTRAP_STORAGE_PATH + "/" + key + ".json"
	if extensions.DoesFileExist(fname) {
		err = os.Remove(fname)
		if err != nil {
			return err
		}
	}
	return
}

func DeleteAllBootStrapFileCache() (err error) {
	if extensions.DoesFileExist(CACHE_BOOTSTRAP_STORAGE_PATH) {
		err = extensions.RemoveDirectory(CACHE_BOOTSTRAP_STORAGE_PATH)
		if err != nil {
			core.Debug.Dump(err)
			return err
		}
		os.MkdirAll(CACHE_BOOTSTRAP_STORAGE_PATH, 0777)
	}
	return
}

func LoadCachedBootStrapFromKeyIntoMemory(key string) (err error) {
	fname := CACHE_BOOTSTRAP_STORAGE_PATH + "/" + key + ".json"
	if extensions.DoesFileExist(fname) {
		var size int64
		size, err = extensions.GetFileSize(fname)
		if err != nil {
			return
		}
		if size > 0 {
			UpdateBootStrapMemoryCache(key, "")
			var data []string
			jsonData, err := extensions.ReadFile(fname)
			if err != nil {
				log.Println("Cache failed to read for " + fname + " deleting file and starting fresh.")
				DeleteBootStrapFileCache(key)
				return err
			}
			err = json.Unmarshal(jsonData, &data)
			if err != nil {
				return err
			}
			_, ok := Model.BootstrapCache[key]
			if ok {
				Model.RLock()
				Model.BootstrapCache[key] = data
				Model.RUnlock()
			}
		}
	}
	return
}

func DoesHashExistInCache(key string, value string) (exists bool) {
	caches, ok := Model.BootstrapCache[key]
	if !ok {
		return exists
	} else {
		return utils.InArray(value, caches)
	}
}

func WriteManifestCacheFile(key string) (err error) {
	ByteManifest.RLock()
	caches, ok := ByteManifest.Cache[key]
	ByteManifest.RUnlock()
	if ok {
		strjson, err := json.Marshal(caches)
		if err != nil {
			return err
		}
		err = ioutil.WriteFile(CACHE_MANIFEST_STORAGE_PATH+"/"+key+".json", []byte(strjson), 0777)
		if err != nil {
			return err
		}
	}
	return nil
}

func UpdateManifestMemoryCache(key string, value string, byteSize int) {
	ByteManifest.Lock()
	_, ok := ByteManifest.Cache[key]
	if !ok {
		ByteManifest.Cache[key] = make(map[string]int, 0)
		ByteManifest.Cache[key][value] = byteSize
	} else {
		ByteManifest.Cache[key][value] = byteSize
	}
	ByteManifest.Unlock()
	return
}

func DeleteManifestFileCache(key string) (err error) {
	fname := CACHE_MANIFEST_STORAGE_PATH + "/" + key + ".json"
	if extensions.DoesFileExist(fname) {
		err = os.Remove(fname)
		if err != nil {
			return err
		}
	}
	return
}

func LoadCachedManifestFromKeyIntoMemory(key string) (err error) {
	fname := CACHE_MANIFEST_STORAGE_PATH + "/" + key + ".json"
	ByteManifest.RLock()
	_, ok := ByteManifest.Cache[key]
	ByteManifest.RUnlock()
	if extensions.DoesFileExist(fname) && !ok {
		var data map[string]int
		jsonData, err := extensions.ReadFile(fname)
		if err != nil {
			log.Println("Cache failed to read for " + fname + " deleting file and starting fresh.")
			DeleteManifestFileCache(key)
			return err
		}
		err = json.Unmarshal(jsonData, &data)
		if err != nil {
			return err
		}
		ByteManifest.Lock()
		ByteManifest.Cache[key] = data
		ByteManifest.Unlock()
	} else if !extensions.DoesFileExist(fname) {
		UpdateManifestMemoryCache(key, "", 0)
	}
	return
}

func DoesHashExistInManifestCache(key string, value string) (exists bool) {
	ByteManifest.RLock()
	_, ok := ByteManifest.Cache[key]
	ByteManifest.RUnlock()
	if !ok {
		return exists
	} else {
		ByteManifest.RLock()
		_, ok = ByteManifest.Cache[key][value]
		ByteManifest.RUnlock()
		if !ok {
			return exists
		}
		return true
	}
}

func DeleteAllManifestFileCache() (err error) {
	if extensions.DoesFileExist(CACHE_MANIFEST_STORAGE_PATH) {
		err = extensions.RemoveDirectory(CACHE_MANIFEST_STORAGE_PATH)
		if err != nil {
			core.Debug.Dump(err)
			return err
		}
		os.MkdirAll(CACHE_MANIFEST_STORAGE_PATH, 0777)
	}
	return
}

// Returns the html by path (key) from group cache
func GetHTMLFile(path string) (string, error) {
	var ctx groupcache.Context
	var data []byte
	err := htmlFileCache.Get(ctx, path, groupcache.AllocatingByteSliceSink(&data))
	if err != nil {
		return "", err
	}

	return string(data[:]), err
}

//Returns binary data by path(key) from group cache
func GetFile(path string) ([]byte, error) {
	var ctx groupcache.Context
	var data []byte
	err := htmlFileCache.Get(ctx, path, groupcache.AllocatingByteSliceSink(&data))
	if err != nil {
		return data, err
	}

	return data, err
}

// Gets a value by Key from group cache
func GetString(key string) (string, error) {
	var ctx groupcache.Context
	var data []byte
	err := stringCache.Get(ctx, key, groupcache.AllocatingByteSliceSink(&data))
	if err != nil {
		return "", err
	}

	return string(data[:]), err
}

// Sets a Key value pair in group cache
func SetString(key string, value string) error {

	var ctx groupcache.Context
	setTempStringCache(key, value)
	var data []byte
	return stringCache.Get(ctx, key, groupcache.AllocatingByteSliceSink(&data))
}

// Will update the group cache http pool.  Use for dynamic systems that update at runtime.
func SetGroupCache(servers []string) {
	peers.Set(servers...)
}

// Creates the Peers for group cache and creates caches for multiple types.
func initializeGroupCache(domain string) {

	//For now use the app domain, later we will read from a list of domains.
	peers = groupcache.NewHTTPPool(domain)
	htmlFileCache = groupcache.NewGroup("htmlFileCache", 64<<20, groupcache.GetterFunc(handleHtmlFileCache))
	stringCache = groupcache.NewGroup("stringCache", 64<<20, groupcache.GetterFunc(handleStringCache))

	log.Println("Initialized Group Cache Succesfully.")

}

// Handles group cache callback on getting http file cache requests.
func handleHtmlFileCache(ctx groupcache.Context, key string, dest groupcache.Sink) error {
	fileName := key
	data, err := extensions.ReadFile(fileName)
	if err != nil {
		return err
	}

	dest.SetBytes(data)
	return nil
}

// Handles group cache callback on getting a string key value pair.
func handleStringCache(ctx groupcache.Context, key string, dest groupcache.Sink) error {

	stringKey := key
	value := getTempStringCache(stringKey)
	dest.SetBytes([]byte(value))
	deleteTempStringCache(stringKey)

	return nil
}

// Safely locks a cache map and gets the value
func getTempStringCache(key string) (value string) {
	tempStringCacheSynced.RLock()
	value = tempStringCacheSynced.cache[key]
	tempStringCacheSynced.RUnlock()
	return
}

// Safely locks a cache map and sets the value
func setTempStringCache(key string, value string) {
	tempStringCacheSynced.Lock()
	tempStringCacheSynced.cache[key] = value
	tempStringCacheSynced.Unlock()
}

// Safely locks a cache map and deletes the value
func deleteTempStringCache(key string) {
	tempStringCacheSynced.Lock()
	delete(tempStringCacheSynced.cache, key)
	tempStringCacheSynced.Unlock()
}
<commit_msg>Found and fixed more Locking and Unlocking Bugs.<commit_after>//  Package fileCache will provide simple file content caching tools for in-Memory access to files.
//  It uses golang/groupcache to cache your data into memory on multiple HTTP Pool servers.
package fileCache

import (
	"log"
	"os"
	"sync"

	"encoding/json"
	"io/ioutil"

	"github.com/DanielRenne/GoCore/core"
	"github.com/DanielRenne/GoCore/core/extensions"
	"github.com/DanielRenne/GoCore/core/serverSettings"
	"github.com/DanielRenne/GoCore/core/utils"
	"github.com/golang/groupcache"
)

const (
	CACHE_STORAGE_PATH = "/usr/local/goCore/caches"
)

const (
	CACHE_BOOTSTRAP_STORAGE_PATH = CACHE_STORAGE_PATH + "/bootstrap"
	CACHE_MANIFEST_STORAGE_PATH  = CACHE_STORAGE_PATH + "/manifests"
)

type model struct {
	sync.RWMutex
	BootstrapCache map[string][]string
}

type byteManifest struct {
	sync.RWMutex
	Cache map[string]map[string]int
}

var Model model
var ByteManifest byteManifest
var peers *groupcache.HTTPPool
var htmlFileCache *groupcache.Group
var stringCache *groupcache.Group

// contains the temporary string cache used to cache large strings.
var tempStringCacheSynced = struct {
	sync.RWMutex
	cache map[string]string
}{cache: make(map[string]string)}

func init() {
	os.MkdirAll(CACHE_BOOTSTRAP_STORAGE_PATH, 0777)
	os.MkdirAll(CACHE_MANIFEST_STORAGE_PATH, 0777)
	Model = model{
		BootstrapCache: make(map[string][]string, 0),
	}
	ByteManifest = byteManifest{
		Cache: make(map[string]map[string]int, 0),
	}
}

//Call Initialize in main before any calls to this package are performed.  serverSettings package must be initialized before fileCache.
func Initialize() {
	if serverSettings.WebConfig.Application.Domain != "" {
		initializeGroupCache(serverSettings.WebConfig.Application.Domain)
	}
}

func WriteBootStrapCacheFile(key string) (err error) {
	Model.RLock()
	caches, ok := Model.BootstrapCache[key]
	Model.RUnlock()
	if ok {
		strjson, err := json.Marshal(caches)
		if err != nil {
			return err
		}
		err = ioutil.WriteFile(CACHE_BOOTSTRAP_STORAGE_PATH+"/"+key+".json", []byte(strjson), 0777)
		if err != nil {
			return err
		}
	}
	return nil
}

func UpdateBootStrapMemoryCache(key string, value string) {
	Model.RLock()
	_, ok := Model.BootstrapCache[key]
	Model.RUnlock()
	if !ok {
		Model.Lock()
		Model.BootstrapCache[key] = utils.Array(value)
		Model.Unlock()
	} else {
		if !DoesHashExistInCache(key, value) {
			Model.Lock()
			Model.BootstrapCache[key] = append(Model.BootstrapCache[key], value)
			Model.Unlock()
		}
	}
	return
}

func DeleteBootStrapFileCache(key string) (err error) {
	fname := CACHE_BOOTSTRAP_STORAGE_PATH + "/" + key + ".json"
	if extensions.DoesFileExist(fname) {
		err = os.Remove(fname)
		if err != nil {
			return err
		}
	}
	return
}

func DeleteAllBootStrapFileCache() (err error) {
	if extensions.DoesFileExist(CACHE_BOOTSTRAP_STORAGE_PATH) {
		err = extensions.RemoveDirectory(CACHE_BOOTSTRAP_STORAGE_PATH)
		if err != nil {
			core.Debug.Dump(err)
			return err
		}
		os.MkdirAll(CACHE_BOOTSTRAP_STORAGE_PATH, 0777)
	}
	return
}

func LoadCachedBootStrapFromKeyIntoMemory(key string) (err error) {
	fname := CACHE_BOOTSTRAP_STORAGE_PATH + "/" + key + ".json"
	if extensions.DoesFileExist(fname) {
		var size int64
		size, err = extensions.GetFileSize(fname)
		if err != nil {
			return
		}
		if size > 0 {
			UpdateBootStrapMemoryCache(key, "")
			var data []string
			jsonData, err := extensions.ReadFile(fname)
			if err != nil {
				log.Println("Cache failed to read for " + fname + " deleting file and starting fresh.")
				DeleteBootStrapFileCache(key)
				return err
			}
			err = json.Unmarshal(jsonData, &data)
			if err != nil {
				return err
			}

			Model.RLock()
			_, ok := Model.BootstrapCache[key]
			Model.RUnlock()
			if ok {
				Model.Lock()
				Model.BootstrapCache[key] = data
				Model.Unlock()
			}
		}
	}
	return
}

func DoesHashExistInCache(key string, value string) (exists bool) {
	Model.RLock()
	caches, ok := Model.BootstrapCache[key]
	Model.RUnlock()
	if !ok {
		return exists
	} else {
		return utils.InArray(value, caches)
	}
}

func WriteManifestCacheFile(key string) (err error) {
	ByteManifest.RLock()
	caches, ok := ByteManifest.Cache[key]
	ByteManifest.RUnlock()
	if ok {
		strjson, err := json.Marshal(caches)
		if err != nil {
			return err
		}
		err = ioutil.WriteFile(CACHE_MANIFEST_STORAGE_PATH+"/"+key+".json", []byte(strjson), 0777)
		if err != nil {
			return err
		}
	}
	return nil
}

func UpdateManifestMemoryCache(key string, value string, byteSize int) {
	ByteManifest.Lock()
	_, ok := ByteManifest.Cache[key]
	if !ok {
		ByteManifest.Cache[key] = make(map[string]int, 0)
		ByteManifest.Cache[key][value] = byteSize
	} else {
		ByteManifest.Cache[key][value] = byteSize
	}
	ByteManifest.Unlock()
	return
}

func DeleteManifestFileCache(key string) (err error) {
	fname := CACHE_MANIFEST_STORAGE_PATH + "/" + key + ".json"
	if extensions.DoesFileExist(fname) {
		err = os.Remove(fname)
		if err != nil {
			return err
		}
	}
	return
}

func LoadCachedManifestFromKeyIntoMemory(key string) (err error) {
	fname := CACHE_MANIFEST_STORAGE_PATH + "/" + key + ".json"
	ByteManifest.RLock()
	_, ok := ByteManifest.Cache[key]
	ByteManifest.RUnlock()
	if extensions.DoesFileExist(fname) && !ok {
		var data map[string]int
		jsonData, err := extensions.ReadFile(fname)
		if err != nil {
			log.Println("Cache failed to read for " + fname + " deleting file and starting fresh.")
			DeleteManifestFileCache(key)
			return err
		}
		err = json.Unmarshal(jsonData, &data)
		if err != nil {
			return err
		}
		ByteManifest.Lock()
		ByteManifest.Cache[key] = data
		ByteManifest.Unlock()
	} else if !extensions.DoesFileExist(fname) {
		UpdateManifestMemoryCache(key, "", 0)
	}
	return
}

func DoesHashExistInManifestCache(key string, value string) (exists bool) {
	ByteManifest.RLock()
	_, ok := ByteManifest.Cache[key]
	ByteManifest.RUnlock()
	if !ok {
		return exists
	} else {
		ByteManifest.RLock()
		_, ok = ByteManifest.Cache[key][value]
		ByteManifest.RUnlock()
		if !ok {
			return exists
		}
		return true
	}
}

func DeleteAllManifestFileCache() (err error) {
	if extensions.DoesFileExist(CACHE_MANIFEST_STORAGE_PATH) {
		err = extensions.RemoveDirectory(CACHE_MANIFEST_STORAGE_PATH)
		if err != nil {
			core.Debug.Dump(err)
			return err
		}
		os.MkdirAll(CACHE_MANIFEST_STORAGE_PATH, 0777)
	}
	return
}

// Returns the html by path (key) from group cache
func GetHTMLFile(path string) (string, error) {
	var ctx groupcache.Context
	var data []byte
	err := htmlFileCache.Get(ctx, path, groupcache.AllocatingByteSliceSink(&data))
	if err != nil {
		return "", err
	}

	return string(data[:]), err
}

//Returns binary data by path(key) from group cache
func GetFile(path string) ([]byte, error) {
	var ctx groupcache.Context
	var data []byte
	err := htmlFileCache.Get(ctx, path, groupcache.AllocatingByteSliceSink(&data))
	if err != nil {
		return data, err
	}

	return data, err
}

// Gets a value by Key from group cache
func GetString(key string) (string, error) {
	var ctx groupcache.Context
	var data []byte
	err := stringCache.Get(ctx, key, groupcache.AllocatingByteSliceSink(&data))
	if err != nil {
		return "", err
	}

	return string(data[:]), err
}

// Sets a Key value pair in group cache
func SetString(key string, value string) error {

	var ctx groupcache.Context
	setTempStringCache(key, value)
	var data []byte
	return stringCache.Get(ctx, key, groupcache.AllocatingByteSliceSink(&data))
}

// Will update the group cache http pool.  Use for dynamic systems that update at runtime.
func SetGroupCache(servers []string) {
	peers.Set(servers...)
}

// Creates the Peers for group cache and creates caches for multiple types.
func initializeGroupCache(domain string) {

	//For now use the app domain, later we will read from a list of domains.
	peers = groupcache.NewHTTPPool(domain)
	htmlFileCache = groupcache.NewGroup("htmlFileCache", 64<<20, groupcache.GetterFunc(handleHtmlFileCache))
	stringCache = groupcache.NewGroup("stringCache", 64<<20, groupcache.GetterFunc(handleStringCache))

	log.Println("Initialized Group Cache Succesfully.")

}

// Handles group cache callback on getting http file cache requests.
func handleHtmlFileCache(ctx groupcache.Context, key string, dest groupcache.Sink) error {
	fileName := key
	data, err := extensions.ReadFile(fileName)
	if err != nil {
		return err
	}

	dest.SetBytes(data)
	return nil
}

// Handles group cache callback on getting a string key value pair.
func handleStringCache(ctx groupcache.Context, key string, dest groupcache.Sink) error {

	stringKey := key
	value := getTempStringCache(stringKey)
	dest.SetBytes([]byte(value))
	deleteTempStringCache(stringKey)

	return nil
}

// Safely locks a cache map and gets the value
func getTempStringCache(key string) (value string) {
	tempStringCacheSynced.RLock()
	value = tempStringCacheSynced.cache[key]
	tempStringCacheSynced.RUnlock()
	return
}

// Safely locks a cache map and sets the value
func setTempStringCache(key string, value string) {
	tempStringCacheSynced.Lock()
	tempStringCacheSynced.cache[key] = value
	tempStringCacheSynced.Unlock()
}

// Safely locks a cache map and deletes the value
func deleteTempStringCache(key string) {
	tempStringCacheSynced.Lock()
	delete(tempStringCacheSynced.cache, key)
	tempStringCacheSynced.Unlock()
}
<|endoftext|>


<commit_before>package scheduler

import (
	"fmt"
)

// Job defines a nomad job specification
type Job struct {
	Region      string
	ID          string
	Name        string
	Type        string
	Priority    int
	AllAtOnce   bool
	Datacenters []string
	Constraints []Constraint
	TaskGroups  []TaskGroup
	Update      Update
}

// Constraint defines a job/task contraint
type Constraint struct {
	LTarget string
	RTarget string
	Operand string
}

// Update defines the update stanza
type Update struct {
	Stagger     int64
	MaxParallel int
}

// TaskGroup defines the task_group stanza
type TaskGroup struct {
	Name          string
	Count         int
	Constraints   []string
	Tasks         []Task
	Resources     Resources
	RestartPolicy RestartPolicy
	Meta          map[string]string
}

// Task defines a job task
type Task struct {
	Name            string
	Driver          string
	Config          Config
	Env             map[string]string
	Services        []NomadService
	Meta            map[string]string
	LogConfig       LogConfig
	KillTimeout     int64
	Templates       []Template
	Artifacts       []Artifact
	Resources       Resources
	DispatchPayload DispatchPayload
}

// DispatchPayload configures tast to have access to dispatch payload
type DispatchPayload struct {
	File string
}

// Resources defines the resources to allocate to a task
type Resources struct {
	CPU      int
	MemoryMB int
	IOPS     int
	DiskMB   int
	Networks []Network
}

// Network defines network allocation
type Network struct {
	MBits        int
	DynamicPorts []DynamicPort
}

// RestartPolicy defines restart policy
type RestartPolicy struct {
	Interval int64
	Attempts int
	Delay    int64
	Mode     string
}

// DynamicPort defines a dynamic port allocation
type DynamicPort struct {
	Label string
}

// Artifact defines an artifact to be downloaded
type Artifact struct {
	GetterSource  string
	RelativeDest  string
	GetterOptions map[string]string
}

// Template defines template objects to render for the task
type Template struct {
	SourcePath   string
	DestPath     string
	EmbeddedTmpl string
	ChangeMode   string
	ChangeSignal string
	Splay        int64
}

// LogConfig defines log configurations
type LogConfig struct {
	MaxFiles      int
	MaxFileSizeMB int
}

// NomadService defines a service
type NomadService struct {
	Name      string
	Tags      []string
	PortLabel string
	Checks    []Check
}

// Check defines a service check
type Check struct {
	ID       string `json:"Id"`
	Name     string
	Type     string
	Path     string
	Port     string
	Timeout  int64
	Interval int64
	Protocol string
}

// Config defines a driver/task configuration
type Config struct {
	NetworkMode string   `json:"network_mode"`
	Privileged  bool     `json:"privileged"`
	ForcePull   bool     `json:"force_pull"`
	Volumes     []string `json:"volumes"`
	Image       string   `json:"image"`
	Command     string   `json:"command"`
	Args        []string `json:"args"`
}

// NomadJob represents a nomad job
type NomadJob struct {
	Job *Job
}

// NewJob creates a new job with some default values.
func NewJob(connectorVersion, id string, count int) *NomadJob {
	return &NomadJob{
		Job: &Job{
			Region:      "",
			ID:          id,
			Name:        id,
			Type:        "service",
			Priority:    50,
			AllAtOnce:   false,
			Datacenters: []string{},
			Constraints: []Constraint{
				Constraint{
					LTarget: "${attr.kernel.name}",
					RTarget: "linux",
					Operand: "=",
				},
			},
			TaskGroups: []TaskGroup{
				TaskGroup{
					Name:  fmt.Sprintf("cocoon-grp-%s", id),
					Count: count,
					Meta: map[string]string{
						"VERSION":   "dual-docker",
						"REPO_USER": "ncodes",
					},
					Tasks: []Task{
						Task{
							Name:   "connector",
							Driver: "docker",
							Config: Config{
								Image:       "${NOMAD_META_REPO_USER}/cocoon-launcher:latest",
								NetworkMode: "host",
								Privileged:  true,
								ForcePull:   true,
								Volumes:     []string{"/var/run/docker.sock:/var/run/docker.sock"},
								Command:     "bash",
								Args:        []string{"/local/runner.sh"},
							},
							Env: map[string]string{
								"VERSION":               "${NOMAD_META_VERSION}",
								"COCOON_ID":             id,
								"COCOON_CODE_URL":       "",
								"COCOON_CODE_TAG":       "",
								"COCOON_CODE_LANG":      "",
								"COCOON_BUILD_PARAMS":   "",
								"COCOON_LINK":           "",
								"COCOON_CONTAINER_NAME": "code-${NOMAD_ALLOC_ID}",

								// The name of the connector runner script and a link to the script.
								// The runner script will fetch and run whatever is found in this environment var.
								"RUN_SCRIPT_NAME": "run-connector.sh",
								"RUN_SCRIPT_URL":  "https://raw.githubusercontent.com/${NOMAD_META_REPO_USER}/cocoon/${NOMAD_META_VERSION}/scripts/run-connector.sh",
							},
							Services: []NomadService{
								NomadService{
									Name:      fmt.Sprintf("cocoons"),
									Tags:      []string{id},
									PortLabel: "RPC",
								},
							},
							Meta: map[string]string{},
							LogConfig: LogConfig{
								MaxFiles:      10,
								MaxFileSizeMB: 10,
							},
							Templates: []Template{},
							Artifacts: []Artifact{
								Artifact{
									GetterSource: "https://raw.githubusercontent.com/${NOMAD_META_REPO_USER}/cocoon/${NOMAD_META_VERSION}/scripts/runner.sh",
								},
							},
							KillTimeout: 10000000000,
							Resources: Resources{
								CPU:      100,
								MemoryMB: 512,
								DiskMB:   800,
								IOPS:     0,
								Networks: []Network{
									Network{
										MBits: 1,
										DynamicPorts: []DynamicPort{
											DynamicPort{Label: "RPC"},
										},
									},
								},
							},
							DispatchPayload: DispatchPayload{},
						},
						Task{
							Name:   "code",
							Driver: "docker",
							Config: Config{
								Image:       "${NOMAD_META_REPO_USER}/launch-go:latest",
								NetworkMode: "bridge",
								ForcePull:   true,
								Command:     "bash",
								Args:        []string{"-c", "echo 'Hello Human. I am alive'; tail -f /dev/null"},
							},
							Env:      map[string]string{},
							Services: []NomadService{},
							Meta:     map[string]string{},
							LogConfig: LogConfig{
								MaxFiles:      10,
								MaxFileSizeMB: 10,
							},
							Templates: []Template{},
							Artifacts: []Artifact{},
							Resources: Resources{
								CPU:      100,
								MemoryMB: 512,
								DiskMB:   800,
								IOPS:     0,
								Networks: []Network{
									Network{
										MBits: 1,
										DynamicPorts: []DynamicPort{
											DynamicPort{Label: "RPC"},
										},
									},
								},
							},
							DispatchPayload: DispatchPayload{},
						},
					},
					Resources: Resources{
						CPU:      100,
						MemoryMB: 512,
						DiskMB:   800,
						IOPS:     0,
						Networks: []Network{},
					},
					RestartPolicy: RestartPolicy{
						Interval: 300000000000,
						Attempts: 10,
						Delay:    25000000000,
						Mode:     "delay",
					},
				},
			},
			Update: Update{
				Stagger:     10000000000,
				MaxParallel: 1,
			},
		},
	}
}

// GetSpec returns the job's specification
func (j *NomadJob) GetSpec() *Job {
	return j.Job
}
<commit_msg>Increase default memory to 1g<commit_after>package scheduler

import (
	"fmt"
)

// Job defines a nomad job specification
type Job struct {
	Region      string
	ID          string
	Name        string
	Type        string
	Priority    int
	AllAtOnce   bool
	Datacenters []string
	Constraints []Constraint
	TaskGroups  []TaskGroup
	Update      Update
}

// Constraint defines a job/task contraint
type Constraint struct {
	LTarget string
	RTarget string
	Operand string
}

// Update defines the update stanza
type Update struct {
	Stagger     int64
	MaxParallel int
}

// TaskGroup defines the task_group stanza
type TaskGroup struct {
	Name          string
	Count         int
	Constraints   []string
	Tasks         []Task
	Resources     Resources
	RestartPolicy RestartPolicy
	Meta          map[string]string
}

// Task defines a job task
type Task struct {
	Name            string
	Driver          string
	Config          Config
	Env             map[string]string
	Services        []NomadService
	Meta            map[string]string
	LogConfig       LogConfig
	KillTimeout     int64
	Templates       []Template
	Artifacts       []Artifact
	Resources       Resources
	DispatchPayload DispatchPayload
}

// DispatchPayload configures tast to have access to dispatch payload
type DispatchPayload struct {
	File string
}

// Resources defines the resources to allocate to a task
type Resources struct {
	CPU      int
	MemoryMB int
	IOPS     int
	DiskMB   int
	Networks []Network
}

// Network defines network allocation
type Network struct {
	MBits        int
	DynamicPorts []DynamicPort
}

// RestartPolicy defines restart policy
type RestartPolicy struct {
	Interval int64
	Attempts int
	Delay    int64
	Mode     string
}

// DynamicPort defines a dynamic port allocation
type DynamicPort struct {
	Label string
}

// Artifact defines an artifact to be downloaded
type Artifact struct {
	GetterSource  string
	RelativeDest  string
	GetterOptions map[string]string
}

// Template defines template objects to render for the task
type Template struct {
	SourcePath   string
	DestPath     string
	EmbeddedTmpl string
	ChangeMode   string
	ChangeSignal string
	Splay        int64
}

// LogConfig defines log configurations
type LogConfig struct {
	MaxFiles      int
	MaxFileSizeMB int
}

// NomadService defines a service
type NomadService struct {
	Name      string
	Tags      []string
	PortLabel string
	Checks    []Check
}

// Check defines a service check
type Check struct {
	ID       string `json:"Id"`
	Name     string
	Type     string
	Path     string
	Port     string
	Timeout  int64
	Interval int64
	Protocol string
}

// Config defines a driver/task configuration
type Config struct {
	NetworkMode string   `json:"network_mode"`
	Privileged  bool     `json:"privileged"`
	ForcePull   bool     `json:"force_pull"`
	Volumes     []string `json:"volumes"`
	Image       string   `json:"image"`
	Command     string   `json:"command"`
	Args        []string `json:"args"`
}

// NomadJob represents a nomad job
type NomadJob struct {
	Job *Job
}

// NewJob creates a new job with some default values.
func NewJob(connectorVersion, id string, count int) *NomadJob {
	return &NomadJob{
		Job: &Job{
			Region:      "",
			ID:          id,
			Name:        id,
			Type:        "service",
			Priority:    50,
			AllAtOnce:   false,
			Datacenters: []string{},
			Constraints: []Constraint{
				Constraint{
					LTarget: "${attr.kernel.name}",
					RTarget: "linux",
					Operand: "=",
				},
			},
			TaskGroups: []TaskGroup{
				TaskGroup{
					Name:  fmt.Sprintf("cocoon-grp-%s", id),
					Count: count,
					Meta: map[string]string{
						"VERSION":   "dual-docker",
						"REPO_USER": "ncodes",
					},
					Tasks: []Task{
						Task{
							Name:   "connector",
							Driver: "docker",
							Config: Config{
								Image:       "${NOMAD_META_REPO_USER}/cocoon-launcher:latest",
								NetworkMode: "host",
								Privileged:  true,
								ForcePull:   true,
								Volumes:     []string{"/var/run/docker.sock:/var/run/docker.sock"},
								Command:     "bash",
								Args:        []string{"/local/runner.sh"},
							},
							Env: map[string]string{
								"VERSION":               "${NOMAD_META_VERSION}",
								"COCOON_ID":             id,
								"COCOON_CODE_URL":       "",
								"COCOON_CODE_TAG":       "",
								"COCOON_CODE_LANG":      "",
								"COCOON_BUILD_PARAMS":   "",
								"COCOON_LINK":           "",
								"COCOON_CONTAINER_NAME": "code-${NOMAD_ALLOC_ID}",

								// The name of the connector runner script and a link to the script.
								// The runner script will fetch and run whatever is found in this environment var.
								"RUN_SCRIPT_NAME": "run-connector.sh",
								"RUN_SCRIPT_URL":  "https://raw.githubusercontent.com/${NOMAD_META_REPO_USER}/cocoon/${NOMAD_META_VERSION}/scripts/run-connector.sh",
							},
							Services: []NomadService{
								NomadService{
									Name:      fmt.Sprintf("cocoons"),
									Tags:      []string{id},
									PortLabel: "RPC",
								},
							},
							Meta: map[string]string{},
							LogConfig: LogConfig{
								MaxFiles:      10,
								MaxFileSizeMB: 10,
							},
							Templates: []Template{},
							Artifacts: []Artifact{
								Artifact{
									GetterSource: "https://raw.githubusercontent.com/${NOMAD_META_REPO_USER}/cocoon/${NOMAD_META_VERSION}/scripts/runner.sh",
								},
							},
							KillTimeout: 10000000000,
							Resources: Resources{
								CPU:      100,
								MemoryMB: 1024,
								DiskMB:   800,
								IOPS:     0,
								Networks: []Network{
									Network{
										MBits: 1,
										DynamicPorts: []DynamicPort{
											DynamicPort{Label: "RPC"},
										},
									},
								},
							},
							DispatchPayload: DispatchPayload{},
						},
						Task{
							Name:   "code",
							Driver: "docker",
							Config: Config{
								Image:       "${NOMAD_META_REPO_USER}/launch-go:latest",
								NetworkMode: "bridge",
								ForcePull:   true,
								Command:     "bash",
								Args:        []string{"-c", "echo 'Hello Human. I am alive'; tail -f /dev/null"},
							},
							Env:      map[string]string{},
							Services: []NomadService{},
							Meta:     map[string]string{},
							LogConfig: LogConfig{
								MaxFiles:      10,
								MaxFileSizeMB: 10,
							},
							Templates: []Template{},
							Artifacts: []Artifact{},
							Resources: Resources{
								CPU:      100,
								MemoryMB: 1024,
								DiskMB:   800,
								IOPS:     0,
								Networks: []Network{
									Network{
										MBits: 1,
										DynamicPorts: []DynamicPort{
											DynamicPort{Label: "RPC"},
										},
									},
								},
							},
							DispatchPayload: DispatchPayload{},
						},
					},
					Resources: Resources{
						CPU:      100,
						MemoryMB: 512,
						DiskMB:   800,
						IOPS:     0,
						Networks: []Network{},
					},
					RestartPolicy: RestartPolicy{
						Interval: 300000000000,
						Attempts: 10,
						Delay:    25000000000,
						Mode:     "delay",
					},
				},
			},
			Update: Update{
				Stagger:     10000000000,
				MaxParallel: 1,
			},
		},
	}
}

// GetSpec returns the job's specification
func (j *NomadJob) GetSpec() *Job {
	return j.Job
}
<|endoftext|>


<commit_before>package clients

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/boltdb/bolt"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/client"
	"github.com/docker/go-connections/nat"
)

// Docker struct to manage all CryutoDev action with Docker
type Docker struct {
	Client *client.Client
}

var images = map[string]string{
	"bitcoin":  "heraware/bitcoin:latest",
	"litecoin": "heraware/litecoin:latest",
}

var ports = map[string]nat.PortMap{
	"bitcoin": map[nat.Port][]nat.PortBinding{
		"20001/tcp": []nat.PortBinding{
			nat.PortBinding{HostIP: "0.0.0.0", HostPort: "20001"},
		},
		"20000/tcp": []nat.PortBinding{
			nat.PortBinding{HostIP: "0.0.0.0", HostPort: "20000"},
		},
	},
	"litecoin": map[nat.Port][]nat.PortBinding{
		"21001/tcp": []nat.PortBinding{
			nat.PortBinding{HostIP: "0.0.0.0", HostPort: "21001"},
		},
		"21000/tcp": []nat.PortBinding{
			nat.PortBinding{HostIP: "0.0.0.0", HostPort: "21000"},
		},
	},
}

// NewDockerClient Return a Docker instance with Docker client
// configured from OS ENV
func NewDockerClient() *Docker {
	client, err := client.NewEnvClient()
	if err != nil {
		panic(err)
	}
	docker := Docker{Client: client}
	return &docker
}

func (d *Docker) getContainerInfo(containerName string) ([]byte, error) {
	var result []byte
	if err := DB.View(func(tx *bolt.Tx) error {
		txBucket := tx.Bucket([]byte("containers"))
		if txBucket == nil {
			return fmt.Errorf("Bucket doesn't exists")
		}
		result = txBucket.Get([]byte(containerName))
		return nil
	}); err != nil {
		return nil, err
	}
	return result, nil
}

func (d *Docker) deleteContainerInfo(containerName string) error {
	return DB.Update(func(tx *bolt.Tx) error {
		txBucket := tx.Bucket([]byte("containers"))
		if txBucket == nil {
			return fmt.Errorf("Bucket doesn't exists")
		}
		return txBucket.Delete([]byte(containerName))
	})
}

func (d *Docker) saveContainerInfo(containerName string, containerID string) error {
	if err := DB.Update(func(tx *bolt.Tx) error {
		b, err := tx.CreateBucketIfNotExists([]byte("containers"))
		if err != nil {
			return err
		}
		if err := b.Put([]byte(containerName), []byte(containerID)); err != nil {
			return err
		}
		return nil
	}); err != nil {
		return err
	}
	return nil
}

func (d *Docker) containerExists(containerName string) bool {
	value, err := d.getContainerInfo(containerName)
	if err != nil {
		return false
	}
	if len(value) == 0 {
		return false
	}
	return true
}

func (d *Docker) runContainer(containerID string) error {
	return d.Client.ContainerStart(context.Background(), containerID, types.ContainerStartOptions{})
}

func (d *Docker) stopContainer(containerID string) error {
	timeout := 10 * time.Second
	return d.Client.ContainerStop(context.Background(), containerID, &timeout)
}

func (d *Docker) createAndRunContainer(name string, image string) {
	containerName := fmt.Sprintf("cryptodev-%s", name)
	if d.containerExists(containerName) {
		log.Fatalf("Container: %s already exists", containerName)
	}
	containerConfig := container.Config{
		Image: images[name],
	}
	hostConfig := container.HostConfig{
		PortBindings: ports[name],
		Privileged:   false,
	}
	containerBody, err := d.Client.ContainerCreate(context.Background(), &containerConfig, &hostConfig, nil, containerName)
	if err != nil {
		panic(err)
	}
	d.saveContainerInfo(containerName, containerBody.ID)
	if err := d.runContainer(containerBody.ID); err != nil {
		panic(err)
	}
	fmt.Println(fmt.Sprintf("Node: %s is created and running - Container ID: %s", containerName, containerBody.ID))
}

// CreateNode create a container with the cryptocurrency client
func (d *Docker) CreateNode(name string) {
	d.createAndRunContainer(name, images[name])
}

func (d *Docker) RunNode(name string) {
	containerName := fmt.Sprintf("cryptodev-%s", name)
	containerID, err := d.getContainerInfo(containerName)
	if err != nil {
		panic(err)
	}
	containerIDString := string(containerID)
	err = d.runContainer(containerIDString)
	if err != nil {
		d.deleteContainerInfo(containerName)
		log.Fatalf("Container: %s doesn't exists, try to run `cryptodev create %s`", containerName, name)
	}
	fmt.Println(fmt.Sprintf("Node: %s is running - Container ID: %s", containerName, containerIDString))
}

func (d *Docker) StopNode(name string) {
	containerName := fmt.Sprintf("cryptodev-%s", name)
	containerID, err := d.getContainerInfo(containerName)
	if err != nil {
		panic(err)
	}
	containerIDString := string(containerID)
	err = d.stopContainer(containerIDString)
	if err != nil {
		d.deleteContainerInfo(containerName)
		log.Fatalf("Container: %s doesn't exists, try to run `cryptodev create %s`", containerName, name)
	}
	fmt.Println(fmt.Sprintf("Node: %s was stopped", containerName))
}
<commit_msg>ETH and ETC into docker.go<commit_after>package clients

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/boltdb/bolt"
	"github.com/docker/docker/api/types"
	"github.com/docker/docker/api/types/container"
	"github.com/docker/docker/client"
	"github.com/docker/go-connections/nat"
)

// Docker struct to manage all CryutoDev action with Docker
type Docker struct {
	Client *client.Client
}

var images = map[string]string{
	"bitcoin":          "heraware/bitcoin:latest",
	"litecoin":         "heraware/litecoin:latest",
	"ethereum":         "heraware/ethereum:latest",
	"ethereum-classic": "herware/ethereum-classic:latest",
}

var ports = map[string]nat.PortMap{
	"bitcoin": map[nat.Port][]nat.PortBinding{
		"20001/tcp": []nat.PortBinding{
			nat.PortBinding{HostIP: "0.0.0.0", HostPort: "20001"},
		},
		"20000/tcp": []nat.PortBinding{
			nat.PortBinding{HostIP: "0.0.0.0", HostPort: "20000"},
		},
	},
	"litecoin": map[nat.Port][]nat.PortBinding{
		"21001/tcp": []nat.PortBinding{
			nat.PortBinding{HostIP: "0.0.0.0", HostPort: "21001"},
		},
		"21000/tcp": []nat.PortBinding{
			nat.PortBinding{HostIP: "0.0.0.0", HostPort: "21000"},
		},
	},
	"ethereum": map[nat.Port][]nat.PortBinding{
		"8545/tcp": []nat.PortBinding{
			nat.PortBinding{HostIP: "0.0.0.0", HostPort: "8545"},
		},
		"30303/tcp": []nat.PortBinding{
			nat.PortBinding{HostIP: "0.0.0.0", HostPort: "30303"},
		},
	},
	"ethereum-classic": map[nat.Port][]nat.PortBinding{
		"8546/tcp": []nat.PortBinding{
			nat.PortBinding{HostIP: "0.0.0.0", HostPort: "8546"},
		},
		"30003/tcp": []nat.PortBinding{
			nat.PortBinding{HostIP: "0.0.0.0", HostPort: "30003"},
		},
	},
}

// NewDockerClient Return a Docker instance with Docker client
// configured from OS ENV
func NewDockerClient() *Docker {
	client, err := client.NewEnvClient()
	if err != nil {
		panic(err)
	}
	docker := Docker{Client: client}
	return &docker
}

func (d *Docker) getContainerInfo(containerName string) ([]byte, error) {
	var result []byte
	if err := DB.View(func(tx *bolt.Tx) error {
		txBucket := tx.Bucket([]byte("containers"))
		if txBucket == nil {
			return fmt.Errorf("Bucket doesn't exists")
		}
		result = txBucket.Get([]byte(containerName))
		return nil
	}); err != nil {
		return nil, err
	}
	return result, nil
}

func (d *Docker) deleteContainerInfo(containerName string) error {
	return DB.Update(func(tx *bolt.Tx) error {
		txBucket := tx.Bucket([]byte("containers"))
		if txBucket == nil {
			return fmt.Errorf("Bucket doesn't exists")
		}
		return txBucket.Delete([]byte(containerName))
	})
}

func (d *Docker) saveContainerInfo(containerName string, containerID string) error {
	if err := DB.Update(func(tx *bolt.Tx) error {
		b, err := tx.CreateBucketIfNotExists([]byte("containers"))
		if err != nil {
			return err
		}
		if err := b.Put([]byte(containerName), []byte(containerID)); err != nil {
			return err
		}
		return nil
	}); err != nil {
		return err
	}
	return nil
}

func (d *Docker) containerExists(containerName string) bool {
	value, err := d.getContainerInfo(containerName)
	if err != nil {
		return false
	}
	if len(value) == 0 {
		return false
	}
	return true
}

func (d *Docker) runContainer(containerID string) error {
	return d.Client.ContainerStart(context.Background(), containerID, types.ContainerStartOptions{})
}

func (d *Docker) stopContainer(containerID string) error {
	timeout := 10 * time.Second
	return d.Client.ContainerStop(context.Background(), containerID, &timeout)
}

func (d *Docker) createAndRunContainer(name string, image string) {
	containerName := fmt.Sprintf("cryptodev-%s", name)
	if d.containerExists(containerName) {
		log.Fatalf("Container: %s already exists", containerName)
	}
	containerConfig := container.Config{
		Image: images[name],
	}
	hostConfig := container.HostConfig{
		PortBindings: ports[name],
		Privileged:   false,
	}
	containerBody, err := d.Client.ContainerCreate(context.Background(), &containerConfig, &hostConfig, nil, containerName)
	if err != nil {
		panic(err)
	}
	d.saveContainerInfo(containerName, containerBody.ID)
	if err := d.runContainer(containerBody.ID); err != nil {
		panic(err)
	}
	fmt.Println(fmt.Sprintf("Node: %s is created and running - Container ID: %s", containerName, containerBody.ID))
}

// CreateNode create a container with the cryptocurrency client
func (d *Docker) CreateNode(name string) {
	d.createAndRunContainer(name, images[name])
}

func (d *Docker) RunNode(name string) {
	containerName := fmt.Sprintf("cryptodev-%s", name)
	containerID, err := d.getContainerInfo(containerName)
	if err != nil {
		panic(err)
	}
	containerIDString := string(containerID)
	err = d.runContainer(containerIDString)
	if err != nil {
		d.deleteContainerInfo(containerName)
		log.Fatalf("Container: %s doesn't exists, try to run `cryptodev create %s`", containerName, name)
	}
	fmt.Println(fmt.Sprintf("Node: %s is running - Container ID: %s", containerName, containerIDString))
}

func (d *Docker) StopNode(name string) {
	containerName := fmt.Sprintf("cryptodev-%s", name)
	containerID, err := d.getContainerInfo(containerName)
	if err != nil {
		panic(err)
	}
	containerIDString := string(containerID)
	err = d.stopContainer(containerIDString)
	if err != nil {
		d.deleteContainerInfo(containerName)
		log.Fatalf("Container: %s doesn't exists, try to run `cryptodev create %s`", containerName, name)
	}
	fmt.Println(fmt.Sprintf("Node: %s was stopped", containerName))
}
<|endoftext|>


<commit_before>/*
 * Copyright ©1998-2022 by Richard A. Wilkes. All rights reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, version 2.0. If a copy of the MPL was not distributed with
 * this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This Source Code Form is "Incompatible With Secondary Licenses", as
 * defined by the Mozilla Public License, version 2.0.
 */

package workspace

import (
	"fmt"
	"path"
	"path/filepath"
	"strings"
	"time"

	"github.com/google/uuid"
	"github.com/richardwilkes/gcs/v5/model/settings"
	"github.com/richardwilkes/gcs/v5/ui/widget"
	"github.com/richardwilkes/toolbox/i18n"
	"github.com/richardwilkes/toolbox/log/jot"
	"github.com/richardwilkes/toolbox/txt"
	"github.com/richardwilkes/toolbox/xio/fs"
	"github.com/richardwilkes/unison"
	"golang.org/x/exp/slices"
)

const workspaceClientDataKey = "workspace"

// Workspace holds the data necessary to track the Workspace.
type Workspace struct {
	Window       *unison.Window
	TopDock      *unison.Dock
	Navigator    *Navigator
	DocumentDock *DocumentDock
}

// ShowUnableToLocateWorkspaceError displays an error dialog.
func ShowUnableToLocateWorkspaceError() {
	unison.ErrorDialogWithMessage(i18n.Text("Unable to locate workspace"), "")
}

// Activate attempts to locate an existing dockable that 'matcher' returns true for. If found, it will have been
// activated and focused.
func Activate(matcher func(d unison.Dockable) bool) (ws *Workspace, dc *unison.DockContainer, found bool) {
	if ws = Any(); ws == nil {
		jot.Error("no workspace available")
		return nil, nil, false
	}
	dc = ws.CurrentlyFocusedDockContainer()
	ws.DocumentDock.RootDockLayout().ForEachDockContainer(func(container *unison.DockContainer) bool {
		for _, one := range container.Dockables() {
			if matcher(one) {
				found = true
				container.SetCurrentDockable(one)
				container.AcquireFocus()
				return true
			}
			if dc == nil {
				dc = container
			}
		}
		return false
	})
	return ws, dc, found
}

// ActiveDockable returns the currently active dockable in the active window.
func ActiveDockable() unison.Dockable {
	ws := FromWindow(unison.ActiveWindow())
	if ws == nil {
		return nil
	}
	dc := ws.CurrentlyFocusedDockContainer()
	if dc == nil {
		return nil
	}
	return dc.CurrentDockable()
}

// FromWindowOrAny first calls FromWindow(wnd) and if that fails to find a Workspace, then calls Any().
func FromWindowOrAny(wnd *unison.Window) *Workspace {
	ws := FromWindow(wnd)
	if ws == nil {
		ws = Any()
	}
	return ws
}

// FromWindow returns the Workspace associated with the given Window, or nil.
func FromWindow(wnd *unison.Window) *Workspace {
	if wnd != nil {
		if data, ok := wnd.ClientData()[workspaceClientDataKey]; ok {
			if w, ok2 := data.(*Workspace); ok2 {
				return w
			}
		}
	}
	return nil
}

// Any first tries to return the workspace for the active window. If that fails, then it looks for any available
// workspace and returns that.
func Any() *Workspace {
	if ws := FromWindow(unison.ActiveWindow()); ws != nil {
		return ws
	}
	for _, wnd := range unison.Windows() {
		if ws := FromWindow(wnd); ws != nil {
			return ws
		}
	}
	return nil
}

// NewWorkspace creates a new Workspace for the given Window.
func NewWorkspace(wnd *unison.Window) *Workspace {
	w := &Workspace{
		Window:       wnd,
		TopDock:      unison.NewDock(),
		Navigator:    newNavigator(),
		DocumentDock: NewDocumentDock(),
	}
	wnd.SetContent(w.TopDock)
	w.TopDock.DockTo(w.Navigator, nil, unison.LeftSide)
	dc := unison.Ancestor[*unison.DockContainer](w.Navigator)
	w.TopDock.DockTo(w.DocumentDock, dc, unison.RightSide)
	dc.SetCurrentDockable(w.Navigator)
	wnd.ClientData()[workspaceClientDataKey] = w
	wnd.AllowCloseCallback = w.allowClose
	wnd.WillCloseCallback = w.willClose
	global := settings.Global()
	if global.WorkspaceFrame != nil {
		wnd.SetFrameRect(*global.WorkspaceFrame)
	} else {
		wnd.SetFrameRect(unison.PrimaryDisplay().Usable)
	}
	// On some platforms, this needs to be done after a delay... but we do it without the delay, too, so that
	// well-behaved platforms don't flash
	w.TopDock.RootDockLayout().SetDividerPosition(global.LibraryExplorer.DividerPosition)
	unison.InvokeTaskAfter(func() {
		w.TopDock.RootDockLayout().SetDividerPosition(global.LibraryExplorer.DividerPosition)
	}, time.Millisecond)
	wnd.ToFront()
	w.Navigator.InitialFocus()
	return w
}

func (w *Workspace) allowClose() bool {
	may := true
	w.DocumentDock.RootDockLayout().ForEachDockContainer(func(dc *unison.DockContainer) bool {
		for _, other := range dc.Dockables() {
			if tc, ok2 := other.(unison.TabCloser); ok2 {
				if _, ok := other.(widget.GroupedCloser); !ok {
					if !tc.MayAttemptClose() {
						may = false
						return true
					}
					if !tc.AttemptClose() {
						may = false
						return true
					}
				}
			}
		}
		return false
	})
	return may
}

func (w *Workspace) willClose() {
	global := settings.Global()
	global.LibraryExplorer.OpenRowKeys = w.Navigator.DisclosedPaths()
	frame := w.Window.FrameRect()
	global.WorkspaceFrame = &frame
	if err := global.Save(); err != nil {
		unison.ErrorDialogWithError(i18n.Text("Unable to save global settings"), err)
	}
}

// CurrentlyFocusedDockContainer returns the currently focused DockContainer, if any.
func (w *Workspace) CurrentlyFocusedDockContainer() *unison.DockContainer {
	if focus := w.Window.Focus(); focus != nil {
		if dc := unison.Ancestor[*unison.DockContainer](focus); dc != nil && dc.Dock == w.DocumentDock.Dock {
			return dc
		}
	}
	return nil
}

// LocateFileBackedDockable searches for a FileBackedDockable with the given path.
func (w *Workspace) LocateFileBackedDockable(filePath string) FileBackedDockable {
	var dockable FileBackedDockable
	w.DocumentDock.RootDockLayout().ForEachDockContainer(func(dc *unison.DockContainer) bool {
		for _, one := range dc.Dockables() {
			if fbd, ok := one.(FileBackedDockable); ok {
				if filePath == fbd.BackingFilePath() {
					dockable = fbd
					return true
				}
			}
		}
		return false
	})
	return dockable
}

// LocateDockContainerForExtension searches for the first FileBackedDockable with the given extension and returns its
// DockContainer.
func (w *Workspace) LocateDockContainerForExtension(ext ...string) *unison.DockContainer {
	var extDC *unison.DockContainer
	w.DocumentDock.RootDockLayout().ForEachDockContainer(func(dc *unison.DockContainer) bool {
		if DockContainerHoldsExtension(dc, ext...) {
			extDC = dc
			return true
		}
		return false
	})
	return extDC
}

// DockContainerHoldsExtension returns true if an immediate child of the given DockContainer has a FileBackedDockable
// with the given extension.
func DockContainerHoldsExtension(dc *unison.DockContainer, ext ...string) bool {
	for _, one := range dc.Dockables() {
		if fbd, ok := one.(FileBackedDockable); ok {
			fbdExt := path.Ext(fbd.BackingFilePath())
			for _, e := range ext {
				if strings.EqualFold(fbdExt, e) {
					return true
				}
			}
		}
	}
	return false
}

// AssociatedUUIDKey is the key used with CloseUUID().
const AssociatedUUIDKey = "associated_uuid"

// CloseUUID attempts to close any Dockables associated with the given UUIDs. Returns false if a dockable refused to
// close.
func CloseUUID(ids map[uuid.UUID]bool) bool {
	allow := true
	for _, wnd := range unison.Windows() {
		if ws := FromWindow(wnd); ws != nil {
			ws.DocumentDock.RootDockLayout().ForEachDockContainer(func(dc *unison.DockContainer) bool {
				for _, other := range dc.Dockables() {
					if tc, ok := other.(unison.TabCloser); ok {
						if otherValue, ok2 := other.AsPanel().ClientData()[AssociatedUUIDKey]; ok2 {
							if otherID, ok3 := otherValue.(uuid.UUID); ok3 && ids[otherID] {
								if !tc.MayAttemptClose() {
									allow = false
									return true
								}
								if !tc.AttemptClose() {
									allow = false
									return true
								}
							}
						}
					}
				}
				return false
			})
		}
	}
	return allow
}

// MayAttemptCloseOfGroup returns true if the grouped Dockables associated with the given dockable may be closed.
func MayAttemptCloseOfGroup(d unison.Dockable) bool {
	allow := true
	traverseGroup(d, func(target widget.GroupedCloser) bool {
		if !target.MayAttemptClose() {
			allow = false
			return true
		}
		return false
	})
	return allow
}

// CloseGroup attempts to close any grouped Dockables associated with the given Dockable. Returns false if a dockable
// refused to close.
func CloseGroup(d unison.Dockable) bool {
	allow := true
	traverseGroup(d, func(target widget.GroupedCloser) bool {
		if !target.MayAttemptClose() {
			allow = false
			return true
		}
		if !target.AttemptClose() {
			allow = false
			return true
		}
		return false
	})
	return allow
}

func traverseGroup(d unison.Dockable, f func(target widget.GroupedCloser) bool) {
	for _, wnd := range unison.Windows() {
		if ws := FromWindow(wnd); ws != nil {
			ws.DocumentDock.RootDockLayout().ForEachDockContainer(func(dc *unison.DockContainer) bool {
				for _, other := range dc.Dockables() {
					if fe, ok := other.(widget.GroupedCloser); ok && fe.CloseWithGroup(d) {
						if f(fe) {
							return true
						}
					}
				}
				return false
			})
		}
	}
}

// SaveDockable attempts to save the contents of the dockable using its existing path.
func SaveDockable(d FileBackedDockable, saver func(filePath string) error, setUnmodified func()) bool {
	filePath := d.BackingFilePath()
	if err := saver(filePath); err != nil {
		unison.ErrorDialogWithError(fmt.Sprintf(i18n.Text("Unable to save %s"), fs.BaseName(filePath)), err)
		return false
	}
	setUnmodified()
	if dc := unison.Ancestor[*unison.DockContainer](d); dc != nil {
		dc.UpdateTitle(d)
	}
	return true
}

// SaveDockableAs attempts to save the contents of the dockable, prompting for a new path.
func SaveDockableAs(d FileBackedDockable, extension string, saver func(filePath string) error, setUnmodifiedAndNewPath func(filePath string)) bool {
	dialog := unison.NewSaveDialog()
	existingPath := d.BackingFilePath()
	dir := filepath.Dir(existingPath)
	if existingPath != dir {
		dialog.SetInitialDirectory(dir)
	}
	dialog.SetAllowedExtensions(extension)
	if dialog.RunModal() {
		filePath := dialog.Path()
		if err := saver(filePath); err != nil {
			unison.ErrorDialogWithError(fmt.Sprintf(i18n.Text("Unable to save as %s"), fs.BaseName(filePath)), err)
			return false
		}
		setUnmodifiedAndNewPath(filePath)
		settings.Global().AddRecentFile(filePath)
		if dc := unison.Ancestor[*unison.DockContainer](d); dc != nil {
			dc.UpdateTitle(d)
		}
		return true
	}
	return false
}

// PromptForDestination puts up a modal dialog to choose one or more destinations if choices contains more than one
// choice. Return an empty list if canceled or there are no selections made.
func PromptForDestination[T FileBackedDockable](choices []T) []T {
	if len(choices) < 2 {
		return choices
	}
	slices.SortFunc(choices, func(a, b T) bool {
		ta := a.Title()
		tb := b.Title()
		if ta == tb {
			return txt.NaturalLess(a.BackingFilePath(), b.BackingFilePath(), true)
		}
		return txt.NaturalLess(ta, tb, true)
	})
	list := unison.NewList[T]()
	list.SetAllowMultipleSelection(true)
	list.DoubleClickCallback = func() {
		if dialog, ok := list.Window().ClientData()[unison.DialogClientDataKey].(*unison.Dialog); ok {
			dialog.Button(unison.ModalResponseOK).Click()
		}
	}
	list.Append(choices...)
	scroll := unison.NewScrollPanel()
	scroll.SetBorder(unison.NewLineBorder(unison.DividerColor, 0, unison.NewUniformInsets(1), false))
	scroll.SetContent(list, unison.FillBehavior, unison.FillBehavior)
	scroll.SetLayoutData(&unison.FlexLayoutData{
		HAlign: unison.FillAlignment,
		VAlign: unison.FillAlignment,
		HGrab:  true,
		VGrab:  true,
	})
	panel := unison.NewPanel()
	panel.SetLayout(&unison.FlexLayout{
		Columns:  1,
		HSpacing: unison.StdHSpacing,
		VSpacing: unison.StdVSpacing,
		HAlign:   unison.FillAlignment,
		VAlign:   unison.FillAlignment,
	})
	label := unison.NewLabel()
	label.Text = i18n.Text("Choose one or more destinations:")
	panel.AddChild(label)
	panel.AddChild(scroll)
	if unison.QuestionDialogWithPanel(panel) != unison.ModalResponseOK || list.Selection.Count() == 0 {
		return nil
	}
	result := make([]T, 0, list.Selection.Count())
	i := list.Selection.FirstSet()
	for i != -1 {
		result = append(result, choices[i])
		i = list.Selection.NextSet(i + 1)
	}
	return result
}
<commit_msg>Remember the position of the library explorer divider<commit_after>/*
 * Copyright ©1998-2022 by Richard A. Wilkes. All rights reserved.
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, version 2.0. If a copy of the MPL was not distributed with
 * this file, You can obtain one at http://mozilla.org/MPL/2.0/.
 *
 * This Source Code Form is "Incompatible With Secondary Licenses", as
 * defined by the Mozilla Public License, version 2.0.
 */

package workspace

import (
	"fmt"
	"path"
	"path/filepath"
	"strings"
	"time"

	"github.com/google/uuid"
	"github.com/richardwilkes/gcs/v5/model/settings"
	"github.com/richardwilkes/gcs/v5/ui/widget"
	"github.com/richardwilkes/toolbox/i18n"
	"github.com/richardwilkes/toolbox/log/jot"
	"github.com/richardwilkes/toolbox/txt"
	"github.com/richardwilkes/toolbox/xio/fs"
	"github.com/richardwilkes/unison"
	"golang.org/x/exp/slices"
)

const workspaceClientDataKey = "workspace"

// Workspace holds the data necessary to track the Workspace.
type Workspace struct {
	Window       *unison.Window
	TopDock      *unison.Dock
	Navigator    *Navigator
	DocumentDock *DocumentDock
}

// ShowUnableToLocateWorkspaceError displays an error dialog.
func ShowUnableToLocateWorkspaceError() {
	unison.ErrorDialogWithMessage(i18n.Text("Unable to locate workspace"), "")
}

// Activate attempts to locate an existing dockable that 'matcher' returns true for. If found, it will have been
// activated and focused.
func Activate(matcher func(d unison.Dockable) bool) (ws *Workspace, dc *unison.DockContainer, found bool) {
	if ws = Any(); ws == nil {
		jot.Error("no workspace available")
		return nil, nil, false
	}
	dc = ws.CurrentlyFocusedDockContainer()
	ws.DocumentDock.RootDockLayout().ForEachDockContainer(func(container *unison.DockContainer) bool {
		for _, one := range container.Dockables() {
			if matcher(one) {
				found = true
				container.SetCurrentDockable(one)
				container.AcquireFocus()
				return true
			}
			if dc == nil {
				dc = container
			}
		}
		return false
	})
	return ws, dc, found
}

// ActiveDockable returns the currently active dockable in the active window.
func ActiveDockable() unison.Dockable {
	ws := FromWindow(unison.ActiveWindow())
	if ws == nil {
		return nil
	}
	dc := ws.CurrentlyFocusedDockContainer()
	if dc == nil {
		return nil
	}
	return dc.CurrentDockable()
}

// FromWindowOrAny first calls FromWindow(wnd) and if that fails to find a Workspace, then calls Any().
func FromWindowOrAny(wnd *unison.Window) *Workspace {
	ws := FromWindow(wnd)
	if ws == nil {
		ws = Any()
	}
	return ws
}

// FromWindow returns the Workspace associated with the given Window, or nil.
func FromWindow(wnd *unison.Window) *Workspace {
	if wnd != nil {
		if data, ok := wnd.ClientData()[workspaceClientDataKey]; ok {
			if w, ok2 := data.(*Workspace); ok2 {
				return w
			}
		}
	}
	return nil
}

// Any first tries to return the workspace for the active window. If that fails, then it looks for any available
// workspace and returns that.
func Any() *Workspace {
	if ws := FromWindow(unison.ActiveWindow()); ws != nil {
		return ws
	}
	for _, wnd := range unison.Windows() {
		if ws := FromWindow(wnd); ws != nil {
			return ws
		}
	}
	return nil
}

// NewWorkspace creates a new Workspace for the given Window.
func NewWorkspace(wnd *unison.Window) *Workspace {
	w := &Workspace{
		Window:       wnd,
		TopDock:      unison.NewDock(),
		Navigator:    newNavigator(),
		DocumentDock: NewDocumentDock(),
	}
	wnd.SetContent(w.TopDock)
	w.TopDock.DockTo(w.Navigator, nil, unison.LeftSide)
	dc := unison.Ancestor[*unison.DockContainer](w.Navigator)
	w.TopDock.DockTo(w.DocumentDock, dc, unison.RightSide)
	dc.SetCurrentDockable(w.Navigator)
	wnd.ClientData()[workspaceClientDataKey] = w
	wnd.AllowCloseCallback = w.allowClose
	wnd.WillCloseCallback = w.willClose
	global := settings.Global()
	if global.WorkspaceFrame != nil {
		wnd.SetFrameRect(*global.WorkspaceFrame)
	} else {
		wnd.SetFrameRect(unison.PrimaryDisplay().Usable)
	}
	// On some platforms, this needs to be done after a delay... but we do it without the delay, too, so that
	// well-behaved platforms don't flash
	w.TopDock.RootDockLayout().SetDividerPosition(global.LibraryExplorer.DividerPosition)
	unison.InvokeTaskAfter(func() {
		w.TopDock.RootDockLayout().SetDividerPosition(global.LibraryExplorer.DividerPosition)
	}, time.Millisecond)
	wnd.ToFront()
	w.Navigator.InitialFocus()
	return w
}

func (w *Workspace) allowClose() bool {
	may := true
	w.DocumentDock.RootDockLayout().ForEachDockContainer(func(dc *unison.DockContainer) bool {
		for _, other := range dc.Dockables() {
			if tc, ok2 := other.(unison.TabCloser); ok2 {
				if _, ok := other.(widget.GroupedCloser); !ok {
					if !tc.MayAttemptClose() {
						may = false
						return true
					}
					if !tc.AttemptClose() {
						may = false
						return true
					}
				}
			}
		}
		return false
	})
	return may
}

func (w *Workspace) willClose() {
	global := settings.Global()
	global.LibraryExplorer.OpenRowKeys = w.Navigator.DisclosedPaths()
	global.LibraryExplorer.DividerPosition = w.TopDock.RootDockLayout().DividerPosition()
	frame := w.Window.FrameRect()
	global.WorkspaceFrame = &frame
	if err := global.Save(); err != nil {
		unison.ErrorDialogWithError(i18n.Text("Unable to save global settings"), err)
	}
}

// CurrentlyFocusedDockContainer returns the currently focused DockContainer, if any.
func (w *Workspace) CurrentlyFocusedDockContainer() *unison.DockContainer {
	if focus := w.Window.Focus(); focus != nil {
		if dc := unison.Ancestor[*unison.DockContainer](focus); dc != nil && dc.Dock == w.DocumentDock.Dock {
			return dc
		}
	}
	return nil
}

// LocateFileBackedDockable searches for a FileBackedDockable with the given path.
func (w *Workspace) LocateFileBackedDockable(filePath string) FileBackedDockable {
	var dockable FileBackedDockable
	w.DocumentDock.RootDockLayout().ForEachDockContainer(func(dc *unison.DockContainer) bool {
		for _, one := range dc.Dockables() {
			if fbd, ok := one.(FileBackedDockable); ok {
				if filePath == fbd.BackingFilePath() {
					dockable = fbd
					return true
				}
			}
		}
		return false
	})
	return dockable
}

// LocateDockContainerForExtension searches for the first FileBackedDockable with the given extension and returns its
// DockContainer.
func (w *Workspace) LocateDockContainerForExtension(ext ...string) *unison.DockContainer {
	var extDC *unison.DockContainer
	w.DocumentDock.RootDockLayout().ForEachDockContainer(func(dc *unison.DockContainer) bool {
		if DockContainerHoldsExtension(dc, ext...) {
			extDC = dc
			return true
		}
		return false
	})
	return extDC
}

// DockContainerHoldsExtension returns true if an immediate child of the given DockContainer has a FileBackedDockable
// with the given extension.
func DockContainerHoldsExtension(dc *unison.DockContainer, ext ...string) bool {
	for _, one := range dc.Dockables() {
		if fbd, ok := one.(FileBackedDockable); ok {
			fbdExt := path.Ext(fbd.BackingFilePath())
			for _, e := range ext {
				if strings.EqualFold(fbdExt, e) {
					return true
				}
			}
		}
	}
	return false
}

// AssociatedUUIDKey is the key used with CloseUUID().
const AssociatedUUIDKey = "associated_uuid"

// CloseUUID attempts to close any Dockables associated with the given UUIDs. Returns false if a dockable refused to
// close.
func CloseUUID(ids map[uuid.UUID]bool) bool {
	allow := true
	for _, wnd := range unison.Windows() {
		if ws := FromWindow(wnd); ws != nil {
			ws.DocumentDock.RootDockLayout().ForEachDockContainer(func(dc *unison.DockContainer) bool {
				for _, other := range dc.Dockables() {
					if tc, ok := other.(unison.TabCloser); ok {
						if otherValue, ok2 := other.AsPanel().ClientData()[AssociatedUUIDKey]; ok2 {
							if otherID, ok3 := otherValue.(uuid.UUID); ok3 && ids[otherID] {
								if !tc.MayAttemptClose() {
									allow = false
									return true
								}
								if !tc.AttemptClose() {
									allow = false
									return true
								}
							}
						}
					}
				}
				return false
			})
		}
	}
	return allow
}

// MayAttemptCloseOfGroup returns true if the grouped Dockables associated with the given dockable may be closed.
func MayAttemptCloseOfGroup(d unison.Dockable) bool {
	allow := true
	traverseGroup(d, func(target widget.GroupedCloser) bool {
		if !target.MayAttemptClose() {
			allow = false
			return true
		}
		return false
	})
	return allow
}

// CloseGroup attempts to close any grouped Dockables associated with the given Dockable. Returns false if a dockable
// refused to close.
func CloseGroup(d unison.Dockable) bool {
	allow := true
	traverseGroup(d, func(target widget.GroupedCloser) bool {
		if !target.MayAttemptClose() {
			allow = false
			return true
		}
		if !target.AttemptClose() {
			allow = false
			return true
		}
		return false
	})
	return allow
}

func traverseGroup(d unison.Dockable, f func(target widget.GroupedCloser) bool) {
	for _, wnd := range unison.Windows() {
		if ws := FromWindow(wnd); ws != nil {
			ws.DocumentDock.RootDockLayout().ForEachDockContainer(func(dc *unison.DockContainer) bool {
				for _, other := range dc.Dockables() {
					if fe, ok := other.(widget.GroupedCloser); ok && fe.CloseWithGroup(d) {
						if f(fe) {
							return true
						}
					}
				}
				return false
			})
		}
	}
}

// SaveDockable attempts to save the contents of the dockable using its existing path.
func SaveDockable(d FileBackedDockable, saver func(filePath string) error, setUnmodified func()) bool {
	filePath := d.BackingFilePath()
	if err := saver(filePath); err != nil {
		unison.ErrorDialogWithError(fmt.Sprintf(i18n.Text("Unable to save %s"), fs.BaseName(filePath)), err)
		return false
	}
	setUnmodified()
	if dc := unison.Ancestor[*unison.DockContainer](d); dc != nil {
		dc.UpdateTitle(d)
	}
	return true
}

// SaveDockableAs attempts to save the contents of the dockable, prompting for a new path.
func SaveDockableAs(d FileBackedDockable, extension string, saver func(filePath string) error, setUnmodifiedAndNewPath func(filePath string)) bool {
	dialog := unison.NewSaveDialog()
	existingPath := d.BackingFilePath()
	dir := filepath.Dir(existingPath)
	if existingPath != dir {
		dialog.SetInitialDirectory(dir)
	}
	dialog.SetAllowedExtensions(extension)
	if dialog.RunModal() {
		filePath := dialog.Path()
		if err := saver(filePath); err != nil {
			unison.ErrorDialogWithError(fmt.Sprintf(i18n.Text("Unable to save as %s"), fs.BaseName(filePath)), err)
			return false
		}
		setUnmodifiedAndNewPath(filePath)
		settings.Global().AddRecentFile(filePath)
		if dc := unison.Ancestor[*unison.DockContainer](d); dc != nil {
			dc.UpdateTitle(d)
		}
		return true
	}
	return false
}

// PromptForDestination puts up a modal dialog to choose one or more destinations if choices contains more than one
// choice. Return an empty list if canceled or there are no selections made.
func PromptForDestination[T FileBackedDockable](choices []T) []T {
	if len(choices) < 2 {
		return choices
	}
	slices.SortFunc(choices, func(a, b T) bool {
		ta := a.Title()
		tb := b.Title()
		if ta == tb {
			return txt.NaturalLess(a.BackingFilePath(), b.BackingFilePath(), true)
		}
		return txt.NaturalLess(ta, tb, true)
	})
	list := unison.NewList[T]()
	list.SetAllowMultipleSelection(true)
	list.DoubleClickCallback = func() {
		if dialog, ok := list.Window().ClientData()[unison.DialogClientDataKey].(*unison.Dialog); ok {
			dialog.Button(unison.ModalResponseOK).Click()
		}
	}
	list.Append(choices...)
	scroll := unison.NewScrollPanel()
	scroll.SetBorder(unison.NewLineBorder(unison.DividerColor, 0, unison.NewUniformInsets(1), false))
	scroll.SetContent(list, unison.FillBehavior, unison.FillBehavior)
	scroll.SetLayoutData(&unison.FlexLayoutData{
		HAlign: unison.FillAlignment,
		VAlign: unison.FillAlignment,
		HGrab:  true,
		VGrab:  true,
	})
	panel := unison.NewPanel()
	panel.SetLayout(&unison.FlexLayout{
		Columns:  1,
		HSpacing: unison.StdHSpacing,
		VSpacing: unison.StdVSpacing,
		HAlign:   unison.FillAlignment,
		VAlign:   unison.FillAlignment,
	})
	label := unison.NewLabel()
	label.Text = i18n.Text("Choose one or more destinations:")
	panel.AddChild(label)
	panel.AddChild(scroll)
	if unison.QuestionDialogWithPanel(panel) != unison.ModalResponseOK || list.Selection.Count() == 0 {
		return nil
	}
	result := make([]T, 0, list.Selection.Count())
	i := list.Selection.FirstSet()
	for i != -1 {
		result = append(result, choices[i])
		i = list.Selection.NextSet(i + 1)
	}
	return result
}
<|endoftext|>


<commit_before>package core6502

import (
	"github.com/simulatedsimian/testbuddy"
	"testing"
)

func TestSplit(t *testing.T) {
	testbuddy.AssertEqual(t, Split("", " \t"), []string{})
	testbuddy.AssertEqual(t, Split("   \t\t\t", " \t"), []string{})
	testbuddy.AssertEqual(t, Split("hello world", " \t"), []string{"hello", "world"})
	testbuddy.AssertEqual(t, Split("    hello     world     ", " \t"), []string{"hello", "world"})
}
<commit_msg>messing with assertions<commit_after>package core6502

import (
	"github.com/simulatedsimian/testbuddy"
	"testing"
)

type T testing.T

func (t *T) AssertEqualsNoErr(val interface{}, err error) func(val interface{}) {
	return func(val interface{}) {

	}
}

func (t *T) AssertErr(val interface{}, err error) {
	testbuddy.AssertEqual((*testing.T)(t), val, nil)
}

func TestSplit(t *testing.T) {
	tt := (*T)(t)

	testbuddy.AssertEqual(t, Split("", " \t"), []string{})
	testbuddy.AssertEqual(t, Split("   \t\t\t", " \t"), []string{})
	testbuddy.AssertEqual(t, Split("hello world", " \t"), []string{"hello", "world"})
	testbuddy.AssertEqual(t, Split("    hello     world     ", " \t"), []string{"hello", "world"})

	tt.AssertEqualsNoErr(ParseUint("123", 16))(1)
}
<|endoftext|>


<commit_before>package main

import (
	//"fmt"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"time"
)

var _ = Describe("Configuration", func() {

	var configuration *Configuration
	var args []string

	BeforeEach(func() {
        args = []string{"file-path.yml"}
		configuration = ParseConfiguration(args)
	})

	Describe("When no config file is found and no command line args are provided", func() {
		Describe("Loading a default configuration", func() {
			It("sets duration (--duration)", func() {
				defaultDuration := time.Duration(0)
				Expect(configuration.WaitTime).To(Equal(defaultDuration))
			})
			It("sets random (--random)", func() {
				Expect(configuration.Random).To(Equal(false))
			})
			It("sets summary (--summary)", func() {
				Expect(configuration.Summary).To(Equal(false))
			})
			It("sets workers (--workers)", func() {
				Expect(configuration.Workers).To(Equal(1))
			})
			It("sets wait-time (--wait-time)", func() {
				defaultWaitTime := time.Duration(0)
				Expect(configuration.WaitTime).To(Equal(defaultWaitTime))
			})
		})
	})

	Describe("When config file is not found in pwd", func() {
		Describe("and config file is found in user home", func() {
		})
	})

	Describe("When config file is found in pwd", func() {
		Describe("and config file is found in user home", func() {
		})
	})

	Describe("When commandline args are provided", func() {
		Describe("overriding the default configuration", func() {
			Describe("for duration (--duration)", func() {
				BeforeEach(func() {
					args = []string{"--duration", "3s", "./path/to/file"}
					configuration = ParseConfiguration(args)
				})
				It("sets the value", func() {
					duration, _ := time.ParseDuration("3s")
					Expect(configuration.Duration).To(Equal(duration))
				})
			})

			Describe("for file", func() {
				BeforeEach(func() {
					args = []string{"./path/to/file"}
					configuration = ParseConfiguration(args)
				})
				It("sets the value", func() {
					Expect(configuration.FilePath).To(Equal("./path/to/file"))
				})
			})

			Describe("for random (--random)", func() {
				BeforeEach(func() {
					args = []string{"--random", "./path/to/file"}
					configuration = ParseConfiguration(args)
				})
				It("sets the value", func() {
					Expect(configuration.Random).To(Equal(true))
				})
			})

			Describe("for summary (--summary)", func() {
				BeforeEach(func() {
					args = []string{"--summary", "./path/to/file"}
					configuration = ParseConfiguration(args)
				})
				It("sets the value", func() {
					Expect(configuration.Summary).To(Equal(true))
				})
			})

			Describe("for workers (--workers)", func() {
				BeforeEach(func() {
					args = []string{"--workers", "3", "./path/to/file"}
					configuration = ParseConfiguration(args)
				})
				It("sets the value", func() {
					Expect(configuration.Workers).To(Equal(3))
				})
			})

			Describe("for wait-time (--wait-time)", func() {
				BeforeEach(func() {
					args = []string{"--wait-time", "3s", "./path/to/file"}
					configuration = ParseConfiguration(args)
				})
				It("sets the value", func() {
					waitTime, _ := time.ParseDuration("3s")
					Expect(configuration.WaitTime).To(Equal(waitTime))
				})
			})
		})
	})
})
<commit_msg>tests to ensure defaults still apply for args not specified on command line<commit_after>package main

import (
	//"fmt"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	"time"
)

var _ = Describe("Configuration", func() {

	var configuration *Configuration
	var args []string
	defaultWaitTime := time.Duration(0)
	defaultDuration := time.Duration(0)

	BeforeEach(func() {
		args = []string{"file-path.yml"}
		configuration = ParseConfiguration(args)
	})

	Describe("When no config file is found and no command line args are provided", func() {
		Describe("Loading a default configuration", func() {
			It("sets duration (--duration)", func() {
				Expect(configuration.WaitTime).To(Equal(defaultDuration))
			})
			It("sets random (--random)", func() {
				Expect(configuration.Random).To(Equal(false))
			})
			It("sets summary (--summary)", func() {
				Expect(configuration.Summary).To(Equal(false))
			})
			It("sets workers (--workers)", func() {
				Expect(configuration.Workers).To(Equal(1))
			})
			It("sets wait-time (--wait-time)", func() {
				Expect(configuration.WaitTime).To(Equal(defaultWaitTime))
			})
		})
	})

	Describe("When config file is not found in pwd", func() {
		Describe("and config file is found in user home", func() {
		})
	})

	Describe("When config file is found in pwd", func() {
		Describe("and config file is found in user home", func() {
		})
	})

	Describe("When commandline args are provided", func() {
		Describe("overriding the default configuration", func() {
			Describe("for duration (--duration)", func() {
				BeforeEach(func() {
					args = []string{"--duration", "3s", "./path/to/file"}
					configuration = ParseConfiguration(args)
				})
				It("applies the override", func() {
					duration, _ := time.ParseDuration("3s")
					Expect(configuration.Duration).To(Equal(duration))
				})

				Describe("leaves the default for", func() {
					It("random", func() {
						Expect(configuration.Random).To(Equal(false))
					})
					It("summary", func() {
						Expect(configuration.Summary).To(Equal(false))
					})
					It("workers", func() {
						Expect(configuration.Workers).To(Equal(1))
					})
					It("wait-time", func() {
						Expect(configuration.WaitTime).To(Equal(defaultWaitTime))
					})
				})
			})

			Describe("for file", func() {
				BeforeEach(func() {
					args = []string{"./path/to/file"}
					configuration = ParseConfiguration(args)
				})
				It("applies the override", func() {
					Expect(configuration.FilePath).To(Equal("./path/to/file"))
				})

				Describe("leaves the default for", func() {
					It("duration", func() {
						Expect(configuration.Duration).To(Equal(defaultDuration))
					})
					It("random", func() {
						Expect(configuration.Random).To(Equal(false))
					})
					It("summary", func() {
						Expect(configuration.Summary).To(Equal(false))
					})
					It("workers", func() {
						Expect(configuration.Workers).To(Equal(1))
					})
					It("wait-time", func() {
						Expect(configuration.WaitTime).To(Equal(defaultWaitTime))
					})
				})
			})

			Describe("for random (--random)", func() {
				BeforeEach(func() {
					args = []string{"--random", "./path/to/file"}
					configuration = ParseConfiguration(args)
				})
				It("applies the override", func() {
					Expect(configuration.Random).To(Equal(true))
				})

				Describe("leaves the default for", func() {
					It("duration", func() {
						Expect(configuration.Duration).To(Equal(defaultDuration))
					})
					It("summary", func() {
						Expect(configuration.Summary).To(Equal(false))
					})
					It("workers", func() {
						Expect(configuration.Workers).To(Equal(1))
					})
					It("wait-time", func() {
						Expect(configuration.WaitTime).To(Equal(defaultWaitTime))
					})
				})
			})

			Describe("for summary (--summary)", func() {
				BeforeEach(func() {
					args = []string{"--summary", "./path/to/file"}
					configuration = ParseConfiguration(args)
				})
				It("applies the override", func() {
					Expect(configuration.Summary).To(Equal(true))
				})

				Describe("leaves the default for", func() {
					It("duration", func() {
						Expect(configuration.Duration).To(Equal(defaultDuration))
					})
					It("random", func() {
						Expect(configuration.Random).To(Equal(false))
					})
					It("workers", func() {
						Expect(configuration.Workers).To(Equal(1))
					})
					It("wait-time", func() {
						Expect(configuration.WaitTime).To(Equal(defaultWaitTime))
					})
				})
			})

			Describe("for workers (--workers)", func() {
				BeforeEach(func() {
					args = []string{"--workers", "3", "./path/to/file"}
					configuration = ParseConfiguration(args)
				})
				It("applies the override", func() {
					Expect(configuration.Workers).To(Equal(3))
				})

				Describe("leaves the default for", func() {
					It("duration", func() {
						Expect(configuration.Duration).To(Equal(defaultDuration))
					})
					It("random", func() {
						Expect(configuration.Random).To(Equal(false))
					})
					It("summary", func() {
						Expect(configuration.Summary).To(Equal(false))
					})
					It("wait-time", func() {
						Expect(configuration.WaitTime).To(Equal(defaultWaitTime))
					})
				})
			})

			Describe("for wait-time (--wait-time)", func() {
				BeforeEach(func() {
					args = []string{"--wait-time", "3s", "./path/to/file"}
					configuration = ParseConfiguration(args)
				})
				It("applies the override", func() {
					waitTime, _ := time.ParseDuration("3s")
					Expect(configuration.WaitTime).To(Equal(waitTime))
				})

				Describe("leaves the default for", func() {
					It("duration", func() {
						Expect(configuration.Duration).To(Equal(defaultDuration))
					})
					It("random", func() {
						Expect(configuration.Random).To(Equal(false))
					})
					It("summary", func() {
						Expect(configuration.Summary).To(Equal(false))
					})
					It("workers", func() {
						Expect(configuration.Workers).To(Equal(1))
					})
				})
			})
		})
	})
})
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/user"
	C "strconv"
	"strings"
	"sync"
	"time"
	"net/http"
	"encoding/json"
	"bytes"
)

var wg sync.WaitGroup
var readChan chan string
var inPath string
var outPath string
var Pattern string
var folderType string

func main() {
	println("SLOTH: GO Edition")
	println("----------------------")
	start := time.Now()
	usr, err := user.Current()
	if err != nil {
		log.Fatal(err)
	}

	defaultIn := usr.HomeDir + "\\Downloads"
	defaultOut := usr.HomeDir + "\\Downloads\\Sloth"

	//InputPath
	in := flag.String("inPath", defaultIn, "readPath")
	if in == nil {

	}

	//OutputPath
	out := flag.String("outPath", defaultOut, "readPath")

	//Pattern
	pattern := flag.String("pattern", "zip", "file search pattern")

	//Folder Type
	fType := flag.String("folderType", "3", "1 \\moddate, 2 \\pattern, 3 none")

	flag.Parse()

	inPath = *in
	outPath = *out
	Pattern = *pattern
	folderType = *fType

	fmt.Println("Input Path: " + inPath)
	fmt.Println("Output Path: " + outPath)
	fmt.Println("Pattern: " + strings.ToUpper(Pattern))
	fmt.Println("Folder Type: " + folderType)

	readChan = make(chan string, 1000)

	//Is the file a file?
	files, err := ioutil.ReadDir(inPath)
	if err != nil {
		println(err.Error())
	}

	//Start workers
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go MoveFiles(readChan)
	}

	//Iterate over each file and move it
	for _, element := range files {
		if !element.IsDir() {
			if string([]byte(element.Name())[len(element.Name())-len(Pattern):]) == Pattern {
				//Count number of go routines
				readChan <- element.Name()
			}
		}
	}

	close(readChan)

	//Wait for all go routines to finish
	wg.Wait()

	elapsed := time.Since(start)
	println("Execution Time: ", elapsed)

	values := map[string]string{"value1": inPath, "value2" : outPath, "value3": Pattern}
	jsonValue, _ := json.Marshal(values)

	resp, err := http.Post("https://maker.ifttt.com/trigger/Sloth_Notify/with/key/cRoTTDKR6fNC2X1MifxRyW", "application/json", bytes.NewBuffer(jsonValue))
	if err != nil {

	}

	println("Notification Sent to IFTTT.com", resp.Status)

}

func MoveFiles(inChan chan string) {

	for fileToMove := range inChan {

		//Input file
		in := inPath + "\\" + fileToMove

		outFolder := CreateOutputPath(inPath, outPath, fileToMove)

		out := outFolder + "\\" + fileToMove

		//create the directory (by default only if it doesn't exist')
		os.MkdirAll(outFolder, 0666)

		err := os.Rename(in, out)
		if err != nil {
			log.Fatal(err)
			println(err)
		}
	}

	//Remove go routine from list
	wg.Done()
}

func CreateOutputPath(inPath string, outPath string, fileToMove string) string {
	fi, err := os.Stat(inPath + "\\" + fileToMove)
	if err != nil {
		log.Fatal(err)
	}

	var outFolder = ""

	switch folderType {

	//1 uses file mod time as the folder
	case "1":
		mTime := fi.ModTime()

		year := C.Itoa(mTime.Year())
		month := C.Itoa(int(time.Now().Month()))
		day := "Day " + C.Itoa(mTime.Day())

		outFolder = outPath + "\\" + year + "\\" + month + "\\" + day

		return outFolder

	//2 uses the pattern as the folder
	case "2":
		outFolder = outPath + "\\" + Pattern

		return outFolder

	//3 will go to the root of defaultOut
	case "3":
		outFolder = outPath + "\\"

		return outFolder

	default:
		return ""
	}
}
<commit_msg>Added folderType 4. Pattern\modddateYear<commit_after>package main

import (
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/user"
	C "strconv"
	"strings"
	"sync"
	"time"
	"net/http"
	"encoding/json"
	"bytes"
)

var wg sync.WaitGroup
var readChan chan string
var inPath string
var outPath string
var Pattern string
var folderType string

func main() {
	println("SLOTH: GO Edition")
	println("----------------------")
	start := time.Now()
	usr, err := user.Current()
	if err != nil {
		log.Fatal(err)
	}

	defaultIn := usr.HomeDir + "\\Downloads"
	defaultOut := usr.HomeDir + "\\Downloads\\Sloth"

	//InputPath
	in := flag.String("inPath", defaultIn, "readPath")
	if in == nil {

	}

	//OutputPath
	out := flag.String("outPath", defaultOut, "readPath")

	//Pattern
	pattern := flag.String("pattern", "zip", "file search pattern")

	//Folder Type
	fType := flag.String("folderType", "4", "1 \\moddate, 2 \\pattern, 3 \\pattern\\moddate year, 4 none")

	flag.Parse()

	inPath = *in
	outPath = *out
	Pattern = *pattern
	folderType = *fType

	fmt.Println("Input Path: " + inPath)
	fmt.Println("Output Path: " + outPath)
	fmt.Println("Pattern: " + strings.ToUpper(Pattern))
	fmt.Println("Folder Type: " + folderType)

	readChan = make(chan string, 1000)

	//Is the file a file?
	files, err := ioutil.ReadDir(inPath)
	if err != nil {
		println(err.Error())
	}

	//Start workers
	for i := 0; i < 1000; i++ {
		wg.Add(1)
		go MoveFiles(readChan)
	}

	//Iterate over each file and move it
	for _, element := range files {
		if !element.IsDir() {
			if string([]byte(element.Name())[len(element.Name())-len(Pattern):]) == Pattern {
				//Count number of go routines
				readChan <- element.Name()
			}
		}
	}

	close(readChan)

	//Wait for all go routines to finish
	wg.Wait()

	values := map[string]string{"value1": inPath, "value2" : outPath, "value3": Pattern}
	jsonValue, _ := json.Marshal(values)

	//Notify IFTTT.com custom Maker channel
	resp, err := http.Post("https://maker.ifttt.com/trigger/Sloth_Notify/with/key/cRoTTDKR6fNC2X1MifxRyW", "application/json", bytes.NewBuffer(jsonValue))
	if err != nil {
		println(resp.Status)
	}

	println("Notification Sent to IFTTT.com", resp.Status)

	elapsed := time.Since(start)
	println("Execution Time: ", elapsed.Minutes())
}

func MoveFiles(inChan chan string) {

	for fileToMove := range inChan {

		//Input file
		in := inPath + "\\" + fileToMove

		outFolder := CreateOutputPath(inPath, outPath, fileToMove)

		out := outFolder + "\\" + fileToMove

		//create the directory (by default only if it doesn't exist')
		os.MkdirAll(outFolder, 0666)

		err := os.Rename(in, out)
		if err != nil {
			log.Fatal(err)
			println(err)
		}
	}

	//Remove go routine from list
	wg.Done()
}

func CreateOutputPath(inPath string, outPath string, fileToMove string) string {
	fi, err := os.Stat(inPath + "\\" + fileToMove)
	if err != nil {
		log.Fatal(err)
	}

	var outFolder = ""
	mTime := fi.ModTime()

	year := C.Itoa(mTime.Year())
	month := C.Itoa(int(time.Now().Month()))
	day := "Day " + C.Itoa(mTime.Day())

	switch folderType {

	//1 uses file mod time as the folder
	case "1":
		outFolder = outPath + "\\" + year + "\\" + month + "\\" + day

		return outFolder

	//2 uses the pattern as the folder
	case "2":
		outFolder = outPath + "\\" + Pattern

		return outFolder

	//3 uses the pattern as the folder and then groups by year
	case "3":
		outFolder = outPath + "\\" + Pattern + "\\" + year
		return outFolder

	//4 will go to the root of defaultOut
	case "4":
		outFolder = outPath + "\\"

		return outFolder

	default:
		return ""
	}
}
<|endoftext|>


<commit_before>// Copyright 2019, OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package conventions

// OpenTelemetry Semantic Convention values for Resource attribute names.
// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-resource-semantic-conventions.md
const (
	AttributeServiceName      = "service.name"
	AttributeServiceNamespace = "service.namespace"
	AttributeServiceInstance  = "service.instance.id"
	AttributeLibraryName      = "library.name"
	AttributeLibraryLanguage  = "library.language"
	AttributeLibraryVersion   = "library.version"
	AttributeContainerName    = "container.name"
	AttributeContainerImage   = "container.image.name"
	AttributeContainerTag     = "container.image.tag"
	AttributeK8sCluster       = "k8s.cluster.name"
	AttributeK8sNamespace     = "k8s.namespace.name"
	AttributeK8sPod           = "k8s.pod.name"
	AttributeK8sDeployment    = "k8s.deployment.name"
	AttributeHostHostname     = "host.hostname"
	AttributeHostID           = "host.id"
	AttributeHostName         = "host.name"
	AttributeHostType         = "host.type"
	AttributeCloudProvider    = "cloud.provider"
	AttributeCloudAccount     = "cloud.account.id"
	AttributeCloudRegion      = "cloud.region"
	AttributeCloudZone        = "cloud.zone"
)

// OpenTelemetry Semantic Convention values for general Span attribute names.
// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-semantic-conventions.md
// Peer attributes currently defined in database conventions
// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-database.md
const (
	AttributeComponent   = "component"
	AttributePeerAddress = "peer.address"  // to be obsoleted
	AttributePeerHost    = "peer.hostname" // to be obsoleted
	AttributePeerIpv4    = "peer.ipv4"     // to be obsoleted
	AttributePeerIpv6    = "peer.ipv6"     // to be obsoleted
	AttributePeerPort    = "peer.port"     // to be obsoleted
	AttributePeerService = "peer.service"  // to be obsoleted
)

// OpenTelemetry Semantic Convention values for component attribute values.
// Possibly being removed due to issue #336
const (
	ComponentTypeHTTP = "http"
	ComponentTypeGRPC = "grpc"
)

// OpenTelemetry Semantic Convention attribute names for HTTP related attributes
// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-http.md
const (
	AttributeHTTPMethod     = "http.method"
	AttributeHTTPURL        = "http.url"
	AttributeHTTPTarget     = "http.target"
	AttributeHTTPHost       = "http.host"
	AttributeHTTPScheme     = "http.scheme"
	AttributeHTTPStatusCode = "http.status_code"
	AttributeHTTPStatusText = "http.status_text"
	AttributeHTTPFlavor     = "http.flavor"
	AttributeHTTPServerName = "http.server_name"
	AttributeHTTPHostName   = "host.name"
	AttributeHTTPHostPort   = "host.port"
	AttributeHTTPRoute      = "http.route"
	AttributeHTTPClientIP   = "http.client_ip"
)

// OpenTelemetry Semantic Convention attribute names for database related attributes
// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-database.md
const (
	AttributeDBType      = "db.type"
	AttributeDBInstance  = "db.instance"
	AttributeDBStatement = "db.statement"
	AttributeDBUser      = "db.user"
)

// OpenTelemetry Semantic Convention attribute names for gRPC related attributes
// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-rpc.md
const (
	EventTypeMessage                 = "message"
	AttributeMessageType             = "message.type"
	MessageTypeReceived              = "RECEIVED"
	MessageTypeSent                  = "SENT"
	AttributeMessageID               = "message.id"
	AttributeMessageCompressedSize   = "message.compressed_size"
	AttributeMessageUncompressedSize = "message.uncompressed_size"
)
<commit_msg>update to latest semantic convention attribute names (#516)<commit_after>// Copyright 2019, OpenTelemetry Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package conventions

// OpenTelemetry Semantic Convention values for Resource attribute names.
// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-resource-semantic-conventions.md
const (
	AttributeServiceName      = "service.name"
	AttributeServiceNamespace = "service.namespace"
	AttributeServiceInstance  = "service.instance.id"
	AttributeServiceVersion   = "service.version"
	AttributeLibraryName      = "library.name"
	AttributeLibraryLanguage  = "library.language"
	AttributeLibraryVersion   = "library.version"
	AttributeContainerName    = "container.name"
	AttributeContainerImage   = "container.image.name"
	AttributeContainerTag     = "container.image.tag"
	AttributeK8sCluster       = "k8s.cluster.name"
	AttributeK8sNamespace     = "k8s.namespace.name"
	AttributeK8sPod           = "k8s.pod.name"
	AttributeK8sDeployment    = "k8s.deployment.name"
	AttributeHostHostname     = "host.hostname"
	AttributeHostID           = "host.id"
	AttributeHostName         = "host.name"
	AttributeHostType         = "host.type"
	AttributeHostImageName    = "host.image.name"
	AttributeHostImageID      = "host.image.id"
	AttributeHostImageVersion = "host.image.version"
	AttributeCloudProvider    = "cloud.provider"
	AttributeCloudAccount     = "cloud.account.id"
	AttributeCloudRegion      = "cloud.region"
	AttributeCloudZone        = "cloud.zone"
)

// OpenTelemetry Semantic Convention values for general Span attribute names.
// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-span-general.md
const (
	AttributeComponent    = "component"
	AttributeNetTransport = "net.transport"
	AttributeNetPeerIP    = "net.peer.ip"
	AttributeNetPeerPort  = "net.peer.port"
	AttributeNetPeerName  = "net.peer.name"
	AttributeNetHostIP    = "net.host.ip"
	AttributeNetHostPort  = "net.host.port"
	AttributeNetHostName  = "net.host.name"
	AttributeEnduserID    = "enduser.id"
	AttributeEnduserRole  = "enduser.role"
	AttributeEnduserScope = "enduser.scope"
)

// OpenTelemetry Semantic Convention values for component attribute values.
// Possibly being removed due to issue #336
const (
	ComponentTypeHTTP = "http"
	ComponentTypeGRPC = "grpc"
)

// OpenTelemetry Semantic Convention attribute names for HTTP related attributes
// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-http.md
const (
	AttributeHTTPMethod     = "http.method"
	AttributeHTTPURL        = "http.url"
	AttributeHTTPTarget     = "http.target"
	AttributeHTTPHost       = "http.host"
	AttributeHTTPScheme     = "http.scheme"
	AttributeHTTPStatusCode = "http.status_code"
	AttributeHTTPStatusText = "http.status_text"
	AttributeHTTPFlavor     = "http.flavor"
	AttributeHTTPServerName = "http.server_name"
	AttributeHTTPHostName   = "host.name"
	AttributeHTTPHostPort   = "host.port"
	AttributeHTTPRoute      = "http.route"
	AttributeHTTPClientIP   = "http.client_ip"
	AttributeHTTPUserAgent  = "http.user_agent"
)

// OpenTelemetry Semantic Convention attribute names for database related attributes
// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-database.md
const (
	AttributeDBType      = "db.type"
	AttributeDBInstance  = "db.instance"
	AttributeDBStatement = "db.statement"
	AttributeDBUser      = "db.user"
)

// OpenTelemetry Semantic Convention attribute names for gRPC related attributes
// See: https://github.com/open-telemetry/opentelemetry-specification/blob/master/specification/data-rpc.md
const (
	AttributeRPCService              = "rpc.service"
	EventTypeMessage                 = "message"
	AttributeMessageType             = "message.type"
	MessageTypeReceived              = "RECEIVED"
	MessageTypeSent                  = "SENT"
	AttributeMessageID               = "message.id"
	AttributeMessageCompressedSize   = "message.compressed_size"
	AttributeMessageUncompressedSize = "message.uncompressed_size"
)
<|endoftext|>