<commit_before>package api

import (
	"errors"
	"fmt"
	"net/http"

	"github.com/NebulousLabs/Sia/crypto"
	"github.com/NebulousLabs/Sia/modules"

	"github.com/julienschmidt/httprouter"
)

var (
	// errStorageFolderNotFound is returned if a call is made looking for a
	// storage folder which does not appear to exist within the storage
	// manager.
	errStorageFolderNotFound = errors.New("storage folder with the provided path could not be found")
)

type (
	// HostGET contains the information that is returned after a GET request to
	// /host - a bunch of information about the status of the host.
	HostGET struct {
		FinancialMetrics modules.HostFinancialMetrics `json:"financialmetrics"`
		InternalSettings modules.HostInternalSettings `json:"internalsettings"`
		NetworkMetrics   modules.HostNetworkMetrics   `json:"networkmetrics"`
	}

	// StorageGET contains the information that is returned after a GET request
	// to /storage - a bunch of information about the status of storage
	// management on the host.
	StorageGET struct {
		StorageFolderMetadata []modules.StorageFolderMetadata
	}
)

// folderIndex determines the index of the storage folder with the provided
// path.
func folderIndex(folderPath string, storageFolders []modules.StorageFolderMetadata) (int, error) {
	for i, sf := range storageFolders {
		if sf.Path == folderPath {
			return i, nil
		}
	}
	return -1, errStorageFolderNotFound
}

// hostHandlerGET handles GET requests to the /host API endpoint, returning key
// information about the host.
func (srv *Server) hostHandlerGET(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	fm := srv.host.FinancialMetrics()
	is := srv.host.InternalSettings()
	nm := srv.host.NetworkMetrics()
	hg := HostGET{
		FinancialMetrics: fm,
		InternalSettings: is,
		NetworkMetrics:   nm,
	}
	writeJSON(w, hg)
}

// hostHandlerPOST handles POST request to the /host API endpoint, which sets
// the internal settings of the host.
func (srv *Server) hostHandlerPOST(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	// Map each query string to a field in the host settings.
	settings := srv.host.InternalSettings()
	qsVars := map[string]interface{}{
		"acceptingcontracts":   &settings.AcceptingContracts,
		"maxduration":          &settings.MaxDuration,
		"maxdownloadbatchsize": &settings.MaxDownloadBatchSize,
		"maxrevisebatchsize":   &settings.MaxReviseBatchSize,
		"netaddress":           &settings.NetAddress,
		"windowsize":           &settings.WindowSize,

		"collateral":            &settings.Collateral,
		"collateralbudget":      &settings.CollateralBudget,
		"maxcollateralfraction": &settings.MaxCollateralFraction,
		"maxcollateral":         &settings.MaxCollateral,

		"downloadlimitgrowth": &settings.DownloadLimitGrowth,
		"downloadlimitcap":    &settings.DownloadLimitCap,
		"downloadspeedlimit":  &settings.DownloadSpeedLimit,
		"uploadlimitgrowth":   &settings.UploadLimitGrowth,
		"uploadlimitcap":      &settings.UploadLimitCap,
		"uploadspeedlimit":    &settings.UploadSpeedLimit,

		"minimumcontractprice":          &settings.MinimumContractPrice,
		"minimumdownloadbandwidthprice": &settings.MinimumDownloadBandwidthPrice,
		"minimumstorageprice":           &settings.MinimumStoragePrice,
		"minimumuploadbandwidthprice":   &settings.MinimumUploadBandwidthPrice,
	}

	// Iterate through the query string and replace any fields that have been
	// altered.
	for qs := range qsVars {
		if req.FormValue(qs) != "" { // skip empty values
			_, err := fmt.Sscan(req.FormValue(qs), qsVars[qs])
			if err != nil {
				writeError(w, "Malformed "+qs, http.StatusBadRequest)
				return
			}
		}
	}
	srv.host.SetInternalSettings(settings)
	writeSuccess(w)
}

// hostAnnounceHandler handles the API call to get the host to announce itself
// to the network.
func (srv *Server) hostAnnounceHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	var err error
	if addr := req.FormValue("netaddress"); addr != "" {
		err = srv.host.AnnounceAddress(modules.NetAddress(addr))
	} else {
		err = srv.host.Announce()
	}
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	writeSuccess(w)
}

// storageHandler returns a bunch of information about storage management on
// the host.
func (srv *Server) storageHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	sfs, err := srv.host.StorageFolders()
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	sg := StorageGET{
		StorageFolderMetadata: sfs,
	}
	writeJSON(w, sg)
}

// storageFoldersAddHandler adds a storage folder to the storage manager.
func (srv *Server) storageFoldersAddHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	folderPath := ps.ByName("folder")
	var folderSize uint64
	_, err := fmt.Sscan(req.FormValue("size"), &folderSize)
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	err = srv.host.AddStorageFolder(folderPath, folderSize)
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	writeSuccess(w)
}

// storageFoldersResizeHandler resizes a storage folder in the storage manager.
func (srv *Server) storageFoldersResizeHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	folderPath := ps.ByName("folder")
	storageFolders, err := srv.host.StorageFolders()
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	folderIndex, err := folderIndex(folderPath, storageFolders)
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}

	var newSize uint64
	_, err = fmt.Sscan(req.FormValue("newsize"), &newSize)
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	err = srv.host.ResizeStorageFolder(folderIndex, newSize)
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	writeSuccess(w)
}

// storageFoldersRemoveHandler removes a storage folder from the storage
// manager.
func (srv *Server) storageFoldersRemoveHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	folderPath := ps.ByName("folder")
	storageFolders, err := srv.host.StorageFolders()
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	folderIndex, err := folderIndex(folderPath, storageFolders)
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}

	var force bool
	_, err = fmt.Sscan(req.FormValue("force"), &force)
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	err = srv.host.RemoveStorageFolder(folderIndex, force)
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	writeSuccess(w)
}

// storageSectorsDeleteHandler handles the call to delete a sector from the
// storage manager.
func (srv *Server) storageSectorsDeleteHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	sectorRoot, err := scanAddress(ps.ByName("merkleroot"))
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	err = srv.host.DeleteSector(crypto.Hash(sectorRoot))
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	writeSuccess(w)
}
<commit_msg>force flag is no longer mandatory in /storage/folders/remove api call<commit_after>package api

import (
	"errors"
	"fmt"
	"net/http"

	"github.com/NebulousLabs/Sia/crypto"
	"github.com/NebulousLabs/Sia/modules"

	"github.com/julienschmidt/httprouter"
)

var (
	// errStorageFolderNotFound is returned if a call is made looking for a
	// storage folder which does not appear to exist within the storage
	// manager.
	errStorageFolderNotFound = errors.New("storage folder with the provided path could not be found")
)

type (
	// HostGET contains the information that is returned after a GET request to
	// /host - a bunch of information about the status of the host.
	HostGET struct {
		FinancialMetrics modules.HostFinancialMetrics `json:"financialmetrics"`
		InternalSettings modules.HostInternalSettings `json:"internalsettings"`
		NetworkMetrics   modules.HostNetworkMetrics   `json:"networkmetrics"`
	}

	// StorageGET contains the information that is returned after a GET request
	// to /storage - a bunch of information about the status of storage
	// management on the host.
	StorageGET struct {
		StorageFolderMetadata []modules.StorageFolderMetadata
	}
)

// folderIndex determines the index of the storage folder with the provided
// path.
func folderIndex(folderPath string, storageFolders []modules.StorageFolderMetadata) (int, error) {
	for i, sf := range storageFolders {
		if sf.Path == folderPath {
			return i, nil
		}
	}
	return -1, errStorageFolderNotFound
}

// hostHandlerGET handles GET requests to the /host API endpoint, returning key
// information about the host.
func (srv *Server) hostHandlerGET(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	fm := srv.host.FinancialMetrics()
	is := srv.host.InternalSettings()
	nm := srv.host.NetworkMetrics()
	hg := HostGET{
		FinancialMetrics: fm,
		InternalSettings: is,
		NetworkMetrics:   nm,
	}
	writeJSON(w, hg)
}

// hostHandlerPOST handles POST request to the /host API endpoint, which sets
// the internal settings of the host.
func (srv *Server) hostHandlerPOST(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	// Map each query string to a field in the host settings.
	settings := srv.host.InternalSettings()
	qsVars := map[string]interface{}{
		"acceptingcontracts":   &settings.AcceptingContracts,
		"maxduration":          &settings.MaxDuration,
		"maxdownloadbatchsize": &settings.MaxDownloadBatchSize,
		"maxrevisebatchsize":   &settings.MaxReviseBatchSize,
		"netaddress":           &settings.NetAddress,
		"windowsize":           &settings.WindowSize,

		"collateral":            &settings.Collateral,
		"collateralbudget":      &settings.CollateralBudget,
		"maxcollateralfraction": &settings.MaxCollateralFraction,
		"maxcollateral":         &settings.MaxCollateral,

		"downloadlimitgrowth": &settings.DownloadLimitGrowth,
		"downloadlimitcap":    &settings.DownloadLimitCap,
		"downloadspeedlimit":  &settings.DownloadSpeedLimit,
		"uploadlimitgrowth":   &settings.UploadLimitGrowth,
		"uploadlimitcap":      &settings.UploadLimitCap,
		"uploadspeedlimit":    &settings.UploadSpeedLimit,

		"minimumcontractprice":          &settings.MinimumContractPrice,
		"minimumdownloadbandwidthprice": &settings.MinimumDownloadBandwidthPrice,
		"minimumstorageprice":           &settings.MinimumStoragePrice,
		"minimumuploadbandwidthprice":   &settings.MinimumUploadBandwidthPrice,
	}

	// Iterate through the query string and replace any fields that have been
	// altered.
	for qs := range qsVars {
		if req.FormValue(qs) != "" { // skip empty values
			_, err := fmt.Sscan(req.FormValue(qs), qsVars[qs])
			if err != nil {
				writeError(w, "Malformed "+qs, http.StatusBadRequest)
				return
			}
		}
	}
	srv.host.SetInternalSettings(settings)
	writeSuccess(w)
}

// hostAnnounceHandler handles the API call to get the host to announce itself
// to the network.
func (srv *Server) hostAnnounceHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	var err error
	if addr := req.FormValue("netaddress"); addr != "" {
		err = srv.host.AnnounceAddress(modules.NetAddress(addr))
	} else {
		err = srv.host.Announce()
	}
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	writeSuccess(w)
}

// storageHandler returns a bunch of information about storage management on
// the host.
func (srv *Server) storageHandler(w http.ResponseWriter, req *http.Request, _ httprouter.Params) {
	sfs, err := srv.host.StorageFolders()
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	sg := StorageGET{
		StorageFolderMetadata: sfs,
	}
	writeJSON(w, sg)
}

// storageFoldersAddHandler adds a storage folder to the storage manager.
func (srv *Server) storageFoldersAddHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	folderPath := ps.ByName("folder")
	var folderSize uint64
	_, err := fmt.Sscan(req.FormValue("size"), &folderSize)
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	err = srv.host.AddStorageFolder(folderPath, folderSize)
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	writeSuccess(w)
}

// storageFoldersResizeHandler resizes a storage folder in the storage manager.
func (srv *Server) storageFoldersResizeHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	folderPath := ps.ByName("folder")
	storageFolders, err := srv.host.StorageFolders()
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	folderIndex, err := folderIndex(folderPath, storageFolders)
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}

	var newSize uint64
	_, err = fmt.Sscan(req.FormValue("newsize"), &newSize)
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	err = srv.host.ResizeStorageFolder(folderIndex, newSize)
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	writeSuccess(w)
}

// storageFoldersRemoveHandler removes a storage folder from the storage
// manager.
func (srv *Server) storageFoldersRemoveHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	folderPath := ps.ByName("folder")
	storageFolders, err := srv.host.StorageFolders()
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	folderIndex, err := folderIndex(folderPath, storageFolders)
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}

	force := req.FormValue("force") == "true"
	err = srv.host.RemoveStorageFolder(folderIndex, force)
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	writeSuccess(w)
}

// storageSectorsDeleteHandler handles the call to delete a sector from the
// storage manager.
func (srv *Server) storageSectorsDeleteHandler(w http.ResponseWriter, req *http.Request, ps httprouter.Params) {
	sectorRoot, err := scanAddress(ps.ByName("merkleroot"))
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	err = srv.host.DeleteSector(crypto.Hash(sectorRoot))
	if err != nil {
		writeError(w, err.Error(), http.StatusBadRequest)
		return
	}
	writeSuccess(w)
}
<|endoftext|>


<commit_before>package rog

import (
	"fmt"
    "bytes"
    "image"
	"image/color"
    "image/draw"
)

type Console struct {
	bg, bgbuf, fg, fgbuf [][]color.Color
	ch, chbuf [][]rune
	w, h int
    font image.Image
}

func NewConsole(width, height int) *Console {
	bg := make([][]color.Color, height)
	bgbuf := make([][]color.Color, height)
	fg := make([][]color.Color, height)
	fgbuf := make([][]color.Color, height)
	ch := make([][]rune, height)
	chbuf := make([][]rune, height)

	for y := 0; y < height; y++ {
		bg[y] = make([]color.Color, width)
		bgbuf[y] = make([]color.Color, width)
		fg[y] = make([]color.Color, width)
		fgbuf[y] = make([]color.Color, width)
		ch[y] = make([]rune, width)
		chbuf[y] = make([]rune, width)
	}

	mask, _, err := image.Decode(bytes.NewBuffer(font()))
	if err != nil {
		panic(err)
	}

	con := &Console{bg, bgbuf, fg, fgbuf, ch, chbuf, width, height, mask}

	for x := 0; x < con.w; x++ {
		for y := 0; y < con.h; y++ {
			con.bg[y][x] = color.Black
			con.bgbuf[y][x] = color.Black
			con.fg[y][x] = color.White
			con.fgbuf[y][x] = color.White
			con.ch[y][x] = ' '
			con.chbuf[y][x] = ' '
		}
	}

	return con
}

func (con *Console) put(x, y int, fg, bg interface{}, ch rune) {
	if ch > 0 {
		con.ch[y][x] = ch
	}

    switch bgcolor := bg.(type) {
    case color.Color:
		con.bg[y][x] = bgcolor
    case Blender:
	    con.bg[y][x] = bgcolor(con.bg[y][x])
    default:
    }

    switch fgcolor := fg.(type) {
    case color.Color:
		con.fg[y][x] = fgcolor
    case Blender:
		con.fg[y][x] = fgcolor(con.bg[y][x])
    default:
    }
}

func (con *Console) set(i, j, x, y, w, h int, fg, bg interface{}, data string, rest ...interface{}) {
    runes := []rune(fmt.Sprintf(data, rest...))
    if len(runes) > 0 {
        if h == 0 {
            h = con.h - y
        }
	    for k := 0; k < len(runes); k++ {
            if i == x+w {
                j += 1
                i = x
            }
            if j == y + h {
                break
            }
		    con.put(i, j, fg, bg, runes[k])
            i += 1
	    }
    } else {
		con.put(i, j, fg, bg, -1)
    }
}

func (con *Console) Set(x, y int, fg, bg interface{}, data string, rest ...interface{}) {
    con.set(x, y, 0, 0, con.w, con.h, fg, bg, data, rest...)
}

func (con *Console) SetR(x, y, w, h int, fg, bg interface{}, data string, rest ...interface{}) {
    con.set(x, y, x, y, w, h, fg, bg, data, rest...)
}

func (con *Console) Fill(x, y, w, h int, fg, bg interface{}, ch rune) {
	for i := x; i < x+w; i++ {
		for j := y; j < y+h; j++ {
			con.Set(i, j, fg, bg, string(ch))
		}
	}
}

func (c *Console) Render(im draw.Image) {
	maskRect := image.Rectangle{image.Point{0, 0}, image.Point{16, 16}}
	for y := 0; y < c.h; y++ {
		for x := 0; x < c.w; x++ {
			bg := c.bg[y][x]
			fg := c.fg[y][x]
			ch := c.ch[y][x]
			if bg != c.bgbuf[y][x] || fg != c.fgbuf[y][x] || ch != c.chbuf[y][x] {
                c.bgbuf[y][x] = bg
                c.fgbuf[y][x] = fg
                c.chbuf[y][x] = ch
				rect := maskRect.Add(image.Point{x * 16, y * 16})
				src := &image.Uniform{bg}
				draw.Draw(im, rect, src, image.ZP, draw.Src)

				if ch != ' ' {
					src = &image.Uniform{fg}
					draw.DrawMask(im, rect, src, image.ZP, c.font, image.Point{int(ch%32) * 16, int(ch/32) * 16}, draw.Over)
				}
			}
		}
	}
}

func (con *Console) Width() int {
	return con.w
}

func (con *Console) Height() int {
	return con.h
}
<commit_msg>Add docs to console.go<commit_after>package rog

import (
	"fmt"
    "bytes"
    "image"
	"image/color"
    "image/draw"
)

// Console is a double buffered grid of unicode characters that can be rendered to an image.Image.
type Console struct {
	bg, bgbuf, fg, fgbuf [][]color.Color
	ch, chbuf [][]rune
	w, h int
    font image.Image
}

// NewConsole creates an empty console.
func NewConsole(width, height int) *Console {
	bg := make([][]color.Color, height)
	bgbuf := make([][]color.Color, height)
	fg := make([][]color.Color, height)
	fgbuf := make([][]color.Color, height)
	ch := make([][]rune, height)
	chbuf := make([][]rune, height)

	for y := 0; y < height; y++ {
		bg[y] = make([]color.Color, width)
		bgbuf[y] = make([]color.Color, width)
		fg[y] = make([]color.Color, width)
		fgbuf[y] = make([]color.Color, width)
		ch[y] = make([]rune, width)
		chbuf[y] = make([]rune, width)
	}

	mask, _, err := image.Decode(bytes.NewBuffer(font()))
	if err != nil {
		panic(err)
	}

	con := &Console{bg, bgbuf, fg, fgbuf, ch, chbuf, width, height, mask}

	for x := 0; x < con.w; x++ {
		for y := 0; y < con.h; y++ {
			con.bg[y][x] = color.Black
			con.bgbuf[y][x] = color.Black
			con.fg[y][x] = color.White
			con.fgbuf[y][x] = color.White
			con.ch[y][x] = ' '
			con.chbuf[y][x] = ' '
		}
	}

	return con
}

func (con *Console) put(x, y int, fg, bg interface{}, ch rune) {
	if ch > 0 {
		con.ch[y][x] = ch
	}

    switch bgcolor := bg.(type) {
    case color.Color:
		con.bg[y][x] = bgcolor
    case Blender:
	    con.bg[y][x] = bgcolor(con.bg[y][x])
    default:
    }

    switch fgcolor := fg.(type) {
    case color.Color:
		con.fg[y][x] = fgcolor
    case Blender:
		con.fg[y][x] = fgcolor(con.bg[y][x])
    default:
    }
}

func (con *Console) set(i, j, x, y, w, h int, fg, bg interface{}, data string, rest ...interface{}) {
    runes := []rune(fmt.Sprintf(data, rest...))
    if len(runes) > 0 {
        if h == 0 {
            h = con.h - y
        }
	    for k := 0; k < len(runes); k++ {
            if i == x+w {
                j += 1
                i = x
            }
            if j == y + h {
                break
            }
		    con.put(i, j, fg, bg, runes[k])
            i += 1
	    }
    } else {
		con.put(i, j, fg, bg, -1)
    }
}

// Set draws a string starting at x,y onto the console, wrapping at the bounds if needed.
func (con *Console) Set(x, y int, fg, bg interface{}, data string, rest ...interface{}) {
    con.set(x, y, 0, 0, con.w, con.h, fg, bg, data, rest...)
}

// Set draws a string starting at x,y onto the console, wrapping at the bounds created by x, y, w, h if needed.
// If h is 0, the text will cut off at the bottom of the console, otherwise it will cut off after the y+h row.
func (con *Console) SetR(x, y, w, h int, fg, bg interface{}, data string, rest ...interface{}) {
    con.set(x, y, x, y, w, h, fg, bg, data, rest...)
}

// Fill draws a rect on the root console using ch.
func (con *Console) Fill(x, y, w, h int, fg, bg interface{}, ch rune) {
	for i := x; i < x+w; i++ {
		for j := y; j < y+h; j++ {
			con.Set(i, j, fg, bg, string(ch))
		}
	}
}

// Render draws the console onto an image.
func (c *Console) Render(im draw.Image) {
	maskRect := image.Rectangle{image.Point{0, 0}, image.Point{16, 16}}
	for y := 0; y < c.h; y++ {
		for x := 0; x < c.w; x++ {
			bg := c.bg[y][x]
			fg := c.fg[y][x]
			ch := c.ch[y][x]
			if bg != c.bgbuf[y][x] || fg != c.fgbuf[y][x] || ch != c.chbuf[y][x] {
                c.bgbuf[y][x] = bg
                c.fgbuf[y][x] = fg
                c.chbuf[y][x] = ch
				rect := maskRect.Add(image.Point{x * 16, y * 16})
				src := &image.Uniform{bg}
				draw.Draw(im, rect, src, image.ZP, draw.Src)

				if ch != ' ' {
					src = &image.Uniform{fg}
					draw.DrawMask(im, rect, src, image.ZP, c.font, image.Point{int(ch%32) * 16, int(ch/32) * 16}, draw.Over)
				}
			}
		}
	}
}

// Width returns the width of the console in cells.
func (con *Console) Width() int {
	return con.w
}

// Height returns the height of the console in cells.
func (con *Console) Height() int {
	return con.h
}
<|endoftext|>


<commit_before>// Package urltitle implements a plugin to watch web URLs, fetch and display title.
package urltitle

import (
	"errors"
	"fmt"
	bot "github.com/StalkR/goircbot"
	irc "github.com/fluffle/goirc/client"
	"html"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"regexp"
	"strings"
	"time"
)

func timeoutDialer(d time.Duration) func(net, addr string) (c net.Conn, err error) {
	return func(netw, addr string) (net.Conn, error) {
		return net.DialTimeout(netw, addr, d)
	}
}

// Title gets an URL and returns its title.
func Title(url string) (string, error) {
	log.Println("urltitle:", url)
	client := &http.Client{
		Transport: &http.Transport{
			Dial: timeoutDialer(3 * time.Second),
		},
	}
	resp, err := client.Get(url)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	contents, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	r, err := regexp.Compile("<title[^>]*>([^<]+)<")
	if err != nil {
		return "", err
	}
	matches := r.FindSubmatch([]byte(contents))
	if len(matches) < 2 {
		return "", errors.New("no title found in page")
	}
	return html.UnescapeString(strings.TrimSpace(string(matches[1]))), nil
}

func watchLine(b *bot.Bot, line *irc.Line, ignoremap map[string]bool) {
	target := line.Args[0]
	if !strings.HasPrefix(target, "#") {
		return
	}
	if _, ignore := ignoremap[line.Nick]; ignore {
		return
	}
	text := line.Args[1]
	if !strings.Contains(text, "http") {
		return
	}
	r, err := regexp.Compile("(?:^|\\s)(https?://[^\\s]+)")
	if err != nil {
		return
	}
	matches := r.FindSubmatch([]byte(text))
	if len(matches) < 2 {
		return
	}
	url := string(matches[1])
	title, err := Title(url)
	if err != nil {
		return
	}
	if len(title) > 200 {
		title = title[:200]
	}
	b.Conn.Privmsg(target, fmt.Sprintf("%s :: %s", url, title))
}

// Register registers the plugin with a bot.
func Register(b *bot.Bot, ignore []string) {
	ignoremap := make(map[string]bool)
	for _, nick := range ignore {
		ignoremap[nick] = true
	}

	b.Conn.AddHandler("privmsg",
		func(conn *irc.Conn, line *irc.Line) {
			watchLine(b, line, ignoremap)
		})
}
<commit_msg>urltitle: log errors<commit_after>// Package urltitle implements a plugin to watch web URLs, fetch and display title.
package urltitle

import (
	"errors"
	"fmt"
	bot "github.com/StalkR/goircbot"
	irc "github.com/fluffle/goirc/client"
	"html"
	"io/ioutil"
	"log"
	"net"
	"net/http"
	"regexp"
	"strings"
	"time"
)

func timeoutDialer(d time.Duration) func(net, addr string) (c net.Conn, err error) {
	return func(netw, addr string) (net.Conn, error) {
		return net.DialTimeout(netw, addr, d)
	}
}

// Title gets an URL and returns its title.
func Title(url string) (string, error) {
	client := &http.Client{
		Transport: &http.Transport{
			Dial: timeoutDialer(3 * time.Second),
		},
	}
	resp, err := client.Get(url)
	if err != nil {
		return "", err
	}
	defer resp.Body.Close()
	contents, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}
	r, err := regexp.Compile("<title[^>]*>([^<]+)<")
	if err != nil {
		return "", err
	}
	matches := r.FindSubmatch([]byte(contents))
	if len(matches) < 2 {
		return "", errors.New("no title found in page")
	}
	return html.UnescapeString(strings.TrimSpace(string(matches[1]))), nil
}

func watchLine(b *bot.Bot, line *irc.Line, ignoremap map[string]bool) {
	target := line.Args[0]
	if !strings.HasPrefix(target, "#") {
		return
	}
	if _, ignore := ignoremap[line.Nick]; ignore {
		return
	}
	text := line.Args[1]
	if !strings.Contains(text, "http") {
		return
	}
	r, err := regexp.Compile("(?:^|\\s)(https?://[^\\s]+)")
	if err != nil {
		return
	}
	matches := r.FindSubmatch([]byte(text))
	if len(matches) < 2 {
		return
	}
	url := string(matches[1])
	title, err := Title(url)
	if err != nil {
		log.Println("urltitle:", err)
		return
	}
	if len(title) > 200 {
		title = title[:200]
	}
	b.Conn.Privmsg(target, fmt.Sprintf("%s :: %s", url, title))
}

// Register registers the plugin with a bot.
func Register(b *bot.Bot, ignore []string) {
	ignoremap := make(map[string]bool)
	for _, nick := range ignore {
		ignoremap[nick] = true
	}

	b.Conn.AddHandler("privmsg",
		func(conn *irc.Conn, line *irc.Line) {
			watchLine(b, line, ignoremap)
		})
}
<|endoftext|>


<commit_before>package endly

import (
	"fmt"
	uuid "github.com/satori/go.uuid"
	"github.com/viant/endly/msg"
	"github.com/viant/toolbox"
	"github.com/viant/toolbox/data"
	"github.com/viant/toolbox/secret"
	"github.com/viant/toolbox/storage"
	"github.com/viant/toolbox/url"
	"math/rand"
	"os"
	"os/exec"
	"path"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

var serviceManagerKey = (*manager)(nil)
var deferFunctionsKey = (*[]func())(nil)

//Context represents a workflow session context/state
type Context struct {
	SessionID       string
	CLIEnabled      bool
	HasLogger       bool
	AsyncUnsafeKeys map[interface{}]bool
	Secrets         *secret.Service
	Wait            *sync.WaitGroup
	Listener        msg.Listener
	Source          *url.Resource
	state           data.Map
	toolbox.Context
	cloned []*Context
	closed int32
}

//Publish publishes event to listeners, it updates current run details like activity workflow name etc ...
func (c *Context) Publish(value interface{}) msg.Event {
	event, ok := value.(msg.Event)
	if !ok {
		event = msg.NewEvent(value)
	}
	if c.Listener != nil {
		c.Listener(event)
	}
	return event
}

//PublishWithStartEvent publishes event to listeners, it updates current run details like activity workflow name etc ...
func (c *Context) PublishWithStartEvent(value interface{}, init msg.Event) msg.Event {
	event := msg.NewEventWithInit(value, init)
	if c.Listener != nil {
		c.Listener(event)
	}
	return event
}

//SetListener sets context event Listener
func (c *Context) SetListener(listener msg.Listener) {
	c.Listener = listener
}

//IsClosed returns true if it is closed.
func (c *Context) IsClosed() bool {
	return atomic.LoadInt32(&c.closed) == 1
}

//Clone clones the context.
func (c *Context) Clone() *Context {
	if len(c.cloned) == 0 {
		c.cloned = make([]*Context, 0)
	}
	result := &Context{}
	result.Wait = &sync.WaitGroup{}
	result.Context = c.Context.Clone()
	result.state = NewDefaultState()
	result.state.Apply(c.state)
	result.SessionID = c.SessionID
	result.Listener = c.Listener
	result.CLIEnabled = c.CLIEnabled
	result.Secrets = c.Secrets
	result.AsyncUnsafeKeys = make(map[interface{}]bool)
	c.cloned = append(c.cloned, result)
	return result
}

func (c *Context) parentURLCandidates() []string {
	var result = make([]string, 0)
	if c.Source != nil {
		baseURL, _ := toolbox.URLSplit(c.Source.URL)
		result = append(result, baseURL)
	}
	currentDirectory, err := os.Getwd()
	if err == nil {
		result = append(result, toolbox.FileSchema+currentDirectory)
	}

	return result
}

//ExpandResource substitutes any $ expression with the key value from the state map if it is present.
func (c *Context) ExpandResource(resource *url.Resource) (*url.Resource, error) {
	if resource == nil {
		return nil, msg.ReportError(fmt.Errorf("resource  was empty"))
	}
	if resource.URL == "" {
		return nil, msg.ReportError(fmt.Errorf("url was empty"))
	}

	if !strings.Contains(resource.URL, "://") {
		for _, parentCandidate := range c.parentURLCandidates() {
			service, err := storage.NewServiceForURL(parentCandidate, "")
			if err != nil {
				continue
			}
			var candidateURL = toolbox.URLPathJoin(parentCandidate, resource.URL)
			if exists, err := service.Exists(candidateURL); exists && err == nil {
				resource.URL = candidateURL
			}
		}
	}
	var result = url.NewResource(c.Expand(resource.URL), c.Expand(resource.Credentials))
	if result.ParsedURL == nil {
		return nil, fmt.Errorf("failed to parse URL %v", result.URL)
	}
	result.Cache = c.Expand(resource.Cache)
	result.CacheExpiryMs = resource.CacheExpiryMs
	return result, nil
}

//Manager returns workflow manager or error
func (c *Context) Manager() (Manager, error) {
	if c == nil {
		return nil, fmt.Errorf("context was nil")
	}
	var manager = &manager{}
	if !c.GetInto(serviceManagerKey, &manager) {
		return nil, msg.ReportError(fmt.Errorf("failed to lookup Manager"))
	}
	return manager, nil
}

//Service returns a service fo provided id or error.
func (c *Context) Service(name string) (Service, error) {
	manager, err := c.Manager()
	if err != nil {
		return nil, err
	}
	return manager.Service(name)
}

//Deffer add function to be executed if context closes. If returns currently registered functions.
func (c *Context) Deffer(functions ...func()) []func() {
	var result *[]func()
	if !c.Contains(deferFunctionsKey) {
		var functions = make([]func(), 0)
		result = &functions
		_ = c.Put(deferFunctionsKey, result)
	} else {
		c.GetInto(deferFunctionsKey, &result)
	}

	*result = append(*result, functions...)
	_ = c.Put(deferFunctionsKey, &result)
	return *result
}

//State returns a context state map.
func (c *Context) State() data.Map {
	if c.state == nil {
		c.state = NewDefaultState()
	}
	return c.state
}

//SetState sets a new state map
func (c *Context) SetState(state data.Map) {
	c.state = state
}

//Clone substitute $ expression if present in the text and state map.
func (c *Context) Expand(text string) string {
	state := c.State()
	return state.ExpandAsText(text)
}

//PublishAndRestore sets supplied value and returns func restoring original values
func (s *Context) PublishAndRestore(values map[string]interface{}) func() {
	var backup = map[string]interface{}{}
	for k, v := range values {
		if value, has := s.state.GetValue(k); has {
			backup[k] = value
		}
		s.state.SetValue(k, v)
	}
	return func() {
		for k, v := range backup {
			s.state.SetValue(k, v)
		}
	}
}

//NewRequest creates a new request for service and action
func (c *Context) NewRequest(serviceName, action string) (interface{}, error) {
	service, err := c.Service(serviceName)
	if err != nil {
		return nil, err
	}
	route, err := service.Route(action)
	if err != nil {
		return nil, err
	}
	return route.RequestProvider(), nil
}

//AsRequest converts a source map into request for provided service and action.
func (c *Context) AsRequest(serviceName, action string, source map[string]interface{}) (request interface{}, err error) {
	if request, err = c.NewRequest(serviceName, action); err != nil {
		return request, err
	}
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("failed to create request, unable to cast %v into %T, %v", source, request, r)
		}
	}()
	expanded := c.state.Expand(source)
	source = toolbox.AsMap(expanded)
	err = toolbox.DefaultConverter.AssignConverted(request, source)
	return request, err
}

//Close closes this context, it executes all deferred function and set closed flag.
func (c *Context) Close() {
	atomic.StoreInt32(&c.closed, 1)
	for _, context := range c.cloned {
		context.Close()
	}
	for _, function := range c.Deffer() {
		function()
	}
}

//MakeAsyncSafe makes this contex async safe

func (c *Context) MakeAsyncSafe() *msg.Events {
	for k := range c.AsyncUnsafeKeys {
		c.Context.Remove(k)
	}
	result := msg.NewEvents()
	c.Listener = result.AsListener()
	return result
}

/*
NewDefaultState returns a new default state.
It comes with the following registered keys:
	* rand - random int64
	*  date -  current date formatted as yyyy-MM-dd
	* time - current time formatted as yyyy-MM-dd hh:mm:ss
	* ts - current timestamp formatted  as yyyyMMddhhmmSSS
	* timestamp.yesterday - timestamp in ms
	* timestamp.now - timestamp in ms
	* timestamp.tomorrow - timestamp in ms
	* tmpDir - temp directory
	* uuid.next - generate unique id
	* uuid.Get - returns previously generated unique id, or generate new
	*.end.XXX where XXX is the ID of the env variable to return
	* all UFD registry functions
*/
func NewDefaultState() data.Map {
	var result = data.NewMap()
	var now = time.Now()
	source := rand.NewSource(now.UnixNano())
	result.Put("rand", source.Int63())
	result.Put("date", now.Format(toolbox.DateFormatToLayout("yyyy-MM-dd")))
	result.Put("time", now.Format(toolbox.DateFormatToLayout("yyyy-MM-dd hh:mm:ss")))
	result.Put("ts", now.Format(toolbox.DateFormatToLayout("yyyyMMddhhmmSSS")))

	result.Put("tmpDir", func(key string) interface{} {
		tempPath := path.Join(os.TempDir(), key)
		exec.Command("mkdir -p " + tempPath)
		return tempPath
	})

	var cachedUUID uuid.UUID
	result.Put("uuid", func(key string) interface{} {
		if key == "next" {
			var err error
			cachedUUID, err = uuid.NewV4()
			if err != nil {
				return ""
			}
		}
		if len(cachedUUID) > 0 {
			return cachedUUID.String()
		}
		return ""
	})

	result.Put("timestamp", func(key string) interface{} {
		var timeDiffProvider = toolbox.NewTimeDiffProvider()
		switch key {
		case "now":
			result, _ := timeDiffProvider.Get(nil, "now", 0, "day", "timestamp")
			return result
		case "tomorrow":
			result, _ := timeDiffProvider.Get(nil, "now", 1, "day", "timestamp")
			return result
		case "yesterday":
			result, _ := timeDiffProvider.Get(nil, "now", -1, "day", "timestamp")
			return result
		}
		return nil
	})

	result.Put("unix", func(key string) interface{} {
		var timeDiffProvider = toolbox.NewTimeDiffProvider()
		switch key {
		case "now":
			result, _ := timeDiffProvider.Get(nil, "now", 0, "day", "unix")
			return result
		case "tomorrow":
			result, _ := timeDiffProvider.Get(nil, "now", 1, "day", "unix")
			return result
		case "yesterday":
			result, _ := timeDiffProvider.Get(nil, "now", -1, "day", "unix")
			return result
		case "hourbefore":
			result, _ := timeDiffProvider.Get(nil, "now", -1, "hour", "unix")
			return result
		case "2hourbefore":
			result, _ := timeDiffProvider.Get(nil, "now", -2, "hour", "unix")
			return result
		case "hourlater":
			result, _ := timeDiffProvider.Get(nil, "now", 1, "hour", "unix")
			return result
		case "2hourlater":
			result, _ := timeDiffProvider.Get(nil, "now", 2, "hour", "unix")
			return result
		}
		return nil
	})

	result.Put("env", func(key string) interface{} {
		return os.Getenv(key)
	})

	for k, v := range UdfRegistry {
		result.Put(k, v)
	}
	return result
}
<commit_msg>updated comments<commit_after>package endly

import (
	"fmt"
	uuid "github.com/satori/go.uuid"
	"github.com/viant/endly/msg"
	"github.com/viant/toolbox"
	"github.com/viant/toolbox/data"
	"github.com/viant/toolbox/secret"
	"github.com/viant/toolbox/storage"
	"github.com/viant/toolbox/url"
	"math/rand"
	"os"
	"os/exec"
	"path"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

var serviceManagerKey = (*manager)(nil)
var deferFunctionsKey = (*[]func())(nil)

//Context represents a workflow session context/state
type Context struct {
	SessionID       string
	CLIEnabled      bool
	HasLogger       bool
	AsyncUnsafeKeys map[interface{}]bool
	Secrets         *secret.Service
	Wait            *sync.WaitGroup
	Listener        msg.Listener
	Source          *url.Resource
	state           data.Map
	toolbox.Context
	cloned []*Context
	closed int32
}

//Publish publishes event to listeners, it updates current run details like activity workflow name etc ...
func (c *Context) Publish(value interface{}) msg.Event {
	event, ok := value.(msg.Event)
	if !ok {
		event = msg.NewEvent(value)
	}
	if c.Listener != nil {
		c.Listener(event)
	}
	return event
}

//PublishWithStartEvent publishes event to listeners, it updates current run details like activity workflow name etc ...
func (c *Context) PublishWithStartEvent(value interface{}, init msg.Event) msg.Event {
	event := msg.NewEventWithInit(value, init)
	if c.Listener != nil {
		c.Listener(event)
	}
	return event
}

//SetListener sets context event Listener
func (c *Context) SetListener(listener msg.Listener) {
	c.Listener = listener
}

//IsClosed returns true if it is closed.
func (c *Context) IsClosed() bool {
	return atomic.LoadInt32(&c.closed) == 1
}

//Clone clones the context.
func (c *Context) Clone() *Context {
	if len(c.cloned) == 0 {
		c.cloned = make([]*Context, 0)
	}
	result := &Context{}
	result.Wait = &sync.WaitGroup{}
	result.Context = c.Context.Clone()
	result.state = NewDefaultState()
	result.state.Apply(c.state)
	result.SessionID = c.SessionID
	result.Listener = c.Listener
	result.CLIEnabled = c.CLIEnabled
	result.Secrets = c.Secrets
	result.AsyncUnsafeKeys = make(map[interface{}]bool)
	c.cloned = append(c.cloned, result)
	return result
}

func (c *Context) parentURLCandidates() []string {
	var result = make([]string, 0)
	if c.Source != nil {
		baseURL, _ := toolbox.URLSplit(c.Source.URL)
		result = append(result, baseURL)
	}
	currentDirectory, err := os.Getwd()
	if err == nil {
		result = append(result, toolbox.FileSchema+currentDirectory)
	}

	return result
}

//ExpandResource substitutes any $ expression with the key value from the state map if it is present.
func (c *Context) ExpandResource(resource *url.Resource) (*url.Resource, error) {
	if resource == nil {
		return nil, msg.ReportError(fmt.Errorf("resource  was empty"))
	}
	if resource.URL == "" {
		return nil, msg.ReportError(fmt.Errorf("url was empty"))
	}

	if !strings.Contains(resource.URL, "://") {
		for _, parentCandidate := range c.parentURLCandidates() {
			service, err := storage.NewServiceForURL(parentCandidate, "")
			if err != nil {
				continue
			}
			var candidateURL = toolbox.URLPathJoin(parentCandidate, resource.URL)
			if exists, err := service.Exists(candidateURL); exists && err == nil {
				resource.URL = candidateURL
			}
		}
	}
	var result = url.NewResource(c.Expand(resource.URL), c.Expand(resource.Credentials))
	if result.ParsedURL == nil {
		return nil, fmt.Errorf("failed to parse URL %v", result.URL)
	}
	result.Cache = c.Expand(resource.Cache)
	result.CacheExpiryMs = resource.CacheExpiryMs
	return result, nil
}

//Manager returns workflow manager or error
func (c *Context) Manager() (Manager, error) {
	if c == nil {
		return nil, fmt.Errorf("context was nil")
	}
	var manager = &manager{}
	if !c.GetInto(serviceManagerKey, &manager) {
		return nil, msg.ReportError(fmt.Errorf("failed to lookup Manager"))
	}
	return manager, nil
}

//Service returns a service fo provided id or error.
func (c *Context) Service(name string) (Service, error) {
	manager, err := c.Manager()
	if err != nil {
		return nil, err
	}
	return manager.Service(name)
}

//Deffer add function to be executed if context closes. If returns currently registered functions.
func (c *Context) Deffer(functions ...func()) []func() {
	var result *[]func()
	if !c.Contains(deferFunctionsKey) {
		var functions = make([]func(), 0)
		result = &functions
		_ = c.Put(deferFunctionsKey, result)
	} else {
		c.GetInto(deferFunctionsKey, &result)
	}

	*result = append(*result, functions...)
	_ = c.Put(deferFunctionsKey, &result)
	return *result
}

//State returns a context state map.
func (c *Context) State() data.Map {
	if c.state == nil {
		c.state = NewDefaultState()
	}
	return c.state
}

//SetState sets a new state map
func (c *Context) SetState(state data.Map) {
	c.state = state
}

//Expand substitute $ expression if present in the text and state map.
func (c *Context) Expand(text string) string {
	state := c.State()
	return state.ExpandAsText(text)
}

//PublishAndRestore sets supplied value and returns func restoring original values
func (s *Context) PublishAndRestore(values map[string]interface{}) func() {
	var backup = map[string]interface{}{}
	for k, v := range values {
		if value, has := s.state.GetValue(k); has {
			backup[k] = value
		}
		s.state.SetValue(k, v)
	}
	return func() {
		for k, v := range backup {
			s.state.SetValue(k, v)
		}
	}
}

//NewRequest creates a new request for service and action
func (c *Context) NewRequest(serviceName, action string) (interface{}, error) {
	service, err := c.Service(serviceName)
	if err != nil {
		return nil, err
	}
	route, err := service.Route(action)
	if err != nil {
		return nil, err
	}
	return route.RequestProvider(), nil
}

//AsRequest converts a source map into request for provided service and action.
func (c *Context) AsRequest(serviceName, action string, source map[string]interface{}) (request interface{}, err error) {
	if request, err = c.NewRequest(serviceName, action); err != nil {
		return request, err
	}
	defer func() {
		if r := recover(); r != nil {
			err = fmt.Errorf("failed to create request, unable to cast %v into %T, %v", source, request, r)
		}
	}()
	expanded := c.state.Expand(source)
	source = toolbox.AsMap(expanded)
	err = toolbox.DefaultConverter.AssignConverted(request, source)
	return request, err
}

//Close closes this context, it executes all deferred function and set closed flag.
func (c *Context) Close() {
	atomic.StoreInt32(&c.closed, 1)
	for _, context := range c.cloned {
		context.Close()
	}
	for _, function := range c.Deffer() {
		function()
	}
}

//MakeAsyncSafe makes this contex async safe

func (c *Context) MakeAsyncSafe() *msg.Events {
	for k := range c.AsyncUnsafeKeys {
		c.Context.Remove(k)
	}
	result := msg.NewEvents()
	c.Listener = result.AsListener()
	return result
}

/*
NewDefaultState returns a new default state.
It comes with the following registered keys:
	* rand - random int64
	*  date -  current date formatted as yyyy-MM-dd
	* time - current time formatted as yyyy-MM-dd hh:mm:ss
	* ts - current timestamp formatted  as yyyyMMddhhmmSSS
	* timestamp.yesterday - timestamp in ms
	* timestamp.now - timestamp in ms
	* timestamp.tomorrow - timestamp in ms
	* tmpDir - temp directory
	* uuid.next - generate unique id
	* uuid.Get - returns previously generated unique id, or generate new
	*.end.XXX where XXX is the ID of the env variable to return
	* all UFD registry functions
*/
func NewDefaultState() data.Map {
	var result = data.NewMap()
	var now = time.Now()
	source := rand.NewSource(now.UnixNano())
	result.Put("rand", source.Int63())
	result.Put("date", now.Format(toolbox.DateFormatToLayout("yyyy-MM-dd")))
	result.Put("time", now.Format(toolbox.DateFormatToLayout("yyyy-MM-dd hh:mm:ss")))
	result.Put("ts", now.Format(toolbox.DateFormatToLayout("yyyyMMddhhmmSSS")))

	result.Put("tmpDir", func(key string) interface{} {
		tempPath := path.Join(os.TempDir(), key)
		exec.Command("mkdir -p " + tempPath)
		return tempPath
	})

	var cachedUUID uuid.UUID
	result.Put("uuid", func(key string) interface{} {
		if key == "next" {
			var err error
			cachedUUID, err = uuid.NewV4()
			if err != nil {
				return ""
			}
		}
		if len(cachedUUID) > 0 {
			return cachedUUID.String()
		}
		return ""
	})

	result.Put("timestamp", func(key string) interface{} {
		var timeDiffProvider = toolbox.NewTimeDiffProvider()
		switch key {
		case "now":
			result, _ := timeDiffProvider.Get(nil, "now", 0, "day", "timestamp")
			return result
		case "tomorrow":
			result, _ := timeDiffProvider.Get(nil, "now", 1, "day", "timestamp")
			return result
		case "yesterday":
			result, _ := timeDiffProvider.Get(nil, "now", -1, "day", "timestamp")
			return result
		}
		return nil
	})

	result.Put("unix", func(key string) interface{} {
		var timeDiffProvider = toolbox.NewTimeDiffProvider()
		switch key {
		case "now":
			result, _ := timeDiffProvider.Get(nil, "now", 0, "day", "unix")
			return result
		case "tomorrow":
			result, _ := timeDiffProvider.Get(nil, "now", 1, "day", "unix")
			return result
		case "yesterday":
			result, _ := timeDiffProvider.Get(nil, "now", -1, "day", "unix")
			return result
		case "hourbefore":
			result, _ := timeDiffProvider.Get(nil, "now", -1, "hour", "unix")
			return result
		case "2hourbefore":
			result, _ := timeDiffProvider.Get(nil, "now", -2, "hour", "unix")
			return result
		case "hourlater":
			result, _ := timeDiffProvider.Get(nil, "now", 1, "hour", "unix")
			return result
		case "2hourlater":
			result, _ := timeDiffProvider.Get(nil, "now", 2, "hour", "unix")
			return result
		}
		return nil
	})

	result.Put("env", func(key string) interface{} {
		return os.Getenv(key)
	})

	for k, v := range UdfRegistry {
		result.Put(k, v)
	}
	return result
}
<|endoftext|>


<commit_before>package fire

import (
	"context"
	"encoding/json"
	"io"
	"net/http"
	"reflect"

	"github.com/256dpi/jsonapi/v2"
	"github.com/256dpi/xo"
	"go.mongodb.org/mongo-driver/bson"

	"github.com/256dpi/fire/coal"
	"github.com/256dpi/fire/stick"
)

// An Operation indicates the purpose of a yield to a callback in the processing
// flow of an API request by a controller. These operations may occur multiple
// times during a single request.
type Operation int

// All the available operations.
const (
	// List operation will be used to authorize the loading of multiple
	// resources from a collection.
	//
	// Note: This operation is also used to load related resources.
	List Operation = 1 << iota

	// Find operation will be used to authorize the loading of a specific
	// resource from a collection.
	//
	// Note: This operation is also used to load a specific related resource.
	Find

	// Create operation will be used to authorize and validate the creation
	// of a new resource in a collection.
	Create

	// Update operation will be used to authorize the loading and validate
	// the updating of a specific resource in a collection.
	//
	// Note: Updates can include attributes, relationships or both.
	Update

	// Delete operation will be used to authorize the loading and validate
	// the deletion of a specific resource in a collection.
	Delete

	// CollectionAction operation will be used to authorize the execution
	// of a callback for a collection action.
	CollectionAction

	// ResourceAction operation will be used to authorize the execution of a
	// callback for a resource action.
	ResourceAction
)

// Read will return true when the operations only reads data.
func (o Operation) Read() bool {
	return o == List || o == Find
}

// Write will return true when the operation writes data.
func (o Operation) Write() bool {
	return o == Create || o == Update || o == Delete
}

// Action will return true when the operation is a collection or resource action.
func (o Operation) Action() bool {
	return o == CollectionAction || o == ResourceAction
}

// String returns the name of the operation.
func (o Operation) String() string {
	switch o {
	case List:
		return "List"
	case Find:
		return "Find"
	case Create:
		return "Create"
	case Update:
		return "Update"
	case Delete:
		return "Delete"
	case CollectionAction:
		return "CollectionAction"
	case ResourceAction:
		return "ResourceAction"
	}

	return ""
}

// Context carries the state of a request and allows callbacks to influence the
// processing of a request.
type Context struct {
	// The context that is cancelled when the timeout has been exceeded or the
	// underlying connection transport has been closed. It may also carry the
	// database session if a transaction is used.
	//
	// Values: lungo.ISessionContext?, trace.Span, *xo.Tracer
	context.Context

	// The custom data map.
	Data stick.Map

	// The current callback stage.
	Stage Stage

	// The deferred callbacks.
	Defers map[Stage][]*Callback

	// The current operation in process.
	//
	// Usage: Read only
	// Availability: Authorizers
	Operation Operation

	// The query that will be used during a List, Find, Update, Delete or
	// ResourceAction operation to select a list of models or a specific model.
	//
	// On Find, Update and Delete operations, the "_id" key is preset to the
	// resource id, while on forwarded List operations the relationship filter
	// is preset.
	//
	// Usage: Read only
	// Availability: Authorizers
	// Operations: !Create, !CollectionAction
	Selector bson.M

	// The filters that will be used during a List, Find, Update, Delete or
	// ResourceAction operation to further filter the selection of a list of
	// models or a specific model.
	//
	// On List operations, attribute and relationship filters are preset.
	//
	// Usage: Append only
	// Availability: Authorizers
	// Operations: !Create, !CollectionAction
	Filters []bson.M

	// The sorting that will be used during List.
	//
	// Usage: No Restriction
	// Availability: Authorizers
	// Operations: List
	Sorting []string

	// Only the whitelisted readable fields are exposed to the client as
	// attributes and relationships. Additionally, only readable fields can
	// be used for filtering and sorting.
	//
	// Usage: Reduce only
	// Availability: Authorizers
	// Operations: !Delete, !ResourceAction, !CollectionAction
	ReadableFields []string

	// Used instead of ReadableFields if set. Allows specifying readable fields
	// on a per model basis. The provided model may be nil if run before any
	// model has been loaded. Must be a subset of ReadableFields.
	GetReadableFields func(coal.Model) []string

	// Only the whitelisted writable fields can be altered by requests.
	//
	// Usage: Reduce only
	// Availability: Authorizers
	// Operations: Create, Update
	WritableFields []string

	// Used instead of WritableFields if set. Allows specifying writable fields
	// on a per model basis. The provided model may be nil if run before any
	// model has been loaded. Must be a subset of WritableFields.
	GetWritableFields func(coal.Model) []string

	// Only the whitelisted readable properties are exposed to the client as
	// attributes.
	//
	// Usage: Reduce only
	// Availability: Authorizers
	// Operations: !Delete, !ResourceActon, !CollectionAction
	ReadableProperties []string

	// Used instead of ReadableProperties if set. Allows specifying readable
	// properties on a per model basis. The provided model may be nil if run
	// before any model has been loaded. Must be a subset of ReadableProperties.
	GetReadableProperties func(coal.Model) []string

	// The filters that will be applied when loading has one and has many
	// relationships.
	//
	// Usage: Append only
	// Availability: Authorizers.
	// Operations: !Create, !CollectionAction
	RelationshipFilters map[string][]bson.M

	// The model that will be created, updated, deleted or is requested by a
	// resource action.
	//
	// Usage: Modify only
	// Availability: Validators
	// Operations: Create, Update, Delete, ResourceAction
	Model coal.Model

	// The models that will be returned for a List operation.
	//
	// Usage: Modify only
	// Availability: Decorators
	// Operations: List
	Models []coal.Model

	// The original model that is being updated. Can be used to lookup up
	// original values of changed fields.
	//
	// Usage: Ready only
	// Availability: Validators
	// Operations: Update
	Original coal.Model

	// The document that has been received by the client.
	//
	// Usage: Read only
	// Availability: Authorizers
	// Operations: !List, !Find, !CollectionAction, !ResourceAction
	Request *jsonapi.Document

	// The document that will be written to the client.
	//
	// Usage: Modify only
	// Availability: Notifiers
	// Operations: !CollectionAction, !ResourceAction
	Response *jsonapi.Document

	// The status code that will be written to the client.
	//
	// Usage: Modify only
	// Availability: Notifiers
	// Operations: !CollectionAction, !ResourceAction
	ResponseCode int

	// The store that is used to retrieve and persist the model.
	//
	// Usage: Read only
	Store *coal.Store

	// The underlying JSON-API request.
	//
	// Usage: Read only
	JSONAPIRequest *jsonapi.Request

	// The underlying HTTP request.
	//
	// Note: The path is not updated when a controller forwards a request to
	// a related controller.
	//
	// Usage: Read only
	HTTPRequest *http.Request

	// The underlying HTTP response writer. The response writer should only be
	// used during collection or resource actions to write a custom response.
	//
	// Usage: Read only
	ResponseWriter http.ResponseWriter

	// The controller that is managing the request.
	//
	// Usage: Read only
	Controller *Controller

	// The group that received the request.
	//
	// Usage: Read only
	Group *Group

	// The current tracer.
	//
	// Usage: Read only
	Tracer *xo.Tracer
}

// With will run the provided function with the specified context temporarily
// set on the context. This is especially useful together with transactions.
func (c *Context) With(ctx context.Context, fn func()) {
	// retain current context and tracer
	oc := c.Context
	ot := c.Tracer

	// swap tracer and context
	if c.Tracer != nil {
		c.Tracer, c.Context = xo.NewTracer(ctx)
	} else {
		c.Context = ctx
	}

	// yield
	fn()

	// switchback
	c.Context = oc
	c.Tracer = ot
}

// Defer will defer the provided handler and run it after all controller
// callbacks have been run at the specified stages.
func (c *Context) Defer(cb *Callback) {
	// check stage
	if cb.Stage <= c.Stage {
		panic("fire: invalid stage")
	}

	// ensure map
	if c.Defers == nil {
		c.Defers = map[Stage][]*Callback{}
	}

	// add callback for all stages
	for _, stage := range cb.Stage.Split() {
		c.Defers[stage] = append(c.Defers[stage], cb)
	}
}

// Query returns the composite query of Selector and Filter.
func (c *Context) Query() bson.M {
	// prepare sub queries
	var subQueries []bson.M

	// add selector if present
	if len(c.Selector) > 0 {
		subQueries = append(subQueries, c.Selector)
	}

	// add filters
	subQueries = append(subQueries, c.Filters...)

	// return empty query if no sub queries are present
	if len(subQueries) == 0 {
		return bson.M{}
	}

	// otherwise, return $and query
	return bson.M{"$and": subQueries}
}

// Modified will return whether the specified field has been changed. During an
// update operation the modification is checked against the original model. For
// all other operations, the field is checked against its zero value.
func (c *Context) Modified(field string) bool {
	// determine old value
	var oldValue interface{}
	if c.Original != nil {
		oldValue = stick.MustGet(c.Original, field)
	} else {
		oldValue = reflect.Zero(coal.GetMeta(c.Model).Fields[field].Type).Interface()
	}

	// get new value
	newValue := stick.MustGet(c.Model, field)

	return !reflect.DeepEqual(newValue, oldValue)
}

// Parse will decode a custom JSON body to the specified object.
func (c *Context) Parse(obj interface{}) error {
	// unmarshal json
	err := json.NewDecoder(c.HTTPRequest.Body).Decode(obj)
	if err == io.EOF {
		return xo.SF("incomplete request body")
	} else if err != nil {
		return xo.W(err)
	}

	return nil
}

// Respond will encode the provided value as JSON and write it to the client.
func (c *Context) Respond(value interface{}) error {
	// encode response
	bytes, err := json.Marshal(value)
	if err != nil {
		return xo.W(err)
	}

	// write token
	_, err = c.ResponseWriter.Write(bytes)
	if err != nil {
		return xo.W(err)
	}

	return nil
}
<commit_msg>corrected getter docs<commit_after>package fire

import (
	"context"
	"encoding/json"
	"io"
	"net/http"
	"reflect"

	"github.com/256dpi/jsonapi/v2"
	"github.com/256dpi/xo"
	"go.mongodb.org/mongo-driver/bson"

	"github.com/256dpi/fire/coal"
	"github.com/256dpi/fire/stick"
)

// An Operation indicates the purpose of a yield to a callback in the processing
// flow of an API request by a controller. These operations may occur multiple
// times during a single request.
type Operation int

// All the available operations.
const (
	// List operation will be used to authorize the loading of multiple
	// resources from a collection.
	//
	// Note: This operation is also used to load related resources.
	List Operation = 1 << iota

	// Find operation will be used to authorize the loading of a specific
	// resource from a collection.
	//
	// Note: This operation is also used to load a specific related resource.
	Find

	// Create operation will be used to authorize and validate the creation
	// of a new resource in a collection.
	Create

	// Update operation will be used to authorize the loading and validate
	// the updating of a specific resource in a collection.
	//
	// Note: Updates can include attributes, relationships or both.
	Update

	// Delete operation will be used to authorize the loading and validate
	// the deletion of a specific resource in a collection.
	Delete

	// CollectionAction operation will be used to authorize the execution
	// of a callback for a collection action.
	CollectionAction

	// ResourceAction operation will be used to authorize the execution of a
	// callback for a resource action.
	ResourceAction
)

// Read will return true when the operations only reads data.
func (o Operation) Read() bool {
	return o == List || o == Find
}

// Write will return true when the operation writes data.
func (o Operation) Write() bool {
	return o == Create || o == Update || o == Delete
}

// Action will return true when the operation is a collection or resource action.
func (o Operation) Action() bool {
	return o == CollectionAction || o == ResourceAction
}

// String returns the name of the operation.
func (o Operation) String() string {
	switch o {
	case List:
		return "List"
	case Find:
		return "Find"
	case Create:
		return "Create"
	case Update:
		return "Update"
	case Delete:
		return "Delete"
	case CollectionAction:
		return "CollectionAction"
	case ResourceAction:
		return "ResourceAction"
	}

	return ""
}

// Context carries the state of a request and allows callbacks to influence the
// processing of a request.
type Context struct {
	// The context that is cancelled when the timeout has been exceeded or the
	// underlying connection transport has been closed. It may also carry the
	// database session if a transaction is used.
	//
	// Values: lungo.ISessionContext?, trace.Span, *xo.Tracer
	context.Context

	// The custom data map.
	Data stick.Map

	// The current callback stage.
	Stage Stage

	// The deferred callbacks.
	Defers map[Stage][]*Callback

	// The current operation in process.
	//
	// Usage: Read only
	// Availability: Authorizers
	Operation Operation

	// The query that will be used during a List, Find, Update, Delete or
	// ResourceAction operation to select a list of models or a specific model.
	//
	// On Find, Update and Delete operations, the "_id" key is preset to the
	// resource id, while on forwarded List operations the relationship filter
	// is preset.
	//
	// Usage: Read only
	// Availability: Authorizers
	// Operations: !Create, !CollectionAction
	Selector bson.M

	// The filters that will be used during a List, Find, Update, Delete or
	// ResourceAction operation to further filter the selection of a list of
	// models or a specific model.
	//
	// On List operations, attribute and relationship filters are preset.
	//
	// Usage: Append only
	// Availability: Authorizers
	// Operations: !Create, !CollectionAction
	Filters []bson.M

	// The sorting that will be used during List.
	//
	// Usage: No Restriction
	// Availability: Authorizers
	// Operations: List
	Sorting []string

	// Only the whitelisted readable fields are exposed to the client as
	// attributes and relationships. Additionally, only readable fields can
	// be used for filtering and sorting.
	//
	// Usage: Reduce only
	// Availability: Authorizers
	// Operations: !Delete, !ResourceAction, !CollectionAction
	ReadableFields []string

	// Used instead of ReadableFields if set. Allows specifying readable fields
	// on a per model basis. The provided model may be nil if run before any
	// model has been loaded. Must be a subset of ReadableFields.
	GetReadableFields func(coal.Model) []string

	// Only the whitelisted writable fields can be altered by requests.
	//
	// Usage: Reduce only
	// Availability: Authorizers
	// Operations: Create, Update
	WritableFields []string

	// Used instead of WritableFields if set. Allows specifying writable fields
	// on a per model basis. Must be a subset of WritableFields.
	GetWritableFields func(coal.Model) []string

	// Only the whitelisted readable properties are exposed to the client as
	// attributes.
	//
	// Usage: Reduce only
	// Availability: Authorizers
	// Operations: !Delete, !ResourceActon, !CollectionAction
	ReadableProperties []string

	// Used instead of ReadableProperties if set. Allows specifying readable
	// properties on a per model basis. Must be a subset of ReadableProperties.
	GetReadableProperties func(coal.Model) []string

	// The filters that will be applied when loading has one and has many
	// relationships.
	//
	// Usage: Append only
	// Availability: Authorizers.
	// Operations: !Create, !CollectionAction
	RelationshipFilters map[string][]bson.M

	// The model that will be created, updated, deleted or is requested by a
	// resource action.
	//
	// Usage: Modify only
	// Availability: Validators
	// Operations: Create, Update, Delete, ResourceAction
	Model coal.Model

	// The models that will be returned for a List operation.
	//
	// Usage: Modify only
	// Availability: Decorators
	// Operations: List
	Models []coal.Model

	// The original model that is being updated. Can be used to lookup up
	// original values of changed fields.
	//
	// Usage: Ready only
	// Availability: Validators
	// Operations: Update
	Original coal.Model

	// The document that has been received by the client.
	//
	// Usage: Read only
	// Availability: Authorizers
	// Operations: !List, !Find, !CollectionAction, !ResourceAction
	Request *jsonapi.Document

	// The document that will be written to the client.
	//
	// Usage: Modify only
	// Availability: Notifiers
	// Operations: !CollectionAction, !ResourceAction
	Response *jsonapi.Document

	// The status code that will be written to the client.
	//
	// Usage: Modify only
	// Availability: Notifiers
	// Operations: !CollectionAction, !ResourceAction
	ResponseCode int

	// The store that is used to retrieve and persist the model.
	//
	// Usage: Read only
	Store *coal.Store

	// The underlying JSON-API request.
	//
	// Usage: Read only
	JSONAPIRequest *jsonapi.Request

	// The underlying HTTP request.
	//
	// Note: The path is not updated when a controller forwards a request to
	// a related controller.
	//
	// Usage: Read only
	HTTPRequest *http.Request

	// The underlying HTTP response writer. The response writer should only be
	// used during collection or resource actions to write a custom response.
	//
	// Usage: Read only
	ResponseWriter http.ResponseWriter

	// The controller that is managing the request.
	//
	// Usage: Read only
	Controller *Controller

	// The group that received the request.
	//
	// Usage: Read only
	Group *Group

	// The current tracer.
	//
	// Usage: Read only
	Tracer *xo.Tracer
}

// With will run the provided function with the specified context temporarily
// set on the context. This is especially useful together with transactions.
func (c *Context) With(ctx context.Context, fn func()) {
	// retain current context and tracer
	oc := c.Context
	ot := c.Tracer

	// swap tracer and context
	if c.Tracer != nil {
		c.Tracer, c.Context = xo.NewTracer(ctx)
	} else {
		c.Context = ctx
	}

	// yield
	fn()

	// switchback
	c.Context = oc
	c.Tracer = ot
}

// Defer will defer the provided handler and run it after all controller
// callbacks have been run at the specified stages.
func (c *Context) Defer(cb *Callback) {
	// check stage
	if cb.Stage <= c.Stage {
		panic("fire: invalid stage")
	}

	// ensure map
	if c.Defers == nil {
		c.Defers = map[Stage][]*Callback{}
	}

	// add callback for all stages
	for _, stage := range cb.Stage.Split() {
		c.Defers[stage] = append(c.Defers[stage], cb)
	}
}

// Query returns the composite query of Selector and Filter.
func (c *Context) Query() bson.M {
	// prepare sub queries
	var subQueries []bson.M

	// add selector if present
	if len(c.Selector) > 0 {
		subQueries = append(subQueries, c.Selector)
	}

	// add filters
	subQueries = append(subQueries, c.Filters...)

	// return empty query if no sub queries are present
	if len(subQueries) == 0 {
		return bson.M{}
	}

	// otherwise, return $and query
	return bson.M{"$and": subQueries}
}

// Modified will return whether the specified field has been changed. During an
// update operation the modification is checked against the original model. For
// all other operations, the field is checked against its zero value.
func (c *Context) Modified(field string) bool {
	// determine old value
	var oldValue interface{}
	if c.Original != nil {
		oldValue = stick.MustGet(c.Original, field)
	} else {
		oldValue = reflect.Zero(coal.GetMeta(c.Model).Fields[field].Type).Interface()
	}

	// get new value
	newValue := stick.MustGet(c.Model, field)

	return !reflect.DeepEqual(newValue, oldValue)
}

// Parse will decode a custom JSON body to the specified object.
func (c *Context) Parse(obj interface{}) error {
	// unmarshal json
	err := json.NewDecoder(c.HTTPRequest.Body).Decode(obj)
	if err == io.EOF {
		return xo.SF("incomplete request body")
	} else if err != nil {
		return xo.W(err)
	}

	return nil
}

// Respond will encode the provided value as JSON and write it to the client.
func (c *Context) Respond(value interface{}) error {
	// encode response
	bytes, err := json.Marshal(value)
	if err != nil {
		return xo.W(err)
	}

	// write token
	_, err = c.ResponseWriter.Write(bytes)
	if err != nil {
		return xo.W(err)
	}

	return nil
}
<|endoftext|>


<commit_before>package app

import (
	"github.com/generationtux/brizo/auth"
	"github.com/go-zoo/bone"
)

// ConfigureRoutes will configure the HTTP routes and appropriate handlers
func ConfigureRoutes() *bone.Mux {
	router := bone.New()

	router.GetFunc("/", rootHandler)

	// Javascript UI
	router.GetFunc("/app", uiHandler)

	// Static Files
	router.Get("/dist/", http.StripPrefix("/dist/", http.FileServer(http.Dir("ui/dist/"))))

	// Healthz endpoint
	router.GetFunc("/healthz", healthzHandler)

	router.GetFunc("/login", auth.AuthMainHandler)
	router.GetFunc("/o/auth/login/github", auth.AuthGithubHandler)
	router.GetFunc("/o/auth/callback/github", auth.AuthGithubCallbackHandler)

	return router
}
<commit_msg>added net/http package back<commit_after>package app

import (
    "net/http"

    "github.com/generationtux/brizo/auth"
	"github.com/go-zoo/bone"
)

// ConfigureRoutes will configure the HTTP routes and appropriate handlers
func ConfigureRoutes() *bone.Mux {
	router := bone.New()

	router.GetFunc("/", rootHandler)

	// Javascript UI
	router.GetFunc("/app", uiHandler)

	// Static Files
	router.Get("/dist/", http.StripPrefix("/dist/", http.FileServer(http.Dir("ui/dist/"))))

	// Healthz endpoint
	router.GetFunc("/healthz", healthzHandler)

	router.GetFunc("/login", auth.AuthMainHandler)
	router.GetFunc("/o/auth/login/github", auth.AuthGithubHandler)
	router.GetFunc("/o/auth/callback/github", auth.AuthGithubCallbackHandler)

	return router
}
<|endoftext|>


<commit_before>package namespace

import (
	"fmt"
	"strings"
	"time"

	ds "github.com/jbenet/go-datastore"
	ktds "github.com/jbenet/go-datastore/keytransform"
	dsq "github.com/jbenet/go-datastore/query"
)

// PrefixTransform constructs a KeyTransform with a pair of functions that
// add or remove the given prefix key.
//
// Warning: will panic if prefix not found when it should be there. This is
// to avoid insidious data inconsistency errors.
func PrefixTransform(prefix ds.Key) ktds.KeyTransform {
	return &ktds.Pair{

		// Convert adds the prefix
		Convert: func(k ds.Key) ds.Key {
			return prefix.Child(k)
		},

		// Invert removes the prefix. panics if prefix not found.
		Invert: func(k ds.Key) ds.Key {
			if !prefix.IsAncestorOf(k) {
				fmt.Errorf("Expected prefix (%s) in key (%s)", prefix, k)
				panic("expected prefix not found")
			}

			s := strings.TrimPrefix(k.String(), prefix.String())
			return ds.NewKey(s)
		},
	}
}

// Wrap wraps a given datastore with a key-prefix.
func Wrap(child ds.Datastore, prefix ds.Key) ktds.Datastore {
	if child == nil {
		panic("child (ds.Datastore) is nil")
	}

	d := ktds.Wrap(child, PrefixTransform(prefix))
	return &datastore{Datastore: d, raw: child, prefix: prefix}
}

type datastore struct {
	prefix ds.Key
	raw    ds.Datastore
	ktds.Datastore
}

// Query implements Query, inverting keys on the way back out.
func (d *datastore) Query(q dsq.Query) (dsq.Results, error) {
	qr, err := d.raw.Query(q)
	if err != nil {
		return nil, err
	}

	ch := make(chan dsq.Result)
	go func() {
		defer close(ch)
		defer qr.Close()

		for r := range qr.Next() {
			if r.Error != nil {
				ch <- r
				continue
			}

			k := ds.NewKey(r.Entry.Key)
			if !d.prefix.IsAncestorOf(k) {
				continue
			}

			r.Entry.Key = d.Datastore.InvertKey(k).String()
			ch <- r
			<-time.After(100 * time.Millisecond)
		}
	}()

	return dsq.DerivedResults(qr, ch), nil
}
<commit_msg>bugfix: had left a debugging time.After in<commit_after>package namespace

import (
	"fmt"
	"strings"

	ds "github.com/jbenet/go-datastore"
	ktds "github.com/jbenet/go-datastore/keytransform"
	dsq "github.com/jbenet/go-datastore/query"
)

// PrefixTransform constructs a KeyTransform with a pair of functions that
// add or remove the given prefix key.
//
// Warning: will panic if prefix not found when it should be there. This is
// to avoid insidious data inconsistency errors.
func PrefixTransform(prefix ds.Key) ktds.KeyTransform {
	return &ktds.Pair{

		// Convert adds the prefix
		Convert: func(k ds.Key) ds.Key {
			return prefix.Child(k)
		},

		// Invert removes the prefix. panics if prefix not found.
		Invert: func(k ds.Key) ds.Key {
			if !prefix.IsAncestorOf(k) {
				fmt.Errorf("Expected prefix (%s) in key (%s)", prefix, k)
				panic("expected prefix not found")
			}

			s := strings.TrimPrefix(k.String(), prefix.String())
			return ds.NewKey(s)
		},
	}
}

// Wrap wraps a given datastore with a key-prefix.
func Wrap(child ds.Datastore, prefix ds.Key) ktds.Datastore {
	if child == nil {
		panic("child (ds.Datastore) is nil")
	}

	d := ktds.Wrap(child, PrefixTransform(prefix))
	return &datastore{Datastore: d, raw: child, prefix: prefix}
}

type datastore struct {
	prefix ds.Key
	raw    ds.Datastore
	ktds.Datastore
}

// Query implements Query, inverting keys on the way back out.
func (d *datastore) Query(q dsq.Query) (dsq.Results, error) {
	qr, err := d.raw.Query(q)
	if err != nil {
		return nil, err
	}

	ch := make(chan dsq.Result)
	go func() {
		defer close(ch)
		defer qr.Close()

		for r := range qr.Next() {
			if r.Error != nil {
				ch <- r
				continue
			}

			k := ds.NewKey(r.Entry.Key)
			if !d.prefix.IsAncestorOf(k) {
				continue
			}

			r.Entry.Key = d.Datastore.InvertKey(k).String()
			ch <- r
		}
	}()

	return dsq.DerivedResults(qr, ch), nil
}
<|endoftext|>


<commit_before>package gron

import (
	"fmt"
	"reflect"
	"sort"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/roylee0704/gron/xtime"
)

// Most test jobs scheduled to run at second 1.
// Test expects to fail after OneSecond: 1.01 seconds.
const OneSecond = 1*time.Second + 10*time.Millisecond

// start cron, stop cron successfully.
func TestNoEntries(t *testing.T) {
	cron := New()
	cron.Start()

	select {
	case <-time.After(OneSecond):
		t.FailNow()
	case <-stop(cron):
	}
}

// add a job, start a cron, expect it runs
func TestAddBeforeRun(t *testing.T) {
	done := make(chan struct{})
	cron := New()
	cron.AddFunc(Every(1*time.Second), func() { done <- struct{}{} })
	cron.Start()
	defer cron.Stop()

	select {
	case <-time.After(OneSecond):
		t.FailNow()
	case <-done:
	}
}

// start a cron, add a job, expect it runs
func TestAddWhileRun(t *testing.T) {
	done := make(chan struct{})
	cron := New()
	cron.Start()
	defer cron.Stop()

	cron.AddFunc(Every(1*time.Second), func() { done <- struct{}{} })

	select {
	case <-time.After(OneSecond):
		t.FailNow()
	case <-done:
	}
}

// Test that invoking stop() before start() silently returns,
// without blocking the stop channel
func TestStopWithoutStart(t *testing.T) {
	cron := New()
	cron.Stop()
}

// start cron, stop cron, add a job, job shouldn't run.
func TestJobDontRunAfterStop(t *testing.T) {
	wg := &sync.WaitGroup{}
	wg.Add(1)

	cron := New()
	cron.Start()
	cron.Stop()
	cron.AddFunc(Every(1*time.Second), func() { wg.Done() })

	select {
	case <-wait(wg):
		t.FailNow()
	case <-time.After(OneSecond):
	}
}

// Test that after first entry has run, subsequent entries are checked and run
// if possessed same schedule as first entry.
func TestConcurrentSchedules(t *testing.T) {
	wg := &sync.WaitGroup{}
	wg.Add(3)
	cron := New()

	cron.AddFunc(Every(5*time.Minute), func() {})
	cron.AddFunc(Every(1*time.Second), func() { wg.Done() })
	cron.AddFunc(Every(1*time.Second), func() { wg.Done() })
	cron.AddFunc(Every(1*time.Second), func() { wg.Done() })

	cron.Start()
	defer cron.Stop()

	select {
	case <-time.After(OneSecond):
		t.FailNow()
	case <-wait(wg):
	}
}

// Test that periodic job runs again, after p interval.
func TestRunJobTwice(t *testing.T) {
	wg := &sync.WaitGroup{}
	wg.Add(2)

	cron := New()
	cron.Start()
	defer cron.Stop()

	cron.AddFunc(Every(1*xtime.Day), func() { wg.Done() })
	cron.AddFunc(Every(1*xtime.Week), func() { wg.Done() })
	cron.AddFunc(Every(1*time.Second), func() { wg.Done() })

	select {
	case <-time.After(2 * OneSecond):
		t.FailNow()
	case <-wait(wg):
	}
}

// arbitrary job struct, with god's view enabled.
type arbitraryJob struct {
	wg *sync.WaitGroup // god's update
	id string          // god's record
}

// implements runnable
func (j arbitraryJob) Run() {
	if j.wg != nil {
		j.wg.Done()
	}
}

// simple test on job implemeter
func TestJobImplementer(t *testing.T) {
	wg := &sync.WaitGroup{}
	wg.Add(2)

	cron := New()
	cron.Add(Every(1*xtime.Day), arbitraryJob{wg, "job-1"}) // merely distraction
	cron.Add(Every(1*time.Second), arbitraryJob{wg, "job-2"})
	cron.Add(Every(1*xtime.Week), arbitraryJob{wg, "job-3"}) // merely distraction

	cron.Start()
	defer cron.Stop()

	select {
	case <-time.After(2 * OneSecond):
		t.FailNow()
	case <-wait(wg):
	}
}

// Test that entries are in correct sequence after n run.
func TestEntryOrdering(t *testing.T) {
	wg := &sync.WaitGroup{}
	wg.Add(2)

	cron := New()

	cron.Add(Every(40*time.Second), arbitraryJob{wg, "job-1"})
	cron.Add(Every(1*time.Second), arbitraryJob{wg, "job-2"})
	cron.Add(Every(1*xtime.Week), arbitraryJob{wg, "job-3"})
	cron.Add(Every(12*time.Second), arbitraryJob{wg, "job-4"})
	cron.Add(Every(8*time.Second), arbitraryJob{wg, "job-5"})

	cron.Start()
	select {
	case <-time.After(2 * OneSecond):
		t.FailNow()
	case <-wait(wg):
	}
	cron.Stop()

	want := []string{"job-2", "job-5", "job-4", "job-1", "job-3"}
	var got []string
	for _, e := range cron.entries {
		got = append(got, e.Job.(arbitraryJob).id)
	}

	for i := range got {
		if want[i] != got[i] {
			t.Errorf("incorrect sequencing: (want) %q != %q (got)", want, got)
			break
		}
	}
}

// Test that entries are chronologically sorted
func TestByTimeSort(t *testing.T) {
	tests := []struct {
		entries string
		want    string
	}{
		// simple cases
		{"10:05, 10:04, 10:03", "10:03, 10:04, 10:05"},
		{"10:05, 10:04, 10:03", "10:03, 10:04, 10:05"},

		// hours dominate
		{"7:00, 8:00, 9:00", "7:00, 8:00, 9:00"},
		{"9:00, 8:00, 7:00", "7:00, 8:00, 9:00"},
		{"9:00, 8:49, 8:09", "8:09, 8:49, 9:00"},

		// seconds dominate
		{"00:00:01, 00:00:03, 00:00:30", "00:00:01, 00:00:03, 00:00:30"},
		{"00:00:03, 00:00:01, 00:00:30", "00:00:01, 00:00:03, 00:00:30"},
		{"00:05:10, 00:04:20, 00:03:30", "00:03:30, 00:04:20, 00:05:10"},

		// days dominate
		{
			"Wed Jun 8 9:05 2016, Tue Jun 7 8:04 2016, Wed Jun 8 9:01 2016",
			"Tue Jun 7 8:04 2016, Wed Jun 8 9:01 2016, Wed Jun 8 9:05 2016",
		},

		// months dominate
		{
			"Sun Jun 4 9:05 2016, Sun Feb 7 8:04 2016, Sun May 8 9:01 2016",
			"Sun Feb 7 8:04 2016, Sun May 8 9:01 2016, Sun Jun 4 9:05 2016",
		},

		// zero hours sort as intended
		{"00:00, 00:00, 00:10", "00:00, 00:00, 00:10"},

		// zero minutes sort as intended
		{"00:00:00, 00:00:00, 00:00:10", "00:00:00, 00:00:00, 00:00:10"},

		// zero times (uninitialised time) should push to back of queue.
		{
			"2016-01-01 00:00, 2016-01-01 00:00, 0001-01-01 00:00",
			"2016-01-01 00:00, 2016-01-01 00:00, 0001-01-01 00:00",
		},
		{
			"2016-01-01 00:01, 2016-01-01 00:00, 0001-01-01 00:00",
			"2016-01-01 00:00, 2016-01-01 00:01, 0001-01-01 00:00",
		},
		{
			"0001-01-01 00:00, 2016-01-01 00:01, 2016-01-01 00:00",
			"2016-01-01 00:00, 2016-01-01 00:01, 0001-01-01 00:00",
		},
		{
			"2016-01-01 00:01, 0001-01-01 00:00, 2016-01-01 00:00",
			"2016-01-01 00:00, 2016-01-01 00:01, 0001-01-01 00:00",
		},
		{
			"0001-01-01 00:00, 0001-01-01 00:00, 2016-01-01 00:00",
			"2016-01-01 00:00, 0001-01-01 00:00, 0001-01-01 00:00",
		},
	}

	for i, test := range tests {

		got := mockEntries(getTimes(test.entries))
		sort.Sort(byTime(got))

		want := mockEntries(getTimes(test.want))

		if !reflect.DeepEqual(got, want) {
			t.Errorf("entries[%d] out of order: \n%v (want)\n%v (got)", i, toS(want), toS(got))
		}
	}
}

func mockEntries(nexts []time.Time) []*Entry {
	var entries []*Entry

	for _, n := range nexts {
		entries = append(entries, &Entry{Next: n})
	}
	return entries
}

// getTimes splits comma-separated time.
func getTimes(s string) []time.Time {

	ts := strings.Split(s, ",")
	ret := make([]time.Time, len(ts))

	for i, t := range ts {
		ret[i] = getTime(strings.Trim(t, " "))
	}
	return ret
}

// wrapper to stringify time instant t
type toS []*Entry

func (entries toS) String() string {
	var ret string
	for _, e := range entries {
		ret += fmt.Sprintf("[%v] ", e.Next)
	}
	return ret
}

// wait signals back when WaitGroup has wait().
func wait(wg *sync.WaitGroup) <-chan bool {
	done := make(chan bool)
	go func() {
		wg.Wait()
		done <- true
	}()
	return done
}

// stop signals back when cron has stop()
func stop(c *Cron) <-chan bool {
	done := make(chan bool)
	go func() {
		c.Stop()
		done <- true
	}()
	return done
}
<commit_msg>update comments for brevity<commit_after>package gron

import (
	"fmt"
	"reflect"
	"sort"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/roylee0704/gron/xtime"
)

// Most test jobs scheduled to run at 1 second mark.
// Test expects to fail after OneSecond: 1.01 seconds.
const OneSecond = 1*time.Second + 10*time.Millisecond

// start cron, stop cron successfully.
func TestNoEntries(t *testing.T) {
	cron := New()
	cron.Start()

	select {
	case <-time.After(OneSecond):
		t.FailNow()
	case <-stop(cron):
	}
}

// add a job, start a cron, expect it runs
func TestAddBeforeRun(t *testing.T) {
	done := make(chan struct{})
	cron := New()
	cron.AddFunc(Every(1*time.Second), func() { done <- struct{}{} })
	cron.Start()
	defer cron.Stop()

	select {
	case <-time.After(OneSecond):
		t.FailNow()
	case <-done:
	}
}

// start a cron, add a job, expect it runs
func TestAddWhileRun(t *testing.T) {
	done := make(chan struct{})
	cron := New()
	cron.Start()
	defer cron.Stop()

	cron.AddFunc(Every(1*time.Second), func() { done <- struct{}{} })

	select {
	case <-time.After(OneSecond):
		t.FailNow()
	case <-done:
	}
}

// Test that invoking stop() before start() silently returns,
// without blocking the stop channel
func TestStopWithoutStart(t *testing.T) {
	cron := New()
	cron.Stop()
}

// start cron, stop cron, add a job, verify job shouldn't run.
func TestJobsDontRunAfterStop(t *testing.T) {
	wg := &sync.WaitGroup{}
	wg.Add(1)

	cron := New()
	cron.Start()
	cron.Stop()
	cron.AddFunc(Every(1*time.Second), func() { wg.Done() })

	select {
	case <-time.After(OneSecond):
		// no job has run
	case <-wait(wg):
		t.FailNow()

	}
}

// Adds an immediate entry, make sure it runs immediately.
// Subsequent entries are checked and run at same instant, iff possessed
// same schedule as first entry.
func TestMultipleEntries(t *testing.T) {
	wg := &sync.WaitGroup{}
	wg.Add(3)
	cron := New()

	cron.AddFunc(Every(5*time.Minute), func() {})
	cron.AddFunc(Every(1*time.Second), func() { wg.Done() })
	cron.AddFunc(Every(1*time.Second), func() { wg.Done() })
	cron.AddFunc(Every(1*time.Second), func() { wg.Done() })
	cron.AddFunc(Every(4*xtime.Week), func() {})

	cron.Start()
	defer cron.Stop()

	select {
	case <-time.After(OneSecond):
		t.FailNow()
	case <-wait(wg):
	}
}

// Test that periodic job runs again, after p interval.
func TestRunJobTwice(t *testing.T) {
	wg := &sync.WaitGroup{}
	wg.Add(2)

	cron := New()
	cron.Start()
	defer cron.Stop()

	cron.AddFunc(Every(1*xtime.Day), func() { wg.Done() })
	cron.AddFunc(Every(1*xtime.Week), func() { wg.Done() })
	cron.AddFunc(Every(1*time.Second), func() { wg.Done() })

	select {
	case <-time.After(2 * OneSecond):
		t.FailNow()
	case <-wait(wg):
	}
}

// arbitrary job struct, with god's view enabled.
type arbitraryJob struct {
	wg *sync.WaitGroup // god's update
	id string          // god's record
}

// implements runnable
func (j arbitraryJob) Run() {
	if j.wg != nil {
		j.wg.Done()
	}
}

// simple test on job types
func TestJobImplementer(t *testing.T) {
	wg := &sync.WaitGroup{}
	wg.Add(2)

	cron := New()
	cron.Add(Every(1*xtime.Day), arbitraryJob{wg, "job-1"}) // merely distraction
	cron.Add(Every(1*time.Second), arbitraryJob{wg, "job-2"})
	cron.Add(Every(1*xtime.Week), arbitraryJob{wg, "job-3"}) // merely distraction

	cron.Start()
	defer cron.Stop()

	select {
	case <-time.After(2 * OneSecond):
		t.FailNow()
	case <-wait(wg):
	}
}

// Test that entries are in correct sequence after n run.
func TestEntryOrdering(t *testing.T) {
	wg := &sync.WaitGroup{}
	wg.Add(2)

	cron := New()

	cron.Add(Every(40*time.Second), arbitraryJob{wg, "job-1"})
	cron.Add(Every(1*time.Second), arbitraryJob{wg, "job-2"})
	cron.Add(Every(1*xtime.Week), arbitraryJob{wg, "job-3"})
	cron.Add(Every(12*time.Second), arbitraryJob{wg, "job-4"})
	cron.Add(Every(8*time.Second), arbitraryJob{wg, "job-5"})

	cron.Start()
	select {
	case <-time.After(2 * OneSecond):
		t.FailNow()
	case <-wait(wg):
	}
	cron.Stop()

	want := []string{"job-2", "job-5", "job-4", "job-1", "job-3"}
	var got []string
	for _, e := range cron.entries {
		got = append(got, e.Job.(arbitraryJob).id)
	}

	for i := range got {
		if want[i] != got[i] {
			t.Errorf("incorrect sequencing: (want) %q != %q (got)", want, got)
			break
		}
	}
}

// Test that entries are chronologically sorted
func TestByTimeSort(t *testing.T) {
	tests := []struct {
		entries string
		want    string
	}{
		// simple cases
		{"10:05, 10:04, 10:03", "10:03, 10:04, 10:05"},
		{"10:05, 10:04, 10:03", "10:03, 10:04, 10:05"},

		// hours dominate
		{"7:00, 8:00, 9:00", "7:00, 8:00, 9:00"},
		{"9:00, 8:00, 7:00", "7:00, 8:00, 9:00"},
		{"9:00, 8:49, 8:09", "8:09, 8:49, 9:00"},

		// seconds dominate
		{"00:00:01, 00:00:03, 00:00:30", "00:00:01, 00:00:03, 00:00:30"},
		{"00:00:03, 00:00:01, 00:00:30", "00:00:01, 00:00:03, 00:00:30"},
		{"00:05:10, 00:04:20, 00:03:30", "00:03:30, 00:04:20, 00:05:10"},

		// days dominate
		{
			"Wed Jun 8 9:05 2016, Tue Jun 7 8:04 2016, Wed Jun 8 9:01 2016",
			"Tue Jun 7 8:04 2016, Wed Jun 8 9:01 2016, Wed Jun 8 9:05 2016",
		},

		// months dominate
		{
			"Sun Jun 4 9:05 2016, Sun Feb 7 8:04 2016, Sun May 8 9:01 2016",
			"Sun Feb 7 8:04 2016, Sun May 8 9:01 2016, Sun Jun 4 9:05 2016",
		},

		// zero hours sort as intended
		{"00:00, 00:00, 00:10", "00:00, 00:00, 00:10"},

		// zero minutes sort as intended
		{"00:00:00, 00:00:00, 00:00:10", "00:00:00, 00:00:00, 00:00:10"},

		// zero times (uninitialised time) should push to back of queue.
		{
			"2016-01-01 00:00, 2016-01-01 00:00, 0001-01-01 00:00",
			"2016-01-01 00:00, 2016-01-01 00:00, 0001-01-01 00:00",
		},
		{
			"2016-01-01 00:01, 2016-01-01 00:00, 0001-01-01 00:00",
			"2016-01-01 00:00, 2016-01-01 00:01, 0001-01-01 00:00",
		},
		{
			"0001-01-01 00:00, 2016-01-01 00:01, 2016-01-01 00:00",
			"2016-01-01 00:00, 2016-01-01 00:01, 0001-01-01 00:00",
		},
		{
			"2016-01-01 00:01, 0001-01-01 00:00, 2016-01-01 00:00",
			"2016-01-01 00:00, 2016-01-01 00:01, 0001-01-01 00:00",
		},
		{
			"0001-01-01 00:00, 0001-01-01 00:00, 2016-01-01 00:00",
			"2016-01-01 00:00, 0001-01-01 00:00, 0001-01-01 00:00",
		},
	}

	for i, test := range tests {

		got := mockEntries(getTimes(test.entries))
		sort.Sort(byTime(got))

		want := mockEntries(getTimes(test.want))

		if !reflect.DeepEqual(got, want) {
			t.Errorf("entries[%d] out of order: \n%v (want)\n%v (got)", i, toS(want), toS(got))
		}
	}
}

func mockEntries(nexts []time.Time) []*Entry {
	var entries []*Entry

	for _, n := range nexts {
		entries = append(entries, &Entry{Next: n})
	}
	return entries
}

// getTimes splits comma-separated time.
func getTimes(s string) []time.Time {

	ts := strings.Split(s, ",")
	ret := make([]time.Time, len(ts))

	for i, t := range ts {
		ret[i] = getTime(strings.Trim(t, " "))
	}
	return ret
}

// wrapper to stringify time instant t
type toS []*Entry

func (entries toS) String() string {
	var ret string
	for _, e := range entries {
		ret += fmt.Sprintf("[%v] ", e.Next)
	}
	return ret
}

// wait signals back when WaitGroup has wait().
func wait(wg *sync.WaitGroup) <-chan bool {
	done := make(chan bool)
	go func() {
		wg.Wait()
		done <- true
	}()
	return done
}

// stop signals back when cron has stop()
func stop(c *Cron) <-chan bool {
	done := make(chan bool)
	go func() {
		c.Stop()
		done <- true
	}()
	return done
}
<|endoftext|>