<commit_before>package client

type (
	// Guest is a guest virtual machine
	// +gen * slice:"Where,Each,SortBy" set
	Guest struct {
		Id       string            `json:"id"`
		Type     string            `json:"type,omitempty"`
		Image    string            `json:"image,omitempty"`
		Nics     []Nic             `json:"nics,omitempty"`
		Disks    []Disk            `json:"disks,omitempty"`
		State    string            `json:"state,omitempty"`  //current State
		Memory   uint              `json:"memory,omitempty"` // Memory in MB
		Cpu      uint              `json:"cpu,omitempty"`    // number of Virtual CPU's
		VNC      int               `json:"vnc,omitempty"`    // VNC port
		Metadata map[string]string `json:"metadata,omitempty"`
	}

	// Nic is a guest network interface controller
	Nic struct {
		Name    string `json:"name,omitempty"`
		Network string `json:"network"`
		Model   string `json:"model"`
		Mac     string `json:"mac,omitempty"`
		Address string `json:"address"`
		Netmask string `json:"netmask"`
		Gateway string `json:"gateway"`
		Device  string `json:"device,omitempty"`
	}

	// Disk is a guest storage disk
	Disk struct {
		Bus    string `json:"bus"`    // the type of disk device to emulate. "ide", "scsi", "sata", virtio"
		Device string `json:"device"` // target device inside the guest, ie "vda", "sda", "hda", etc
		Size   uint64 `json:"size"`   // size in MB.  On create, this is not used for image based disks.
		Volume string `json:"volume"` // zfs zvol
		Image  string `json:"image"`  // which image to clone.  If this is not set, then a blank zvol is created
		Source string `json:"source"` // the device name: /dev/zvol/...
	}

	// GuestDiskMetrics is a set of metrics on a guest's storage disk
	GuestDiskMetrics struct {
		Disk       string  `json:"disk"`
		ReadOps    int64   `json:"read_ops"`
		ReadBytes  int64   `json:"read_bytes"`
		ReadTime   float64 `json:"read_time"`
		WriteOps   int64   `json:"write_ops"`
		WriteBytes int64   `json:"write_bytes"`
		WriteTime  float64 `json:"write_time"`
		FlushOps   int64   `json:"flush_ops"`
		FlushTime  float64 `json:"flush_time"`
	}

	// GuestCpuMetrics is a set of metrics on a guest's cpu
	GuestCpuMetrics struct {
		CpuTime  float64 `json:"cpu_time"`
		VcpuTime float64 `json:"vcpu_time"`
	}

	// GuestNicMetrics is a set of metrics on a guests's nic
	GuestNicMetrics struct {
		Name      string `json:"name"`
		RxBytes   int64  `json:"rx_bytes"`
		RxPackets int64  `json:"rx_packets"`
		RxErrs    int64  `json:"rx_errors"`
		RxDrop    int64  `json:"rx_drops"`
		TxBytes   int64  `json:"tx_bytes"`
		TxPackets int64  `json:"tx_packets"`
		TxErrs    int64  `json:"tx_errors"`
		TxDrop    int64  `json:"tx_drops"`
	}
)
<commit_msg>Add VLANs array to guest<commit_after>package client

type (
	// Guest is a guest virtual machine
	// +gen * slice:"Where,Each,SortBy" set
	Guest struct {
		Id       string            `json:"id"`
		Type     string            `json:"type,omitempty"`
		Image    string            `json:"image,omitempty"`
		Nics     []Nic             `json:"nics,omitempty"`
		Disks    []Disk            `json:"disks,omitempty"`
		State    string            `json:"state,omitempty"`  //current State
		Memory   uint              `json:"memory,omitempty"` // Memory in MB
		Cpu      uint              `json:"cpu,omitempty"`    // number of Virtual CPU's
		VNC      int               `json:"vnc,omitempty"`    // VNC port
		Metadata map[string]string `json:"metadata,omitempty"`
	}

	// Nic is a guest network interface controller
	Nic struct {
		Name    string `json:"name,omitempty"`
		Network string `json:"network"`
		Model   string `json:"model"`
		Mac     string `json:"mac,omitempty"`
		Address string `json:"address"`
		Netmask string `json:"netmask"`
		Gateway string `json:"gateway"`
		Device  string `json:"device,omitempty"`
		VLANs   []int  `json:"vlans"`
	}

	// Disk is a guest storage disk
	Disk struct {
		Bus    string `json:"bus"`    // the type of disk device to emulate. "ide", "scsi", "sata", virtio"
		Device string `json:"device"` // target device inside the guest, ie "vda", "sda", "hda", etc
		Size   uint64 `json:"size"`   // size in MB.  On create, this is not used for image based disks.
		Volume string `json:"volume"` // zfs zvol
		Image  string `json:"image"`  // which image to clone.  If this is not set, then a blank zvol is created
		Source string `json:"source"` // the device name: /dev/zvol/...
	}

	// GuestDiskMetrics is a set of metrics on a guest's storage disk
	GuestDiskMetrics struct {
		Disk       string  `json:"disk"`
		ReadOps    int64   `json:"read_ops"`
		ReadBytes  int64   `json:"read_bytes"`
		ReadTime   float64 `json:"read_time"`
		WriteOps   int64   `json:"write_ops"`
		WriteBytes int64   `json:"write_bytes"`
		WriteTime  float64 `json:"write_time"`
		FlushOps   int64   `json:"flush_ops"`
		FlushTime  float64 `json:"flush_time"`
	}

	// GuestCpuMetrics is a set of metrics on a guest's cpu
	GuestCpuMetrics struct {
		CpuTime  float64 `json:"cpu_time"`
		VcpuTime float64 `json:"vcpu_time"`
	}

	// GuestNicMetrics is a set of metrics on a guests's nic
	GuestNicMetrics struct {
		Name      string `json:"name"`
		RxBytes   int64  `json:"rx_bytes"`
		RxPackets int64  `json:"rx_packets"`
		RxErrs    int64  `json:"rx_errors"`
		RxDrop    int64  `json:"rx_drops"`
		TxBytes   int64  `json:"tx_bytes"`
		TxPackets int64  `json:"tx_packets"`
		TxErrs    int64  `json:"tx_errors"`
		TxDrop    int64  `json:"tx_drops"`
	}
)
<|endoftext|>


<commit_before>// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package ssa

import (
	"fmt"
	"sort"
)

// cse does common-subexpression elimination on the Function.
// Values are just relinked, nothing is deleted. A subsequent deadcode
// pass is required to actually remove duplicate expressions.
func cse(f *Func) {
	// Two values are equivalent if they satisfy the following definition:
	// equivalent(v, w):
	//   v.op == w.op
	//   v.type == w.type
	//   v.aux == w.aux
	//   v.auxint == w.auxint
	//   len(v.args) == len(w.args)
	//   v.block == w.block if v.op == OpPhi
	//   equivalent(v.args[i], w.args[i]) for i in 0..len(v.args)-1

	// The algorithm searches for a partition of f's values into
	// equivalence classes using the above definition.
	// It starts with a coarse partition and iteratively refines it
	// until it reaches a fixed point.

	// Make initial coarse partitions by using a subset of the conditions above.
	a := make([]*Value, 0, f.NumValues())
	auxIDs := auxmap{}
	for _, b := range f.Blocks {
		for _, v := range b.Values {
			if auxIDs[v.Aux] == 0 {
				auxIDs[v.Aux] = int32(len(auxIDs)) + 1
			}
			if v.Type.IsMemory() {
				continue // memory values can never cse
			}
			if opcodeTable[v.Op].commutative && len(v.Args) == 2 && v.Args[1].ID < v.Args[0].ID {
				// Order the arguments of binary commutative operations.
				v.Args[0], v.Args[1] = v.Args[1], v.Args[0]
			}
			a = append(a, v)
		}
	}
	partition := partitionValues(a, auxIDs)

	// map from value id back to eqclass id
	valueEqClass := make([]ID, f.NumValues())
	for _, b := range f.Blocks {
		for _, v := range b.Values {
			// Use negative equivalence class #s for unique values.
			valueEqClass[v.ID] = -v.ID
		}
	}
	var pNum ID = 1
	for _, e := range partition {
		if f.pass.debug > 1 && len(e) > 500 {
			fmt.Printf("CSE.large partition (%d): ", len(e))
			for j := 0; j < 3; j++ {
				fmt.Printf("%s ", e[j].LongString())
			}
			fmt.Println()
		}

		for _, v := range e {
			valueEqClass[v.ID] = pNum
		}
		if f.pass.debug > 2 && len(e) > 1 {
			fmt.Printf("CSE.partition #%d:", pNum)
			for _, v := range e {
				fmt.Printf(" %s", v.String())
			}
			fmt.Printf("\n")
		}
		pNum++
	}

	// Split equivalence classes at points where they have
	// non-equivalent arguments.  Repeat until we can't find any
	// more splits.
	var splitPoints []int
	for {
		changed := false

		// partition can grow in the loop. By not using a range loop here,
		// we process new additions as they arrive, avoiding O(n^2) behavior.
		for i := 0; i < len(partition); i++ {
			e := partition[i]

			// Sort by eq class of arguments.
			sort.Sort(partitionByArgClass{e, valueEqClass})

			// Find split points.
			splitPoints = append(splitPoints[:0], 0)
			for j := 1; j < len(e); j++ {
				v, w := e[j-1], e[j]
				eqArgs := true
				for k, a := range v.Args {
					b := w.Args[k]
					if valueEqClass[a.ID] != valueEqClass[b.ID] {
						eqArgs = false
						break
					}
				}
				if !eqArgs {
					splitPoints = append(splitPoints, j)
				}
			}
			if len(splitPoints) == 1 {
				continue // no splits, leave equivalence class alone.
			}

			// Move another equivalence class down in place of e.
			partition[i] = partition[len(partition)-1]
			partition = partition[:len(partition)-1]
			i--

			// Add new equivalence classes for the parts of e we found.
			splitPoints = append(splitPoints, len(e))
			for j := 0; j < len(splitPoints)-1; j++ {
				f := e[splitPoints[j]:splitPoints[j+1]]
				if len(f) == 1 {
					// Don't add singletons.
					valueEqClass[f[0].ID] = -f[0].ID
					continue
				}
				for _, v := range f {
					valueEqClass[v.ID] = pNum
				}
				pNum++
				partition = append(partition, f)
			}
			changed = true
		}

		if !changed {
			break
		}
	}

	sdom := f.sdom()

	// Compute substitutions we would like to do. We substitute v for w
	// if v and w are in the same equivalence class and v dominates w.
	rewrite := make([]*Value, f.NumValues())
	for _, e := range partition {
		sort.Sort(partitionByDom{e, sdom})
		for i := 0; i < len(e)-1; i++ {
			// e is sorted by domorder, so a maximal dominant element is first in the slice
			v := e[i]
			if v == nil {
				continue
			}

			e[i] = nil
			// Replace all elements of e which v dominates
			for j := i + 1; j < len(e); j++ {
				w := e[j]
				if w == nil {
					continue
				}
				if sdom.isAncestorEq(v.Block, w.Block) {
					rewrite[w.ID] = v
					e[j] = nil
				} else {
					// e is sorted by domorder, so v.Block doesn't dominate any subsequent blocks in e
					break
				}
			}
		}
	}

	// if we rewrite a tuple generator to a new one in a different block,
	// copy its selectors to the new generator's block, so tuple generator
	// and selectors stay together.
	// be careful not to copy same selectors more than once (issue 16741).
	copiedSelects := make(map[ID][]*Value)
	for _, b := range f.Blocks {
	out:
		for _, v := range b.Values {
			if rewrite[v.ID] != nil {
				continue
			}
			if v.Op != OpSelect0 && v.Op != OpSelect1 {
				continue
			}
			if !v.Args[0].Type.IsTuple() {
				f.Fatalf("arg of tuple selector %s is not a tuple: %s", v.String(), v.Args[0].LongString())
			}
			t := rewrite[v.Args[0].ID]
			if t != nil && t.Block != b {
				// v.Args[0] is tuple generator, CSE'd into a different block as t, v is left behind
				for _, c := range copiedSelects[t.ID] {
					if v.Op == c.Op {
						// an equivalent selector is already copied
						rewrite[v.ID] = c
						continue out
					}
				}
				c := v.copyInto(t.Block)
				rewrite[v.ID] = c
				copiedSelects[t.ID] = append(copiedSelects[t.ID], c)
			}
		}
	}

	rewrites := int64(0)

	// Apply substitutions
	for _, b := range f.Blocks {
		for _, v := range b.Values {
			for i, w := range v.Args {
				if x := rewrite[w.ID]; x != nil {
					v.SetArg(i, x)
					rewrites++
				}
			}
		}
		if v := b.Control; v != nil {
			if x := rewrite[v.ID]; x != nil {
				if v.Op == OpNilCheck {
					// nilcheck pass will remove the nil checks and log
					// them appropriately, so don't mess with them here.
					continue
				}
				b.SetControl(x)
			}
		}
	}
	if f.pass.stats > 0 {
		f.LogStat("CSE REWRITES", rewrites)
	}
}

// An eqclass approximates an equivalence class. During the
// algorithm it may represent the union of several of the
// final equivalence classes.
type eqclass []*Value

// partitionValues partitions the values into equivalence classes
// based on having all the following features match:
//  - opcode
//  - type
//  - auxint
//  - aux
//  - nargs
//  - block # if a phi op
//  - first two arg's opcodes and auxint
//  - NOT first two arg's aux; that can break CSE.
// partitionValues returns a list of equivalence classes, each
// being a sorted by ID list of *Values. The eqclass slices are
// backed by the same storage as the input slice.
// Equivalence classes of size 1 are ignored.
func partitionValues(a []*Value, auxIDs auxmap) []eqclass {
	sort.Sort(sortvalues{a, auxIDs})

	var partition []eqclass
	for len(a) > 0 {
		v := a[0]
		j := 1
		for ; j < len(a); j++ {
			w := a[j]
			if cmpVal(v, w, auxIDs) != CMPeq {
				break
			}
		}
		if j > 1 {
			partition = append(partition, a[:j])
		}
		a = a[j:]
	}

	return partition
}
func lt2Cmp(isLt bool) Cmp {
	if isLt {
		return CMPlt
	}
	return CMPgt
}

type auxmap map[interface{}]int32

func cmpVal(v, w *Value, auxIDs auxmap) Cmp {
	// Try to order these comparison by cost (cheaper first)
	if v.Op != w.Op {
		return lt2Cmp(v.Op < w.Op)
	}
	if v.AuxInt != w.AuxInt {
		return lt2Cmp(v.AuxInt < w.AuxInt)
	}
	if len(v.Args) != len(w.Args) {
		return lt2Cmp(len(v.Args) < len(w.Args))
	}
	if v.Op == OpPhi && v.Block != w.Block {
		return lt2Cmp(v.Block.ID < w.Block.ID)
	}
	if v.Type.IsMemory() {
		// We will never be able to CSE two values
		// that generate memory.
		return lt2Cmp(v.ID < w.ID)
	}

	if tc := v.Type.Compare(w.Type); tc != CMPeq {
		return tc
	}

	if v.Aux != w.Aux {
		if v.Aux == nil {
			return CMPlt
		}
		if w.Aux == nil {
			return CMPgt
		}
		return lt2Cmp(auxIDs[v.Aux] < auxIDs[w.Aux])
	}

	return CMPeq
}

// Sort values to make the initial partition.
type sortvalues struct {
	a      []*Value // array of values
	auxIDs auxmap   // aux -> aux ID map
}

func (sv sortvalues) Len() int      { return len(sv.a) }
func (sv sortvalues) Swap(i, j int) { sv.a[i], sv.a[j] = sv.a[j], sv.a[i] }
func (sv sortvalues) Less(i, j int) bool {
	v := sv.a[i]
	w := sv.a[j]
	if cmp := cmpVal(v, w, sv.auxIDs); cmp != CMPeq {
		return cmp == CMPlt
	}

	// Sort by value ID last to keep the sort result deterministic.
	return v.ID < w.ID
}

type partitionByDom struct {
	a    []*Value // array of values
	sdom SparseTree
}

func (sv partitionByDom) Len() int      { return len(sv.a) }
func (sv partitionByDom) Swap(i, j int) { sv.a[i], sv.a[j] = sv.a[j], sv.a[i] }
func (sv partitionByDom) Less(i, j int) bool {
	v := sv.a[i]
	w := sv.a[j]
	return sv.sdom.domorder(v.Block) < sv.sdom.domorder(w.Block)
}

type partitionByArgClass struct {
	a       []*Value // array of values
	eqClass []ID     // equivalence class IDs of values
}

func (sv partitionByArgClass) Len() int      { return len(sv.a) }
func (sv partitionByArgClass) Swap(i, j int) { sv.a[i], sv.a[j] = sv.a[j], sv.a[i] }
func (sv partitionByArgClass) Less(i, j int) bool {
	v := sv.a[i]
	w := sv.a[j]
	for i, a := range v.Args {
		b := w.Args[i]
		if sv.eqClass[a.ID] < sv.eqClass[b.ID] {
			return true
		}
		if sv.eqClass[a.ID] > sv.eqClass[b.ID] {
			return false
		}
	}
	return false
}
<commit_msg>cmd/compile: reuse sort helpers<commit_after>// Copyright 2015 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package ssa

import (
	"fmt"
	"sort"
)

// cse does common-subexpression elimination on the Function.
// Values are just relinked, nothing is deleted. A subsequent deadcode
// pass is required to actually remove duplicate expressions.
func cse(f *Func) {
	// Two values are equivalent if they satisfy the following definition:
	// equivalent(v, w):
	//   v.op == w.op
	//   v.type == w.type
	//   v.aux == w.aux
	//   v.auxint == w.auxint
	//   len(v.args) == len(w.args)
	//   v.block == w.block if v.op == OpPhi
	//   equivalent(v.args[i], w.args[i]) for i in 0..len(v.args)-1

	// The algorithm searches for a partition of f's values into
	// equivalence classes using the above definition.
	// It starts with a coarse partition and iteratively refines it
	// until it reaches a fixed point.

	// Make initial coarse partitions by using a subset of the conditions above.
	a := make([]*Value, 0, f.NumValues())
	auxIDs := auxmap{}
	for _, b := range f.Blocks {
		for _, v := range b.Values {
			if auxIDs[v.Aux] == 0 {
				auxIDs[v.Aux] = int32(len(auxIDs)) + 1
			}
			if v.Type.IsMemory() {
				continue // memory values can never cse
			}
			if opcodeTable[v.Op].commutative && len(v.Args) == 2 && v.Args[1].ID < v.Args[0].ID {
				// Order the arguments of binary commutative operations.
				v.Args[0], v.Args[1] = v.Args[1], v.Args[0]
			}
			a = append(a, v)
		}
	}
	partition := partitionValues(a, auxIDs)

	// map from value id back to eqclass id
	valueEqClass := make([]ID, f.NumValues())
	for _, b := range f.Blocks {
		for _, v := range b.Values {
			// Use negative equivalence class #s for unique values.
			valueEqClass[v.ID] = -v.ID
		}
	}
	var pNum ID = 1
	for _, e := range partition {
		if f.pass.debug > 1 && len(e) > 500 {
			fmt.Printf("CSE.large partition (%d): ", len(e))
			for j := 0; j < 3; j++ {
				fmt.Printf("%s ", e[j].LongString())
			}
			fmt.Println()
		}

		for _, v := range e {
			valueEqClass[v.ID] = pNum
		}
		if f.pass.debug > 2 && len(e) > 1 {
			fmt.Printf("CSE.partition #%d:", pNum)
			for _, v := range e {
				fmt.Printf(" %s", v.String())
			}
			fmt.Printf("\n")
		}
		pNum++
	}

	// Split equivalence classes at points where they have
	// non-equivalent arguments.  Repeat until we can't find any
	// more splits.
	var splitPoints []int
	byArgClass := new(partitionByArgClass) // reuseable partitionByArgClass to reduce allocations
	for {
		changed := false

		// partition can grow in the loop. By not using a range loop here,
		// we process new additions as they arrive, avoiding O(n^2) behavior.
		for i := 0; i < len(partition); i++ {
			e := partition[i]

			// Sort by eq class of arguments.
			byArgClass.a = e
			byArgClass.eqClass = valueEqClass
			sort.Sort(byArgClass)

			// Find split points.
			splitPoints = append(splitPoints[:0], 0)
			for j := 1; j < len(e); j++ {
				v, w := e[j-1], e[j]
				eqArgs := true
				for k, a := range v.Args {
					b := w.Args[k]
					if valueEqClass[a.ID] != valueEqClass[b.ID] {
						eqArgs = false
						break
					}
				}
				if !eqArgs {
					splitPoints = append(splitPoints, j)
				}
			}
			if len(splitPoints) == 1 {
				continue // no splits, leave equivalence class alone.
			}

			// Move another equivalence class down in place of e.
			partition[i] = partition[len(partition)-1]
			partition = partition[:len(partition)-1]
			i--

			// Add new equivalence classes for the parts of e we found.
			splitPoints = append(splitPoints, len(e))
			for j := 0; j < len(splitPoints)-1; j++ {
				f := e[splitPoints[j]:splitPoints[j+1]]
				if len(f) == 1 {
					// Don't add singletons.
					valueEqClass[f[0].ID] = -f[0].ID
					continue
				}
				for _, v := range f {
					valueEqClass[v.ID] = pNum
				}
				pNum++
				partition = append(partition, f)
			}
			changed = true
		}

		if !changed {
			break
		}
	}

	sdom := f.sdom()

	// Compute substitutions we would like to do. We substitute v for w
	// if v and w are in the same equivalence class and v dominates w.
	rewrite := make([]*Value, f.NumValues())
	byDom := new(partitionByDom) // reusable partitionByDom to reduce allocs
	for _, e := range partition {
		byDom.a = e
		byDom.sdom = sdom
		sort.Sort(byDom)
		for i := 0; i < len(e)-1; i++ {
			// e is sorted by domorder, so a maximal dominant element is first in the slice
			v := e[i]
			if v == nil {
				continue
			}

			e[i] = nil
			// Replace all elements of e which v dominates
			for j := i + 1; j < len(e); j++ {
				w := e[j]
				if w == nil {
					continue
				}
				if sdom.isAncestorEq(v.Block, w.Block) {
					rewrite[w.ID] = v
					e[j] = nil
				} else {
					// e is sorted by domorder, so v.Block doesn't dominate any subsequent blocks in e
					break
				}
			}
		}
	}

	// if we rewrite a tuple generator to a new one in a different block,
	// copy its selectors to the new generator's block, so tuple generator
	// and selectors stay together.
	// be careful not to copy same selectors more than once (issue 16741).
	copiedSelects := make(map[ID][]*Value)
	for _, b := range f.Blocks {
	out:
		for _, v := range b.Values {
			if rewrite[v.ID] != nil {
				continue
			}
			if v.Op != OpSelect0 && v.Op != OpSelect1 {
				continue
			}
			if !v.Args[0].Type.IsTuple() {
				f.Fatalf("arg of tuple selector %s is not a tuple: %s", v.String(), v.Args[0].LongString())
			}
			t := rewrite[v.Args[0].ID]
			if t != nil && t.Block != b {
				// v.Args[0] is tuple generator, CSE'd into a different block as t, v is left behind
				for _, c := range copiedSelects[t.ID] {
					if v.Op == c.Op {
						// an equivalent selector is already copied
						rewrite[v.ID] = c
						continue out
					}
				}
				c := v.copyInto(t.Block)
				rewrite[v.ID] = c
				copiedSelects[t.ID] = append(copiedSelects[t.ID], c)
			}
		}
	}

	rewrites := int64(0)

	// Apply substitutions
	for _, b := range f.Blocks {
		for _, v := range b.Values {
			for i, w := range v.Args {
				if x := rewrite[w.ID]; x != nil {
					v.SetArg(i, x)
					rewrites++
				}
			}
		}
		if v := b.Control; v != nil {
			if x := rewrite[v.ID]; x != nil {
				if v.Op == OpNilCheck {
					// nilcheck pass will remove the nil checks and log
					// them appropriately, so don't mess with them here.
					continue
				}
				b.SetControl(x)
			}
		}
	}
	if f.pass.stats > 0 {
		f.LogStat("CSE REWRITES", rewrites)
	}
}

// An eqclass approximates an equivalence class. During the
// algorithm it may represent the union of several of the
// final equivalence classes.
type eqclass []*Value

// partitionValues partitions the values into equivalence classes
// based on having all the following features match:
//  - opcode
//  - type
//  - auxint
//  - aux
//  - nargs
//  - block # if a phi op
//  - first two arg's opcodes and auxint
//  - NOT first two arg's aux; that can break CSE.
// partitionValues returns a list of equivalence classes, each
// being a sorted by ID list of *Values. The eqclass slices are
// backed by the same storage as the input slice.
// Equivalence classes of size 1 are ignored.
func partitionValues(a []*Value, auxIDs auxmap) []eqclass {
	sort.Sort(sortvalues{a, auxIDs})

	var partition []eqclass
	for len(a) > 0 {
		v := a[0]
		j := 1
		for ; j < len(a); j++ {
			w := a[j]
			if cmpVal(v, w, auxIDs) != CMPeq {
				break
			}
		}
		if j > 1 {
			partition = append(partition, a[:j])
		}
		a = a[j:]
	}

	return partition
}
func lt2Cmp(isLt bool) Cmp {
	if isLt {
		return CMPlt
	}
	return CMPgt
}

type auxmap map[interface{}]int32

func cmpVal(v, w *Value, auxIDs auxmap) Cmp {
	// Try to order these comparison by cost (cheaper first)
	if v.Op != w.Op {
		return lt2Cmp(v.Op < w.Op)
	}
	if v.AuxInt != w.AuxInt {
		return lt2Cmp(v.AuxInt < w.AuxInt)
	}
	if len(v.Args) != len(w.Args) {
		return lt2Cmp(len(v.Args) < len(w.Args))
	}
	if v.Op == OpPhi && v.Block != w.Block {
		return lt2Cmp(v.Block.ID < w.Block.ID)
	}
	if v.Type.IsMemory() {
		// We will never be able to CSE two values
		// that generate memory.
		return lt2Cmp(v.ID < w.ID)
	}

	if tc := v.Type.Compare(w.Type); tc != CMPeq {
		return tc
	}

	if v.Aux != w.Aux {
		if v.Aux == nil {
			return CMPlt
		}
		if w.Aux == nil {
			return CMPgt
		}
		return lt2Cmp(auxIDs[v.Aux] < auxIDs[w.Aux])
	}

	return CMPeq
}

// Sort values to make the initial partition.
type sortvalues struct {
	a      []*Value // array of values
	auxIDs auxmap   // aux -> aux ID map
}

func (sv sortvalues) Len() int      { return len(sv.a) }
func (sv sortvalues) Swap(i, j int) { sv.a[i], sv.a[j] = sv.a[j], sv.a[i] }
func (sv sortvalues) Less(i, j int) bool {
	v := sv.a[i]
	w := sv.a[j]
	if cmp := cmpVal(v, w, sv.auxIDs); cmp != CMPeq {
		return cmp == CMPlt
	}

	// Sort by value ID last to keep the sort result deterministic.
	return v.ID < w.ID
}

type partitionByDom struct {
	a    []*Value // array of values
	sdom SparseTree
}

func (sv partitionByDom) Len() int      { return len(sv.a) }
func (sv partitionByDom) Swap(i, j int) { sv.a[i], sv.a[j] = sv.a[j], sv.a[i] }
func (sv partitionByDom) Less(i, j int) bool {
	v := sv.a[i]
	w := sv.a[j]
	return sv.sdom.domorder(v.Block) < sv.sdom.domorder(w.Block)
}

type partitionByArgClass struct {
	a       []*Value // array of values
	eqClass []ID     // equivalence class IDs of values
}

func (sv partitionByArgClass) Len() int      { return len(sv.a) }
func (sv partitionByArgClass) Swap(i, j int) { sv.a[i], sv.a[j] = sv.a[j], sv.a[i] }
func (sv partitionByArgClass) Less(i, j int) bool {
	v := sv.a[i]
	w := sv.a[j]
	for i, a := range v.Args {
		b := w.Args[i]
		if sv.eqClass[a.ID] < sv.eqClass[b.ID] {
			return true
		}
		if sv.eqClass[a.ID] > sv.eqClass[b.ID] {
			return false
		}
	}
	return false
}
<|endoftext|>


<commit_before>package backup

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path"
	"time"

	"golang.org/x/net/context"

	"github.com/Sirupsen/logrus"
	"github.com/coreos/etcd/clientv3"
	"github.com/coreos/kube-etcd-controller/pkg/util/etcdutil"
	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/client/unversioned"
	"k8s.io/kubernetes/pkg/labels"
)

type Backup struct {
	kclient *unversioned.Client

	clusterName string
	policy      Policy
	listenAddr  string
	backupDir   string

	backupNow chan chan error
}

func New(kclient *unversioned.Client, clusterName string, policy Policy, listenAddr string) *Backup {
	return &Backup{
		kclient:     kclient,
		clusterName: clusterName,
		policy:      policy,
		listenAddr:  listenAddr,
		backupDir:   "/home/backup/",

		backupNow: make(chan chan struct{}),
	}
}

func (b *Backup) Run() {
	// It will be no-op if backup dir existed.
	if err := os.MkdirAll(b.backupDir, 0700); err != nil {
		panic(err)
	}

	go b.startHTTP()

	lastSnapRev := int64(0)
	interval := defaultSnapshotInterval
	if b.policy.SnapshotIntervalInSecond != 0 {
		interval = time.Duration(b.policy.SnapshotIntervalInSecond) * time.Second
	}
	for {
		var ackchan chan struct{}
		select {
		case <-time.After(interval):
		case ackchan = <-b.backupNow:
			logrus.Info("received a backup request")
		}

		lastSnapRev, err = b.saveSnap(lastSnapRev)

		if ackchan != nil {
			ackchan <- err
		}
	}
}
func (b *Backup) saveSnap(lastSnapRev int64) (int64, error) {
	pods, err := b.kclient.Pods("default").List(api.ListOptions{
		LabelSelector: labels.SelectorFromSet(map[string]string{
			"app":          "etcd",
			"etcd_cluster": b.clusterName,
		}),
	})
	if err != nil {
		return lastSnapRev, err
	}
	if len(pods.Items) == 0 {
		logrus.Warning("no running pods found.")
		return lastSnapRev, nil
	}
	member, rev, err := getMemberWithMaxRev(pods)
	if err != nil {
		return lastSnapRev, err
	}
	if member == nil {
		logrus.Warning("no reachable member")
		return lastSnapRev, nil
	}
	if rev == lastSnapRev {
		logrus.Info("skipped creating new backup: no change since last time")
		return lastSnapRev, nil
	}

	log.Printf("saving backup for cluster (%s)", b.clusterName)
	if err := writeSnap(member, b.backupDir, rev); err != nil {
		err = fmt.Errorf("write snapshot failed: %v", err)
		logrus.Error(err)
		return lastSnapRev, err
	}
	return rev, nil
}

func writeSnap(m *etcdutil.Member, backupDir string, rev int64) error {
	cfg := clientv3.Config{
		Endpoints:   []string{m.ClientAddr()},
		DialTimeout: 5 * time.Second,
	}
	etcdcli, err := clientv3.New(cfg)
	if err != nil {
		return fmt.Errorf("failed to create etcd client (%v)", err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)

	rc, err := etcdcli.Maintenance.Snapshot(ctx)
	if err != nil {
		return fmt.Errorf("failed to receive snapshot (%v)", err)
	}
	defer rc.Close()

	// TODO: custom backup dir
	tmpfile, err := ioutil.TempFile(backupDir, "snapshot")
	n, err := io.Copy(tmpfile, rc)
	if err != nil {
		tmpfile.Close()
		os.Remove(tmpfile.Name())
		return fmt.Errorf("failed to save snapshot: %v", err)
	}
	cancel()
	tmpfile.Close()
	nextSnapshotName := path.Join(backupDir, makeFilename(rev))
	err = os.Rename(tmpfile.Name(), nextSnapshotName)
	if err != nil {
		os.Remove(tmpfile.Name())
		return fmt.Errorf("rename snapshot from %s to %s failed: %v", tmpfile.Name(), nextSnapshotName, err)
	}
	log.Printf("saved snapshot %s (size: %d) successfully", nextSnapshotName, n)
	return nil
}

func makeFilename(rev int64) string {
	return fmt.Sprintf("%016x.backup", rev)
}

func getMemberWithMaxRev(pods *api.PodList) (*etcdutil.Member, int64, error) {
	var member *etcdutil.Member
	maxRev := int64(0)
	for i := range pods.Items {
		m := &etcdutil.Member{Name: pods.Items[i].Name}
		cfg := clientv3.Config{
			Endpoints:   []string{m.ClientAddr()},
			DialTimeout: 5 * time.Second,
		}
		etcdcli, err := clientv3.New(cfg)
		if err != nil {
			return nil, 0, fmt.Errorf("failed to create etcd client (%v)", err)
		}
		resp, err := etcdcli.Get(context.TODO(), "/", clientv3.WithSerializable())
		if err != nil {
			return nil, 0, fmt.Errorf("etcdcli.Get failed: %v", err)
		}
		logrus.Infof("member: %s, revision: %d", m.Name, resp.Header.Revision)
		if resp.Header.Revision > maxRev {
			maxRev = resp.Header.Revision
			member = m
		}
	}
	return member, maxRev, nil
}
<commit_msg>fix build and logging<commit_after>package backup

import (
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path"
	"time"

	"golang.org/x/net/context"

	"github.com/Sirupsen/logrus"
	"github.com/coreos/etcd/clientv3"
	"github.com/coreos/kube-etcd-controller/pkg/util/etcdutil"
	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/client/unversioned"
	"k8s.io/kubernetes/pkg/labels"
)

type Backup struct {
	kclient *unversioned.Client

	clusterName string
	policy      Policy
	listenAddr  string
	backupDir   string

	backupNow chan chan error
}

func New(kclient *unversioned.Client, clusterName string, policy Policy, listenAddr string) *Backup {
	return &Backup{
		kclient:     kclient,
		clusterName: clusterName,
		policy:      policy,
		listenAddr:  listenAddr,
		backupDir:   "/home/backup/",

		backupNow: make(chan chan error),
	}
}

func (b *Backup) Run() {
	// It will be no-op if backup dir existed.
	if err := os.MkdirAll(b.backupDir, 0700); err != nil {
		panic(err)
	}

	go b.startHTTP()

	lastSnapRev := int64(0)
	interval := defaultSnapshotInterval
	if b.policy.SnapshotIntervalInSecond != 0 {
		interval = time.Duration(b.policy.SnapshotIntervalInSecond) * time.Second
	}
	for {
		var ackchan chan error
		select {
		case <-time.After(interval):
		case ackchan = <-b.backupNow:
			logrus.Info("received a backup request")
		}

		rev, err := b.saveSnap(lastSnapRev)
		if err != nil {
			logrus.Errorf("failed to save snapshot: %v", err)
		}
		lastSnapRev = rev

		if ackchan != nil {
			ackchan <- err
		}
	}
}

func (b *Backup) saveSnap(lastSnapRev int64) (int64, error) {
	pods, err := b.kclient.Pods("default").List(api.ListOptions{
		LabelSelector: labels.SelectorFromSet(map[string]string{
			"app":          "etcd",
			"etcd_cluster": b.clusterName,
		}),
	})
	if err != nil {
		return lastSnapRev, err
	}
	if len(pods.Items) == 0 {
		logrus.Warning("no running pods found.")
		return lastSnapRev, nil
	}
	member, rev, err := getMemberWithMaxRev(pods)
	if err != nil {
		return lastSnapRev, err
	}
	if member == nil {
		logrus.Warning("no reachable member")
		return lastSnapRev, nil
	}
	if rev == lastSnapRev {
		logrus.Info("skipped creating new backup: no change since last time")
		return lastSnapRev, nil
	}

	log.Printf("saving backup for cluster (%s)", b.clusterName)
	if err := writeSnap(member, b.backupDir, rev); err != nil {
		err = fmt.Errorf("write snapshot failed: %v", err)
		return lastSnapRev, err
	}
	return rev, nil
}

func writeSnap(m *etcdutil.Member, backupDir string, rev int64) error {
	cfg := clientv3.Config{
		Endpoints:   []string{m.ClientAddr()},
		DialTimeout: 5 * time.Second,
	}
	etcdcli, err := clientv3.New(cfg)
	if err != nil {
		return fmt.Errorf("failed to create etcd client (%v)", err)
	}

	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)

	rc, err := etcdcli.Maintenance.Snapshot(ctx)
	if err != nil {
		return fmt.Errorf("failed to receive snapshot (%v)", err)
	}
	defer rc.Close()

	// TODO: custom backup dir
	tmpfile, err := ioutil.TempFile(backupDir, "snapshot")
	n, err := io.Copy(tmpfile, rc)
	if err != nil {
		tmpfile.Close()
		os.Remove(tmpfile.Name())
		return fmt.Errorf("failed to save snapshot: %v", err)
	}
	cancel()
	tmpfile.Close()
	nextSnapshotName := path.Join(backupDir, makeFilename(rev))
	err = os.Rename(tmpfile.Name(), nextSnapshotName)
	if err != nil {
		os.Remove(tmpfile.Name())
		return fmt.Errorf("rename snapshot from %s to %s failed: %v", tmpfile.Name(), nextSnapshotName, err)
	}
	log.Printf("saved snapshot %s (size: %d) successfully", nextSnapshotName, n)
	return nil
}

func makeFilename(rev int64) string {
	return fmt.Sprintf("%016x.backup", rev)
}

func getMemberWithMaxRev(pods *api.PodList) (*etcdutil.Member, int64, error) {
	var member *etcdutil.Member
	maxRev := int64(0)
	for i := range pods.Items {
		m := &etcdutil.Member{Name: pods.Items[i].Name}
		cfg := clientv3.Config{
			Endpoints:   []string{m.ClientAddr()},
			DialTimeout: 5 * time.Second,
		}
		etcdcli, err := clientv3.New(cfg)
		if err != nil {
			return nil, 0, fmt.Errorf("failed to create etcd client (%v)", err)
		}
		resp, err := etcdcli.Get(context.TODO(), "/", clientv3.WithSerializable())
		if err != nil {
			return nil, 0, fmt.Errorf("etcdcli.Get failed: %v", err)
		}
		logrus.Infof("member: %s, revision: %d", m.Name, resp.Header.Revision)
		if resp.Header.Revision > maxRev {
			maxRev = resp.Header.Revision
			member = m
		}
	}
	return member, maxRev, nil
}
<|endoftext|>


<commit_before>package epub

import "time"

// LCP struct for the lcp license
type LCP struct {
	ID         string     `json:"id"`
	Issued     *time.Time `json:"issued"`
	Provider   string     `json:"provider"`
	Updated    *time.Time `json:"updated,omitempty"`
	Encryption struct {
		Profile    string `json:"profile"`
		ContentKey struct {
			EncryptedValue string `json:"encrypted_value"`
			Algorithm      string `json:"algorithm"`
		} `json:"content_key"`
		UserKey struct {
			TextHint  string `json:"text_hint"`
			Algorithm string `json:"algorithm"`
			KeyCheck  string `json:"key_check"`
		} `json:"user_key"`
	} `json:"encryption"`
	Links []struct {
		Rel       string `json:"rel"`
		Href      string `json:"href"`
		Title     string `json:"title"`
		Type      string `json:"type"`
		Templated string `json:"templated"`
		Profile   string `json:"profile"`
		Length    int    `json:"length"`
		Hash      string `json:"hash"`
	} `json:"links"`
	Rights struct {
		Print int        `json:"print"`
		Copy  int        `json:"copy"`
		Start *time.Time `json:"start"`
		End   *time.Time `json:"end"`
	}
	User struct {
		ID        string   `json:"id"`
		Email     string   `json:"email"`
		Name      string   `json:"name"`
		Encrypted []string `json:"encrypted"`
	}
	Signature struct {
		Algorithm   string `json:"algorithm"`
		Certificate string `json:"certificate"`
		Value       string `json:"value"`
	}
}
<commit_msg>close #32, fix missing json metadata<commit_after>package epub

import "time"

// LCP struct for the lcp license
type LCP struct {
	ID         string     `json:"id"`
	Issued     *time.Time `json:"issued"`
	Provider   string     `json:"provider"`
	Updated    *time.Time `json:"updated,omitempty"`
	Encryption struct {
		Profile    string `json:"profile"`
		ContentKey struct {
			EncryptedValue string `json:"encrypted_value"`
			Algorithm      string `json:"algorithm"`
		} `json:"content_key"`
		UserKey struct {
			TextHint  string `json:"text_hint"`
			Algorithm string `json:"algorithm"`
			KeyCheck  string `json:"key_check"`
		} `json:"user_key"`
	} `json:"encryption"`
	Links []struct {
		Rel       string `json:"rel"`
		Href      string `json:"href"`
		Title     string `json:"title"`
		Type      string `json:"type"`
		Templated string `json:"templated"`
		Profile   string `json:"profile"`
		Length    int    `json:"length"`
		Hash      string `json:"hash"`
	} `json:"links"`
	Rights struct {
		Print int        `json:"print"`
		Copy  int        `json:"copy"`
		Start *time.Time `json:"start"`
		End   *time.Time `json:"end"`
	} `json:"rights"`
	User struct {
		ID        string   `json:"id"`
		Email     string   `json:"email"`
		Name      string   `json:"name"`
		Encrypted []string `json:"encrypted"`
	} `json:"user"`
	Signature struct {
		Algorithm   string `json:"algorithm"`
		Certificate string `json:"certificate"`
		Value       string `json:"value"`
	} `json:"signature"`
}
<|endoftext|>


<commit_before>package parser

import (
	"andyk/docs/date"
	"andyk/docs/util"
	"strings"
	"time"
)

type MetaData struct {
	Language string
	Date     time.Time
	Tags     []string
	ItemType string
}

func (metaData MetaData) String() string {
	s := "Language: " + metaData.Language
	s += "\nDate: " + metaData.Date.String()
	s += "\nTags: " + strings.Join(metaData.Tags, ", ")
	s += "\nType: " + metaData.ItemType

	return s
}

func ParseMetaData(lines []string, itemTypeCallback func() string) (MetaData, Match, []string) {

	metaDataLocation, lines := locateMetaData(lines)
	if !metaDataLocation.Found {
		return MetaData{}, metaDataLocation, lines
	}

	// assemble meta data
	var metaData MetaData

	for _, line := range metaDataLocation.Matches {
		isKeyValuePair, matches := util.IsMatch(line, MetaDataPattern)

		// skip if line is not a key-value pair
		if !isKeyValuePair {
			continue
		}

		// prepare key and value
		key := strings.ToLower(strings.TrimSpace(matches[1]))
		value := strings.TrimSpace(matches[2])

		switch strings.ToLower(key) {

		case "language":
			{
				metaData.Language = value
				break
			}

		case "date":
			{
				date, err := date.ParseIso8601Date(value)
				if err == nil {
					metaData.Date = date
				}
				break
			}

		case "tags":
			{
				metaData.Tags = getTagsFromValue(value)
				break
			}

		case "type":
			{
				itemTypeString := strings.ToLower(value)
				if itemTypeString != "" {
					metaData.ItemType = itemTypeString
				}
				break
			}

		}
	}

	// make sure the item type is set
	if metaData.ItemType == "" {

		// use the provided item type callback
		metaData.ItemType = itemTypeCallback()
	}

	return metaData, metaDataLocation, lines
}

// locateMetaData checks if the current Document
// contains meta data.
func locateMetaData(lines []string) (Match, []string) {

	// Find the last horizontal rule in the document
	lastFoundHorizontalRulePosition := -1
	for lineNumber, line := range lines {

		lineMatchesHorizontalRulePattern := HorizontalRulePattern.MatchString(line)
		if lineMatchesHorizontalRulePattern {
			lastFoundHorizontalRulePosition = lineNumber
		}

	}

	// If there is no horizontal rule there is no meta data
	if lastFoundHorizontalRulePosition == -1 {
		return NotFound(), lines
	}

	// If the document has no more lines than
	// the last found horizontal rule there is no
	// room for meta data
	metaDataStartLine := lastFoundHorizontalRulePosition + 1
	if len(lines) <= metaDataStartLine {
		return NotFound(), lines
	}

	// Check if the last horizontal rule is followed
	// either by white space or be meta data
	for _, line := range lines[metaDataStartLine:] {

		lineMatchesMetaDataPattern := MetaDataPattern.MatchString(line)
		if lineMatchesMetaDataPattern {

			endLine := len(lines)
			return Found(lines[metaDataStartLine:endLine]), lines

		}

		lineIsEmpty := EmptyLinePattern.MatchString(line)
		if !lineIsEmpty {
			return NotFound(), lines
		}

	}

	return NotFound(), lines
}

func getTagsFromValue(value string) []string {
	rawTags := strings.Split(value, ",")
	tags := make([]string, 0, 1)

	for _, tag := range rawTags {
		trimmedTag := strings.TrimSpace(tag)
		if trimmedTag != "" {
			tags = append(tags, trimmedTag)
		}

	}

	return tags
}
<commit_msg>Meta data parsing bug fix: The parse meta data function did not cut of the meta data. This way the meta data got treated as content<commit_after>package parser

import (
	"andyk/docs/date"
	"andyk/docs/util"
	"strings"
	"time"
)

type MetaData struct {
	Language string
	Date     time.Time
	Tags     []string
	ItemType string
}

func (metaData MetaData) String() string {
	s := "Language: " + metaData.Language
	s += "\nDate: " + metaData.Date.String()
	s += "\nTags: " + strings.Join(metaData.Tags, ", ")
	s += "\nType: " + metaData.ItemType

	return s
}

func ParseMetaData(lines []string, itemTypeCallback func() string) (MetaData, Match, []string) {

	metaDataLocation, lines := locateMetaData(lines)
	if !metaDataLocation.Found {
		return MetaData{}, metaDataLocation, lines
	}

	// assemble meta data
	var metaData MetaData

	for _, line := range metaDataLocation.Matches {
		isKeyValuePair, matches := util.IsMatch(line, MetaDataPattern)

		// skip if line is not a key-value pair
		if !isKeyValuePair {
			continue
		}

		// prepare key and value
		key := strings.ToLower(strings.TrimSpace(matches[1]))
		value := strings.TrimSpace(matches[2])

		switch strings.ToLower(key) {

		case "language":
			{
				metaData.Language = value
				break
			}

		case "date":
			{
				date, err := date.ParseIso8601Date(value)
				if err == nil {
					metaData.Date = date
				}
				break
			}

		case "tags":
			{
				metaData.Tags = getTagsFromValue(value)
				break
			}

		case "type":
			{
				itemTypeString := strings.ToLower(value)
				if itemTypeString != "" {
					metaData.ItemType = itemTypeString
				}
				break
			}

		}
	}

	// make sure the item type is set
	if metaData.ItemType == "" {

		// use the provided item type callback
		metaData.ItemType = itemTypeCallback()
	}

	return metaData, metaDataLocation, lines
}

// locateMetaData checks if the current Document
// contains meta data.
func locateMetaData(lines []string) (Match, []string) {

	// Find the last horizontal rule in the document
	lastFoundHorizontalRulePosition := -1
	for lineNumber, line := range lines {

		lineMatchesHorizontalRulePattern := HorizontalRulePattern.MatchString(line)
		if lineMatchesHorizontalRulePattern {
			lastFoundHorizontalRulePosition = lineNumber
		}

	}

	// If there is no horizontal rule there is no meta data
	if lastFoundHorizontalRulePosition == -1 {
		return NotFound(), lines
	}

	// If the document has no more lines than
	// the last found horizontal rule there is no
	// room for meta data
	metaDataStartLine := lastFoundHorizontalRulePosition + 1
	if len(lines) <= metaDataStartLine {
		return NotFound(), lines
	}

	// the last line of content
	contentEndPosition := lastFoundHorizontalRulePosition - 1

	// Check if the last horizontal rule is followed
	// either by white space or be meta data
	for _, line := range lines[metaDataStartLine:] {

		lineMatchesMetaDataPattern := MetaDataPattern.MatchString(line)
		if lineMatchesMetaDataPattern {

			endLine := len(lines)
			return Found(lines[metaDataStartLine:endLine]), lines[0:contentEndPosition]

		}

		lineIsEmpty := EmptyLinePattern.MatchString(line)
		if !lineIsEmpty {
			return NotFound(), lines
		}

	}

	return NotFound(), lines
}

func getTagsFromValue(value string) []string {
	rawTags := strings.Split(value, ",")
	tags := make([]string, 0, 1)

	for _, tag := range rawTags {
		trimmedTag := strings.TrimSpace(tag)
		if trimmedTag != "" {
			tags = append(tags, trimmedTag)
		}

	}

	return tags
}
<|endoftext|>


<commit_before>package influxdb_output

import (
	"context"
	"crypto/tls"
	"encoding/json"
	"io/ioutil"
	"log"
	"time"

	influxdb2 "github.com/influxdata/influxdb-client-go/v2"
	"github.com/karimra/gnmic/formatters"
	"github.com/karimra/gnmic/outputs"
	"github.com/openconfig/gnmi/proto/gnmi"
	"github.com/prometheus/client_golang/prometheus"
	"google.golang.org/protobuf/proto"
)

const (
	defaultURL               = "http://localhost:8086"
	defaultBatchSize         = 1000
	defaultFlushTimer        = 10 * time.Second
	defaultHealthCheckPeriod = 30 * time.Second

	numWorkers    = 1
	loggingPrefix = "[influxdb_output] "
)

func init() {
	outputs.Register("influxdb", func() outputs.Output {
		return &InfluxDBOutput{
			Cfg:       &Config{},
			eventChan: make(chan *formatters.EventMsg),
			reset:     make(chan struct{}),
			startSig:  make(chan struct{}),
			logger:    log.New(ioutil.Discard, loggingPrefix, log.LstdFlags|log.Lmicroseconds),
		}
	})
}

type InfluxDBOutput struct {
	Cfg       *Config
	client    influxdb2.Client
	logger    *log.Logger
	cancelFn  context.CancelFunc
	eventChan chan *formatters.EventMsg
	reset     chan struct{}
	startSig  chan struct{}
	wasup     bool
	evps      []formatters.EventProcessor
}
type Config struct {
	URL               string        `mapstructure:"url,omitempty"`
	Org               string        `mapstructure:"org,omitempty"`
	Bucket            string        `mapstructure:"bucket,omitempty"`
	Token             string        `mapstructure:"token,omitempty"`
	BatchSize         uint          `mapstructure:"batch-size,omitempty"`
	FlushTimer        time.Duration `mapstructure:"flush-timer,omitempty"`
	UseGzip           bool          `mapstructure:"use-gzip,omitempty"`
	EnableTLS         bool          `mapstructure:"enable-tls,omitempty"`
	HealthCheckPeriod time.Duration `mapstructure:"health-check-period,omitempty"`
	Debug             bool          `mapstructure:"debug,omitempty"`
	EventProcessors   []string      `mapstructure:"event-processors,omitempty"`
	EnableMetrics     bool          `mapstructure:"enable-metrics,omitempty"`
}

func (k *InfluxDBOutput) String() string {
	b, err := json.Marshal(k)
	if err != nil {
		return ""
	}
	return string(b)
}

func (i *InfluxDBOutput) SetLogger(logger *log.Logger) {
	if logger != nil && i.logger != nil {
		i.logger.SetOutput(logger.Writer())
		i.logger.SetFlags(logger.Flags())
	}
}

func (i *InfluxDBOutput) SetEventProcessors(ps map[string]map[string]interface{}, logger *log.Logger, tcs map[string]interface{}) {
	for _, epName := range i.Cfg.EventProcessors {
		if epCfg, ok := ps[epName]; ok {
			epType := ""
			for k := range epCfg {
				epType = k
				break
			}
			if in, ok := formatters.EventProcessors[epType]; ok {
				ep := in()
				err := ep.Init(epCfg[epType], formatters.WithLogger(logger),formatters.WithTargets(tcs))
				if err != nil {
					i.logger.Printf("failed initializing event processor '%s' of type='%s': %v", epName, epType, err)
					continue
				}
				i.evps = append(i.evps, ep)
				i.logger.Printf("added event processor '%s' of type=%s to influxdb output", epName, epType)
			}
		}
	}
}

func (i *InfluxDBOutput) Init(ctx context.Context, name string, cfg map[string]interface{}, opts ...outputs.Option) error {
	err := outputs.DecodeConfig(cfg, i.Cfg)
	if err != nil {
		return err
	}
	for _, opt := range opts {
		opt(i)
	}
	if i.Cfg.URL == "" {
		i.Cfg.URL = defaultURL
	}
	if i.Cfg.BatchSize == 0 {
		i.Cfg.BatchSize = defaultBatchSize
	}
	if i.Cfg.FlushTimer == 0 {
		i.Cfg.FlushTimer = defaultFlushTimer
	}
	if i.Cfg.HealthCheckPeriod == 0 {
		i.Cfg.HealthCheckPeriod = defaultHealthCheckPeriod
	}

	iopts := influxdb2.DefaultOptions().
		SetUseGZip(i.Cfg.UseGzip).
		SetBatchSize(i.Cfg.BatchSize).
		SetFlushInterval(uint(i.Cfg.FlushTimer.Milliseconds()))
	if i.Cfg.EnableTLS {
		iopts.SetTLSConfig(&tls.Config{
			InsecureSkipVerify: true,
		})
	}
	if i.Cfg.Debug {
		iopts.SetLogLevel(3)
	}
	ctx, i.cancelFn = context.WithCancel(ctx)
CRCLIENT:
	i.client = influxdb2.NewClientWithOptions(i.Cfg.URL, i.Cfg.Token, iopts)
	// start influx health check
	err = i.health(ctx)
	if err != nil {
		i.logger.Printf("failed to check influxdb health: %v", err)
		time.Sleep(10 * time.Second)
		goto CRCLIENT
	}
	i.wasup = true
	go i.healthCheck(ctx)
	i.logger.Printf("initialized influxdb client: %s", i.String())

	for k := 0; k < numWorkers; k++ {
		go i.worker(ctx, k)
	}
	go func() {
		<-ctx.Done()
		i.Close()
	}()
	return nil
}

func (i *InfluxDBOutput) Write(ctx context.Context, rsp proto.Message, meta outputs.Meta) {
	if rsp == nil {
		return
	}
	switch rsp := rsp.(type) {
	case *gnmi.SubscribeResponse:
		measName := "default"
		if subName, ok := meta["subscription-name"]; ok {
			measName = subName
		}
		events, err := formatters.ResponseToEventMsgs(measName, rsp, meta, i.evps...)
		if err != nil {
			i.logger.Printf("failed to convert message to event: %v", err)
			return
		}
		for _, ev := range events {
			select {
			case <-ctx.Done():
				return
			case <-i.reset:
				return
			case i.eventChan <- ev:
			}
		}
	}
}

func (i *InfluxDBOutput) WriteEvent(ctx context.Context, ev *formatters.EventMsg) {}

func (i *InfluxDBOutput) Close() error {
	i.logger.Printf("closing client...")
	i.cancelFn()
	i.logger.Printf("closed.")
	return nil
}
func (i *InfluxDBOutput) RegisterMetrics(reg *prometheus.Registry) {}

func (i *InfluxDBOutput) healthCheck(ctx context.Context) {
	ticker := time.NewTicker(i.Cfg.HealthCheckPeriod)
	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			i.health(ctx)
		}
	}
}

func (i *InfluxDBOutput) health(ctx context.Context) error {
	res, err := i.client.Health(ctx)
	if err != nil {
		i.logger.Printf("failed health check: %v", err)
		if i.wasup {
			close(i.reset)
			i.reset = make(chan struct{})
		}
		return err
	}
	if res != nil {
		b, err := json.Marshal(res)
		if err != nil {
			i.logger.Printf("failed to marshal health check result: %v", err)
			i.logger.Printf("health check result: %+v", res)
			if i.wasup {
				close(i.reset)
				i.reset = make(chan struct{})
			}
			return err
		}
		i.wasup = true
		close(i.startSig)
		i.startSig = make(chan struct{})
		i.logger.Printf("health check result: %s", string(b))
		return nil
	}
	i.wasup = true
	close(i.startSig)
	i.startSig = make(chan struct{})
	i.logger.Print("health check result is nil")
	return nil
}

func (i *InfluxDBOutput) worker(ctx context.Context, idx int) {
	firstStart := true
START:
	if !firstStart {
		i.logger.Printf("worker-%d waiting for client recovery", idx)
		<-i.startSig
	}
	i.logger.Printf("starting worker-%d", idx)
	writer := i.client.WriteAPI(i.Cfg.Org, i.Cfg.Bucket)
	//defer writer.Flush()
	for {
		select {
		case <-ctx.Done():
			if ctx.Err() != nil {
				i.logger.Printf("worker-%d err=%v", idx, ctx.Err())
			}
			i.logger.Printf("worker-%d terminating...", idx)
			return
		case ev := <-i.eventChan:
			writer.WritePoint(influxdb2.NewPoint(ev.Name, ev.Tags, ev.Values, time.Unix(0, ev.Timestamp)))
		case <-i.reset:
			firstStart = false
			i.logger.Printf("resetting worker-%d...", idx)
			goto START
		case err := <-writer.Errors():
			i.logger.Printf("worker-%d write error: %v", idx, err)
		}
	}
}

func (i *InfluxDBOutput) SetName(name string)        {}
func (i *InfluxDBOutput) SetClusterName(name string) {}
<commit_msg>convert decimal64 to float64<commit_after>package influxdb_output

import (
	"context"
	"crypto/tls"
	"encoding/json"
	"io/ioutil"
	"log"
	"math"
	"time"

	influxdb2 "github.com/influxdata/influxdb-client-go/v2"
	"github.com/karimra/gnmic/formatters"
	"github.com/karimra/gnmic/outputs"
	"github.com/openconfig/gnmi/proto/gnmi"
	"github.com/prometheus/client_golang/prometheus"
	"google.golang.org/protobuf/proto"
)

const (
	defaultURL               = "http://localhost:8086"
	defaultBatchSize         = 1000
	defaultFlushTimer        = 10 * time.Second
	defaultHealthCheckPeriod = 30 * time.Second

	numWorkers    = 1
	loggingPrefix = "[influxdb_output] "
)

func init() {
	outputs.Register("influxdb", func() outputs.Output {
		return &InfluxDBOutput{
			Cfg:       &Config{},
			eventChan: make(chan *formatters.EventMsg),
			reset:     make(chan struct{}),
			startSig:  make(chan struct{}),
			logger:    log.New(ioutil.Discard, loggingPrefix, log.LstdFlags|log.Lmicroseconds),
		}
	})
}

type InfluxDBOutput struct {
	Cfg       *Config
	client    influxdb2.Client
	logger    *log.Logger
	cancelFn  context.CancelFunc
	eventChan chan *formatters.EventMsg
	reset     chan struct{}
	startSig  chan struct{}
	wasup     bool
	evps      []formatters.EventProcessor
}
type Config struct {
	URL               string        `mapstructure:"url,omitempty"`
	Org               string        `mapstructure:"org,omitempty"`
	Bucket            string        `mapstructure:"bucket,omitempty"`
	Token             string        `mapstructure:"token,omitempty"`
	BatchSize         uint          `mapstructure:"batch-size,omitempty"`
	FlushTimer        time.Duration `mapstructure:"flush-timer,omitempty"`
	UseGzip           bool          `mapstructure:"use-gzip,omitempty"`
	EnableTLS         bool          `mapstructure:"enable-tls,omitempty"`
	HealthCheckPeriod time.Duration `mapstructure:"health-check-period,omitempty"`
	Debug             bool          `mapstructure:"debug,omitempty"`
	EventProcessors   []string      `mapstructure:"event-processors,omitempty"`
	EnableMetrics     bool          `mapstructure:"enable-metrics,omitempty"`
}

func (k *InfluxDBOutput) String() string {
	b, err := json.Marshal(k)
	if err != nil {
		return ""
	}
	return string(b)
}

func (i *InfluxDBOutput) SetLogger(logger *log.Logger) {
	if logger != nil && i.logger != nil {
		i.logger.SetOutput(logger.Writer())
		i.logger.SetFlags(logger.Flags())
	}
}

func (i *InfluxDBOutput) SetEventProcessors(ps map[string]map[string]interface{}, logger *log.Logger, tcs map[string]interface{}) {
	for _, epName := range i.Cfg.EventProcessors {
		if epCfg, ok := ps[epName]; ok {
			epType := ""
			for k := range epCfg {
				epType = k
				break
			}
			if in, ok := formatters.EventProcessors[epType]; ok {
				ep := in()
				err := ep.Init(epCfg[epType], formatters.WithLogger(logger), formatters.WithTargets(tcs))
				if err != nil {
					i.logger.Printf("failed initializing event processor '%s' of type='%s': %v", epName, epType, err)
					continue
				}
				i.evps = append(i.evps, ep)
				i.logger.Printf("added event processor '%s' of type=%s to influxdb output", epName, epType)
			}
		}
	}
}

func (i *InfluxDBOutput) Init(ctx context.Context, name string, cfg map[string]interface{}, opts ...outputs.Option) error {
	err := outputs.DecodeConfig(cfg, i.Cfg)
	if err != nil {
		return err
	}
	for _, opt := range opts {
		opt(i)
	}
	if i.Cfg.URL == "" {
		i.Cfg.URL = defaultURL
	}
	if i.Cfg.BatchSize == 0 {
		i.Cfg.BatchSize = defaultBatchSize
	}
	if i.Cfg.FlushTimer == 0 {
		i.Cfg.FlushTimer = defaultFlushTimer
	}
	if i.Cfg.HealthCheckPeriod == 0 {
		i.Cfg.HealthCheckPeriod = defaultHealthCheckPeriod
	}

	iopts := influxdb2.DefaultOptions().
		SetUseGZip(i.Cfg.UseGzip).
		SetBatchSize(i.Cfg.BatchSize).
		SetFlushInterval(uint(i.Cfg.FlushTimer.Milliseconds()))
	if i.Cfg.EnableTLS {
		iopts.SetTLSConfig(&tls.Config{
			InsecureSkipVerify: true,
		})
	}
	if i.Cfg.Debug {
		iopts.SetLogLevel(3)
	}
	ctx, i.cancelFn = context.WithCancel(ctx)
CRCLIENT:
	i.client = influxdb2.NewClientWithOptions(i.Cfg.URL, i.Cfg.Token, iopts)
	// start influx health check
	err = i.health(ctx)
	if err != nil {
		i.logger.Printf("failed to check influxdb health: %v", err)
		time.Sleep(10 * time.Second)
		goto CRCLIENT
	}
	i.wasup = true
	go i.healthCheck(ctx)
	i.logger.Printf("initialized influxdb client: %s", i.String())

	for k := 0; k < numWorkers; k++ {
		go i.worker(ctx, k)
	}
	go func() {
		<-ctx.Done()
		i.Close()
	}()
	return nil
}

func (i *InfluxDBOutput) Write(ctx context.Context, rsp proto.Message, meta outputs.Meta) {
	if rsp == nil {
		return
	}
	switch rsp := rsp.(type) {
	case *gnmi.SubscribeResponse:
		measName := "default"
		if subName, ok := meta["subscription-name"]; ok {
			measName = subName
		}
		events, err := formatters.ResponseToEventMsgs(measName, rsp, meta, i.evps...)
		if err != nil {
			i.logger.Printf("failed to convert message to event: %v", err)
			return
		}
		for _, ev := range events {
			select {
			case <-ctx.Done():
				return
			case <-i.reset:
				return
			case i.eventChan <- ev:
			}
		}
	}
}

func (i *InfluxDBOutput) WriteEvent(ctx context.Context, ev *formatters.EventMsg) {}

func (i *InfluxDBOutput) Close() error {
	i.logger.Printf("closing client...")
	i.cancelFn()
	i.logger.Printf("closed.")
	return nil
}
func (i *InfluxDBOutput) RegisterMetrics(reg *prometheus.Registry) {}

func (i *InfluxDBOutput) healthCheck(ctx context.Context) {
	ticker := time.NewTicker(i.Cfg.HealthCheckPeriod)
	for {
		select {
		case <-ctx.Done():
			return
		case <-ticker.C:
			i.health(ctx)
		}
	}
}

func (i *InfluxDBOutput) health(ctx context.Context) error {
	res, err := i.client.Health(ctx)
	if err != nil {
		i.logger.Printf("failed health check: %v", err)
		if i.wasup {
			close(i.reset)
			i.reset = make(chan struct{})
		}
		return err
	}
	if res != nil {
		b, err := json.Marshal(res)
		if err != nil {
			i.logger.Printf("failed to marshal health check result: %v", err)
			i.logger.Printf("health check result: %+v", res)
			if i.wasup {
				close(i.reset)
				i.reset = make(chan struct{})
			}
			return err
		}
		i.wasup = true
		close(i.startSig)
		i.startSig = make(chan struct{})
		i.logger.Printf("health check result: %s", string(b))
		return nil
	}
	i.wasup = true
	close(i.startSig)
	i.startSig = make(chan struct{})
	i.logger.Print("health check result is nil")
	return nil
}

func (i *InfluxDBOutput) worker(ctx context.Context, idx int) {
	firstStart := true
START:
	if !firstStart {
		i.logger.Printf("worker-%d waiting for client recovery", idx)
		<-i.startSig
	}
	i.logger.Printf("starting worker-%d", idx)
	writer := i.client.WriteAPI(i.Cfg.Org, i.Cfg.Bucket)
	//defer writer.Flush()
	for {
		select {
		case <-ctx.Done():
			if ctx.Err() != nil {
				i.logger.Printf("worker-%d err=%v", idx, ctx.Err())
			}
			i.logger.Printf("worker-%d terminating...", idx)
			return
		case ev := <-i.eventChan:
			for n, v := range ev.Values {
				switch v := v.(type) {
				case *gnmi.Decimal64:
					ev.Values[n] = float64(v.Digits) / math.Pow10(int(v.Precision))
				}
			}
			writer.WritePoint(influxdb2.NewPoint(ev.Name, ev.Tags, ev.Values, time.Unix(0, ev.Timestamp)))
		case <-i.reset:
			firstStart = false
			i.logger.Printf("resetting worker-%d...", idx)
			goto START
		case err := <-writer.Errors():
			i.logger.Printf("worker-%d write error: %v", idx, err)
		}
	}
}

func (i *InfluxDBOutput) SetName(name string)        {}
func (i *InfluxDBOutput) SetClusterName(name string) {}
<|endoftext|>


<commit_before>package main

import (
	"github.com/bhcleek/packer-provisioner-ansible/provisioner/ansible"
	"github.com/mitchellh/packer/packer/plugin"
)

func main() {
	server, err := plugin.Server()
	if err != nil {
		panic(err)
	}
	server.RegisterProvisioner(new(ansible.Provisioner))
	server.Serve()
}
<commit_msg>correct plugin imports<commit_after>package main

import (
	"github.com/mitchellh/packer/packer/plugin"
	"github.com/mitchellh/packer/provisioner/ansible"
)

func main() {
	server, err := plugin.Server()
	if err != nil {
		panic(err)
	}
	server.RegisterProvisioner(new(ansible.Provisioner))
	server.Serve()
}
<|endoftext|>


<commit_before>package repository

import (
	"bytes"
	"encoding/hex"
	"errors"
	"io"
	"io/ioutil"

	"github.com/hoffie/larasync/helpers/crypto"
)

var (
	// ErrInvalidPublicKeySize will get thrown if a string is passed
	// which couldn't be encoded to the correct size to pass it as a
	// Public Key signature.
	ErrInvalidPublicKeySize = errors.New("Invalid public key size.")
)

// AuthorizationManager handles the Authorizations of a specific
//
type AuthorizationManager struct {
	storage ContentStorage
}

func newAuthorizationManager(storage ContentStorage) *AuthorizationManager {
	return &AuthorizationManager{
		storage: storage,
	}
}

// Set adds the authorization to the given backend and encrypts it
// first.
func (am *AuthorizationManager) Set(
	signaturePubKey [PublicKeySize]byte,
	encryptionKey [EncryptionKeySize]byte,
	authorization *Authorization,
) error {

	data := &bytes.Buffer{}
	_, err := authorization.WriteTo(data)
	if err != nil {
		return err
	}

	box := crypto.NewBox(encryptionKey)
	enc, err := box.EncryptWithRandomKey(data.Bytes())
	if err != nil {
		return err
	}

	return am.SetData(signaturePubKey, bytes.NewReader(enc))
}

// SetData adds for the already encrypted byte data and the given public key
// to the storage backend.
func (am *AuthorizationManager) SetData(
	pubKey [PublicKeySize]byte,
	reader io.Reader,
) error {
	pubKeyString := hex.EncodeToString(pubKey[:])
	return am.storage.Set(pubKeyString, reader)
}

// GetReaderString returns the reader for the given publicKey string representation.
func (am *AuthorizationManager) GetReaderString(key string) (io.ReadCloser, error) {
	byteKey, err := hex.DecodeString(key)
	if err != nil {
		return nil, err
	}

	if len(byteKey) != PublicKeySize {
		return nil, ErrInvalidPublicKeySize
	}
	inputKey := [PublicKeySize]byte{}
	copy(inputKey[:], byteKey)

	return am.GetReader(inputKey)
}

// GetReader returns a reader for the authorization stored with the passed PublicKey.
func (am *AuthorizationManager) GetReader(key [PublicKeySize]byte) (io.ReadCloser, error) {
	publicKeyString := hex.EncodeToString(key[:])
	return am.storage.Get(publicKeyString)
}

// Get returns the Authorization for the given public Signature Key and
// tries to decrypt it with the passed encryptionKey.
func (am *AuthorizationManager) Get(
	signaturePubKey [PublicKeySize]byte,
	encryptionKey [EncryptionKeySize]byte,
) (*Authorization, error) {
	reader, err := am.GetReader(signaturePubKey)
	if err != nil {
		return nil, err
	}

	enc, err := ioutil.ReadAll(reader)
	if err != nil {
		return nil, err
	}

	box := crypto.NewBox(encryptionKey)
	data, err := box.DecryptContent(enc)
	if err != nil {
		return nil, err
	}

	auth := &Authorization{}
	_, err = auth.ReadFrom(bytes.NewBuffer(data))
	if err != nil {
		return nil, err
	}

	_ = reader.Close()

	return auth, nil
}

// ExistsForString returns if there is a authorization stored
// for a given publicKey string representation.
func (am *AuthorizationManager) ExistsForString(publicKey string) bool {
	return am.storage.Exists(publicKey)
}

// Exists returns if there is a key existing for the given
// publicKey.
func (am *AuthorizationManager) Exists(key [PublicKeySize]byte) bool {
	keyString := hex.EncodeToString(key[:])
	return am.ExistsForString(keyString)
}

// DeleteForString deletes the authorization which is stored for the
// given publicKey string representation.
func (am *AuthorizationManager) DeleteForString(publicKey string) error {
	return am.storage.Delete(publicKey)
}

// Delete removes the authorization which is stored for the signature
// which has the given PublicKey.
func (am *AuthorizationManager) Delete(key [PublicKeySize]byte) error {
	keyString := hex.EncodeToString(key[:])
	return am.DeleteForString(keyString)
}
<commit_msg>repository: refactored authorization serialization to its own function.<commit_after>package repository

import (
	"bytes"
	"encoding/hex"
	"errors"
	"io"
	"io/ioutil"

	"github.com/hoffie/larasync/helpers/crypto"
)

var (
	// ErrInvalidPublicKeySize will get thrown if a string is passed
	// which couldn't be encoded to the correct size to pass it as a
	// Public Key signature.
	ErrInvalidPublicKeySize = errors.New("Invalid public key size.")
)

// AuthorizationManager handles the Authorizations of a specific
//
type AuthorizationManager struct {
	storage ContentStorage
}

func newAuthorizationManager(storage ContentStorage) *AuthorizationManager {
	return &AuthorizationManager{
		storage: storage,
	}
}

// Set adds the authorization to the given backend and encrypts it
// first.
func (am *AuthorizationManager) Set(
	signaturePubKey [PublicKeySize]byte,
	encryptionKey [EncryptionKeySize]byte,
	authorization *Authorization,
) error {
	enc, err := am.Serialize(encryptionKey, authorization)
	if err != nil {
		return err
	}

	return am.SetData(signaturePubKey, bytes.NewReader(enc))
}

// Serialize encrypts the Authorization with the given secret
// and returns the encrypted result.
func (am *AuthorizationManager) Serialize(
	encryptionKey [EncryptionKeySize]byte,
	authorization *Authorization,
) ([]byte, error) {
	data := &bytes.Buffer{}
	_, err := authorization.WriteTo(data)
	if err != nil {
		return nil, err
	}

	box := crypto.NewBox(encryptionKey)
	enc, err := box.EncryptWithRandomKey(data.Bytes())
	if err != nil {
		return nil, err
	}
	return enc, nil
}

// SetData adds for the already encrypted byte data and the given public key
// to the storage backend.
func (am *AuthorizationManager) SetData(
	pubKey [PublicKeySize]byte,
	reader io.Reader,
) error {
	pubKeyString := hex.EncodeToString(pubKey[:])
	return am.storage.Set(pubKeyString, reader)
}

// GetReaderString returns the reader for the given publicKey string representation.
func (am *AuthorizationManager) GetReaderString(key string) (io.ReadCloser, error) {
	byteKey, err := hex.DecodeString(key)
	if err != nil {
		return nil, err
	}

	if len(byteKey) != PublicKeySize {
		return nil, ErrInvalidPublicKeySize
	}
	inputKey := [PublicKeySize]byte{}
	copy(inputKey[:], byteKey)

	return am.GetReader(inputKey)
}

// GetReader returns a reader for the authorization stored with the passed PublicKey.
func (am *AuthorizationManager) GetReader(key [PublicKeySize]byte) (io.ReadCloser, error) {
	publicKeyString := hex.EncodeToString(key[:])
	return am.storage.Get(publicKeyString)
}

// Get returns the Authorization for the given public Signature Key and
// tries to decrypt it with the passed encryptionKey.
func (am *AuthorizationManager) Get(
	signaturePubKey [PublicKeySize]byte,
	encryptionKey [EncryptionKeySize]byte,
) (*Authorization, error) {
	reader, err := am.GetReader(signaturePubKey)
	if err != nil {
		return nil, err
	}

	enc, err := ioutil.ReadAll(reader)
	if err != nil {
		return nil, err
	}

	box := crypto.NewBox(encryptionKey)
	data, err := box.DecryptContent(enc)
	if err != nil {
		return nil, err
	}

	auth := &Authorization{}
	_, err = auth.ReadFrom(bytes.NewBuffer(data))
	if err != nil {
		return nil, err
	}

	_ = reader.Close()

	return auth, nil
}

// ExistsForString returns if there is a authorization stored
// for a given publicKey string representation.
func (am *AuthorizationManager) ExistsForString(publicKey string) bool {
	return am.storage.Exists(publicKey)
}

// Exists returns if there is a key existing for the given
// publicKey.
func (am *AuthorizationManager) Exists(key [PublicKeySize]byte) bool {
	keyString := hex.EncodeToString(key[:])
	return am.ExistsForString(keyString)
}

// DeleteForString deletes the authorization which is stored for the
// given publicKey string representation.
func (am *AuthorizationManager) DeleteForString(publicKey string) error {
	return am.storage.Delete(publicKey)
}

// Delete removes the authorization which is stored for the signature
// which has the given PublicKey.
func (am *AuthorizationManager) Delete(key [PublicKeySize]byte) error {
	keyString := hex.EncodeToString(key[:])
	return am.DeleteForString(keyString)
}
<|endoftext|>


<commit_before>package filesystem

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/naoty/todo/todo"
)

// FileSystem represents a repository backed by file system.
type FileSystem struct {
	root        string
	archivedDir string
}

type index struct {
	Todos    map[string][]int `json:"todos"`
	Archived map[string][]int `json:"archived"`
	Metadata metadata         `json:"metadata"`
}

type metadata struct {
	LastID int `json:"lastId"`
}

// New returns a new FileSystem.
func New() (*FileSystem, error) {
	root := os.Getenv("TODOS_PATH")
	if root == "" {
		home, err := os.UserHomeDir()
		if err != nil {
			return nil, err
		}

		root = filepath.Join(home, ".todos")
	}

	archivedDir := filepath.Join(root, "archived")
	if _, err := os.Stat(root); os.IsNotExist(err) {
		err := os.Mkdir(root, 0755)
		if err != nil {
			return nil, err
		}

		err = os.Mkdir(archivedDir, 0755)
		if err != nil {
			return nil, err
		}
	}

	return &FileSystem{root: root, archivedDir: archivedDir}, nil
}

// Get implements Repository interface.
func (repo *FileSystem) Get(id int) (*todo.Todo, error) {
	filename := fmt.Sprintf("%d.md", id)
	path := filepath.Join(repo.root, filename)

	if _, err := os.Stat(path); os.IsNotExist(err) {
		return nil, fmt.Errorf("TODO not found: %d", id)
	}

	file, err := os.Open(path)
	if err != nil {
		return nil, fmt.Errorf("failed to get TODO: %w", err)
	}
	defer file.Close()

	content, err := ioutil.ReadAll(file)
	if err != nil {
		return nil, fmt.Errorf("failed to get TODO: %w", err)
	}

	td, err := Parse(string(content))
	if err != nil {
		return nil, fmt.Errorf("failed to get TODO: %w", err)
	}

	td.ID = id
	return td, nil
}

// List implements Repository interface.
func (repo *FileSystem) List() ([]*todo.Todo, error) {
	todos := map[int]*todo.Todo{}

	err := filepath.Walk(repo.root, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if !strings.HasSuffix(path, ".md") {
			return nil
		}

		id, err := parseID(path)
		if err != nil {
			return err
		}

		file, err := os.Open(path)
		if err != nil {
			return err
		}
		defer file.Close()

		content, err := ioutil.ReadAll(file)
		if err != nil {
			return err
		}

		td, err := Parse(string(content))
		if err != nil {
			return err
		}

		td.ID = id
		todos[id] = td

		return nil
	})

	if err != nil {
		return nil, fmt.Errorf("failed to get todos from %s: %w", repo.root, err)
	}

	i, err := repo.readIndex()
	if err != nil {
		return nil, fmt.Errorf("failed to get todos from %s: %w", repo.root, err)
	}

	ordered := orderTodos(todos, i, "")

	return ordered, nil
}

// Add implements Repository interface.
func (repo *FileSystem) Add(title string, parent *int) error {
	todos, err := repo.List()
	if err != nil {
		return fmt.Errorf("failed to get next id: %w", err)
	}

	if *parent > 0 && !isExists(todos, *parent) {
		return fmt.Errorf("parent not found: %d", *parent)
	}

	idx, err := repo.readIndex()
	if err != nil {
		return fmt.Errorf("failed to add TODO: %w", err)
	}

	nextID := idx.generateNextID()
	filename := fmt.Sprintf("%d.md", nextID)
	path := filepath.Join(repo.root, filename)

	if _, err := os.Stat(path); err == nil {
		return fmt.Errorf("already exist: %s", path)
	}

	file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
	if err != nil {
		return fmt.Errorf("failed to add TODO: %w", err)
	}
	defer file.Close()

	content := newContent(title)
	_, err = file.WriteString(content)
	if err != nil {
		return fmt.Errorf("failed to add TODO: %w", err)
	}

	parentID := ""
	if *parent > 0 {
		parentID = fmt.Sprintf("%d", *parent)
	}

	idx.Todos[parentID] = append(idx.Todos[parentID], nextID)
	err = repo.writeIndex(idx)
	if err != nil {
		return fmt.Errorf("failed to add TODO: %w", err)
	}

	return nil
}

// Update implements Repository interface.
func (repo *FileSystem) Update(td *todo.Todo) error {
	filename := fmt.Sprintf("%d.md", td.ID)
	path := filepath.Join(repo.root, filename)

	if _, err := os.Stat(path); os.IsNotExist(err) {
		return fmt.Errorf("file not found: %s", path)
	}

	file, err := os.OpenFile(path, os.O_RDWR|os.O_TRUNC, 0644)
	if err != nil {
		return fmt.Errorf("failed to update TODO: %w", err)
	}
	defer file.Close()

	data := Marshal(td)
	_, err = file.Write(data)
	if err != nil {
		return fmt.Errorf("failed to update TODO: %w", err)
	}

	return nil
}

// Delete implements Repository interface.
func (repo *FileSystem) Delete(id int) error {
	filename := fmt.Sprintf("%d.md", id)
	path := filepath.Join(repo.root, filename)

	if _, err := os.Stat(path); os.IsNotExist(err) {
		return fmt.Errorf("file not found: %s", path)
	}

	err := os.Remove(path)
	if err != nil {
		return fmt.Errorf("failed to delete TODO: %w", err)
	}

	idx, err := repo.readIndex()
	if err != nil {
		return fmt.Errorf("failed to delete TODO from index: %w", err)
	}

	key := fmt.Sprintf("%d", id)
	subIDs, ok := idx.Todos[key]

	if !ok {
		subIDs, ok = idx.Archived[key]
	}

	if ok {
		// Delete files of sub-TODOs of a deleted TODO
		for _, id := range subIDs {
			filename := fmt.Sprintf("%d.md", id)
			path := filepath.Join(repo.root, filename)

			if _, err := os.Stat(path); os.IsNotExist(err) {
				return fmt.Errorf("file not found: %s", path)
			}

			err := os.Remove(path)
			if err != nil {
				return fmt.Errorf("failed to delete TODO: %w", err)
			}
		}
	}

	// Delete the IDs of sub-TODOs of deleted TODO from index.json
	delete(idx.Todos, key)
	delete(idx.Archived, key)

	// Delete the ID of deleted TODO from index.json
	for k, ids := range idx.Todos {
		var _ids []int
		for _, _id := range ids {
			if _id == id {
				continue
			}
			_ids = append(_ids, _id)
		}

		if len(_ids) == 0 {
			delete(idx.Todos, k)
			continue
		}

		idx.Todos[k] = _ids
	}

	for k, ids := range idx.Archived {
		var _ids []int
		for _, _id := range ids {
			if _id == id {
				continue
			}
			_ids = append(_ids, _id)
		}

		if len(_ids) == 0 {
			delete(idx.Todos, k)
			continue
		}

		idx.Archived[k] = _ids
	}

	err = repo.writeIndex(idx)
	if err != nil {
		return fmt.Errorf("failed to delete TODO from index: %w", err)
	}

	return nil
}

// Open implements Repository interface.
func (repo *FileSystem) Open(id int) error {
	filename := fmt.Sprintf("%d.md", id)
	path := filepath.Join(repo.root, filename)

	cmd := exec.Command("open", path)
	err := cmd.Run()
	if err != nil {
		return fmt.Errorf("failed to open %s: %w", path, err)
	}

	return nil
}

// Move implements Repository interface.
func (repo *FileSystem) Move(id int, parent *int, position int) error {
	if position < 1 {
		return fmt.Errorf("position number must be larger than 0: %d", position)
	}

	if id == *parent {
		return fmt.Errorf("cannot move TODO under itself: %d", id)
	}

	idx, err := repo.readIndex()
	if err != nil {
		return fmt.Errorf("failed to move TODO: %w", err)
	}

	to := position - 1
	toKey := ""
	if *parent != 0 {
		toKey = fmt.Sprintf("%d", *parent)
	}

	if to > len(idx.Todos[toKey]) {
		return fmt.Errorf("position number is out of range: %d", position)
	}

	var from int
	var fromKey string
	for k, ids := range idx.Todos {
		for i, _id := range ids {
			if _id == id {
				from = i
				fromKey = k
				break
			}
		}
	}

	idx.Todos[fromKey] = append(idx.Todos[fromKey][:from], idx.Todos[fromKey][from+1:]...)
	idx.Todos[toKey] = append(idx.Todos[toKey][:to], append([]int{id}, idx.Todos[toKey][to:]...)...)

	if len(idx.Todos[fromKey]) == 0 {
		delete(idx.Todos, fromKey)
	}

	err = repo.writeIndex(idx)
	if err != nil {
		return fmt.Errorf("failed to move TODO: %w", err)
	}

	return nil
}

// Archive implements Repository interface.
func (repo *FileSystem) Archive(id int) error {
	idx, err := repo.readIndex()
	if err != nil {
		return fmt.Errorf("failed to move TODO: %w", err)
	}

	key := ""
	index := 0
	for k, ids := range idx.Todos {
		for i, _id := range ids {
			if _id == id {
				key = k
				index = i
				break
			}
		}
	}

	idx.Todos[key] = append(idx.Todos[key][:index], idx.Todos[key][index+1:]...)
	idx.Archived[key] = append(idx.Archived[key], id)

	if len(idx.Todos[key]) == 0 {
		delete(idx.Todos, key)
	}

	key = fmt.Sprintf("%d", id)
	if _, ok := idx.Todos[key]; ok {
		idx.Archived[key] = append(idx.Archived[key], idx.Todos[key]...)
		delete(idx.Todos, key)
	}

	err = repo.writeIndex(idx)
	if err != nil {
		return fmt.Errorf("failed to move TODO: %w", err)
	}

	return nil
}

func parseID(path string) (int, error) {
	text := strings.TrimRight(filepath.Base(path), filepath.Ext(path))
	return strconv.Atoi(text)
}

func newContent(title string) string {
	return strings.Trim(fmt.Sprintf(`
---
title: %s
state: undone
---
`, title), "\n")
}

func (repo *FileSystem) readIndex() (*index, error) {
	path := filepath.Join(repo.root, "index.json")
	if _, err := os.Stat(path); os.IsNotExist(err) {
		meta := metadata{LastID: 0}
		return &index{
			Metadata: meta,
			Todos: map[string][]int{
				"": {},
			},
			Archived: map[string][]int{},
		}, nil
	}

	file, err := os.Open(path)
	if err != nil {
		return nil, fmt.Errorf("failed to read index from %s: %w", path, err)
	}
	defer file.Close()

	content, err := ioutil.ReadAll(file)
	if err != nil {
		return nil, fmt.Errorf("failed to read index from %s: %w", path, err)
	}

	var i index
	err = json.Unmarshal(content, &i)
	if err != nil {
		return nil, fmt.Errorf("failed to read index from %s: %w", path, err)
	}

	return &i, nil
}

func (repo *FileSystem) writeIndex(i *index) error {
	path := filepath.Join(repo.root, "index.json")

	data, err := json.MarshalIndent(*i, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to write index to %s: %w", path, err)
	}

	err = ioutil.WriteFile(path, data, 0644)
	if err != nil {
		return fmt.Errorf("failed to write index to %s: %w", path, err)
	}

	return nil
}

func (idx *index) generateNextID() int {
	id := idx.Metadata.LastID + 1
	idx.Metadata.LastID = id
	return id
}

func maxID(todos []*todo.Todo) int {
	result := 0

	for _, td := range todos {
		if td.ID > result {
			result = td.ID
		}

		maxSubID := maxID(td.Todos)
		if maxSubID > result {
			result = maxSubID
		}
	}

	return result
}

func orderTodos(todos map[int]*todo.Todo, i *index, key string) []*todo.Todo {
	var result []*todo.Todo

	for _, id := range i.Todos[key] {
		td, ok := todos[id]
		if !ok {
			continue
		}

		newKey := fmt.Sprintf("%d", td.ID)
		td.Todos = orderTodos(todos, i, newKey)
		result = append(result, td)
	}

	return result
}

func isExists(todos []*todo.Todo, id int) bool {
	for _, td := range todos {
		if td.ID == id {
			return true
		}

		result := isExists(td.Todos, id)
		if result {
			return true
		}
	}

	return false
}
<commit_msg>Move archived TODO files into archived directory<commit_after>package filesystem

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"os"
	"os/exec"
	"path/filepath"
	"strconv"
	"strings"

	"github.com/naoty/todo/todo"
)

// FileSystem represents a repository backed by file system.
type FileSystem struct {
	root        string
	archivedDir string
}

type index struct {
	Todos    map[string][]int `json:"todos"`
	Archived map[string][]int `json:"archived"`
	Metadata metadata         `json:"metadata"`
}

type metadata struct {
	LastID int `json:"lastId"`
}

// New returns a new FileSystem.
func New() (*FileSystem, error) {
	root := os.Getenv("TODOS_PATH")
	if root == "" {
		home, err := os.UserHomeDir()
		if err != nil {
			return nil, err
		}

		root = filepath.Join(home, ".todos")
	}

	archivedDir := filepath.Join(root, "archived")
	if _, err := os.Stat(root); os.IsNotExist(err) {
		err := os.Mkdir(root, 0755)
		if err != nil {
			return nil, err
		}

		err = os.Mkdir(archivedDir, 0755)
		if err != nil {
			return nil, err
		}
	}

	return &FileSystem{root: root, archivedDir: archivedDir}, nil
}

// Get implements Repository interface.
func (repo *FileSystem) Get(id int) (*todo.Todo, error) {
	filename := fmt.Sprintf("%d.md", id)
	path := filepath.Join(repo.root, filename)

	if _, err := os.Stat(path); os.IsNotExist(err) {
		return nil, fmt.Errorf("TODO not found: %d", id)
	}

	file, err := os.Open(path)
	if err != nil {
		return nil, fmt.Errorf("failed to get TODO: %w", err)
	}
	defer file.Close()

	content, err := ioutil.ReadAll(file)
	if err != nil {
		return nil, fmt.Errorf("failed to get TODO: %w", err)
	}

	td, err := Parse(string(content))
	if err != nil {
		return nil, fmt.Errorf("failed to get TODO: %w", err)
	}

	td.ID = id
	return td, nil
}

// List implements Repository interface.
func (repo *FileSystem) List() ([]*todo.Todo, error) {
	todos := map[int]*todo.Todo{}

	err := filepath.Walk(repo.root, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}

		if info.IsDir() && strings.HasSuffix(path, "archived") {
			return filepath.SkipDir
		}

		if !strings.HasSuffix(path, ".md") {
			return nil
		}

		id, err := parseID(path)
		if err != nil {
			return err
		}

		file, err := os.Open(path)
		if err != nil {
			return err
		}
		defer file.Close()

		content, err := ioutil.ReadAll(file)
		if err != nil {
			return err
		}

		td, err := Parse(string(content))
		if err != nil {
			return err
		}

		td.ID = id
		todos[id] = td

		return nil
	})

	if err != nil {
		return nil, fmt.Errorf("failed to get todos from %s: %w", repo.root, err)
	}

	i, err := repo.readIndex()
	if err != nil {
		return nil, fmt.Errorf("failed to get todos from %s: %w", repo.root, err)
	}

	ordered := orderTodos(todos, i, "")

	return ordered, nil
}

// Add implements Repository interface.
func (repo *FileSystem) Add(title string, parent *int) error {
	todos, err := repo.List()
	if err != nil {
		return fmt.Errorf("failed to get next id: %w", err)
	}

	if *parent > 0 && !isExists(todos, *parent) {
		return fmt.Errorf("parent not found: %d", *parent)
	}

	idx, err := repo.readIndex()
	if err != nil {
		return fmt.Errorf("failed to add TODO: %w", err)
	}

	nextID := idx.generateNextID()
	filename := fmt.Sprintf("%d.md", nextID)
	path := filepath.Join(repo.root, filename)

	if _, err := os.Stat(path); err == nil {
		return fmt.Errorf("already exist: %s", path)
	}

	file, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0644)
	if err != nil {
		return fmt.Errorf("failed to add TODO: %w", err)
	}
	defer file.Close()

	content := newContent(title)
	_, err = file.WriteString(content)
	if err != nil {
		return fmt.Errorf("failed to add TODO: %w", err)
	}

	parentID := ""
	if *parent > 0 {
		parentID = fmt.Sprintf("%d", *parent)
	}

	idx.Todos[parentID] = append(idx.Todos[parentID], nextID)
	err = repo.writeIndex(idx)
	if err != nil {
		return fmt.Errorf("failed to add TODO: %w", err)
	}

	return nil
}

// Update implements Repository interface.
func (repo *FileSystem) Update(td *todo.Todo) error {
	filename := fmt.Sprintf("%d.md", td.ID)
	path := filepath.Join(repo.root, filename)

	if _, err := os.Stat(path); os.IsNotExist(err) {
		return fmt.Errorf("file not found: %s", path)
	}

	file, err := os.OpenFile(path, os.O_RDWR|os.O_TRUNC, 0644)
	if err != nil {
		return fmt.Errorf("failed to update TODO: %w", err)
	}
	defer file.Close()

	data := Marshal(td)
	_, err = file.Write(data)
	if err != nil {
		return fmt.Errorf("failed to update TODO: %w", err)
	}

	return nil
}

// Delete implements Repository interface.
func (repo *FileSystem) Delete(id int) error {
	filename := fmt.Sprintf("%d.md", id)
	path := filepath.Join(repo.root, filename)

	if _, err := os.Stat(path); os.IsNotExist(err) {
		return fmt.Errorf("file not found: %s", path)
	}

	err := os.Remove(path)
	if err != nil {
		return fmt.Errorf("failed to delete TODO: %w", err)
	}

	idx, err := repo.readIndex()
	if err != nil {
		return fmt.Errorf("failed to delete TODO from index: %w", err)
	}

	key := fmt.Sprintf("%d", id)
	subIDs, ok := idx.Todos[key]

	if !ok {
		subIDs, ok = idx.Archived[key]
	}

	if ok {
		// Delete files of sub-TODOs of a deleted TODO
		for _, id := range subIDs {
			filename := fmt.Sprintf("%d.md", id)
			path := filepath.Join(repo.root, filename)

			if _, err := os.Stat(path); os.IsNotExist(err) {
				return fmt.Errorf("file not found: %s", path)
			}

			err := os.Remove(path)
			if err != nil {
				return fmt.Errorf("failed to delete TODO: %w", err)
			}
		}
	}

	// Delete the IDs of sub-TODOs of deleted TODO from index.json
	delete(idx.Todos, key)
	delete(idx.Archived, key)

	// Delete the ID of deleted TODO from index.json
	for k, ids := range idx.Todos {
		var _ids []int
		for _, _id := range ids {
			if _id == id {
				continue
			}
			_ids = append(_ids, _id)
		}

		if len(_ids) == 0 {
			delete(idx.Todos, k)
			continue
		}

		idx.Todos[k] = _ids
	}

	for k, ids := range idx.Archived {
		var _ids []int
		for _, _id := range ids {
			if _id == id {
				continue
			}
			_ids = append(_ids, _id)
		}

		if len(_ids) == 0 {
			delete(idx.Todos, k)
			continue
		}

		idx.Archived[k] = _ids
	}

	err = repo.writeIndex(idx)
	if err != nil {
		return fmt.Errorf("failed to delete TODO from index: %w", err)
	}

	return nil
}

// Open implements Repository interface.
func (repo *FileSystem) Open(id int) error {
	filename := fmt.Sprintf("%d.md", id)
	path := filepath.Join(repo.root, filename)

	cmd := exec.Command("open", path)
	err := cmd.Run()
	if err != nil {
		return fmt.Errorf("failed to open %s: %w", path, err)
	}

	return nil
}

// Move implements Repository interface.
func (repo *FileSystem) Move(id int, parent *int, position int) error {
	if position < 1 {
		return fmt.Errorf("position number must be larger than 0: %d", position)
	}

	if id == *parent {
		return fmt.Errorf("cannot move TODO under itself: %d", id)
	}

	idx, err := repo.readIndex()
	if err != nil {
		return fmt.Errorf("failed to move TODO: %w", err)
	}

	to := position - 1
	toKey := ""
	if *parent != 0 {
		toKey = fmt.Sprintf("%d", *parent)
	}

	if to > len(idx.Todos[toKey]) {
		return fmt.Errorf("position number is out of range: %d", position)
	}

	var from int
	var fromKey string
	for k, ids := range idx.Todos {
		for i, _id := range ids {
			if _id == id {
				from = i
				fromKey = k
				break
			}
		}
	}

	idx.Todos[fromKey] = append(idx.Todos[fromKey][:from], idx.Todos[fromKey][from+1:]...)
	idx.Todos[toKey] = append(idx.Todos[toKey][:to], append([]int{id}, idx.Todos[toKey][to:]...)...)

	if len(idx.Todos[fromKey]) == 0 {
		delete(idx.Todos, fromKey)
	}

	err = repo.writeIndex(idx)
	if err != nil {
		return fmt.Errorf("failed to move TODO: %w", err)
	}

	return nil
}

// Archive implements Repository interface.
func (repo *FileSystem) Archive(id int) error {
	idx, err := repo.readIndex()
	if err != nil {
		return fmt.Errorf("failed to archive TODO: %w", err)
	}

	filename := fmt.Sprintf("%d.md", id)
	path := filepath.Join(repo.root, filename)
	archivedPath := filepath.Join(repo.archivedDir, filename)
	err = os.Rename(path, archivedPath)
	if err != nil {
		return fmt.Errorf("failed to archive TODO: %w", err)
	}

	key := ""
	index := 0
	for k, ids := range idx.Todos {
		for i, _id := range ids {
			if _id == id {
				key = k
				index = i
				break
			}
		}
	}

	idx.Todos[key] = append(idx.Todos[key][:index], idx.Todos[key][index+1:]...)
	idx.Archived[key] = append(idx.Archived[key], id)

	if len(idx.Todos[key]) == 0 {
		delete(idx.Todos, key)
	}

	key = fmt.Sprintf("%d", id)
	if subIDs, ok := idx.Todos[key]; ok {
		for _, subID := range subIDs {
			filename := fmt.Sprintf("%d.md", subID)
			path := filepath.Join(repo.root, filename)
			archivedPath := filepath.Join(repo.archivedDir, filename)
			err := os.Rename(path, archivedPath)
			if err != nil {
				return fmt.Errorf("failed to archive TODO: %w", err)
			}
		}

		idx.Archived[key] = append(idx.Archived[key], idx.Todos[key]...)
		delete(idx.Todos, key)
	}

	err = repo.writeIndex(idx)
	if err != nil {
		return fmt.Errorf("failed to move TODO: %w", err)
	}

	return nil
}

func parseID(path string) (int, error) {
	text := strings.TrimRight(filepath.Base(path), filepath.Ext(path))
	return strconv.Atoi(text)
}

func newContent(title string) string {
	return strings.Trim(fmt.Sprintf(`
---
title: %s
state: undone
---
`, title), "\n")
}

func (repo *FileSystem) readIndex() (*index, error) {
	path := filepath.Join(repo.root, "index.json")
	if _, err := os.Stat(path); os.IsNotExist(err) {
		meta := metadata{LastID: 0}
		return &index{
			Metadata: meta,
			Todos: map[string][]int{
				"": {},
			},
			Archived: map[string][]int{},
		}, nil
	}

	file, err := os.Open(path)
	if err != nil {
		return nil, fmt.Errorf("failed to read index from %s: %w", path, err)
	}
	defer file.Close()

	content, err := ioutil.ReadAll(file)
	if err != nil {
		return nil, fmt.Errorf("failed to read index from %s: %w", path, err)
	}

	var i index
	err = json.Unmarshal(content, &i)
	if err != nil {
		return nil, fmt.Errorf("failed to read index from %s: %w", path, err)
	}

	return &i, nil
}

func (repo *FileSystem) writeIndex(i *index) error {
	path := filepath.Join(repo.root, "index.json")

	data, err := json.MarshalIndent(*i, "", "  ")
	if err != nil {
		return fmt.Errorf("failed to write index to %s: %w", path, err)
	}

	err = ioutil.WriteFile(path, data, 0644)
	if err != nil {
		return fmt.Errorf("failed to write index to %s: %w", path, err)
	}

	return nil
}

func (idx *index) generateNextID() int {
	id := idx.Metadata.LastID + 1
	idx.Metadata.LastID = id
	return id
}

func maxID(todos []*todo.Todo) int {
	result := 0

	for _, td := range todos {
		if td.ID > result {
			result = td.ID
		}

		maxSubID := maxID(td.Todos)
		if maxSubID > result {
			result = maxSubID
		}
	}

	return result
}

func orderTodos(todos map[int]*todo.Todo, i *index, key string) []*todo.Todo {
	var result []*todo.Todo

	for _, id := range i.Todos[key] {
		td, ok := todos[id]
		if !ok {
			continue
		}

		newKey := fmt.Sprintf("%d", td.ID)
		td.Todos = orderTodos(todos, i, newKey)
		result = append(result, td)
	}

	return result
}

func isExists(todos []*todo.Todo, id int) bool {
	for _, td := range todos {
		if td.ID == id {
			return true
		}

		result := isExists(td.Todos, id)
		if result {
			return true
		}
	}

	return false
}
<|endoftext|>