<commit_before>// Copyright 2015-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
//     http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.

package docker

import (
	"io"
	"strings"
	"time"

	"golang.org/x/net/context"

	"github.com/aws/amazon-ecs-init/ecs-init/backoff"
	"github.com/aws/amazon-ecs-init/ecs-init/config"

	log "github.com/cihub/seelog"
	godocker "github.com/fsouza/go-dockerclient"
)

const (
	logDir   = "/log"
	dataDir  = "/data"
	readOnly = ":ro"
	// set default to /var/run instead of /var/run/docker.sock in case
	// /var/run/docker.sock is deleted and recreated outside the container
	defaultDockerEndpoint = "/var/run"
	// networkMode specifies the networkmode to create the agent container
	networkMode = "host"
	// usernsMode specifies the userns mode to create the agent container
	usernsMode = "host"
	// minBackoffDuration specifies the minimum backoff duration for ping to
	// return a success response from the docker socket
	minBackoffDuration = time.Second
	// maxBackoffDuration specifies the maximum backoff duration for ping to
	// return a success response from docker socket
	maxBackoffDuration = 5 * time.Second
	// backoffJitterMultiple specifies the backoff jitter multiplier
	// coefficient when pinging the docker socket
	backoffJitterMultiple = 0.2
	// backoffMultiple specifies the backoff multiplier coefficient when
	// pinging the docker socket
	backoffMultiple = 2
	// maxRetries specifies the maximum number of retries for ping to return
	// a successful response from the docker socket
	maxRetries = 5
)

// Client enables business logic for running the Agent inside Docker
type Client struct {
	docker dockerclient
	fs     fileSystem
}

// NewClient reutrns a new Client
func NewClient() (*Client, error) {
	// Create a backoff for pinging the docker socker. This should result in 17-19
	// seconds of delay in the worst-case between different actions that depend on
	// docker
	pingBackoff := backoff.NewBackoff(minBackoffDuration, maxBackoffDuration, backoffJitterMultiple,
		backoffMultiple, maxRetries)
	client, err := newDockerClient(godockerClientFactory{}, pingBackoff)
	if err != nil {
		return nil, err
	}
	return &Client{
		docker: client,
		fs:     standardFS,
	}, nil
}

// IsAgentImageLoaded returns true if the Agent image is loaded in Docker
func (c *Client) IsAgentImageLoaded() (bool, error) {
	images, err := c.docker.ListImages(godocker.ListImagesOptions{
		All: true,
	})
	if err != nil {
		return false, err
	}
	for _, image := range images {
		for _, repoTag := range image.RepoTags {
			if repoTag == config.AgentImageName {
				return true, nil
			}
		}
	}
	return false, nil
}

// LoadImage loads an io.Reader into Docker
func (c *Client) LoadImage(image io.Reader) error {
	return c.docker.LoadImage(godocker.LoadImageOptions{image, context.TODO()})
}

// RemoveExistingAgentContainer remvoes any existing container named
// "ecs-agent" or returns without error if none is found
func (c *Client) RemoveExistingAgentContainer() error {
	containerToRemove, err := c.findAgentContainer()
	if err != nil {
		return err
	}
	if containerToRemove == "" {
		log.Info("No existing agent container to remove.")
		return nil
	}
	log.Infof("Removing existing agent container ID: %s", containerToRemove)
	err = c.docker.RemoveContainer(godocker.RemoveContainerOptions{
		ID:    containerToRemove,
		Force: true,
	})
	return err
}

func (c *Client) findAgentContainer() (string, error) {
	// TODO pagination
	containers, err := c.docker.ListContainers(godocker.ListContainersOptions{
		All: true,
		Filters: map[string][]string{
			"status": []string{},
		},
	})
	if err != nil {
		return "", err
	}
	agentContainerName := "/" + config.AgentContainerName
	for _, container := range containers {
		for _, name := range container.Names {
			log.Infof("Container name: %s", name)
			if name == agentContainerName {
				return container.ID, nil
			}
		}
	}
	return "", nil
}

// StartAgent starts the Agent in Docker and returns the exit code from the container
func (c *Client) StartAgent() (int, error) {
	container, err := c.docker.CreateContainer(godocker.CreateContainerOptions{
		Name:       config.AgentContainerName,
		Config:     c.getContainerConfig(),
		HostConfig: c.getHostConfig(),
	})
	if err != nil {
		return 0, err
	}
	err = c.docker.StartContainer(container.ID, nil)
	if err != nil {
		return 0, err
	}
	return c.docker.WaitContainer(container.ID)
}

func (c *Client) getContainerConfig() *godocker.Config {

	// default environment variables
	envVariables := map[string]string{
		"ECS_LOGFILE":                           logDir + "/" + config.AgentLogFile,
		"ECS_DATADIR":                           dataDir,
		"ECS_AGENT_CONFIG_FILE_PATH":            config.AgentJSONConfigFile(),
		"ECS_UPDATE_DOWNLOAD_DIR":               config.CacheDirectory(),
		"ECS_UPDATES_ENABLED":                   "true",
		"ECS_AVAILABLE_LOGGING_DRIVERS":         `["json-file","syslog","awslogs"]`,
		"ECS_ENABLE_TASK_IAM_ROLE":              "true",
		"ECS_ENABLE_TASK_IAM_ROLE_NETWORK_HOST": "true",
	}

	// merge in user-supplied environment variables
	for envKey, envValue := range c.loadEnvVariables() {
		envVariables[envKey] = envValue
	}

	var env []string
	for envKey, envValue := range envVariables {
		env = append(env, envKey+"="+envValue)
	}

	return &godocker.Config{
		Env:   env,
		Image: config.AgentImageName,
	}
}

func (c *Client) loadEnvVariables() map[string]string {
	envVariables := make(map[string]string)

	file, err := c.fs.ReadFile(config.AgentConfigFile())
	if err != nil {
		return envVariables
	}

	lines := strings.Split(strings.TrimSpace(string(file)), "\n")
	for _, line := range lines {
		parts := strings.SplitN(strings.TrimSpace(line), "=", 2)
		if len(parts) != 2 {
			continue
		}
		envVariables[parts[0]] = parts[1]
	}

	return envVariables
}

func (c *Client) getHostConfig() *godocker.HostConfig {
	dockerEndpointAgent := defaultDockerEndpoint
	dockerUnixSocketSourcePath, fromEnv := config.DockerUnixSocket()
	if fromEnv {
		dockerEndpointAgent = "/var/run/docker.sock"
	}

	binds := []string{
		dockerUnixSocketSourcePath + ":" + dockerEndpointAgent,
		config.LogDirectory() + ":" + logDir,
		config.AgentDataDirectory() + ":" + dataDir,
		config.AgentConfigDirectory() + ":" + config.AgentConfigDirectory(),
		config.CacheDirectory() + ":" + config.CacheDirectory(),
	}
	return &godocker.HostConfig{
		Binds:       binds,
		NetworkMode: networkMode,
		UsernsMode:  usernsMode,
	}
}

// StopAgent stops the Agent in docker if one is running
func (c *Client) StopAgent() error {
	id, err := c.findAgentContainer()
	if err != nil {
		return err
	}
	if id == "" {
		log.Info("No running Agent to stop")
		return nil
	}
	stopContainerTimeoutSeconds := uint(10)
	return c.docker.StopContainer(id, stopContainerTimeoutSeconds)
}
<commit_msg>Adding CAP_NET_ADMIN for starting agent<commit_after>// Copyright 2015-2017 Amazon.com, Inc. or its affiliates. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License"). You may
// not use this file except in compliance with the License. A copy of the
// License is located at
//
//     http://aws.amazon.com/apache2.0/
//
// or in the "license" file accompanying this file. This file is distributed
// on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
// express or implied. See the License for the specific language governing
// permissions and limitations under the License.

package docker

import (
	"io"
	"strings"
	"time"

	"golang.org/x/net/context"

	"github.com/aws/amazon-ecs-init/ecs-init/backoff"
	"github.com/aws/amazon-ecs-init/ecs-init/config"

	log "github.com/cihub/seelog"
	godocker "github.com/fsouza/go-dockerclient"
)

const (
	logDir   = "/log"
	dataDir  = "/data"
	readOnly = ":ro"
	// set default to /var/run instead of /var/run/docker.sock in case
	// /var/run/docker.sock is deleted and recreated outside the container
	defaultDockerEndpoint = "/var/run"
	// networkMode specifies the networkmode to create the agent container
	networkMode = "host"
	// usernsMode specifies the userns mode to create the agent container
	usernsMode = "host"
	// minBackoffDuration specifies the minimum backoff duration for ping to
	// return a success response from the docker socket
	minBackoffDuration = time.Second
	// maxBackoffDuration specifies the maximum backoff duration for ping to
	// return a success response from docker socket
	maxBackoffDuration = 5 * time.Second
	// backoffJitterMultiple specifies the backoff jitter multiplier
	// coefficient when pinging the docker socket
	backoffJitterMultiple = 0.2
	// backoffMultiple specifies the backoff multiplier coefficient when
	// pinging the docker socket
	backoffMultiple = 2
	// maxRetries specifies the maximum number of retries for ping to return
	// a successful response from the docker socket
	maxRetries = 5
	// CAP_NET_ADMIN to start agent
	// For more information on capabilities, please read this manpage:
	// http://man7.org/linux/man-pages/man7/capabilities.7.html
	CAP_NET_ADMIN = "NET_ADMIN"
)

// Client enables business logic for running the Agent inside Docker
type Client struct {
	docker dockerclient
	fs     fileSystem
}

// NewClient reutrns a new Client
func NewClient() (*Client, error) {
	// Create a backoff for pinging the docker socker. This should result in 17-19
	// seconds of delay in the worst-case between different actions that depend on
	// docker
	pingBackoff := backoff.NewBackoff(minBackoffDuration, maxBackoffDuration, backoffJitterMultiple,
		backoffMultiple, maxRetries)
	client, err := newDockerClient(godockerClientFactory{}, pingBackoff)
	if err != nil {
		return nil, err
	}
	return &Client{
		docker: client,
		fs:     standardFS,
	}, nil
}

// IsAgentImageLoaded returns true if the Agent image is loaded in Docker
func (c *Client) IsAgentImageLoaded() (bool, error) {
	images, err := c.docker.ListImages(godocker.ListImagesOptions{
		All: true,
	})
	if err != nil {
		return false, err
	}
	for _, image := range images {
		for _, repoTag := range image.RepoTags {
			if repoTag == config.AgentImageName {
				return true, nil
			}
		}
	}
	return false, nil
}

// LoadImage loads an io.Reader into Docker
func (c *Client) LoadImage(image io.Reader) error {
	return c.docker.LoadImage(godocker.LoadImageOptions{image, context.TODO()})
}

// RemoveExistingAgentContainer remvoes any existing container named
// "ecs-agent" or returns without error if none is found
func (c *Client) RemoveExistingAgentContainer() error {
	containerToRemove, err := c.findAgentContainer()
	if err != nil {
		return err
	}
	if containerToRemove == "" {
		log.Info("No existing agent container to remove.")
		return nil
	}
	log.Infof("Removing existing agent container ID: %s", containerToRemove)
	err = c.docker.RemoveContainer(godocker.RemoveContainerOptions{
		ID:    containerToRemove,
		Force: true,
	})
	return err
}

func (c *Client) findAgentContainer() (string, error) {
	// TODO pagination
	containers, err := c.docker.ListContainers(godocker.ListContainersOptions{
		All: true,
		Filters: map[string][]string{
			"status": []string{},
		},
	})
	if err != nil {
		return "", err
	}
	agentContainerName := "/" + config.AgentContainerName
	for _, container := range containers {
		for _, name := range container.Names {
			log.Infof("Container name: %s", name)
			if name == agentContainerName {
				return container.ID, nil
			}
		}
	}
	return "", nil
}

// StartAgent starts the Agent in Docker and returns the exit code from the container
func (c *Client) StartAgent() (int, error) {
	container, err := c.docker.CreateContainer(godocker.CreateContainerOptions{
		Name:       config.AgentContainerName,
		Config:     c.getContainerConfig(),
		HostConfig: c.getHostConfig(),
	})
	if err != nil {
		return 0, err
	}
	err = c.docker.StartContainer(container.ID, nil)
	if err != nil {
		return 0, err
	}
	return c.docker.WaitContainer(container.ID)
}

func (c *Client) getContainerConfig() *godocker.Config {

	// default environment variables
	envVariables := map[string]string{
		"ECS_LOGFILE":                           logDir + "/" + config.AgentLogFile,
		"ECS_DATADIR":                           dataDir,
		"ECS_AGENT_CONFIG_FILE_PATH":            config.AgentJSONConfigFile(),
		"ECS_UPDATE_DOWNLOAD_DIR":               config.CacheDirectory(),
		"ECS_UPDATES_ENABLED":                   "true",
		"ECS_AVAILABLE_LOGGING_DRIVERS":         `["json-file","syslog","awslogs"]`,
		"ECS_ENABLE_TASK_IAM_ROLE":              "true",
		"ECS_ENABLE_TASK_IAM_ROLE_NETWORK_HOST": "true",
	}

	// merge in user-supplied environment variables
	for envKey, envValue := range c.loadEnvVariables() {
		envVariables[envKey] = envValue
	}

	var env []string
	for envKey, envValue := range envVariables {
		env = append(env, envKey+"="+envValue)
	}

	return &godocker.Config{
		Env:   env,
		Image: config.AgentImageName,
	}
}

func (c *Client) loadEnvVariables() map[string]string {
	envVariables := make(map[string]string)

	file, err := c.fs.ReadFile(config.AgentConfigFile())
	if err != nil {
		return envVariables
	}

	lines := strings.Split(strings.TrimSpace(string(file)), "\n")
	for _, line := range lines {
		parts := strings.SplitN(strings.TrimSpace(line), "=", 2)
		if len(parts) != 2 {
			continue
		}
		envVariables[parts[0]] = parts[1]
	}

	return envVariables
}

func (c *Client) getHostConfig() *godocker.HostConfig {
	dockerEndpointAgent := defaultDockerEndpoint
	dockerUnixSocketSourcePath, fromEnv := config.DockerUnixSocket()
	if fromEnv {
		dockerEndpointAgent = "/var/run/docker.sock"
	}

	binds := []string{
		dockerUnixSocketSourcePath + ":" + dockerEndpointAgent,
		config.LogDirectory() + ":" + logDir,
		config.AgentDataDirectory() + ":" + dataDir,
		config.AgentConfigDirectory() + ":" + config.AgentConfigDirectory(),
		config.CacheDirectory() + ":" + config.CacheDirectory(),
	}
	return &godocker.HostConfig{
		Binds:       binds,
		NetworkMode: networkMode,
		UsernsMode:  usernsMode,
		CapAdd:      []string{CAP_NET_ADMIN},
	}
}

// StopAgent stops the Agent in docker if one is running
func (c *Client) StopAgent() error {
	id, err := c.findAgentContainer()
	if err != nil {
		return err
	}
	if id == "" {
		log.Info("No running Agent to stop")
		return nil
	}
	stopContainerTimeoutSeconds := uint(10)
	return c.docker.StopContainer(id, stopContainerTimeoutSeconds)
}
<|endoftext|>


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

package itemhandler

import (
	"fmt"
	"github.com/andreaskoch/allmark2/common/logger"
	"github.com/andreaskoch/allmark2/common/paths"
	"github.com/andreaskoch/allmark2/common/route"
	"github.com/andreaskoch/allmark2/services/conversion"
	"github.com/andreaskoch/allmark2/ui/web/server/handler/handlerutil"
	"github.com/andreaskoch/allmark2/ui/web/server/index"
	"github.com/andreaskoch/allmark2/ui/web/view/templates"
	"github.com/andreaskoch/allmark2/ui/web/view/viewmodel"
	"io"
	"net/http"
)

func New(logger logger.Logger, index *index.Index, patherFactory paths.PatherFactory, converter conversion.Converter) *ItemHandler {
	return &ItemHandler{
		logger:        logger,
		index:         index,
		patherFactory: patherFactory,
		converter:     converter,
	}
}

type ItemHandler struct {
	logger        logger.Logger
	index         *index.Index
	patherFactory paths.PatherFactory
	converter     conversion.Converter
}

func (handler *ItemHandler) Func() func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {

		// get the request route
		requestPath := handlerutil.GetRequestedPathFromRequest(r)
		requestRoute, err := route.NewFromRequest(requestPath)
		if err != nil {
			fmt.Fprintln(w, "%s", err)
			return
		}

		// make sure the request body is closed
		defer r.Body.Close()

		// check if there is a item for the request
		item, found := handler.index.IsMatch(*requestRoute)
		if !found {
			fmt.Fprintln(w, "item not found")
			return
		}

		// convert content
		pathProvider := handler.patherFactory.Relative()
		convertedContent, err := handler.converter.Convert(pathProvider, item)

		if err != nil {
			fmt.Fprintln(w, "Unable to convert content. Error: %s", err)
			return
		}

		// create a view model
		viewModel := viewmodel.Model{
			Type:               item.Type.String(),
			Title:              item.Title,
			Description:        item.Description,
			Content:            convertedContent,
			ToplevelNavigation: getToplevelNavigation(handler.index),
		}

		render(w, viewModel)

		// Childs
		childs := handler.index.GetChilds(item.Route())
		for _, child := range childs {
			fmt.Fprintf(w, "Child: %s\n", child.Title)
		}
	}
}

func getToplevelNavigation(index *index.Index) *viewmodel.ToplevelNavigation {
	root, err := route.NewFromRequest("")
	if err != nil {
		return nil
	}

	toplevelEntries := make([]*viewmodel.ToplevelEntry, 0)
	for _, child := range index.GetChilds(root) {

		// skip all childs which are not first level
		if child.Route().Level() != 1 {
			continue
		}

		toplevelEntries = append(toplevelEntries, &viewmodel.ToplevelEntry{
			Title: child.Title,
			Path:  child.Route().Value(),
		})

	}

	return &viewmodel.ToplevelNavigation{
		Entries: toplevelEntries,
	}
}

func render(writer io.Writer, viewModel viewmodel.Model) {

	templateProvider := templates.NewProvider(".")

	// get a template
	if template, err := templateProvider.GetFullTemplate(viewModel.Type); err == nil {

		err := template.Execute(writer, viewModel)
		if err != nil {
			fmt.Println(err)
		}

	} else {

		fmt.Fprintf(writer, "No template for item of type %q.", viewModel.Type)

	}

}
<commit_msg>Added the breadcrumb navigation to the item renderer / handler.<commit_after>// Copyright 2013 Andreas Koch. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package itemhandler

import (
	"fmt"
	"github.com/andreaskoch/allmark2/common/logger"
	"github.com/andreaskoch/allmark2/common/paths"
	"github.com/andreaskoch/allmark2/common/route"
	"github.com/andreaskoch/allmark2/model"
	"github.com/andreaskoch/allmark2/services/conversion"
	"github.com/andreaskoch/allmark2/ui/web/server/handler/handlerutil"
	"github.com/andreaskoch/allmark2/ui/web/server/index"
	"github.com/andreaskoch/allmark2/ui/web/view/templates"
	"github.com/andreaskoch/allmark2/ui/web/view/viewmodel"
	"io"
	"net/http"
)

func New(logger logger.Logger, index *index.Index, patherFactory paths.PatherFactory, converter conversion.Converter) *ItemHandler {
	return &ItemHandler{
		logger:        logger,
		index:         index,
		patherFactory: patherFactory,
		converter:     converter,
	}
}

type ItemHandler struct {
	logger        logger.Logger
	index         *index.Index
	patherFactory paths.PatherFactory
	converter     conversion.Converter
}

func (handler *ItemHandler) Func() func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {

		// get the request route
		requestPath := handlerutil.GetRequestedPathFromRequest(r)
		requestRoute, err := route.NewFromRequest(requestPath)
		if err != nil {
			fmt.Fprintln(w, "%s", err)
			return
		}

		// make sure the request body is closed
		defer r.Body.Close()

		// check if there is a item for the request
		item, found := handler.index.IsMatch(*requestRoute)
		if !found {
			fmt.Fprintln(w, "item not found")
			return
		}

		// convert content
		pathProvider := handler.patherFactory.Relative()
		convertedContent, err := handler.converter.Convert(pathProvider, item)

		if err != nil {
			fmt.Fprintln(w, "Unable to convert content. Error: %s", err)
			return
		}

		// create a view model
		viewModel := viewmodel.Model{
			Type:                 item.Type.String(),
			Title:                item.Title,
			Description:          item.Description,
			Content:              convertedContent,
			ToplevelNavigation:   getToplevelNavigation(handler.index),
			BreadcrumbNavigation: getBreadcrumbNavigation(handler.index, item),
		}

		render(w, viewModel)

		// Childs
		childs := handler.index.GetChilds(item.Route())
		for _, child := range childs {
			fmt.Fprintf(w, "Child: %s\n", child.Title)
		}
	}
}

func getToplevelNavigation(index *index.Index) *viewmodel.ToplevelNavigation {
	root, err := route.NewFromRequest("")
	if err != nil {
		return nil
	}

	toplevelEntries := make([]*viewmodel.ToplevelEntry, 0)
	for _, child := range index.GetChilds(root) {

		// skip all childs which are not first level
		if child.Route().Level() != 1 {
			continue
		}

		toplevelEntries = append(toplevelEntries, &viewmodel.ToplevelEntry{
			Title: child.Title,
			Path:  child.Route().Value(),
		})

	}

	return &viewmodel.ToplevelNavigation{
		Entries: toplevelEntries,
	}
}

func getBreadcrumbNavigation(index *index.Index, item *model.Item) *viewmodel.BreadcrumbNavigation {

	// create a new bread crumb navigation
	navigation := &viewmodel.BreadcrumbNavigation{
		Entries: make([]*viewmodel.Breadcrumb, 0),
	}

	// abort if item or model is nil
	if item == nil {
		return navigation
	}

	// recurse if there is a parent
	if parent := index.GetParent(item.Route()); parent != nil {
		navigation.Entries = append(navigation.Entries, getBreadcrumbNavigation(index, parent).Entries...)
	}

	// append a new navigation entry and return it
	navigation.Entries = append(navigation.Entries, &viewmodel.Breadcrumb{
		Title: item.Title,
		Level: item.Route().Level(),
		Path:  item.Route().Value(),
	})

	return navigation
}

func render(writer io.Writer, viewModel viewmodel.Model) {

	templateProvider := templates.NewProvider(".")

	// get a template
	if template, err := templateProvider.GetFullTemplate(viewModel.Type); err == nil {

		err := template.Execute(writer, viewModel)
		if err != nil {
			fmt.Println(err)
		}

	} else {

		fmt.Fprintf(writer, "No template for item of type %q.", viewModel.Type)

	}

}
<|endoftext|>


<commit_before>package emssdb

import (
	"github.com/syndtr/goleveldb/leveldb"
	"github.com/syndtr/goleveldb/leveldb/filter"
	"github.com/syndtr/goleveldb/leveldb/opt"
	"github.com/syndtr/goleveldb/leveldb/util"
	//"runtime"
	"sync"
	"time"
)

type DB struct {
	db          *leveldb.DB
	options     opt.Options
	writer      *Writer
	expireDelay time.Duration
	end         bool
	waitgroup   sync.WaitGroup
}

func newDB() *DB {
	var db DB
	return &db
}

func OpenDB(options Options) (that *DB, err error) {
	main_db_path := options.Path
	cache_size := options.CacheSize
	write_buffer_size := 4
	block_size := 4
	compression := options.Compression
	if options.ExpireDelay <= time.Second {
		options.ExpireDelay = time.Second
	}

	if cache_size <= 0 {
		cache_size = 8
	}

	//log::path,cache_size,block_size,write_buffer,compression

	d := newDB()
	d.options.ErrorIfMissing = false
	d.options.Filter = filter.NewBloomFilter(10)
	//d.Options.BlockCacher = leveldb::NewLRUCache(cache_size * 1048576)
	d.options.BlockSize = block_size * 1024
	d.options.WriteBuffer = write_buffer_size * 1024 * 1024
	d.expireDelay = options.ExpireDelay
	if compression {
		d.options.Compression = opt.SnappyCompression
	} else {
		d.options.Compression = opt.NoCompression
	}

	if tdb, err := leveldb.OpenFile(main_db_path, &d.options); err == nil {
		//runtime.SetFinalizer(d,
		//	func(d *DB) {
		//		d.db.Close()
		//	})
		d.db = tdb
		d.writer = NewWriter(d.db)
		go d.expireDaemon()
		return d, nil
	} else {
		return nil, err
	}
}

func (d *DB) Close() {
	d.end = true
	d.waitgroup.Wait()
	d.db.Close()
}

//	// return (start, end], not include start
func (d *DB) Iterator(start Bytes, end Bytes) (ret *Iterator) {
	if len(start) == 0 {
		start = nil
	}
	if len(end) == 0 {
		end = nil
	}
	var iopt opt.ReadOptions
	iopt.DontFillCache = true
	it := d.db.NewIterator(&util.Range{Start: start, Limit: end}, &iopt)
	return NewIterator(it, FORWARD)
}

func (d *DB) RevIterator(start Bytes, end Bytes) (ret *Iterator) {
	if len(start) == 0 {
		start = nil
	}
	if len(end) == 0 {
		end = nil
	}
	var iopt opt.ReadOptions
	iopt.DontFillCache = true
	it := d.db.NewIterator(&util.Range{Start: start, Limit: end}, &iopt)
	it.Last()
	it.Next()
	return NewIterator(it, BACKWARD)
}

func (d *DB) Info() (ret map[string]string) {
	//  "leveldb.num-files-at-level<N>" - return the number of files at level <N>,
	//     where <N> is an ASCII representation of a level number (e.g. "0").
	//  "leveldb.stats" - returns a multi-line string that describes statistics
	//     about the internal operation of the DB.
	//  "leveldb.sstables" - returns a multi-line string that describes all
	//     of the sstables that make up the db contents.
	var keys []string
	info := make(map[string]string)

	//for i := 0; i < 7; i++ {
	//	s := fmt.Sprintf("leveldb.num-files-at-level%d", i)
	//	keys = append(keys, s)
	//}

	keys = append(keys, "leveldb.stats")
	//keys = append(keys, "leveldb.sstables")

	for _, key := range keys {
		if val, err := d.db.GetProperty(key); err == nil {
			info[key] = val
		}
	}

	return info
}

func (d *DB) Compact() (err error) {
	return d.db.CompactRange(util.Range{})
}

func (d *DB) KeyRange(keys []string) {
	//	int ret = 0;
	//	std::string kstart, kend;
	//	std::string hstart, hend;
	//	std::string zstart, zend;
	//
	//	Iterator *it;
	//
	//	it = d->iterator(encode_kv_key(""), "", 1);
	//	if(it->next()){
	//		Bytes ks = it->key();
	//		if(ks.data()[0] == DataType::KV){
	//			std::string n;
	//			if(decode_kv_key(ks, &n) == -1){
	//				ret = -1;
	//			}else{
	//				kstart = n;
	//			}
	//		}
	//	}
	//	delete it;
	//
	//	it = d->rev_iterator(encode_kv_key("\xff"), "", 1);
	//	if(it->next()){
	//		Bytes ks = it->key();
	//		if(ks.data()[0] == DataType::KV){
	//			std::string n;
	//			if(decode_kv_key(ks, &n) == -1){
	//				ret = -1;
	//			}else{
	//				kend = n;
	//			}
	//		}
	//	}
	//	delete it;
	//
	//	it = d->iterator(encode_hsize_key(""), "", 1);
	//	if(it->next()){
	//		Bytes ks = it->key();
	//		if(ks.data()[0] == DataType::HSIZE){
	//			std::string n;
	//			if(decode_hsize_key(ks, &n) == -1){
	//				ret = -1;
	//			}else{
	//				hstart = n;
	//			}
	//		}
	//	}
	//	delete it;
	//
	//	it = d->rev_iterator(encode_hsize_key("\xff"), "", 1);
	//	if(it->next()){
	//		Bytes ks = it->key();
	//		if(ks.data()[0] == DataType::HSIZE){
	//			std::string n;
	//			if(decode_hsize_key(ks, &n) == -1){
	//				ret = -1;
	//			}else{
	//				hend = n;
	//			}
	//		}
	//	}
	//	delete it;
	//
	//	it = d->iterator(encode_zsize_key(""), "", 1);
	//	if(it->next()){
	//		Bytes ks = it->key();
	//		if(ks.data()[0] == DataType::ZSIZE){
	//			std::string n;
	//			if(decode_hsize_key(ks, &n) == -1){
	//				ret = -1;
	//			}else{
	//				zstart = n;
	//			}
	//		}
	//	}
	//	delete it;
	//
	//	it = d->rev_iterator(encode_zsize_key("\xff"), "", 1);
	//	if(it->next()){
	//		Bytes ks = it->key();
	//		if(ks.data()[0] == DataType::ZSIZE){
	//			std::string n;
	//			if(decode_hsize_key(ks, &n) == -1){
	//				ret = -1;
	//			}else{
	//				zend = n;
	//			}
	//		}
	//	}
	//	delete it;
	//
	//	keys->push_back(kstart);
	//	keys->push_back(kend);
	//	keys->push_back(hstart);
	//	keys->push_back(hend);
	//	keys->push_back(zstart);
	//	keys->push_back(zend);
	//
	//	return ret;
}

//
//	/* raw operates */
//
//	// repl: whether to sync d operation to slaves
func (d *DB) RawSet(key Bytes, val Bytes) (err error) {
	//var writeOpts opt.WriteOptions
	return d.db.Put(key, val, nil)
}
func (d *DB) RawDel(key Bytes) (err error) {
	//var writeOpts opt.WriteOptions
	return d.db.Delete(key, nil)
}
func (d *DB) RawGet(key Bytes) (val Bytes, err error) {
	//var writeOpts opt.WriteOptions
	return d.db.Get(key, nil)
}
<commit_msg>fix cache_size<commit_after>package emssdb

import (
	"github.com/syndtr/goleveldb/leveldb"
	"github.com/syndtr/goleveldb/leveldb/filter"
	"github.com/syndtr/goleveldb/leveldb/opt"
	"github.com/syndtr/goleveldb/leveldb/util"
	//"runtime"
	"sync"
	"time"
)

type DB struct {
	db          *leveldb.DB
	options     opt.Options
	writer      *Writer
	expireDelay time.Duration
	end         bool
	waitgroup   sync.WaitGroup
}

func newDB() *DB {
	var db DB
	return &db
}

func OpenDB(options Options) (that *DB, err error) {
	main_db_path := options.Path
	cache_size := options.CacheSize
	write_buffer_size := 4
	block_size := 4
	compression := options.Compression
	if options.ExpireDelay <= time.Second {
		options.ExpireDelay = time.Second
	}

	if cache_size <= 0 {
		cache_size = 8
	}

	//log::path,cache_size,block_size,write_buffer,compression

	d := newDB()
	d.options.ErrorIfMissing = false
	d.options.Filter = filter.NewBloomFilter(10)
	//d.Options.BlockCacher = leveldb::NewLRUCache(cache_size * 1048576)
	d.options.BlockCacheCapacity = cache_size * 1024 * 1024
	d.options.BlockSize = block_size * 1024
	d.options.WriteBuffer = write_buffer_size * 1024 * 1024
	d.expireDelay = options.ExpireDelay
	if compression {
		d.options.Compression = opt.SnappyCompression
	} else {
		d.options.Compression = opt.NoCompression
	}

	if tdb, err := leveldb.OpenFile(main_db_path, &d.options); err == nil {
		//runtime.SetFinalizer(d,
		//	func(d *DB) {
		//		d.db.Close()
		//	})
		d.db = tdb
		d.writer = NewWriter(d.db)
		go d.expireDaemon()
		return d, nil
	} else {
		return nil, err
	}
}

func (d *DB) Close() {
	d.end = true
	d.waitgroup.Wait()
	d.db.Close()
}

//	// return (start, end], not include start
func (d *DB) Iterator(start Bytes, end Bytes) (ret *Iterator) {
	if len(start) == 0 {
		start = nil
	}
	if len(end) == 0 {
		end = nil
	}
	var iopt opt.ReadOptions
	iopt.DontFillCache = true
	it := d.db.NewIterator(&util.Range{Start: start, Limit: end}, &iopt)
	return NewIterator(it, FORWARD)
}

func (d *DB) RevIterator(start Bytes, end Bytes) (ret *Iterator) {
	if len(start) == 0 {
		start = nil
	}
	if len(end) == 0 {
		end = nil
	}
	var iopt opt.ReadOptions
	iopt.DontFillCache = true
	it := d.db.NewIterator(&util.Range{Start: start, Limit: end}, &iopt)
	it.Last()
	it.Next()
	return NewIterator(it, BACKWARD)
}

func (d *DB) Info() (ret map[string]string) {
	//  "leveldb.num-files-at-level<N>" - return the number of files at level <N>,
	//     where <N> is an ASCII representation of a level number (e.g. "0").
	//  "leveldb.stats" - returns a multi-line string that describes statistics
	//     about the internal operation of the DB.
	//  "leveldb.sstables" - returns a multi-line string that describes all
	//     of the sstables that make up the db contents.
	var keys []string
	info := make(map[string]string)

	//for i := 0; i < 7; i++ {
	//	s := fmt.Sprintf("leveldb.num-files-at-level%d", i)
	//	keys = append(keys, s)
	//}

	keys = append(keys, "leveldb.stats")
	//keys = append(keys, "leveldb.sstables")

	for _, key := range keys {
		if val, err := d.db.GetProperty(key); err == nil {
			info[key] = val
		}
	}

	return info
}

func (d *DB) Compact() (err error) {
	return d.db.CompactRange(util.Range{})
}

func (d *DB) KeyRange(keys []string) {
	//	int ret = 0;
	//	std::string kstart, kend;
	//	std::string hstart, hend;
	//	std::string zstart, zend;
	//
	//	Iterator *it;
	//
	//	it = d->iterator(encode_kv_key(""), "", 1);
	//	if(it->next()){
	//		Bytes ks = it->key();
	//		if(ks.data()[0] == DataType::KV){
	//			std::string n;
	//			if(decode_kv_key(ks, &n) == -1){
	//				ret = -1;
	//			}else{
	//				kstart = n;
	//			}
	//		}
	//	}
	//	delete it;
	//
	//	it = d->rev_iterator(encode_kv_key("\xff"), "", 1);
	//	if(it->next()){
	//		Bytes ks = it->key();
	//		if(ks.data()[0] == DataType::KV){
	//			std::string n;
	//			if(decode_kv_key(ks, &n) == -1){
	//				ret = -1;
	//			}else{
	//				kend = n;
	//			}
	//		}
	//	}
	//	delete it;
	//
	//	it = d->iterator(encode_hsize_key(""), "", 1);
	//	if(it->next()){
	//		Bytes ks = it->key();
	//		if(ks.data()[0] == DataType::HSIZE){
	//			std::string n;
	//			if(decode_hsize_key(ks, &n) == -1){
	//				ret = -1;
	//			}else{
	//				hstart = n;
	//			}
	//		}
	//	}
	//	delete it;
	//
	//	it = d->rev_iterator(encode_hsize_key("\xff"), "", 1);
	//	if(it->next()){
	//		Bytes ks = it->key();
	//		if(ks.data()[0] == DataType::HSIZE){
	//			std::string n;
	//			if(decode_hsize_key(ks, &n) == -1){
	//				ret = -1;
	//			}else{
	//				hend = n;
	//			}
	//		}
	//	}
	//	delete it;
	//
	//	it = d->iterator(encode_zsize_key(""), "", 1);
	//	if(it->next()){
	//		Bytes ks = it->key();
	//		if(ks.data()[0] == DataType::ZSIZE){
	//			std::string n;
	//			if(decode_hsize_key(ks, &n) == -1){
	//				ret = -1;
	//			}else{
	//				zstart = n;
	//			}
	//		}
	//	}
	//	delete it;
	//
	//	it = d->rev_iterator(encode_zsize_key("\xff"), "", 1);
	//	if(it->next()){
	//		Bytes ks = it->key();
	//		if(ks.data()[0] == DataType::ZSIZE){
	//			std::string n;
	//			if(decode_hsize_key(ks, &n) == -1){
	//				ret = -1;
	//			}else{
	//				zend = n;
	//			}
	//		}
	//	}
	//	delete it;
	//
	//	keys->push_back(kstart);
	//	keys->push_back(kend);
	//	keys->push_back(hstart);
	//	keys->push_back(hend);
	//	keys->push_back(zstart);
	//	keys->push_back(zend);
	//
	//	return ret;
}

//
//	/* raw operates */
//
//	// repl: whether to sync d operation to slaves
func (d *DB) RawSet(key Bytes, val Bytes) (err error) {
	//var writeOpts opt.WriteOptions
	return d.db.Put(key, val, nil)
}
func (d *DB) RawDel(key Bytes) (err error) {
	//var writeOpts opt.WriteOptions
	return d.db.Delete(key, nil)
}
func (d *DB) RawGet(key Bytes) (val Bytes, err error) {
	//var writeOpts opt.WriteOptions
	return d.db.Get(key, nil)
}
<|endoftext|>


<commit_before>package sqlxchain

import (
	"database/sql"
	"github.com/jmoiron/sqlx"
	"github.com/johnnylee/util"
)

var log = util.NewPrefixLogger("SqlxChain")

// SqlxChain is a thin wrapper around a sqlx.DB.
type SqlxChain struct {
	Db           *sqlx.DB
	errConverter func(error) error
}

func New(driver, dns string) (*SqlxChain, error) {
	var err error

	sc := new(SqlxChain)
	if sc.Db, err = sqlx.Open(driver, dns); err != nil {
		return nil, err
	}

	return sc, nil
}

func (sc *SqlxChain) ErrorConverter(f func(error) error) {
	sc.errConverter = f
}

func (sc *SqlxChain) Context() *DbContext {
	ctx := new(DbContext)
	ctx.db = sc.Db
	ctx.errConverter = sc.errConverter
	return ctx
}

type DbContext struct {
	db           *sqlx.DB
	tx           *sqlx.Tx
	err          error
	errConverter func(error) error
	result       sql.Result
}

func (d *DbContext) Begin() *DbContext {
	if d.err != nil {
		return d
	}
	d.tx, d.err = d.db.Beginx()
	return d
}

func (d *DbContext) Exec(query string, args ...interface{}) *DbContext {
	if d.err != nil {
		return d
	}
	if d.tx != nil {
		d.result, d.err = d.tx.Exec(query, args...)
	} else {
		d.result, d.err = d.db.Exec(query, args...)
	}
	return d
}

func (d *DbContext) Get(
	dest interface{}, query string, args ...interface{}) *DbContext {
	if d.err != nil {
		return d
	}
	if d.tx != nil {
		d.err = d.tx.Get(dest, query, args...)
	} else {
		d.err = d.db.Get(dest, query, args...)
	}
	return d
}

func (d *DbContext) Select(
	dest interface{}, query string, args ...interface{}) *DbContext {
	if d.err != nil {
		return d
	}
	if d.tx != nil {
		d.err = d.tx.Select(dest, query, args...)
	} else {
		d.err = d.db.Select(dest, query, args...)
	}
	return d
}

func (d *DbContext) LastInsertId(id *int64) *DbContext {
	if d.err != nil {
		return d
	}
	*id, d.err = d.result.LastInsertId()
	return d
}

func (d *DbContext) RowsAffected(n *int64) *DbContext {
	if d.err != nil {
		return d
	}
	*n, d.err = d.result.RowsAffected()
	return d
}

func (d *DbContext) LogErr(msg string, args ...interface{}) *DbContext {
	if d.err != nil {
		log.Err(d.err, msg, args...)
	}
	return d
}

func (d *DbContext) Commit() *DbContext {
	defer func() {
		d.tx = nil
	}()

	if d.err != nil {
		if err := d.tx.Rollback(); err != nil {
			log.Err(err, "When rolling back transaction")
		}
		return d
	}

	d.err = d.tx.Commit()
	return d
}

func (d *DbContext) Err() error {
	if d.errConverter != nil {
		return d.errConverter(d.err)
	}
	return d.err
}
<commit_msg>Changing logging to a custom function, like the error converter<commit_after>package sqlxchain

import (
	"database/sql"
	"github.com/jmoiron/sqlx"
)

type ErrorConvertFunc func(err error) error
type ErrorLogFunc func(err error, format string, v ...interface{})

// SqlxChain is a thin wrapper around a sqlx.DB.
type SqlxChain struct {
	Db           *sqlx.DB
	errConverter ErrorConvertFunc
	errLogger    ErrorLogFunc
}

func New(driver, dns string) (*SqlxChain, error) {
	var err error

	sc := new(SqlxChain)
	if sc.Db, err = sqlx.Open(driver, dns); err != nil {
		return nil, err
	}

	return sc, nil
}

func (sc *SqlxChain) ErrorConverter(f ErrorConvertFunc) {
	sc.errConverter = f
}

func (sc *SqlxChain) ErrorLogger(f ErrorLogFunc) {
	sc.errLogger = f
}

func (sc *SqlxChain) Context() *DbContext {
	ctx := new(DbContext)
	ctx.db = sc.Db
	ctx.errConverter = sc.errConverter
	ctx.errLogger = sc.errLogger
	return ctx
}

type DbContext struct {
	db           *sqlx.DB
	tx           *sqlx.Tx
	err          error
	errConverter ErrorConvertFunc
	errLogger    ErrorLogFunc
	result       sql.Result
}

func (d *DbContext) Begin() *DbContext {
	if d.err != nil {
		return d
	}
	d.tx, d.err = d.db.Beginx()
	return d
}

func (d *DbContext) Exec(query string, args ...interface{}) *DbContext {
	if d.err != nil {
		return d
	}
	if d.tx != nil {
		d.result, d.err = d.tx.Exec(query, args...)
	} else {
		d.result, d.err = d.db.Exec(query, args...)
	}
	return d
}

func (d *DbContext) Get(
	dest interface{}, query string, args ...interface{}) *DbContext {
	if d.err != nil {
		return d
	}
	if d.tx != nil {
		d.err = d.tx.Get(dest, query, args...)
	} else {
		d.err = d.db.Get(dest, query, args...)
	}
	return d
}

func (d *DbContext) Select(
	dest interface{}, query string, args ...interface{}) *DbContext {
	if d.err != nil {
		return d
	}
	if d.tx != nil {
		d.err = d.tx.Select(dest, query, args...)
	} else {
		d.err = d.db.Select(dest, query, args...)
	}
	return d
}

func (d *DbContext) LastInsertId(id *int64) *DbContext {
	if d.err != nil {
		return d
	}
	*id, d.err = d.result.LastInsertId()
	return d
}

func (d *DbContext) RowsAffected(n *int64) *DbContext {
	if d.err != nil {
		return d
	}
	*n, d.err = d.result.RowsAffected()
	return d
}

func (d *DbContext) Commit() *DbContext {
	if d.tx == nil {
		return d
	}

	defer func() {
		d.tx = nil
	}()

	if d.err != nil {
		if err := d.tx.Rollback(); err != nil {
			d.logErr(d.convertErr(err), "When rolling back transaction")
		}
		return d
	}

	d.err = d.tx.Commit()
	return d
}

func (d *DbContext) convertErr(err error) error {
	if err != nil && d.errConverter != nil {
		err = d.errConverter(err)
	}
	return err
}

func (d *DbContext) Err() error {
	return d.convertErr(d.err)
}

func (d *DbContext) logErr(err error, msg string, args ...interface{}) {
	if err != nil && d.errLogger != nil {
		d.errLogger(err, msg, args...)
	}
}

func (d *DbContext) LogErr(msg string, args ...interface{}) *DbContext {
	d.logErr(d.err, msg, args...)
	return d
}
<|endoftext|>


<commit_before>package hydrocarbon

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"time"

	_ "github.com/lib/pq"
)

var (
	ErrInvalidSession = errors.New("invalid session token")
)

// A DB is responsible for all interactions with postgres
type DB struct {
	sql *sql.DB
}

// NewDB returns a new database
func NewDB(dsn string, autoExplain bool) (*DB, error) {
	db, err := sql.Open("postgres", dsn)
	if err != nil {
		return nil, err
	}

	err = runMigrations(db)
	if err != nil {
		return nil, err
	}

	if autoExplain {
		db.Exec(`LOAD 'auto_explain';`)
		db.Exec(`SET auto_explain.log_min_duration = 0;`)
	}

	return &DB{
		sql: db,
	}, nil
}

// CreateOrGetUser creates a new user and returns the users ID
func (db *DB) CreateOrGetUser(ctx context.Context, email string) (string, bool, error) {
	row := db.sql.QueryRowContext(ctx, `
	INSERT INTO users
	(email)
	VALUES ($1)
	ON CONFLICT (email)
	DO UPDATE SET email = EXCLUDED.email
	RETURNING id, stripe_subscription_id;`, email)

	var userID string
	var stripeSubID sql.NullString
	err := row.Scan(&userID, &stripeSubID)
	if err != nil {
		return "", false, err
	}

	return userID, stripeSubID.Valid, nil
}

// SetStripeIDs sets a users stripe IDs
func (db *DB) SetStripeIDs(ctx context.Context, userID, customerID, subID string) error {
	_, err := db.sql.ExecContext(ctx, `
	UPDATE users
	SET (stripe_customer_id, stripe_subscription_id) = ($1, $2)
	WHERE id = $3;`, customerID, subID, userID)

	return err
}

// CreateLoginToken creates a new one-time-use login token
func (db *DB) CreateLoginToken(ctx context.Context, userID, userAgent, ip string) (string, error) {
	row := db.sql.QueryRowContext(ctx, `
	INSERT INTO login_tokens
	(user_id, user_agent, ip)
	VALUES ($1, $2, $3::cidr)
	RETURNING token;`, userID, userAgent, ip)

	var token string
	err := row.Scan(&token)
	if err != nil {
		return "", err
	}

	return token, nil
}

// ActivateLoginToken activates the given LoginToken and returns the user
// the token was for
func (db *DB) ActivateLoginToken(ctx context.Context, token string) (string, error) {
	row := db.sql.QueryRowContext(ctx, `
	UPDATE login_tokens
	SET used = true
	WHERE token = $1
	AND expires_at > now()
	AND used = false
	RETURNING user_id;`, token)

	var userID string
	err := row.Scan(&userID)
	if err != nil {
		if err == sql.ErrNoRows {
			return "", errors.New("token invalid")
		}
		return "", err
	}

	return userID, nil
}

// CreateSession creates a new session for the user ID and returns the
// session key
func (db *DB) CreateSession(ctx context.Context, userID, userAgent, ip string) (email string, key string, err error) {
	row := db.sql.QueryRowContext(ctx, `
	INSERT INTO sessions
	(user_id, user_agent, ip)
	VALUES ($1, $2, $3::cidr)
	RETURNING key;`, userID, userAgent, ip)
	err = row.Scan(&key)
	if err != nil {
		return "", "", err
	}

	row = db.sql.QueryRowContext(ctx, `
	SELECT email
	FROM users
	WHERE id = $1`, userID)
	err = row.Scan(&email)
	if err != nil {
		return "", "", err
	}

	return email, key, nil
}

// A Session is a session
type Session struct {
	CreatedAt time.Time `json:"created_at"`
	UserAgent string    `json:"user_agent"`
	IP        string    `json:"ip"`
	Active    bool      `json:"active"`
}

// ListSessions lists all sessions a user has
func (db *DB) ListSessions(ctx context.Context, key string, page int) ([]*Session, error) {
	rows, err := db.sql.QueryContext(ctx, `
	SELECT created_at, user_agent, ip, active
	FROM sessions
	WHERE user_id = (SELECT user_id FROM sessions WHERE key = $1)
	LIMIT 25
	OFFSET $2`, key, page)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var out []*Session
	for rows.Next() {
		var s Session
		err = rows.Scan(&s.CreatedAt, &s.UserAgent, &s.IP, &s.Active)
		if err != nil {
			return nil, err
		}
		out = append(out, &s)
	}

	return out, nil
}

// DeactivateSession invalidates the current session
func (db *DB) DeactivateSession(ctx context.Context, key string) error {
	_, err := db.sql.QueryContext(ctx, `
	UPDATE sessions
	SET (active) = (false)
	WHERE key = $1;`, key)

	return err
}

// AddFeed adds the given URL to the users default folder
// and links it across feed_folder
func (db *DB) AddFeed(ctx context.Context, sessionKey, folderID, title, plugin, feedURL string) (err error) {
	if folderID == "" {
		// ensure we don't shadow folderID
		var err error
		folderID, err = db.getDefaultFolderID(ctx, sessionKey)
		if err != nil {
			return err
		}
	}

	tx, err := db.sql.BeginTx(ctx, nil)
	if err != nil {
		return err
	}

	row := tx.QueryRowContext(ctx, `
	INSERT INTO feeds
	(title, plugin, url)
	VALUES ($1, $2, $3)
	RETURNING id;`, title, plugin, feedURL)

	var feedID string
	err = row.Scan(&feedID)
	if err != nil {
		txErr := tx.Rollback()
		if txErr != nil {
			return fmt.Errorf("%s - %s", err, txErr)
		}
		return err
	}

	_, err = tx.ExecContext(ctx, `
	INSERT INTO feed_folders
	(user_id, folder_id, feed_id)
	VALUES ((SELECT user_id FROM sessions WHERE key = $1), $2, $3);`, sessionKey, folderID, feedID)
	if err != nil {
		txErr := tx.Rollback()
		if txErr != nil {
			return fmt.Errorf("%s - %s", err, txErr)
		}
		return err
	}

	return tx.Commit()
}

// getDefaultFolderID returns a users default folder ID
func (db *DB) getDefaultFolderID(ctx context.Context, sessionKey string) (string, error) {
	row := db.sql.QueryRowContext(ctx, `
	SELECT id FROM folders
	WHERE name = 'default'
	AND user_id = (SELECT user_id FROM sessions WHERE key = $1);`, sessionKey)

	var fid string
	err := row.Scan(&fid)
	if err != nil {
		// if there is no default folder, go create one
		if err == sql.ErrNoRows {
			row := db.sql.QueryRowContext(ctx, `
			INSERT INTO folders
			(user_id)
			VALUES ((SELECT user_id FROM sessions WHERE key = $1 LIMIT 1))
			RETURNING id;`, sessionKey)

			err := row.Scan(&fid)
			if err != nil {
				return "", fmt.Errorf("could not create default folder: %s", err)
			}

		} else {
			return "", fmt.Errorf("could not find default folder: %s", err)
		}

	}

	return fid, nil
}

func (db *DB) AddFolder(ctx context.Context, sessionKey, name string) (string, error) {
	row := db.sql.QueryRow(`
	INSERT INTO folders (user_id, name) VALUES ((SELECT user_id FROM sessions WHERE key = $1), $2)`, sessionKey, name)

	var id string
	err := row.Scan(&id)
	if err != nil {
		return "", err
	}

	return id, nil
}

// RemoveFeed removes the given feed ID from the user
func (db *DB) RemoveFeed(ctx context.Context, sessionKey, folderID, feedID string) error {
	_, err := db.sql.ExecContext(ctx, `
	DELETE FROM feed_folders
	WHERE user_id = (SELECT user_id FROM sessions WHERE key = $1 LIMIT 1)
	AND folder_id = $2
	AND feed_id = $3;`, sessionKey, folderID, feedID)

	return err
}

// GetFolders returns all of the folders for a user - if there are none it creates a
// default folder
func (db *DB) GetFolders(ctx context.Context, sessionKey string) ([]*Folder, error) {
	rows, err := db.sql.QueryContext(ctx, `
	SELECT fo.name as folder_name, fo.id as folder_id
	FROM folders fo
	WHERE fo.user_id = (SELECT user_id FROM sessions WHERE key = $1 LIMIT 1)
	ORDER BY fo.created_at DESC;`, sessionKey)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	folders := make([]*Folder, 0)
	for rows.Next() {
		var folderName, folderID string

		err := rows.Scan(&folderName, &folderID)
		if err != nil {
			return nil, err
		}

		folders = append(folders, &Folder{
			ID:    folderID,
			Title: folderName,
		})
	}

	return folders, nil
}

// GetFeed returns a single feed
func (db *DB) GetFeed(ctx context.Context, sessionKey, feedID string, limit, offset int) (*Feed, error) {
	rows, err := db.sql.QueryContext(ctx, `
	SELECT fe.id, fe.title, po.id, po.title, po.author, po.body, po.url, po.created_at, po.updated_at, rs.user_id
 	FROM feeds fe
 	LEFT JOIN posts po ON (fe.id = po.feed_id)
	JOIN read_statuses rs ON (rs.post_id = po.id AND rs.user_id = (SELECT user_id FROM sessions WHERE key = $1 LIMIT 1))
	WHERE fe.id = $2
	LIMIT $3 OFFSET $4;`, sessionKey, feedID, limit, offset)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	feed := &Feed{
		ID:    feedID,
		Posts: make([]*Post, 0),
	}
	for rows.Next() {
		var feedID, feedTitle, postID, postTitle, postAuthor, postBody, url string
		var userID sql.NullString
		var createdAt, updatedAt time.Time

		err := rows.Scan(&feedID, &feedTitle, &postID, &postTitle, &postAuthor, &postBody, &url, &createdAt, &updatedAt, &userID)
		if err != nil {
			return nil, err
		}

		feed.Title = feedTitle

		feed.Posts = append(feed.Posts, &Post{
			ID:          postID,
			CreatedAt:   createdAt,
			UpdatedAt:   updatedAt,
			Author:      postAuthor,
			Title:       postTitle,
			Body:        postBody,
			OriginalURL: url,
			Read:        userID.Valid,
		})

	}

	return feed, nil
}

// UpdatePosts inserts a smattering of posts into the db
func (db *DB) UpdatePosts(ctx context.Context, feedID string, posts []*Post) error {
	for _, p := range posts {
		var contentHash string
		err := db.sql.QueryRow(`
		INSERT INTO posts
		(feed_id, content_hash, title, author, body, url)
		VALUES
		($1, $2, $3, $4, $5, $6)
		ON CONFLICT (content_hash) DO NOTHING
		RETURNING content_hash;`, feedID, p.ContentHash(), p.Title, p.Author, p.Body, p.OriginalURL).Scan(&contentHash)
		if err != nil {
			if err == sql.ErrNoRows {
				continue
			}
			return err
		}
	}

	return nil
}
<commit_msg>missing RETURNING clause<commit_after>package hydrocarbon

import (
	"context"
	"database/sql"
	"errors"
	"fmt"
	"time"

	_ "github.com/lib/pq"
)

var (
	ErrInvalidSession = errors.New("invalid session token")
)

// A DB is responsible for all interactions with postgres
type DB struct {
	sql *sql.DB
}

// NewDB returns a new database
func NewDB(dsn string, autoExplain bool) (*DB, error) {
	db, err := sql.Open("postgres", dsn)
	if err != nil {
		return nil, err
	}

	err = runMigrations(db)
	if err != nil {
		return nil, err
	}

	if autoExplain {
		db.Exec(`LOAD 'auto_explain';`)
		db.Exec(`SET auto_explain.log_min_duration = 0;`)
	}

	return &DB{
		sql: db,
	}, nil
}

// CreateOrGetUser creates a new user and returns the users ID
func (db *DB) CreateOrGetUser(ctx context.Context, email string) (string, bool, error) {
	row := db.sql.QueryRowContext(ctx, `
	INSERT INTO users
	(email)
	VALUES ($1)
	ON CONFLICT (email)
	DO UPDATE SET email = EXCLUDED.email
	RETURNING id, stripe_subscription_id;`, email)

	var userID string
	var stripeSubID sql.NullString
	err := row.Scan(&userID, &stripeSubID)
	if err != nil {
		return "", false, err
	}

	return userID, stripeSubID.Valid, nil
}

// SetStripeIDs sets a users stripe IDs
func (db *DB) SetStripeIDs(ctx context.Context, userID, customerID, subID string) error {
	_, err := db.sql.ExecContext(ctx, `
	UPDATE users
	SET (stripe_customer_id, stripe_subscription_id) = ($1, $2)
	WHERE id = $3;`, customerID, subID, userID)

	return err
}

// CreateLoginToken creates a new one-time-use login token
func (db *DB) CreateLoginToken(ctx context.Context, userID, userAgent, ip string) (string, error) {
	row := db.sql.QueryRowContext(ctx, `
	INSERT INTO login_tokens
	(user_id, user_agent, ip)
	VALUES ($1, $2, $3::cidr)
	RETURNING token;`, userID, userAgent, ip)

	var token string
	err := row.Scan(&token)
	if err != nil {
		return "", err
	}

	return token, nil
}

// ActivateLoginToken activates the given LoginToken and returns the user
// the token was for
func (db *DB) ActivateLoginToken(ctx context.Context, token string) (string, error) {
	row := db.sql.QueryRowContext(ctx, `
	UPDATE login_tokens
	SET used = true
	WHERE token = $1
	AND expires_at > now()
	AND used = false
	RETURNING user_id;`, token)

	var userID string
	err := row.Scan(&userID)
	if err != nil {
		if err == sql.ErrNoRows {
			return "", errors.New("token invalid")
		}
		return "", err
	}

	return userID, nil
}

// CreateSession creates a new session for the user ID and returns the
// session key
func (db *DB) CreateSession(ctx context.Context, userID, userAgent, ip string) (email string, key string, err error) {
	row := db.sql.QueryRowContext(ctx, `
	INSERT INTO sessions
	(user_id, user_agent, ip)
	VALUES ($1, $2, $3::cidr)
	RETURNING key;`, userID, userAgent, ip)
	err = row.Scan(&key)
	if err != nil {
		return "", "", err
	}

	row = db.sql.QueryRowContext(ctx, `
	SELECT email
	FROM users
	WHERE id = $1`, userID)
	err = row.Scan(&email)
	if err != nil {
		return "", "", err
	}

	return email, key, nil
}

// A Session is a session
type Session struct {
	CreatedAt time.Time `json:"created_at"`
	UserAgent string    `json:"user_agent"`
	IP        string    `json:"ip"`
	Active    bool      `json:"active"`
}

// ListSessions lists all sessions a user has
func (db *DB) ListSessions(ctx context.Context, key string, page int) ([]*Session, error) {
	rows, err := db.sql.QueryContext(ctx, `
	SELECT created_at, user_agent, ip, active
	FROM sessions
	WHERE user_id = (SELECT user_id FROM sessions WHERE key = $1)
	LIMIT 25
	OFFSET $2`, key, page)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	var out []*Session
	for rows.Next() {
		var s Session
		err = rows.Scan(&s.CreatedAt, &s.UserAgent, &s.IP, &s.Active)
		if err != nil {
			return nil, err
		}
		out = append(out, &s)
	}

	return out, nil
}

// DeactivateSession invalidates the current session
func (db *DB) DeactivateSession(ctx context.Context, key string) error {
	_, err := db.sql.QueryContext(ctx, `
	UPDATE sessions
	SET (active) = (false)
	WHERE key = $1;`, key)

	return err
}

// AddFeed adds the given URL to the users default folder
// and links it across feed_folder
func (db *DB) AddFeed(ctx context.Context, sessionKey, folderID, title, plugin, feedURL string) (err error) {
	if folderID == "" {
		// ensure we don't shadow folderID
		var err error
		folderID, err = db.getDefaultFolderID(ctx, sessionKey)
		if err != nil {
			return err
		}
	}

	tx, err := db.sql.BeginTx(ctx, nil)
	if err != nil {
		return err
	}

	row := tx.QueryRowContext(ctx, `
	INSERT INTO feeds
	(title, plugin, url)
	VALUES ($1, $2, $3)
	RETURNING id;`, title, plugin, feedURL)

	var feedID string
	err = row.Scan(&feedID)
	if err != nil {
		txErr := tx.Rollback()
		if txErr != nil {
			return fmt.Errorf("%s - %s", err, txErr)
		}
		return err
	}

	_, err = tx.ExecContext(ctx, `
	INSERT INTO feed_folders
	(user_id, folder_id, feed_id)
	VALUES ((SELECT user_id FROM sessions WHERE key = $1), $2, $3);`, sessionKey, folderID, feedID)
	if err != nil {
		txErr := tx.Rollback()
		if txErr != nil {
			return fmt.Errorf("%s - %s", err, txErr)
		}
		return err
	}

	return tx.Commit()
}

// getDefaultFolderID returns a users default folder ID
func (db *DB) getDefaultFolderID(ctx context.Context, sessionKey string) (string, error) {
	row := db.sql.QueryRowContext(ctx, `
	SELECT id FROM folders
	WHERE name = 'default'
	AND user_id = (SELECT user_id FROM sessions WHERE key = $1);`, sessionKey)

	var fid string
	err := row.Scan(&fid)
	if err != nil {
		// if there is no default folder, go create one
		if err == sql.ErrNoRows {
			row := db.sql.QueryRowContext(ctx, `
			INSERT INTO folders
			(user_id)
			VALUES ((SELECT user_id FROM sessions WHERE key = $1 LIMIT 1))
			RETURNING id;`, sessionKey)

			err := row.Scan(&fid)
			if err != nil {
				return "", fmt.Errorf("could not create default folder: %s", err)
			}

		} else {
			return "", fmt.Errorf("could not find default folder: %s", err)
		}

	}

	return fid, nil
}

func (db *DB) AddFolder(ctx context.Context, sessionKey, name string) (string, error) {
	row := db.sql.QueryRow(`
	INSERT INTO folders
	(user_id, name)
	VALUES
	((SELECT user_id FROM sessions WHERE key = $1), $2)
	RETURNING id;`, sessionKey, name)

	var id string
	err := row.Scan(&id)
	if err != nil {
		return "", err
	}

	return id, nil
}

// RemoveFeed removes the given feed ID from the user
func (db *DB) RemoveFeed(ctx context.Context, sessionKey, folderID, feedID string) error {
	_, err := db.sql.ExecContext(ctx, `
	DELETE FROM feed_folders
	WHERE user_id = (SELECT user_id FROM sessions WHERE key = $1 LIMIT 1)
	AND folder_id = $2
	AND feed_id = $3;`, sessionKey, folderID, feedID)

	return err
}

// GetFolders returns all of the folders for a user - if there are none it creates a
// default folder
func (db *DB) GetFolders(ctx context.Context, sessionKey string) ([]*Folder, error) {
	rows, err := db.sql.QueryContext(ctx, `
	SELECT fo.name as folder_name, fo.id as folder_id
	FROM folders fo
	WHERE fo.user_id = (SELECT user_id FROM sessions WHERE key = $1 LIMIT 1)
	ORDER BY fo.created_at DESC;`, sessionKey)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	folders := make([]*Folder, 0)
	for rows.Next() {
		var folderName, folderID string

		err := rows.Scan(&folderName, &folderID)
		if err != nil {
			return nil, err
		}

		folders = append(folders, &Folder{
			ID:    folderID,
			Title: folderName,
		})
	}

	return folders, nil
}

// GetFeed returns a single feed
func (db *DB) GetFeed(ctx context.Context, sessionKey, feedID string, limit, offset int) (*Feed, error) {
	rows, err := db.sql.QueryContext(ctx, `
	SELECT fe.id, fe.title, po.id, po.title, po.author, po.body, po.url, po.created_at, po.updated_at, rs.user_id
 	FROM feeds fe
 	LEFT JOIN posts po ON (fe.id = po.feed_id)
	JOIN read_statuses rs ON (rs.post_id = po.id AND rs.user_id = (SELECT user_id FROM sessions WHERE key = $1 LIMIT 1))
	WHERE fe.id = $2
	LIMIT $3 OFFSET $4;`, sessionKey, feedID, limit, offset)
	if err != nil {
		return nil, err
	}
	defer rows.Close()

	feed := &Feed{
		ID:    feedID,
		Posts: make([]*Post, 0),
	}
	for rows.Next() {
		var feedID, feedTitle, postID, postTitle, postAuthor, postBody, url string
		var userID sql.NullString
		var createdAt, updatedAt time.Time

		err := rows.Scan(&feedID, &feedTitle, &postID, &postTitle, &postAuthor, &postBody, &url, &createdAt, &updatedAt, &userID)
		if err != nil {
			return nil, err
		}

		feed.Title = feedTitle

		feed.Posts = append(feed.Posts, &Post{
			ID:          postID,
			CreatedAt:   createdAt,
			UpdatedAt:   updatedAt,
			Author:      postAuthor,
			Title:       postTitle,
			Body:        postBody,
			OriginalURL: url,
			Read:        userID.Valid,
		})

	}

	return feed, nil
}

// UpdatePosts inserts a smattering of posts into the db
func (db *DB) UpdatePosts(ctx context.Context, feedID string, posts []*Post) error {
	for _, p := range posts {
		var contentHash string
		err := db.sql.QueryRow(`
		INSERT INTO posts
		(feed_id, content_hash, title, author, body, url)
		VALUES
		($1, $2, $3, $4, $5, $6)
		ON CONFLICT (content_hash) DO NOTHING
		RETURNING content_hash;`, feedID, p.ContentHash(), p.Title, p.Author, p.Body, p.OriginalURL).Scan(&contentHash)
		if err != nil {
			if err == sql.ErrNoRows {
				continue
			}
			return err
		}
	}

	return nil
}
<|endoftext|>


<commit_before>// Copyright (c) 2013 VividCortex. Please see the LICENSE file for license terms.

/*
Package dbcontrol limits the number of active connections for database/sql.

Implementations of Go's database/sql package prior to Go 1.2, don't let the user
put a limit on the number of active connections to the underlying DB.  If enough
concurrent requests are made, so that the package runs out of available
connections, then more are requested from the driver no matter how many you have
at the pool already. Hence, unless you take precautions in your design/code, or
there's specific support from the actual DB driver you're using, you are likely
to hit some other limit (DB engine itself, OS) or simply run out of resources.

None of those situations are desirable, of course. If you hit DB or OS limits on
the number of connections, then many of your statements will start failing cause
no connection is available for them to use. You can get around it if you're
lucky enough to have driver support, but then you are tied to a particular DB.

This package is a wrapper on Go's standard database/sql, providing a general
mechanism so that you're free to use statements as usual, yet have the number of
active connections limited. A wrapper DB type is declared, that supports all
standard operations from database/sql. To use it, you should set the maximum
number of connections you want to allow, just like:

	dbcontrol.SetConcurrency(10)

All databases opened by the package afterwards will use a maximum of 10
connections. You can change this setting as often as you wish, but keep in mind
that the number is bound to databases as they are opened, i.e., changing this
concurrency setting has no effect on already-opened databases. Note also that
you can get the default non-limited behavior by setting concurrency to zero. To
create a DB you open it as usual using the database/sql package and then proceed
to wrap it up with this package. You no longer have to keep track of the
original sql.DB object; you should always use the dbcontrol.DB object returned
by the NewDB() call, like so:

	sqldb, err := sql.Open("mysql", dsn)
	if err != nil {
		log.Fatal(err)
	}

	db := dbcontrol.NewDB(sqldb)

Note that sql.Row, sql.Rows and sql.Stmt types are overridden by this package,
but that's probably transparent unless you declare the types explicitly. If you
declare variables using the := operator you'll be fine. Usage now follows the
expected pattern from database/sql:

	rows, err := db.Query("select id, name from customers")
	if err != nil {
		log.Fatal(err)
	}

	for rows.Next() {
		var id int
		var name string
		if err := rows.Scan(&id, &name); err != nil {
			log.Fatal(err)
		}

		fmt.Println(id, name)
	}

The full set of features at database/sql is supported, including transactions,
even though not all functions need to be overridden. This package was designed
to provide the feature with minimum overhead, and thus uses knowledge of
database/sql internals to know when locking is required/appropriate. As an
extension, you can set a channel to receive the locking duration each time a
connection has to be waited for. This can work as an aid to help you tune the
pool size or otherwise work around concurrency problems.

Note that only functions specific to this package or with altered semantics are
documented. Please refer to the database/sql package documentation for more
information.
*/
package dbcontrol

import (
	"database/sql"
	"sync"
	"time"
)

// DB is the main type wrapping up sql.DB. You should use it just like you would
// sql.DB. If a connection is required and not available, the statement using
// the type will block until another connection is returned to the pool.
type DB struct {
	*sql.DB
	maxConns   int
	sem        chan bool
	blockCh    chan<- time.Duration
	blockChMux sync.RWMutex
}

// NewDB initializes a new DB wrapper on a given sql.DB.
func NewDB(sqldb *sql.DB) *DB {
	db := &DB{DB: sqldb}

	if c := Concurrency(); c > 0 {
		// Let's create a token channel and feed it with c tokens
		db.sem = make(chan bool, c)

		for i := 0; i < c; i++ {
			db.sem <- true
		}

		// This is actually required, otherwise connections are quickly
		// discarded, even if new ones have to be immediately opened.
		db.DB.SetMaxIdleConns(c)
		db.maxConns = c
	}

	return db
}

// MaxConn returns the maximum number of connections for the DB.
func (db *DB) MaxConns() int {
	return db.maxConns
}
<commit_msg>Add Open() to mimic database/sql.Open()<commit_after>// Copyright (c) 2013 VividCortex. Please see the LICENSE file for license terms.

/*
Package dbcontrol limits the number of active connections for database/sql.

Implementations of Go's database/sql package prior to Go 1.2, don't let the user
put a limit on the number of active connections to the underlying DB.  If enough
concurrent requests are made, so that the package runs out of available
connections, then more are requested from the driver no matter how many you have
at the pool already. Hence, unless you take precautions in your design/code, or
there's specific support from the actual DB driver you're using, you are likely
to hit some other limit (DB engine itself, OS) or simply run out of resources.

None of those situations are desirable, of course. If you hit DB or OS limits on
the number of connections, then many of your statements will start failing cause
no connection is available for them to use. You can get around it if you're
lucky enough to have driver support, but then you are tied to a particular DB.

This package is a wrapper on Go's standard database/sql, providing a general
mechanism so that you're free to use statements as usual, yet have the number of
active connections limited. A wrapper DB type is declared, that supports all
standard operations from database/sql. To use it, you should set the maximum
number of connections you want to allow, just like:

	dbcontrol.SetConcurrency(10)

All databases opened by the package afterwards will use a maximum of 10
connections. You can change this setting as often as you wish, but keep in mind
that the number is bound to databases as they are opened, i.e., changing this
concurrency setting has no effect on already-opened databases. Note also that
you can get the default non-limited behavior by setting concurrency to zero. To
open a DB you proceed just like with the database/sql package, like so:

	db, err := dbcontrol.Open("mysql", dsn)

Note that sql.Row, sql.Rows and sql.Stmt types are overridden by this package,
but that's probably transparent unless you declare the types explicitly. If you
declare variables using the := operator you'll be fine. Usage now follows the
expected pattern from database/sql:

	rows, err := db.Query("select id, name from customers")
	if err != nil {
		log.Fatal(err)
	}

	for rows.Next() {
		var id int
		var name string
		if err := rows.Scan(&id, &name); err != nil {
			log.Fatal(err)
		}

		fmt.Println(id, name)
	}

The full set of features at database/sql is supported, including transactions,
even though not all functions need to be overridden. This package was designed
to provide the feature with minimum overhead, and thus uses knowledge of
database/sql internals to know when locking is required/appropriate. As an
extension, you can set a channel to receive the locking duration each time a
connection has to be waited for. This can work as an aid to help you tune the
pool size or otherwise work around concurrency problems.

Note that only functions specific to this package or with altered semantics are
documented. Please refer to the database/sql package documentation for more
information.
*/
package dbcontrol

import (
	"database/sql"
	"sync"
	"time"
)

// DB is the main type wrapping up sql.DB. You should use it just like you would
// sql.DB. If a connection is required and not available, the statement using
// the type will block until another connection is returned to the pool.
type DB struct {
	*sql.DB
	maxConns   int
	sem        chan bool
	blockCh    chan<- time.Duration
	blockChMux sync.RWMutex
}

func Open(driver, dsn string) (*DB, error) {
	sqldb, err := sql.Open(driver, dsn)
	if err != nil {
		return nil, err
	}

	// We wrap *sql.DB into our DB
	db := &DB{DB: sqldb}

	if c := Concurrency(); c > 0 {
		// Let's create a token channel and feed it with c tokens
		db.sem = make(chan bool, c)

		for i := 0; i < c; i++ {
			db.sem <- true
		}

		// This is actually required, otherwise connections are quickly
		// discarded, even if new ones have to be immediately opened.
		db.DB.SetMaxIdleConns(c)
		db.maxConns = c
	}

	return db, nil
}

// MaxConn returns the maximum number of connections for the DB.
func (db *DB) MaxConns() int {
	return db.maxConns
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"log"
	"time"

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

type Event struct {
	EnvelopeId  uuid.UUID
	Id          uuid.UUID
	Date        string
	Name        string
	Balance     int
	Target      int
	MonthTarget int
	Deleted     bool
}

type Envelope struct {
	// Values in Euro-cents
	Id          uuid.UUID
	Balance     int
	Target      int
	Name        string
	MonthDelta  int
	MonthTarget int
}

type DB struct {
	db     *sql.DB
	Events chan Event
}

func OpenDB() (*DB, error) {
	db, err := sql.Open("sqlite3", "envelopes.sqlite")
	if err != nil {
		return nil, err
	}

	rv := &DB{db, make(chan Event)}

	if err := rv.setup(); err != nil {
		return nil, err
	}

	var count int64
	if err := db.QueryRow("SELECT count(*) FROM envelopes WHERE deleted = 'false'").Scan(&count); err != nil {
		return nil, err
	}
	log.Printf(`DB contains %d envelopes`, count)

	return rv, nil
}

func (d *DB) Close() error {
	return d.db.Close()
}

func (d *DB) setup() error {
	tx, err := d.db.Begin()
	if err != nil {
		return err
	}

	if _, err := tx.Exec(`
		CREATE TABLE IF NOT EXISTS envelopes
		(id UUID PRIMARY KEY, name STRING,
		 balance INTEGER,
		 target INTEGER, monthtarget INTEGER,
		 deleted BOOLEAN)`); err != nil {
		return err
	}

	if _, err := tx.Exec(`
		CREATE TABLE IF NOT EXISTS history
		(id UUID PRIMARY KEY,
		 envelope UUID, date DATETIME, name STRING,
		 balance INTEGER, target INTEGER, monthtarget INTEGER,
		 deleted BOOLEAN,
		 FOREIGN KEY(envelope) REFERENCES envelopes(id))`); err != nil {
		return err
	}

	return tx.Commit()
}

func (d *DB) AllEnvelopes() []*Envelope {
	rv := []*Envelope{}

	rows, err := d.db.Query(`
		SELECT e.id, e.name, e.balance, e.target, e.monthtarget, h.balance
		FROM envelopes AS e LEFT OUTER JOIN
			(SELECT envelope, sum(balance) AS balance, date
			 FROM history
			 WHERE date > DATE('now', 'start of month')
			 GROUP BY envelope) AS h
		ON e.id = h.envelope
		WHERE e.deleted = 'false'`)
	if err != nil {
		log.Printf(`error querying DB: %v`, err)
		return nil
	}
	defer rows.Close()

	for rows.Next() {
		var e Envelope
		var delta sql.NullInt64
		if err := rows.Scan(&e.Id, &e.Name, &e.Balance, &e.Target, &e.MonthTarget, &delta); err != nil {
			log.Printf(`error querying DB: %v`, err)
			return nil
		}
		if delta.Valid {
			e.MonthDelta = int(delta.Int64)
		}
		rv = append(rv, &e)
	}

	return rv
}

func (d *DB) DeleteEnvelope(id string) {
	/* TODO: make sure that id is a well formed UUID */

	_, err := d.db.Exec("UPDATE envelopes SET deleted = 'true' WHERE id = $1", id)
	if err != nil {
		log.Printf(`error deleting envelope: %s`, err)
	}

	_, err = d.db.Exec(`
		INSERT INTO history
		VALUES ($1, $2, '', 0, 0, datetime('now'), 'true', 0)`, uuid.New(), id)
	if err != nil {
		log.Printf(`error deleting envelope history: %s`, err)
	}
}

func (d *DB) Envelope(id uuid.UUID) (*Envelope, error) {
	tx, err := d.db.Begin()
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	return d.envelopeWithTx(tx, id)
}

func (d *DB) envelopeWithTx(tx *sql.Tx, id uuid.UUID) (*Envelope, error) {
	e := Envelope{Id: id}

	err := tx.QueryRow(`
		SELECT id, name, balance, target, monthtarget
		FROM envelopes
		WHERE id = $1 AND deleted = 'false'`, id).Scan(&e.Id, &e.Name, &e.Balance, &e.Target, &e.MonthTarget)
	if err == nil {
		return &e, nil
	}

	e.Id = uuid.New()
	if _, err := tx.Exec(`INSERT INTO envelopes VALUES ($1, "", 0, 0, 'false', 0)`, e.Id); err != nil {
		return nil, err
	}
	return &e, nil
}

func (d *DB) EnvelopeWithHistory(id uuid.UUID) (*Envelope, []Event, error) {
	events := []Event{}

	tx, err := d.db.Begin()
	if err != nil {
		return nil, events, err
	}
	defer tx.Rollback()

	envelope, err := d.envelopeWithTx(tx, id)
	if err != nil {
		return nil, events, err
	}

	rows, err := tx.Query(`
		SELECT id, date, name, balance, target, monthtarget, deleted
		FROM history
		WHERE envelope = $1`, id)
	if err != nil {
		return nil, events, err
	}
	defer rows.Close()

	for rows.Next() {
		var e Event
		var eventId uuid.UUID
		if err := rows.Scan(&eventId, &e.Date, &e.Name, &e.Balance, &e.Target, &e.MonthTarget, &e.Deleted); err != nil {
			log.Printf(`can't scan event %s: %s`, eventId, err)
		}
		if e.Deleted {
			e.Name = envelope.Name
		}
		events = append(events, e)
	}

	return envelope, events, nil
}

func (d *DB) MergeEvent(e Event) error {
	log.Printf(`merging event %v`, e)

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

	env, err := d.envelopeWithTx(tx, e.EnvelopeId)
	if err != nil {
		return err
	}

	_, err = tx.Exec(`
		INSERT INTO history (id, envelope, name, balance, target, monthtarget, date, deleted)
		VALUES ($1, $2, $3, $4, $5, $6, datetime('now'), 'false')`,
		e.Id, e.EnvelopeId, e.Name, e.Balance, e.Target, e.MonthTarget)
	if err != nil {
		tx.Rollback()
		return err
	}

	if e.Name == "" {
		e.Name = env.Name
	}
	res, err := tx.Exec(`
		UPDATE envelopes
		SET name = $1, balance = $2, target = $3, monthtarget = $4
		WHERE id = $5`, e.Name, env.Balance+e.Balance, env.Target+e.Target, env.MonthTarget+e.MonthTarget, env.Id)
	rows, _ := res.RowsAffected()
	log.Printf(`%d affected rows`, rows)

	if err != nil {
		tx.Rollback()
		return err
	}

	return tx.Commit()
}

func (d *DB) UpdateEnvelope(id uuid.UUID, name string, dBalance, dTarget, dMonthTarget int) error {
	env, err := d.Envelope(id)
	if err != nil {
		return err
	}

	/* Make parameters relative */
	dBalance -= env.Balance
	dTarget -= env.Target
	dMonthTarget -= env.MonthTarget

	log.Printf(`dB: %d dT: %d dMT: %d`, dBalance, dTarget, dMonthTarget)

	evt := Event{
		EnvelopeId:  env.Id,
		Id:          uuid.New(),
		Date:        time.Now().String(),
		Name:        name,
		Balance:     dBalance,
		Target:      dTarget,
		MonthTarget: dMonthTarget,
		Deleted:     false,
	}

	d.Events <- evt

	return d.MergeEvent(evt)
}
<commit_msg>Clean up DB inserts<commit_after>package main

import (
	"database/sql"
	"log"
	"time"

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

type Event struct {
	EnvelopeId  uuid.UUID
	Id          uuid.UUID
	Date        string
	Name        string
	Balance     int
	Target      int
	MonthTarget int
	Deleted     bool
}

type Envelope struct {
	// Values in Euro-cents
	Id          uuid.UUID
	Balance     int
	Target      int
	Name        string
	MonthDelta  int
	MonthTarget int
}

type DB struct {
	db     *sql.DB
	Events chan Event
}

func OpenDB() (*DB, error) {
	db, err := sql.Open("sqlite3", "envelopes.sqlite")
	if err != nil {
		return nil, err
	}

	rv := &DB{db, make(chan Event)}

	if err := rv.setup(); err != nil {
		return nil, err
	}

	var count int64
	if err := db.QueryRow("SELECT count(*) FROM envelopes WHERE deleted = 'false'").Scan(&count); err != nil {
		return nil, err
	}
	log.Printf(`DB contains %d envelopes`, count)

	return rv, nil
}

func (d *DB) Close() error {
	return d.db.Close()
}

func (d *DB) setup() error {
	tx, err := d.db.Begin()
	if err != nil {
		return err
	}

	if _, err := tx.Exec(`
		CREATE TABLE IF NOT EXISTS envelopes
		(id UUID PRIMARY KEY, name STRING,
		 balance INTEGER,
		 target INTEGER, monthtarget INTEGER,
		 deleted BOOLEAN)`); err != nil {
		return err
	}

	if _, err := tx.Exec(`
		CREATE TABLE IF NOT EXISTS history
		(id UUID PRIMARY KEY,
		 envelope UUID, date DATETIME, name STRING,
		 balance INTEGER, target INTEGER, monthtarget INTEGER,
		 deleted BOOLEAN,
		 FOREIGN KEY(envelope) REFERENCES envelopes(id))`); err != nil {
		return err
	}

	return tx.Commit()
}

func (d *DB) AllEnvelopes() []*Envelope {
	rv := []*Envelope{}

	rows, err := d.db.Query(`
		SELECT e.id, e.name, e.balance, e.target, e.monthtarget, h.balance
		FROM envelopes AS e LEFT OUTER JOIN
			(SELECT envelope, sum(balance) AS balance, date
			 FROM history
			 WHERE date > DATE('now', 'start of month')
			 GROUP BY envelope) AS h
		ON e.id = h.envelope
		WHERE e.deleted = 'false'`)
	if err != nil {
		log.Printf(`error querying DB: %v`, err)
		return nil
	}
	defer rows.Close()

	for rows.Next() {
		var e Envelope
		var delta sql.NullInt64
		if err := rows.Scan(&e.Id, &e.Name, &e.Balance, &e.Target, &e.MonthTarget, &delta); err != nil {
			log.Printf(`error querying DB: %v`, err)
			return nil
		}
		if delta.Valid {
			e.MonthDelta = int(delta.Int64)
		}
		rv = append(rv, &e)
	}

	return rv
}

func (d *DB) DeleteEnvelope(id string) {
	/* TODO: make sure that id is a well formed UUID */

	_, err := d.db.Exec("UPDATE envelopes SET deleted = 'true' WHERE id = $1", id)
	if err != nil {
		log.Printf(`error deleting envelope: %s`, err)
	}

	_, err = d.db.Exec(`
		INSERT INTO history (id, envelope, name, balance, target, monthtarget, deleted, date)
		VALUES ($1, $2, '', 0, 0, 0, 'true', datetime('now'))`, uuid.New(), id)
	if err != nil {
		log.Printf(`error deleting envelope history: %s`, err)
	}
}

func (d *DB) Envelope(id uuid.UUID) (*Envelope, error) {
	tx, err := d.db.Begin()
	if err != nil {
		return nil, err
	}
	defer tx.Rollback()

	return d.envelopeWithTx(tx, id)
}

func (d *DB) envelopeWithTx(tx *sql.Tx, id uuid.UUID) (*Envelope, error) {
	e := Envelope{Id: id}

	err := tx.QueryRow(`
		SELECT id, name, balance, target, monthtarget
		FROM envelopes
		WHERE id = $1 AND deleted = 'false'`, id).Scan(&e.Id, &e.Name, &e.Balance, &e.Target, &e.MonthTarget)
	if err == nil {
		return &e, nil
	}

	if _, err := tx.Exec(`
		INSERT INTO envelopes(id, name, balance, target, monthtarget, deleted)
		VALUES ($1, "", 0, 0, 0, 'false')`, e.Id); err != nil {
		return nil, err
	}
	return &e, nil
}

func (d *DB) EnvelopeWithHistory(id uuid.UUID) (*Envelope, []Event, error) {
	events := []Event{}

	tx, err := d.db.Begin()
	if err != nil {
		return nil, events, err
	}
	defer tx.Rollback()

	envelope, err := d.envelopeWithTx(tx, id)
	if err != nil {
		return nil, events, err
	}

	rows, err := tx.Query(`
		SELECT id, date, name, balance, target, monthtarget, deleted
		FROM history
		WHERE envelope = $1`, id)
	if err != nil {
		return nil, events, err
	}
	defer rows.Close()

	for rows.Next() {
		var e Event
		var eventId uuid.UUID
		if err := rows.Scan(&eventId, &e.Date, &e.Name, &e.Balance, &e.Target, &e.MonthTarget, &e.Deleted); err != nil {
			log.Printf(`can't scan event %s: %s`, eventId, err)
		}
		if e.Deleted {
			e.Name = envelope.Name
		}
		events = append(events, e)
	}

	return envelope, events, nil
}

func (d *DB) MergeEvent(e Event) error {
	log.Printf(`merging event %v`, e)

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

	env, err := d.envelopeWithTx(tx, e.EnvelopeId)
	if err != nil {
		return err
	}

	_, err = tx.Exec(`
		INSERT INTO history (id, envelope, name, balance, target, monthtarget, date, deleted)
		VALUES ($1, $2, $3, $4, $5, $6, datetime('now'), 'false')`,
		e.Id, e.EnvelopeId, e.Name, e.Balance, e.Target, e.MonthTarget)
	if err != nil {
		tx.Rollback()
		return err
	}

	if e.Name == "" {
		e.Name = env.Name
	}
	res, err := tx.Exec(`
		UPDATE envelopes
		SET name = $1, balance = $2, target = $3, monthtarget = $4
		WHERE id = $5`, e.Name, env.Balance+e.Balance, env.Target+e.Target, env.MonthTarget+e.MonthTarget, env.Id)
	rows, _ := res.RowsAffected()
	log.Printf(`%d affected rows`, rows)

	if err != nil {
		tx.Rollback()
		return err
	}

	return tx.Commit()
}

func (d *DB) UpdateEnvelope(id uuid.UUID, name string, dBalance, dTarget, dMonthTarget int) error {
	env, err := d.Envelope(id)
	if err != nil {
		return err
	}

	/* Make parameters relative */
	dBalance -= env.Balance
	dTarget -= env.Target
	dMonthTarget -= env.MonthTarget

	log.Printf(`dB: %d dT: %d dMT: %d`, dBalance, dTarget, dMonthTarget)

	evt := Event{
		EnvelopeId:  env.Id,
		Id:          uuid.New(),
		Date:        time.Now().String(),
		Name:        name,
		Balance:     dBalance,
		Target:      dTarget,
		MonthTarget: dMonthTarget,
		Deleted:     false,
	}

	d.Events <- evt

	return d.MergeEvent(evt)
}
<|endoftext|>


<commit_before>package main

import (
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"strings"
)

const spool = "/var/tmp/godep"

var cmdGo = &Command{
	Usage: "go command [args...]",
	Short: "run the go tool in a sandbox",
	Long: `
Go runs the go tool in a temporary GOPATH sandbox
with the dependencies listed in file Godeps.
`,
	Run: runGo,
}

// Set up a sandbox and run the go tool. The sandbox is built
// out of specific checked-out revisions of repos. We keep repos
// and revs materialized on disk under the assumption that disk
// space is cheap and plentiful, and writing files is slow.
// Everything is kept in the spool directory.
func runGo(cmd *Command, args []string) {
	g, err := ReadGodeps("Godeps")
	if err != nil {
		log.Fatalln(err)
	}
	gopath, err := sandboxAll(g.Deps)
	if err != nil {
		log.Fatalln(err)
	}

	if s := os.Getenv("GOPATH"); s != "" {
		gopath += ":" + os.Getenv("GOPATH")
	}
	c := exec.Command("go", args...)
	c.Env = append(envNoGopath(), "GOPATH="+gopath)
	c.Stdin = os.Stdin
	c.Stdout = os.Stdout
	c.Stderr = os.Stderr
	err = c.Run()
	if err != nil {
		log.Fatalln("go", err)
	}
}

func envNoGopath() (a []string) {
	for _, s := range os.Environ() {
		if !strings.HasPrefix(s, "GOPATH=") {
			a = append(a, s)
		}
	}
	return a
}

// sandboxAll ensures that the commits in deps are available
// on disk, and returns a GOPATH string that will cause them
// to be used.
func sandboxAll(a []Dependency) (gopath string, err error) {
	var path []string
	for _, dep := range a {
		dir, err := sandbox(dep)
		if err != nil {
			return "", err
		}
		path = append(path, dir)
	}
	return strings.Join(path, ":"), nil
}

// sandbox ensures that commit d is available on disk,
// and returns a GOPATH string that will cause it to be used.
func sandbox(d Dependency) (gopath string, err error) {
	if !exists(d.RepoPath()) {
		if err = d.CreateRepo("fast", "main"); err != nil {
			return "", fmt.Errorf("create repo: %s", err)
		}
	}
	err = d.checkout()
	if err != nil && d.FastRemotePath() != "" {
		err = d.fetchAndCheckout("fast")
	}
	if err != nil {
		err = d.fetchAndCheckout("main")
	}
	if err != nil {
		return "", err
	}
	return d.Gopath(), nil
}

func rands(n int) string {
	b := make([]byte, n)
	if _, err := io.ReadFull(rand.Reader, b); err != nil {
		log.Fatal("rands", err)
	}
	return hex.EncodeToString(b)[:n]
}
<commit_msg>remove unreachable code<commit_after>package main

import (
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"
)

const spool = "/var/tmp/godep"

var cmdGo = &Command{
	Usage: "go command [args...]",
	Short: "run the go tool in a sandbox",
	Long: `
Go runs the go tool in a temporary GOPATH sandbox
with the dependencies listed in file Godeps.
`,
	Run: runGo,
}

// Set up a sandbox and run the go tool. The sandbox is built
// out of specific checked-out revisions of repos. We keep repos
// and revs materialized on disk under the assumption that disk
// space is cheap and plentiful, and writing files is slow.
// Everything is kept in the spool directory.
func runGo(cmd *Command, args []string) {
	g, err := ReadGodeps("Godeps")
	if err != nil {
		log.Fatalln(err)
	}
	gopath, err := sandboxAll(g.Deps)
	if err != nil {
		log.Fatalln(err)
	}

	if s := os.Getenv("GOPATH"); s != "" {
		gopath += ":" + os.Getenv("GOPATH")
	}
	c := exec.Command("go", args...)
	c.Env = append(envNoGopath(), "GOPATH="+gopath)
	c.Stdin = os.Stdin
	c.Stdout = os.Stdout
	c.Stderr = os.Stderr
	err = c.Run()
	if err != nil {
		log.Fatalln("go", err)
	}
}

func envNoGopath() (a []string) {
	for _, s := range os.Environ() {
		if !strings.HasPrefix(s, "GOPATH=") {
			a = append(a, s)
		}
	}
	return a
}

// sandboxAll ensures that the commits in deps are available
// on disk, and returns a GOPATH string that will cause them
// to be used.
func sandboxAll(a []Dependency) (gopath string, err error) {
	var path []string
	for _, dep := range a {
		dir, err := sandbox(dep)
		if err != nil {
			return "", err
		}
		path = append(path, dir)
	}
	return strings.Join(path, ":"), nil
}

// sandbox ensures that commit d is available on disk,
// and returns a GOPATH string that will cause it to be used.
func sandbox(d Dependency) (gopath string, err error) {
	if !exists(d.RepoPath()) {
		if err = d.CreateRepo("fast", "main"); err != nil {
			return "", fmt.Errorf("create repo: %s", err)
		}
	}
	err = d.checkout()
	if err != nil && d.FastRemotePath() != "" {
		err = d.fetchAndCheckout("fast")
	}
	if err != nil {
		err = d.fetchAndCheckout("main")
	}
	if err != nil {
		return "", err
	}
	return d.Gopath(), nil
}
<|endoftext|>