<commit_before>// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package http

import (
	"context"
	"crypto/tls"
	"errors"
	"io/ioutil"
	"net/http"
	"runtime/pprof"
	"sync"
	"sync/atomic"
	"time"

	humanize "github.com/dustin/go-humanize"

	"github.com/minio/minio/internal/config"
	"github.com/minio/minio/internal/config/api"
	xtls "github.com/minio/minio/internal/config/identity/tls"
	"github.com/minio/minio/internal/fips"
	"github.com/minio/pkg/certs"
	"github.com/minio/pkg/env"
)

const (
	serverShutdownPoll = 500 * time.Millisecond

	// DefaultShutdownTimeout - default shutdown timeout used for graceful http server shutdown.
	DefaultShutdownTimeout = 5 * time.Second

	// DefaultMaxHeaderBytes - default maximum HTTP header size in bytes.
	DefaultMaxHeaderBytes = 1 * humanize.MiByte
)

// Server - extended http.Server supports multiple addresses to serve and enhanced connection handling.
type Server struct {
	http.Server
	Addrs           []string      // addresses on which the server listens for new connection.
	ShutdownTimeout time.Duration // timeout used for graceful server shutdown.
	listenerMutex   sync.Mutex    // to guard 'listener' field.
	listener        *httpListener // HTTP listener for all 'Addrs' field.
	inShutdown      uint32        // indicates whether the server is in shutdown or not
	requestCount    int32         // counter holds no. of request in progress.
}

// GetRequestCount - returns number of request in progress.
func (srv *Server) GetRequestCount() int {
	return int(atomic.LoadInt32(&srv.requestCount))
}

// Start - start HTTP server
func (srv *Server) Start(ctx context.Context) (err error) {
	// Take a copy of server fields.
	var tlsConfig *tls.Config
	if srv.TLSConfig != nil {
		tlsConfig = srv.TLSConfig.Clone()
	}
	handler := srv.Handler // if srv.Handler holds non-synced state -> possible data race

	// Create new HTTP listener.
	var listener *httpListener
	listener, err = newHTTPListener(
		ctx,
		srv.Addrs,
	)
	if err != nil {
		return err
	}

	// Wrap given handler to do additional
	// * return 503 (service unavailable) if the server in shutdown.
	wrappedHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// If server is in shutdown.
		if atomic.LoadUint32(&srv.inShutdown) != 0 {
			// To indicate disable keep-alives
			w.Header().Set("Connection", "close")
			w.WriteHeader(http.StatusForbidden)
			w.Write([]byte(http.ErrServerClosed.Error()))
			w.(http.Flusher).Flush()
			return
		}

		atomic.AddInt32(&srv.requestCount, 1)
		defer atomic.AddInt32(&srv.requestCount, -1)

		// Handle request using passed handler.
		handler.ServeHTTP(w, r)
	})

	srv.listenerMutex.Lock()
	srv.Handler = wrappedHandler
	srv.listener = listener
	srv.listenerMutex.Unlock()

	// Start servicing with listener.
	if tlsConfig != nil {
		return srv.Server.Serve(tls.NewListener(listener, tlsConfig))
	}
	return srv.Server.Serve(listener)
}

// Shutdown - shuts down HTTP server.
func (srv *Server) Shutdown() error {
	srv.listenerMutex.Lock()
	if srv.listener == nil {
		srv.listenerMutex.Unlock()
		return http.ErrServerClosed
	}
	srv.listenerMutex.Unlock()

	if atomic.AddUint32(&srv.inShutdown, 1) > 1 {
		// shutdown in progress
		return http.ErrServerClosed
	}

	// Close underneath HTTP listener.
	srv.listenerMutex.Lock()
	err := srv.listener.Close()
	srv.listenerMutex.Unlock()
	if err != nil {
		return err
	}

	// Wait for opened connection to be closed up to Shutdown timeout.
	shutdownTimeout := srv.ShutdownTimeout
	shutdownTimer := time.NewTimer(shutdownTimeout)
	ticker := time.NewTicker(serverShutdownPoll)
	defer ticker.Stop()
	for {
		select {
		case <-shutdownTimer.C:
			// Write all running goroutines.
			tmp, err := ioutil.TempFile("", "minio-goroutines-*.txt")
			if err == nil {
				_ = pprof.Lookup("goroutine").WriteTo(tmp, 1)
				tmp.Close()
				return errors.New("timed out. some connections are still active. goroutines written to " + tmp.Name())
			}
			return errors.New("timed out. some connections are still active")
		case <-ticker.C:
			if atomic.LoadInt32(&srv.requestCount) <= 0 {
				return nil
			}
		}
	}
}

// NewServer - creates new HTTP server using given arguments.
func NewServer(addrs []string, handler http.Handler, getCert certs.GetCertificateFunc) *Server {
	secureCiphers := env.Get(api.EnvAPISecureCiphers, config.EnableOn) == config.EnableOn
	var tlsConfig *tls.Config
	if getCert != nil {
		tlsConfig = &tls.Config{
			PreferServerCipherSuites: true,
			MinVersion:               tls.VersionTLS12,
			NextProtos:               []string{"http/1.1", "h2"},
			GetCertificate:           getCert,
		}

		tlsClientIdentity := env.Get(xtls.EnvIdentityTLSEnabled, "") == config.EnableOn
		if tlsClientIdentity {
			tlsConfig.ClientAuth = tls.RequestClientCert
		}

		if secureCiphers || fips.Enabled {
			tlsConfig.CipherSuites = fips.CipherSuitesTLS()
			tlsConfig.CurvePreferences = fips.EllipticCurvesTLS()
		}
	}

	httpServer := &Server{
		Addrs:           addrs,
		ShutdownTimeout: DefaultShutdownTimeout,
	}
	httpServer.Handler = handler
	httpServer.TLSConfig = tlsConfig
	httpServer.MaxHeaderBytes = DefaultMaxHeaderBytes

	return httpServer
}
<commit_msg>tls: Avoid 3DES cipher (#13459)<commit_after>// Copyright (c) 2015-2021 MinIO, Inc.
//
// This file is part of MinIO Object Storage stack
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

package http

import (
	"context"
	"crypto/tls"
	"errors"
	"io/ioutil"
	"net/http"
	"runtime/pprof"
	"sync"
	"sync/atomic"
	"time"

	humanize "github.com/dustin/go-humanize"

	"github.com/minio/minio/internal/config"
	"github.com/minio/minio/internal/config/api"
	xtls "github.com/minio/minio/internal/config/identity/tls"
	"github.com/minio/minio/internal/fips"
	"github.com/minio/pkg/certs"
	"github.com/minio/pkg/env"
)

const (
	serverShutdownPoll = 500 * time.Millisecond

	// DefaultShutdownTimeout - default shutdown timeout used for graceful http server shutdown.
	DefaultShutdownTimeout = 5 * time.Second

	// DefaultMaxHeaderBytes - default maximum HTTP header size in bytes.
	DefaultMaxHeaderBytes = 1 * humanize.MiByte
)

// Server - extended http.Server supports multiple addresses to serve and enhanced connection handling.
type Server struct {
	http.Server
	Addrs           []string      // addresses on which the server listens for new connection.
	ShutdownTimeout time.Duration // timeout used for graceful server shutdown.
	listenerMutex   sync.Mutex    // to guard 'listener' field.
	listener        *httpListener // HTTP listener for all 'Addrs' field.
	inShutdown      uint32        // indicates whether the server is in shutdown or not
	requestCount    int32         // counter holds no. of request in progress.
}

// GetRequestCount - returns number of request in progress.
func (srv *Server) GetRequestCount() int {
	return int(atomic.LoadInt32(&srv.requestCount))
}

// Start - start HTTP server
func (srv *Server) Start(ctx context.Context) (err error) {
	// Take a copy of server fields.
	var tlsConfig *tls.Config
	if srv.TLSConfig != nil {
		tlsConfig = srv.TLSConfig.Clone()
	}
	handler := srv.Handler // if srv.Handler holds non-synced state -> possible data race

	// Create new HTTP listener.
	var listener *httpListener
	listener, err = newHTTPListener(
		ctx,
		srv.Addrs,
	)
	if err != nil {
		return err
	}

	// Wrap given handler to do additional
	// * return 503 (service unavailable) if the server in shutdown.
	wrappedHandler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		// If server is in shutdown.
		if atomic.LoadUint32(&srv.inShutdown) != 0 {
			// To indicate disable keep-alives
			w.Header().Set("Connection", "close")
			w.WriteHeader(http.StatusForbidden)
			w.Write([]byte(http.ErrServerClosed.Error()))
			w.(http.Flusher).Flush()
			return
		}

		atomic.AddInt32(&srv.requestCount, 1)
		defer atomic.AddInt32(&srv.requestCount, -1)

		// Handle request using passed handler.
		handler.ServeHTTP(w, r)
	})

	srv.listenerMutex.Lock()
	srv.Handler = wrappedHandler
	srv.listener = listener
	srv.listenerMutex.Unlock()

	// Start servicing with listener.
	if tlsConfig != nil {
		return srv.Server.Serve(tls.NewListener(listener, tlsConfig))
	}
	return srv.Server.Serve(listener)
}

// Shutdown - shuts down HTTP server.
func (srv *Server) Shutdown() error {
	srv.listenerMutex.Lock()
	if srv.listener == nil {
		srv.listenerMutex.Unlock()
		return http.ErrServerClosed
	}
	srv.listenerMutex.Unlock()

	if atomic.AddUint32(&srv.inShutdown, 1) > 1 {
		// shutdown in progress
		return http.ErrServerClosed
	}

	// Close underneath HTTP listener.
	srv.listenerMutex.Lock()
	err := srv.listener.Close()
	srv.listenerMutex.Unlock()
	if err != nil {
		return err
	}

	// Wait for opened connection to be closed up to Shutdown timeout.
	shutdownTimeout := srv.ShutdownTimeout
	shutdownTimer := time.NewTimer(shutdownTimeout)
	ticker := time.NewTicker(serverShutdownPoll)
	defer ticker.Stop()
	for {
		select {
		case <-shutdownTimer.C:
			// Write all running goroutines.
			tmp, err := ioutil.TempFile("", "minio-goroutines-*.txt")
			if err == nil {
				_ = pprof.Lookup("goroutine").WriteTo(tmp, 1)
				tmp.Close()
				return errors.New("timed out. some connections are still active. goroutines written to " + tmp.Name())
			}
			return errors.New("timed out. some connections are still active")
		case <-ticker.C:
			if atomic.LoadInt32(&srv.requestCount) <= 0 {
				return nil
			}
		}
	}
}

// NewServer - creates new HTTP server using given arguments.
func NewServer(addrs []string, handler http.Handler, getCert certs.GetCertificateFunc) *Server {
	secureCiphers := env.Get(api.EnvAPISecureCiphers, config.EnableOn) == config.EnableOn
	var tlsConfig *tls.Config
	if getCert != nil {
		tlsConfig = &tls.Config{
			PreferServerCipherSuites: true,
			MinVersion:               tls.VersionTLS12,
			NextProtos:               []string{"http/1.1", "h2"},
			GetCertificate:           getCert,
		}

		tlsClientIdentity := env.Get(xtls.EnvIdentityTLSEnabled, "") == config.EnableOn
		if tlsClientIdentity {
			tlsConfig.ClientAuth = tls.RequestClientCert
		}

		if secureCiphers || fips.Enabled {
			// Hardened ciphers
			tlsConfig.CipherSuites = fips.CipherSuitesTLS()
			tlsConfig.CurvePreferences = fips.EllipticCurvesTLS()
		} else {
			// Default ciphers while excluding those with security issues
			for _, cipher := range tls.CipherSuites() {
				tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, cipher.ID)
			}
		}
	}

	httpServer := &Server{
		Addrs:           addrs,
		ShutdownTimeout: DefaultShutdownTimeout,
	}
	httpServer.Handler = handler
	httpServer.TLSConfig = tlsConfig
	httpServer.MaxHeaderBytes = DefaultMaxHeaderBytes

	return httpServer
}
<|endoftext|>


<commit_before>package models

import (
	"database/sql"
	"fmt"
	"log"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/brnstz/bus/internal/etc"
	"github.com/brnstz/upsert"
	"github.com/jmoiron/sqlx"
)

// Stop is a single transit stop for a particular route. If a
// stop serves more than one route, there are multiple distinct
// entries for that stop.
type Stop struct {
	ID       string `json:"stop_id" db:"stop_id" upsert:"key"`
	RouteID  string `json:"route_id" db:"route_id" upsert:"key"`
	AgencyID string `json:"agency_id" db:"agency_id" upsert:"key"`
	Name     string `json:"stop_name" db:"stop_name"`

	DirectionID int    `json:"direction_id" db:"direction_id"`
	Headsign    string `json:"headsign" db:"headsign"`

	Lat float64 `json:"lat" db:"lat" upsert:"omit"`
	Lon float64 `json:"lon" db:"lon" upsert:"omit"`

	// Location is an "earth" field value that combines lat and lon into
	// a single field.
	Location interface{} `json:"-" db:"location" upsert_value:"ll_to_earth(:lat, :lon)"`

	// Dist, Scheduled, and Live fields that are only filled in
	// when returning a response from an API request. Do not directly
	// display them in JSON since
	Dist      float64      `json:"-" db:"dist" upsert:"omit"`
	Scheduled []*Departure `json:"-" db:"-" upsert:"omit"`
	Live      []*Departure `json:"-" db:"-" upsert:"omit"`
}

// Table implements the upsert.Upserter interface, returning the table
// where we save stops.
func (s *Stop) Table() string {
	return "stop"
}

// Save saves a stop to the database
func (s *Stop) Save() error {
	_, err := upsert.Upsert(etc.DBConn, s)
	return err
}

// appendLive calls either the bus time API or the subway datamine API
// to add live info to our stop info.
func (s *Stop) appendLive(now time.Time) {
	route, err := GetRoute(s.RouteID)
	if err != nil {
		log.Println("can't load route", err)
		return
	}

	if route.Type == Bus {
		departures, err := GetLiveBus(
			s.RouteID, strconv.Itoa(s.DirectionID),
			s.ID,
		)
		if err != nil {
			log.Println("can't append live schedules")
			return
		}

		sort.Sort(departures)

		for i := 0; i < len(departures) && i < maxDepartures; i++ {
			s.Live = append(s.Live, departures[i])
		}
	} else if route.Type == Subway {

		departures, err := GetLiveSubways(
			s.RouteID, strconv.Itoa(s.DirectionID),
			s.ID,
		)
		if err != nil {
			log.Println("can't append live subway sched", err)
			return
		}

		sort.Sort(departures)
		for i := 0; i < len(departures) && i < maxDepartures; i++ {
			s.Live = append(s.Live, departures[i])
		}

	}
}

// String returns a descriptive string for this stop.
func (s Stop) String() string {
	return fmt.Sprintf("{%v %v %v %v %v @ (%v,%v)}",
		s.ID, s.Name, s.RouteID, s.Headsign, s.DirectionID, s.Lat, s.Lon,
	)
}

// Key() returns the unique string for this stop, so we can identify
// unique stops in the loader.
func (s Stop) Key() string {
	return fmt.Sprintf("%v%v", s.ID, s.RouteID)
}

// setDepartures checks the database and any relevant APIs to set the scheduled
// and live departures for this stop
func (s *Stop) setDepartures(now time.Time, db sqlx.Ext) (err error) {

	allDepartures := Departures{}

	yesterday := baseTime(now.Add(-time.Hour * 12))
	today := baseTime(now)

	yesterdayName := strings.ToLower(yesterday.Format("Monday"))
	todayName := strings.ToLower(now.Format("Monday"))

	func() {
		if yesterdayName != todayName {
			var yesterdayID string
			// Looks for trips starting yesterday that arrive here
			// after midnight
			yesterdayID, err = getServiceIDByDay(
				db, s.RouteID, yesterdayName, yesterday,
			)
			if err == sql.ErrNoRows {
				err = nil
				log.Println("no rows, ok, moving on")
				return
			}
			if err != nil {
				log.Println("can't get yesterday id", err)
				return
			}

			nowSecs :=
				now.Hour()*3600 + now.Minute()*60 + now.Second() + midnightSecs

			departures, err := getDepartures(
				s.AgencyID, s.RouteID, s.ID, yesterdayID,
				nowSecs, yesterday)
			if err != nil {
				log.Println("can't get departures", err)
				return
			}

			log.Println("yesterday departures", departures)

			allDepartures = append(allDepartures, departures...)
		}
	}()

	func() {
		var todayID string
		todayID, err = getServiceIDByDay(db, s.RouteID, todayName, today)
		if err == sql.ErrNoRows {
			err = nil
			log.Println("no rows there", err)
			return
		}
		if err != nil {
			log.Println("can't get today id", err)
			return
		}

		nowSecs := now.Hour()*3600 + now.Minute()*60 + now.Second()

		departures, err := getDepartures(
			s.AgencyID, s.RouteID, s.ID, todayID,
			nowSecs, today)
		if err != nil {
			log.Println("can't get departures", err)
			return
		}

		allDepartures = append(allDepartures, departures...)

	}()

	sort.Sort(allDepartures)

	for i, d := range allDepartures {
		if i > maxDepartures {
			break
		}
		s.Scheduled = append(s.Scheduled, d)
	}

	// After reading scheduled times in the db, try to also append any
	// live info available
	s.appendLive(now)

	return
}

//
func GetStopsByQuery(db sqlx.Ext, sq *StopQuery) (stops []Stop, err error) {
	now := time.Now()

	// Get rows matching the stop query
	rows, err := sqlx.NamedQuery(db, sq.Query(), sq)
	if err != nil {
		log.Println("can't get stops", err)
		return
	}

	for rows.Next() {
		var stop Stop

		err = rows.StructScan(&stop)
		if err != nil {
			log.Println("can't scan stop", err)
			return
		}

		err = stop.setDepartures(now, db)
		if err != nil {
			log.Println("can't set departures", err)
			return
		}

		stops = append(stops, stop)
	}

	return
}

func getServiceIDByDay(db sqlx.Ext, routeID, day string, now time.Time) (serviceID string, err error) {

	// Select the service_id that:
	//   * matches our routeID and day
	//   * has an end_date after now
	//   * has a start_date before now
	//   * if there's more than one, choose the one with the latest start_date

	row := db.QueryRowx(`
		SELECT service_id
		FROM   service_route_day
		WHERE  day = $1 AND
			   end_date >= $2 AND
			   start_date <= $3 AND
			   route_id = $4
		ORDER BY start_date DESC
		LIMIT 1
	`,
		day, now, now, routeID,
	)

	err = row.Scan(&serviceID)
	if err != nil {
		log.Println("can't scan service id", err, day, now, routeID)
		return
	}

	return
}
<commit_msg>comment<commit_after>package models

import (
	"database/sql"
	"fmt"
	"log"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/brnstz/bus/internal/etc"
	"github.com/brnstz/upsert"
	"github.com/jmoiron/sqlx"
)

// Stop is a single transit stop for a particular route. If a
// stop serves more than one route, there are multiple distinct
// entries for that stop.
type Stop struct {
	ID       string `json:"stop_id" db:"stop_id" upsert:"key"`
	RouteID  string `json:"route_id" db:"route_id" upsert:"key"`
	AgencyID string `json:"agency_id" db:"agency_id" upsert:"key"`
	Name     string `json:"stop_name" db:"stop_name"`

	DirectionID int    `json:"direction_id" db:"direction_id"`
	Headsign    string `json:"headsign" db:"headsign"`

	Lat float64 `json:"lat" db:"lat" upsert:"omit"`
	Lon float64 `json:"lon" db:"lon" upsert:"omit"`

	// Location is an "earth" field value that combines lat and lon into
	// a single field.
	Location interface{} `json:"-" db:"location" upsert_value:"ll_to_earth(:lat, :lon)"`

	// Dist, Scheduled, and Live fields that are only filled in
	// when returning a response from an API request. Do not directly
	// display them in JSON since
	Dist      float64      `json:"-" db:"dist" upsert:"omit"`
	Scheduled []*Departure `json:"-" db:"-" upsert:"omit"`
	Live      []*Departure `json:"-" db:"-" upsert:"omit"`
}

// Table implements the upsert.Upserter interface, returning the table
// where we save stops.
func (s *Stop) Table() string {
	return "stop"
}

// Save saves a stop to the database
func (s *Stop) Save() error {
	_, err := upsert.Upsert(etc.DBConn, s)
	return err
}

// appendLive calls either the bus time API or the subway datamine API
// to add live info to our stop info.
func (s *Stop) appendLive(now time.Time) {
	route, err := GetRoute(s.RouteID)
	if err != nil {
		log.Println("can't load route", err)
		return
	}

	if route.Type == Bus {
		departures, err := GetLiveBus(
			s.RouteID, strconv.Itoa(s.DirectionID),
			s.ID,
		)
		if err != nil {
			log.Println("can't append live schedules")
			return
		}

		sort.Sort(departures)

		for i := 0; i < len(departures) && i < maxDepartures; i++ {
			s.Live = append(s.Live, departures[i])
		}
	} else if route.Type == Subway {

		departures, err := GetLiveSubways(
			s.RouteID, strconv.Itoa(s.DirectionID),
			s.ID,
		)
		if err != nil {
			log.Println("can't append live subway sched", err)
			return
		}

		sort.Sort(departures)
		for i := 0; i < len(departures) && i < maxDepartures; i++ {
			s.Live = append(s.Live, departures[i])
		}

	}
}

// String returns a descriptive string for this stop.
func (s Stop) String() string {
	return fmt.Sprintf("{%v %v %v %v %v @ (%v,%v)}",
		s.ID, s.Name, s.RouteID, s.Headsign, s.DirectionID, s.Lat, s.Lon,
	)
}

// Key() returns the unique string for this stop, so we can identify
// unique stops in the loader.
func (s Stop) Key() string {
	return fmt.Sprintf("%v%v", s.ID, s.RouteID)
}

// setDepartures checks the database and any relevant APIs to set the scheduled
// and live departures for this stop
func (s *Stop) setDepartures(now time.Time, db sqlx.Ext) (err error) {

	allDepartures := Departures{}

	yesterday := baseTime(now.Add(-time.Hour * 12))
	today := baseTime(now)

	yesterdayName := strings.ToLower(yesterday.Format("Monday"))
	todayName := strings.ToLower(now.Format("Monday"))

	func() {
		if yesterdayName != todayName {
			var yesterdayID string
			// Looks for trips starting yesterday that arrive here
			// after midnight
			yesterdayID, err = getServiceIDByDay(
				db, s.RouteID, yesterdayName, yesterday,
			)
			if err == sql.ErrNoRows {
				err = nil
				log.Println("no rows, ok, moving on")
				return
			}
			if err != nil {
				log.Println("can't get yesterday id", err)
				return
			}

			nowSecs :=
				now.Hour()*3600 + now.Minute()*60 + now.Second() + midnightSecs

			departures, err := getDepartures(
				s.AgencyID, s.RouteID, s.ID, yesterdayID,
				nowSecs, yesterday)
			if err != nil {
				log.Println("can't get departures", err)
				return
			}

			log.Println("yesterday departures", departures)

			allDepartures = append(allDepartures, departures...)
		}
	}()

	func() {
		var todayID string
		todayID, err = getServiceIDByDay(db, s.RouteID, todayName, today)
		if err == sql.ErrNoRows {
			err = nil
			log.Println("no rows there", err)
			return
		}
		if err != nil {
			log.Println("can't get today id", err)
			return
		}

		nowSecs := now.Hour()*3600 + now.Minute()*60 + now.Second()

		departures, err := getDepartures(
			s.AgencyID, s.RouteID, s.ID, todayID,
			nowSecs, today)
		if err != nil {
			log.Println("can't get departures", err)
			return
		}

		allDepartures = append(allDepartures, departures...)

	}()

	sort.Sort(allDepartures)

	for i, d := range allDepartures {
		if i > maxDepartures {
			break
		}
		s.Scheduled = append(s.Scheduled, d)
	}

	// After reading scheduled times in the db, try to also append any
	// live info available
	s.appendLive(now)

	return
}

// GetStopsByQuery returns stops matching this StopQuery
func GetStopsByQuery(db sqlx.Ext, sq *StopQuery) (stops []Stop, err error) {
	now := time.Now()

	// Get rows matching the stop query
	rows, err := sqlx.NamedQuery(db, sq.Query(), sq)
	if err != nil {
		log.Println("can't get stops", err)
		return
	}

	for rows.Next() {
		var stop Stop

		err = rows.StructScan(&stop)
		if err != nil {
			log.Println("can't scan stop", err)
			return
		}

		err = stop.setDepartures(now, db)
		if err != nil {
			log.Println("can't set departures", err)
			return
		}

		stops = append(stops, stop)
	}

	return
}

func getServiceIDByDay(db sqlx.Ext, routeID, day string, now time.Time) (serviceID string, err error) {

	// Select the service_id that:
	//   * matches our routeID and day
	//   * has an end_date after now
	//   * has a start_date before now
	//   * if there's more than one, choose the one with the latest start_date

	row := db.QueryRowx(`
		SELECT service_id
		FROM   service_route_day
		WHERE  day = $1 AND
			   end_date >= $2 AND
			   start_date <= $3 AND
			   route_id = $4
		ORDER BY start_date DESC
		LIMIT 1
	`,
		day, now, now, routeID,
	)

	err = row.Scan(&serviceID)
	if err != nil {
		log.Println("can't scan service id", err, day, now, routeID)
		return
	}

	return
}
<|endoftext|>


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

package ebiten

import (
	"fmt"
	"math"
	"sync"
	"sync/atomic"

	"github.com/hajimehoshi/ebiten/v2/internal/buffered"
	"github.com/hajimehoshi/ebiten/v2/internal/clock"
	"github.com/hajimehoshi/ebiten/v2/internal/debug"
	"github.com/hajimehoshi/ebiten/v2/internal/driver"
	"github.com/hajimehoshi/ebiten/v2/internal/hooks"
)

type uiContext struct {
	game      Game
	offscreen *Image
	screen    *Image

	updateCalled bool

	outsideSizeUpdated bool
	outsideWidth       float64
	outsideHeight      float64

	err atomic.Value

	m sync.Mutex
}

var theUIContext = &uiContext{}

func (c *uiContext) set(game Game) {
	c.m.Lock()
	defer c.m.Unlock()
	c.game = game
}

func (c *uiContext) setError(err error) {
	c.err.Store(err)
}

func (c *uiContext) Layout(outsideWidth, outsideHeight float64) {
	// The given outside size can be 0 e.g. just after restoring from the fullscreen mode on Windows (#1589)
	// Just ignore such cases. Otherwise, creating a zero-sized framebuffer causes a panic.
	if outsideWidth == 0 || outsideHeight == 0 {
		return
	}
	c.outsideSizeUpdated = true
	c.outsideWidth = outsideWidth
	c.outsideHeight = outsideHeight
}

func (c *uiContext) updateOffscreen() {
	sw, sh := c.game.Layout(int(c.outsideWidth), int(c.outsideHeight))
	if sw <= 0 || sh <= 0 {
		panic("ebiten: Layout must return positive numbers")
	}

	if c.offscreen != nil && !c.outsideSizeUpdated {
		if w, h := c.offscreen.Size(); w == sw && h == sh {
			return
		}
	}
	c.outsideSizeUpdated = false

	if c.screen != nil {
		c.screen.Dispose()
		c.screen = nil
	}

	if c.offscreen != nil {
		if w, h := c.offscreen.Size(); w != sw || h != sh {
			c.offscreen.Dispose()
			c.offscreen = nil
		}
	}
	if c.offscreen == nil {
		c.offscreen = NewImage(sw, sh)
		c.offscreen.mipmap.SetVolatile(IsScreenClearedEveryFrame())
	}

	// TODO: This is duplicated with mobile/ebitenmobileview/funcs.go. Refactor this.
	d := uiDriver().DeviceScaleFactor()
	c.screen = newScreenFramebufferImage(int(c.outsideWidth*d), int(c.outsideHeight*d))
}

func (c *uiContext) setScreenClearedEveryFrame(cleared bool) {
	c.m.Lock()
	defer c.m.Unlock()

	if c.offscreen != nil {
		c.offscreen.mipmap.SetVolatile(cleared)
	}
}

func (c *uiContext) setWindowResizable(resizable bool) {
	c.m.Lock()
	defer c.m.Unlock()

	if w := uiDriver().Window(); w != nil {
		w.SetResizable(resizable)
	}
}

func (c *uiContext) screenScale(deviceScaleFactor float64) float64 {
	if c.offscreen == nil {
		return 0
	}
	sw, sh := c.offscreen.Size()
	scaleX := c.outsideWidth / float64(sw) * deviceScaleFactor
	scaleY := c.outsideHeight / float64(sh) * deviceScaleFactor
	return math.Min(scaleX, scaleY)
}

func (c *uiContext) offsets(deviceScaleFactor float64) (float64, float64) {
	if c.offscreen == nil {
		return 0, 0
	}
	sw, sh := c.offscreen.Size()
	s := c.screenScale(deviceScaleFactor)
	width := float64(sw) * s
	height := float64(sh) * s
	x := (c.outsideWidth*deviceScaleFactor - width) / 2
	y := (c.outsideHeight*deviceScaleFactor - height) / 2
	return x, y
}

func (c *uiContext) Update() error {
	// TODO: If updateCount is 0 and vsync is disabled, swapping buffers can be skipped.

	if err, ok := c.err.Load().(error); ok && err != nil {
		return err
	}
	if err := buffered.BeginFrame(); err != nil {
		return err
	}
	if err := c.update(clock.Update(MaxTPS())); err != nil {
		return err
	}
	if err := buffered.EndFrame(); err != nil {
		return err
	}
	return nil
}

func (c *uiContext) ForceUpdate() error {
	// ForceUpdate can be invoked even if uiContext it not initialized yet (#1591).
	if c.outsideWidth == 0 || c.outsideHeight == 0 {
		return nil
	}

	if err, ok := c.err.Load().(error); ok && err != nil {
		return err
	}
	if err := buffered.BeginFrame(); err != nil {
		return err
	}
	if err := c.update(1); err != nil {
		return err
	}
	if err := buffered.EndFrame(); err != nil {
		return err
	}
	return nil
}

func (c *uiContext) update(updateCount int) error {
	c.updateOffscreen()

	// Ensure that Update is called once before Draw so that Update can be used for initialization.
	if !c.updateCalled && updateCount == 0 {
		updateCount = 1
		c.updateCalled = true
	}
	debug.Logf("--\nUpdate count per frame: %d\n", updateCount)

	for i := 0; i < updateCount; i++ {
		if err := hooks.RunBeforeUpdateHooks(); err != nil {
			return err
		}
		if err := c.game.Update(); err != nil {
			return err
		}
		uiDriver().ResetForFrame()
	}

	// Even though updateCount == 0, the offscreen is cleared and Draw is called when vscync is enabled.
	// Draw should not update the game state and then the screen should not be updated without Update, but
	// users might want to process something at Draw with the time intervals of FPS.
	// When vsync is disabled, as performance matters, skip calling Draw when possible (#1520).
	if updateCount > 0 || IsVsyncEnabled() {
		if IsScreenClearedEveryFrame() {
			c.offscreen.Clear()
		}
		c.game.Draw(c.offscreen)
	}

	// This clear is needed for fullscreen mode or some mobile platforms (#622).
	c.screen.Clear()

	op := &DrawImageOptions{}

	s := c.screenScale(uiDriver().DeviceScaleFactor())
	switch vd := uiDriver().Graphics().FramebufferYDirection(); vd {
	case driver.Upward:
		op.GeoM.Scale(s, -s)
		_, h := c.offscreen.Size()
		op.GeoM.Translate(0, float64(h)*s)
	case driver.Downward:
		op.GeoM.Scale(s, s)
	default:
		panic(fmt.Sprintf("ebiten: invalid v-direction: %d", vd))
	}

	op.GeoM.Translate(c.offsets(uiDriver().DeviceScaleFactor()))
	op.CompositeMode = CompositeModeCopy

	// filterScreen works with >=1 scale, but does not well with <1 scale.
	// Use regular FilterLinear instead so far (#669).
	if s >= 1 {
		op.Filter = filterScreen
	} else {
		op.Filter = FilterLinear
	}
	c.screen.DrawImage(c.offscreen, op)
	return nil
}

func (c *uiContext) AdjustPosition(x, y float64, deviceScaleFactor float64) (float64, float64) {
	ox, oy := c.offsets(deviceScaleFactor)
	s := c.screenScale(deviceScaleFactor)
	// The scale 0 indicates that the offscreen is not initialized yet.
	// As any cursor values don't make sense, just return NaN.
	if s == 0 {
		return math.NaN(), math.NaN()
	}
	return (x*deviceScaleFactor - ox) / s, (y*deviceScaleFactor - oy) / s
}
<commit_msg>Revert "ebiten: Do not skip Draw when vsync is disabled"<commit_after>// Copyright 2014 Hajime Hoshi
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package ebiten

import (
	"fmt"
	"math"
	"sync"
	"sync/atomic"

	"github.com/hajimehoshi/ebiten/v2/internal/buffered"
	"github.com/hajimehoshi/ebiten/v2/internal/clock"
	"github.com/hajimehoshi/ebiten/v2/internal/debug"
	"github.com/hajimehoshi/ebiten/v2/internal/driver"
	"github.com/hajimehoshi/ebiten/v2/internal/hooks"
)

type uiContext struct {
	game      Game
	offscreen *Image
	screen    *Image

	updateCalled bool

	outsideSizeUpdated bool
	outsideWidth       float64
	outsideHeight      float64

	err atomic.Value

	m sync.Mutex
}

var theUIContext = &uiContext{}

func (c *uiContext) set(game Game) {
	c.m.Lock()
	defer c.m.Unlock()
	c.game = game
}

func (c *uiContext) setError(err error) {
	c.err.Store(err)
}

func (c *uiContext) Layout(outsideWidth, outsideHeight float64) {
	// The given outside size can be 0 e.g. just after restoring from the fullscreen mode on Windows (#1589)
	// Just ignore such cases. Otherwise, creating a zero-sized framebuffer causes a panic.
	if outsideWidth == 0 || outsideHeight == 0 {
		return
	}
	c.outsideSizeUpdated = true
	c.outsideWidth = outsideWidth
	c.outsideHeight = outsideHeight
}

func (c *uiContext) updateOffscreen() {
	sw, sh := c.game.Layout(int(c.outsideWidth), int(c.outsideHeight))
	if sw <= 0 || sh <= 0 {
		panic("ebiten: Layout must return positive numbers")
	}

	if c.offscreen != nil && !c.outsideSizeUpdated {
		if w, h := c.offscreen.Size(); w == sw && h == sh {
			return
		}
	}
	c.outsideSizeUpdated = false

	if c.screen != nil {
		c.screen.Dispose()
		c.screen = nil
	}

	if c.offscreen != nil {
		if w, h := c.offscreen.Size(); w != sw || h != sh {
			c.offscreen.Dispose()
			c.offscreen = nil
		}
	}
	if c.offscreen == nil {
		c.offscreen = NewImage(sw, sh)
		c.offscreen.mipmap.SetVolatile(IsScreenClearedEveryFrame())
	}

	// TODO: This is duplicated with mobile/ebitenmobileview/funcs.go. Refactor this.
	d := uiDriver().DeviceScaleFactor()
	c.screen = newScreenFramebufferImage(int(c.outsideWidth*d), int(c.outsideHeight*d))
}

func (c *uiContext) setScreenClearedEveryFrame(cleared bool) {
	c.m.Lock()
	defer c.m.Unlock()

	if c.offscreen != nil {
		c.offscreen.mipmap.SetVolatile(cleared)
	}
}

func (c *uiContext) setWindowResizable(resizable bool) {
	c.m.Lock()
	defer c.m.Unlock()

	if w := uiDriver().Window(); w != nil {
		w.SetResizable(resizable)
	}
}

func (c *uiContext) screenScale(deviceScaleFactor float64) float64 {
	if c.offscreen == nil {
		return 0
	}
	sw, sh := c.offscreen.Size()
	scaleX := c.outsideWidth / float64(sw) * deviceScaleFactor
	scaleY := c.outsideHeight / float64(sh) * deviceScaleFactor
	return math.Min(scaleX, scaleY)
}

func (c *uiContext) offsets(deviceScaleFactor float64) (float64, float64) {
	if c.offscreen == nil {
		return 0, 0
	}
	sw, sh := c.offscreen.Size()
	s := c.screenScale(deviceScaleFactor)
	width := float64(sw) * s
	height := float64(sh) * s
	x := (c.outsideWidth*deviceScaleFactor - width) / 2
	y := (c.outsideHeight*deviceScaleFactor - height) / 2
	return x, y
}

func (c *uiContext) Update() error {
	// TODO: If updateCount is 0 and vsync is disabled, swapping buffers can be skipped.

	if err, ok := c.err.Load().(error); ok && err != nil {
		return err
	}
	if err := buffered.BeginFrame(); err != nil {
		return err
	}
	if err := c.update(clock.Update(MaxTPS())); err != nil {
		return err
	}
	if err := buffered.EndFrame(); err != nil {
		return err
	}
	return nil
}

func (c *uiContext) ForceUpdate() error {
	// ForceUpdate can be invoked even if uiContext it not initialized yet (#1591).
	if c.outsideWidth == 0 || c.outsideHeight == 0 {
		return nil
	}

	if err, ok := c.err.Load().(error); ok && err != nil {
		return err
	}
	if err := buffered.BeginFrame(); err != nil {
		return err
	}
	if err := c.update(1); err != nil {
		return err
	}
	if err := buffered.EndFrame(); err != nil {
		return err
	}
	return nil
}

func (c *uiContext) update(updateCount int) error {
	c.updateOffscreen()

	// Ensure that Update is called once before Draw so that Update can be used for initialization.
	if !c.updateCalled && updateCount == 0 {
		updateCount = 1
		c.updateCalled = true
	}
	debug.Logf("--\nUpdate count per frame: %d\n", updateCount)

	for i := 0; i < updateCount; i++ {
		if err := hooks.RunBeforeUpdateHooks(); err != nil {
			return err
		}
		if err := c.game.Update(); err != nil {
			return err
		}
		uiDriver().ResetForFrame()
	}

	// Even though updateCount == 0, the offscreen is cleared and Draw is called.
	// Draw should not update the game state and then the screen should not be updated without Update, but
	// users might want to process something at Draw with the time intervals of FPS.
	if IsScreenClearedEveryFrame() {
		c.offscreen.Clear()
	}
	c.game.Draw(c.offscreen)

	// This clear is needed for fullscreen mode or some mobile platforms (#622).
	c.screen.Clear()

	op := &DrawImageOptions{}

	s := c.screenScale(uiDriver().DeviceScaleFactor())
	switch vd := uiDriver().Graphics().FramebufferYDirection(); vd {
	case driver.Upward:
		op.GeoM.Scale(s, -s)
		_, h := c.offscreen.Size()
		op.GeoM.Translate(0, float64(h)*s)
	case driver.Downward:
		op.GeoM.Scale(s, s)
	default:
		panic(fmt.Sprintf("ebiten: invalid v-direction: %d", vd))
	}

	op.GeoM.Translate(c.offsets(uiDriver().DeviceScaleFactor()))
	op.CompositeMode = CompositeModeCopy

	// filterScreen works with >=1 scale, but does not well with <1 scale.
	// Use regular FilterLinear instead so far (#669).
	if s >= 1 {
		op.Filter = filterScreen
	} else {
		op.Filter = FilterLinear
	}
	c.screen.DrawImage(c.offscreen, op)
	return nil
}

func (c *uiContext) AdjustPosition(x, y float64, deviceScaleFactor float64) (float64, float64) {
	ox, oy := c.offsets(deviceScaleFactor)
	s := c.screenScale(deviceScaleFactor)
	// The scale 0 indicates that the offscreen is not initialized yet.
	// As any cursor values don't make sense, just return NaN.
	if s == 0 {
		return math.NaN(), math.NaN()
	}
	return (x*deviceScaleFactor - ox) / s, (y*deviceScaleFactor - oy) / s
}
<|endoftext|>


<commit_before>/* Utility functions and methods. Should probably absorbe what's in "common.go"
 * right now. */

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

/*
Package util contains various utility functions that are useful across all of goiardi.
*/
package util

import (
	"fmt"
	"github.com/ctdk/goiardi/config"
	"net/http"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"
)

// NoDBConfigured is an error for when no database has been configured for use,
// yet an SQL function is being called.
var NoDBConfigured = &gerror{msg: "no db configured, but you tried to use one", status: http.StatusInternalServerError}

// GoiardiObj is an interface for helping goiardi/chef objects, like cookbooks,
// roles, etc., be able to easily make URLs and be identified by name.
type GoiardiObj interface {
	GetName() string
	URLType() string
}

type gerror struct {
	msg    string
	status int
}

// Gerror is an error type that includes an http status code (defaults to
// http.BadRequest).
type Gerror interface {
	String() string
	Error() string
	Status() int
	SetStatus(int)
}

// New makes a new Gerror. Usually you want Errorf.
func New(text string) Gerror {
	return &gerror{msg: text,
		status: http.StatusBadRequest,
	}
}

// Errorf creates a new Gerror, with a formatted error string.
func Errorf(format string, a ...interface{}) Gerror {
	return New(fmt.Sprintf(format, a...))
}

// CastErr will easily cast a different kind of error to a Gerror.
func CastErr(err error) Gerror {
	return Errorf(err.Error())
}

// Error returns the Gerror error message.
func (e *gerror) Error() string {
	return e.msg
}

func (e *gerror) String() string {
	return e.msg
}

// Set the Gerror HTTP status code.
func (e *gerror) SetStatus(s int) {
	e.status = s
}

// Returns the Gerror's HTTP status code.
func (e *gerror) Status() int {
	return e.status
}

// ObjURL crafts a URL for an object.
func ObjURL(obj GoiardiObj) string {
	baseURL := config.ServerBaseURL()
	fullURL := fmt.Sprintf("%s/%s/%s", baseURL, obj.URLType(), obj.GetName())
	return fullURL
}

// CustomObjURL crafts a URL for a Goiardi object with additional path elements.
func CustomObjURL(obj GoiardiObj, path string) string {
	chkPath(&path)
	return fmt.Sprintf("%s%s", ObjURL(obj), path)
}

// CustomURL crafts a URL from the provided path, without providing an object.
func CustomURL(path string) string {
	chkPath(&path)
	return fmt.Sprintf("%s%s", config.ServerBaseURL(), path)
}

func chkPath(p *string) {
	if (*p)[0] != '/' {
		*p = fmt.Sprintf("/%s", *p)
	}
}

// FlattenObj flattens an object and expand its keys into a map[string]string so
// it's suitable for indexing, either with solr (eventually) or with the whipped
// up replacement for local mode. Objects fed into this function *must* have the
// "json" tag set for their struct members.
func FlattenObj(obj interface{}) map[string]interface{} {
	expanded := make(map[string]interface{})
	s := reflect.ValueOf(obj).Elem()
	for i := 0; i < s.NumField(); i++ {
		if !s.Field(i).CanInterface() {
			continue
		}
		v := s.Field(i).Interface()
		key := s.Type().Field(i).Tag.Get("json")
		var mergeKey string
		if key == "automatic" || key == "normal" || key == "default" || key == "override" || key == "raw_data" {
			mergeKey = ""
		} else {
			mergeKey = key
		}
		subExpand := DeepMerge(mergeKey, v)
		/* Now merge the returned map */
		for k, u := range subExpand {
			expanded[k] = u
		}
	}
	return expanded
}

// MapifyObject turns an object into a map[string]interface{}. Useful for when
// you have a slice of objects that you need to trim, mutilate, fold, etc.
// before returning them as JSON.
func MapifyObject(obj interface{}) map[string]interface{} {
	mapified := make(map[string]interface{})
	s := reflect.ValueOf(obj).Elem()
	for i := 0; i < s.NumField(); i++ {
		if !s.Field(i).CanInterface() {
			continue
		}
		v := s.Field(i).Interface()
		key := s.Type().Field(i).Tag.Get("json")
		mapified[key] = v
	}
	return mapified
}

// Indexify prepares a flattened object for indexing by turning it into a sorted
// slice of strings formatted like "key:value".
func Indexify(flattened map[string]interface{}) []string {
	var readyToIndex []string
	for k, v := range flattened {
		switch v := v.(type) {
		case string:
			v = escapeStr(v)
			line := fmt.Sprintf("%s:%s", k, v)
			readyToIndex = append(readyToIndex, line)
		case []string:
			for _, w := range v {
				w = escapeStr(w)
				line := fmt.Sprintf("%s:%s", k, w)
				readyToIndex = append(readyToIndex, line)
			}
		default:
			err := fmt.Errorf("We should never have been able to reach this state. Key %s had a value %v of type %T", k, v, v)
			panic(err)
		}
	}
	sort.Strings(readyToIndex)
	return readyToIndex
}

func escapeStr(s string) string {
	s = strings.Replace(s, "[", "\\[", -1)
	s = strings.Replace(s, "]", "\\]", -1)
	s = strings.Replace(s, "::", "\\:\\:", -1)
	return s
}

// DeepMerge merges disparate data structures into a flat hash.
func DeepMerge(key string, source interface{}) map[string]interface{} {
	merger := make(map[string]interface{})
	var sep string
	if config.Config.DotSearch {
		sep = "."
	} else {
		sep = "_"
	}
	switch v := source.(type) {
	case map[string]interface{}:
		/* We also need to get things like
		 * "default_attributes:key" indexed. */
		topLev := make([]string, len(v))
		n := 0
		for k, u := range v {
			if key != "" {
				topLev[n] = k
				n++
			}
			var nkey string
			if key == "" {
				nkey = k
			} else {
				nkey = fmt.Sprintf("%s%s%s", key, sep, k)
			}
			nm := DeepMerge(nkey, u)
			for j, q := range nm {
				merger[j] = q
			}
		}
		if key != "" {
			merger[key] = topLev
		}
	case map[string]string:
		/* We also need to get things like
		 * "default_attributes:key" indexed. */
		topLev := make([]string, len(v))
		n := 0
		for k, u := range v {
			if key != "" {
				topLev[n] = k
				n++
			}
			var nkey string
			if key == "" {
				nkey = k
			} else {
				nkey = fmt.Sprintf("%s%s%s", key, k)
			}
			merger[nkey] = u
		}
		if key != "" {
			merger[key] = topLev
		}

	case []interface{}:
		km := make([]string, len(v))
		for i, w := range v {
			km[i] = stringify(w)
		}
		merger[key] = km
	case []string:
		km := make([]string, len(v))
		for i, w := range v {
			km[i] = stringify(w)
		}
		merger[key] = km
		/* If this is the run list, break recipes and roles out
		 * into their own separate indexes as well. */
		if key == "run_list" {
			roleMatch := regexp.MustCompile(`^(recipe|role)\[(.*)\]`)
			var roles []string
			var recipes []string
			for _, w := range v {
				rItem := roleMatch.FindStringSubmatch(stringify(w))
				if rItem != nil {
					rType := rItem[1]
					rThing := rItem[2]
					if rType == "role" {
						roles = append(roles, rThing)
					} else if rType == "recipe" {
						recipes = append(recipes, rThing)
					}
				}
			}
			if len(roles) > 0 {
				merger["role"] = roles
			}
			if len(recipes) > 0 {
				merger["recipe"] = recipes
			}
		}
	default:
		merger[key] = stringify(v)
	}
	return merger
}

func stringify(source interface{}) string {
	switch s := source.(type) {
	case string:
		return s
	case uint8, uint16, uint32, uint64:
		n := reflect.ValueOf(s).Uint()
		str := strconv.FormatUint(n, 10)
		return str
	case int8, int16, int32, int64:
		n := reflect.ValueOf(s).Int()
		str := strconv.FormatInt(n, 10)
		return str
	case float32, float64:
		n := reflect.ValueOf(s).Float()
		str := strconv.FormatFloat(n, 'f', -1, 64)
		return str
	case bool:
		str := strconv.FormatBool(s)
		return str
	default:
		/* Just send back whatever %v gives */
		str := fmt.Sprintf("%v", s)
		return str
	}
}

// PgSearchKey removes characters from search term fields that make the ltree
// data type unhappy. This leads to the postgres-based search being, perhaps,
// somewhat less precise than the solr (or ersatz solr) based search, but at the
// same time one that's less resource demanding and covers almost all known use
// cases. Potential bug: Postgres considers some, but not all, unicode letters
// as being alphanumeric; i.e. golang and postgres both consider 'ü' to be a
// letter, but golang accepts 'ሀ' as a letter while postgres does not. This is
// reasonably unlikely to be an issue, but if you're using lots of non-European
// characters in your attributes this could be a problem. We're accepting more
// than raw ASCII alnum however because it's better behavior and because
// Postgres does accept at least some other alphabets as being alphanumeric.
func PgSearchKey(key string) string {
	re := regexp.MustCompile(`[^\pL\pN_]`)
	bs := regexp.MustCompile(`_{2,}`)
	k := re.ReplaceAllString(key, "_")
	k = bs.ReplaceAllString(k, "_")
	k = strings.Trim(k, "_")
	return k
}
<commit_msg>Have to preserve . in ltree query keys too, of course<commit_after>/* Utility functions and methods. Should probably absorbe what's in "common.go"
 * right now. */

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

/*
Package util contains various utility functions that are useful across all of goiardi.
*/
package util

import (
	"fmt"
	"github.com/ctdk/goiardi/config"
	"net/http"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"
)

// NoDBConfigured is an error for when no database has been configured for use,
// yet an SQL function is being called.
var NoDBConfigured = &gerror{msg: "no db configured, but you tried to use one", status: http.StatusInternalServerError}

// GoiardiObj is an interface for helping goiardi/chef objects, like cookbooks,
// roles, etc., be able to easily make URLs and be identified by name.
type GoiardiObj interface {
	GetName() string
	URLType() string
}

type gerror struct {
	msg    string
	status int
}

// Gerror is an error type that includes an http status code (defaults to
// http.BadRequest).
type Gerror interface {
	String() string
	Error() string
	Status() int
	SetStatus(int)
}

// New makes a new Gerror. Usually you want Errorf.
func New(text string) Gerror {
	return &gerror{msg: text,
		status: http.StatusBadRequest,
	}
}

// Errorf creates a new Gerror, with a formatted error string.
func Errorf(format string, a ...interface{}) Gerror {
	return New(fmt.Sprintf(format, a...))
}

// CastErr will easily cast a different kind of error to a Gerror.
func CastErr(err error) Gerror {
	return Errorf(err.Error())
}

// Error returns the Gerror error message.
func (e *gerror) Error() string {
	return e.msg
}

func (e *gerror) String() string {
	return e.msg
}

// Set the Gerror HTTP status code.
func (e *gerror) SetStatus(s int) {
	e.status = s
}

// Returns the Gerror's HTTP status code.
func (e *gerror) Status() int {
	return e.status
}

// ObjURL crafts a URL for an object.
func ObjURL(obj GoiardiObj) string {
	baseURL := config.ServerBaseURL()
	fullURL := fmt.Sprintf("%s/%s/%s", baseURL, obj.URLType(), obj.GetName())
	return fullURL
}

// CustomObjURL crafts a URL for a Goiardi object with additional path elements.
func CustomObjURL(obj GoiardiObj, path string) string {
	chkPath(&path)
	return fmt.Sprintf("%s%s", ObjURL(obj), path)
}

// CustomURL crafts a URL from the provided path, without providing an object.
func CustomURL(path string) string {
	chkPath(&path)
	return fmt.Sprintf("%s%s", config.ServerBaseURL(), path)
}

func chkPath(p *string) {
	if (*p)[0] != '/' {
		*p = fmt.Sprintf("/%s", *p)
	}
}

// FlattenObj flattens an object and expand its keys into a map[string]string so
// it's suitable for indexing, either with solr (eventually) or with the whipped
// up replacement for local mode. Objects fed into this function *must* have the
// "json" tag set for their struct members.
func FlattenObj(obj interface{}) map[string]interface{} {
	expanded := make(map[string]interface{})
	s := reflect.ValueOf(obj).Elem()
	for i := 0; i < s.NumField(); i++ {
		if !s.Field(i).CanInterface() {
			continue
		}
		v := s.Field(i).Interface()
		key := s.Type().Field(i).Tag.Get("json")
		var mergeKey string
		if key == "automatic" || key == "normal" || key == "default" || key == "override" || key == "raw_data" {
			mergeKey = ""
		} else {
			mergeKey = key
		}
		subExpand := DeepMerge(mergeKey, v)
		/* Now merge the returned map */
		for k, u := range subExpand {
			expanded[k] = u
		}
	}
	return expanded
}

// MapifyObject turns an object into a map[string]interface{}. Useful for when
// you have a slice of objects that you need to trim, mutilate, fold, etc.
// before returning them as JSON.
func MapifyObject(obj interface{}) map[string]interface{} {
	mapified := make(map[string]interface{})
	s := reflect.ValueOf(obj).Elem()
	for i := 0; i < s.NumField(); i++ {
		if !s.Field(i).CanInterface() {
			continue
		}
		v := s.Field(i).Interface()
		key := s.Type().Field(i).Tag.Get("json")
		mapified[key] = v
	}
	return mapified
}

// Indexify prepares a flattened object for indexing by turning it into a sorted
// slice of strings formatted like "key:value".
func Indexify(flattened map[string]interface{}) []string {
	var readyToIndex []string
	for k, v := range flattened {
		switch v := v.(type) {
		case string:
			v = escapeStr(v)
			line := fmt.Sprintf("%s:%s", k, v)
			readyToIndex = append(readyToIndex, line)
		case []string:
			for _, w := range v {
				w = escapeStr(w)
				line := fmt.Sprintf("%s:%s", k, w)
				readyToIndex = append(readyToIndex, line)
			}
		default:
			err := fmt.Errorf("We should never have been able to reach this state. Key %s had a value %v of type %T", k, v, v)
			panic(err)
		}
	}
	sort.Strings(readyToIndex)
	return readyToIndex
}

func escapeStr(s string) string {
	s = strings.Replace(s, "[", "\\[", -1)
	s = strings.Replace(s, "]", "\\]", -1)
	s = strings.Replace(s, "::", "\\:\\:", -1)
	return s
}

// DeepMerge merges disparate data structures into a flat hash.
func DeepMerge(key string, source interface{}) map[string]interface{} {
	merger := make(map[string]interface{})
	var sep string
	if config.Config.DotSearch {
		sep = "."
	} else {
		sep = "_"
	}
	switch v := source.(type) {
	case map[string]interface{}:
		/* We also need to get things like
		 * "default_attributes:key" indexed. */
		topLev := make([]string, len(v))
		n := 0
		for k, u := range v {
			if key != "" {
				topLev[n] = k
				n++
			}
			var nkey string
			if key == "" {
				nkey = k
			} else {
				nkey = fmt.Sprintf("%s%s%s", key, sep, k)
			}
			nm := DeepMerge(nkey, u)
			for j, q := range nm {
				merger[j] = q
			}
		}
		if key != "" {
			merger[key] = topLev
		}
	case map[string]string:
		/* We also need to get things like
		 * "default_attributes:key" indexed. */
		topLev := make([]string, len(v))
		n := 0
		for k, u := range v {
			if key != "" {
				topLev[n] = k
				n++
			}
			var nkey string
			if key == "" {
				nkey = k
			} else {
				nkey = fmt.Sprintf("%s%s%s", key, k)
			}
			merger[nkey] = u
		}
		if key != "" {
			merger[key] = topLev
		}

	case []interface{}:
		km := make([]string, len(v))
		for i, w := range v {
			km[i] = stringify(w)
		}
		merger[key] = km
	case []string:
		km := make([]string, len(v))
		for i, w := range v {
			km[i] = stringify(w)
		}
		merger[key] = km
		/* If this is the run list, break recipes and roles out
		 * into their own separate indexes as well. */
		if key == "run_list" {
			roleMatch := regexp.MustCompile(`^(recipe|role)\[(.*)\]`)
			var roles []string
			var recipes []string
			for _, w := range v {
				rItem := roleMatch.FindStringSubmatch(stringify(w))
				if rItem != nil {
					rType := rItem[1]
					rThing := rItem[2]
					if rType == "role" {
						roles = append(roles, rThing)
					} else if rType == "recipe" {
						recipes = append(recipes, rThing)
					}
				}
			}
			if len(roles) > 0 {
				merger["role"] = roles
			}
			if len(recipes) > 0 {
				merger["recipe"] = recipes
			}
		}
	default:
		merger[key] = stringify(v)
	}
	return merger
}

func stringify(source interface{}) string {
	switch s := source.(type) {
	case string:
		return s
	case uint8, uint16, uint32, uint64:
		n := reflect.ValueOf(s).Uint()
		str := strconv.FormatUint(n, 10)
		return str
	case int8, int16, int32, int64:
		n := reflect.ValueOf(s).Int()
		str := strconv.FormatInt(n, 10)
		return str
	case float32, float64:
		n := reflect.ValueOf(s).Float()
		str := strconv.FormatFloat(n, 'f', -1, 64)
		return str
	case bool:
		str := strconv.FormatBool(s)
		return str
	default:
		/* Just send back whatever %v gives */
		str := fmt.Sprintf("%v", s)
		return str
	}
}

// PgSearchKey removes characters from search term fields that make the ltree
// data type unhappy. This leads to the postgres-based search being, perhaps,
// somewhat less precise than the solr (or ersatz solr) based search, but at the
// same time one that's less resource demanding and covers almost all known use
// cases. Potential bug: Postgres considers some, but not all, unicode letters
// as being alphanumeric; i.e. golang and postgres both consider 'ü' to be a
// letter, but golang accepts 'ሀ' as a letter while postgres does not. This is
// reasonably unlikely to be an issue, but if you're using lots of non-European
// characters in your attributes this could be a problem. We're accepting more
// than raw ASCII alnum however because it's better behavior and because
// Postgres does accept at least some other alphabets as being alphanumeric.
func PgSearchKey(key string) string {
	re := regexp.MustCompile(`[^\pL\pN_\.]`)
	bs := regexp.MustCompile(`_{2,}`)
	k := re.ReplaceAllString(key, "_")
	k = bs.ReplaceAllString(k, "_")
	k = strings.Trim(k, "_")
	return k
}
<|endoftext|>


<commit_before>package util

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"regexp"
	"strconv"
	"time"

	"github.com/akutz/gofig"
	"github.com/akutz/gotil"
	apiversion "github.com/emccode/libstorage/api"
	"github.com/emccode/libstorage/api/context"
	apiserver "github.com/emccode/libstorage/api/server"
	apitypes "github.com/emccode/libstorage/api/types"

	"github.com/emccode/rexray/core"
)

const (
	logDirPathSuffix = "/var/log/rexray"
	etcDirPathSuffix = "/etc/rexray"
	binDirPathSuffix = "/usr/bin"
	runDirPathSuffix = "/var/run/rexray"
	libDirPathSuffix = "/var/lib/rexray"

	// UnitFilePath is the path to the SystemD service's unit file.
	UnitFilePath = "/etc/systemd/system/rexray.service"

	// InitFilePath is the path to the SystemV Service's init script.
	InitFilePath = "/etc/init.d/rexray"

	// EnvFileName is the name of the environment file used by the SystemD
	// service.
	EnvFileName = "rexray.env"
)

var (
	thisExeDir     string
	thisExeName    string
	thisExeAbsPath string

	prefix string

	binDirPath  string
	binFilePath string
	logDirPath  string
	libDirPath  string
	runDirPath  string
	etcDirPath  string
	pidFilePath string
)

func init() {
	prefix = os.Getenv("REXRAY_HOME")

	thisExeDir, thisExeName, thisExeAbsPath = gotil.GetThisPathParts()
}

// GetPrefix gets the root path to the REX-Ray data.
func GetPrefix() string {
	return prefix
}

// Prefix sets the root path to the REX-Ray data.
func Prefix(p string) {
	if p == "" || p == "/" {
		return
	}

	binDirPath = ""
	binFilePath = ""
	logDirPath = ""
	libDirPath = ""
	runDirPath = ""
	etcDirPath = ""
	pidFilePath = ""

	prefix = p
}

// IsPrefixed returns a flag indicating whether or not a prefix value is set.
func IsPrefixed() bool {
	return !(prefix == "" || prefix == "/")
}

// Install executes the system install command.
func Install(args ...string) {
	exec.Command("install", args...).Run()
}

// InstallChownRoot executes the system install command and chowns the target
// to the root user and group.
func InstallChownRoot(args ...string) {
	a := []string{"-o", "0", "-g", "0"}
	for _, i := range args {
		a = append(a, i)
	}
	exec.Command("install", a...).Run()
}

// InstallDirChownRoot executes the system install command with a -d flag and
// chowns the target to the root user and group.
func InstallDirChownRoot(dirPath string) {
	InstallChownRoot("-d", dirPath)
}

// EtcDirPath returns the path to the REX-Ray etc directory.
func EtcDirPath() string {
	if etcDirPath == "" {
		etcDirPath = fmt.Sprintf("%s%s", prefix, etcDirPathSuffix)
		os.MkdirAll(etcDirPath, 0755)
	}
	return etcDirPath
}

// RunDirPath returns the path to the REX-Ray run directory.
func RunDirPath() string {
	if runDirPath == "" {
		runDirPath = fmt.Sprintf("%s%s", prefix, runDirPathSuffix)
		os.MkdirAll(runDirPath, 0755)
	}
	return runDirPath
}

// LogDirPath returns the path to the REX-Ray log directory.
func LogDirPath() string {
	if logDirPath == "" {
		logDirPath = fmt.Sprintf("%s%s", prefix, logDirPathSuffix)
		os.MkdirAll(logDirPath, 0755)
	}
	return logDirPath
}

// LibDirPath returns the path to the REX-Ray bin directory.
func LibDirPath() string {
	if libDirPath == "" {
		libDirPath = fmt.Sprintf("%s%s", prefix, libDirPathSuffix)
		os.MkdirAll(libDirPath, 0755)
	}
	return libDirPath
}

// LibFilePath returns the path to a file inside the REX-Ray lib directory
// with the provided file name.
func LibFilePath(fileName string) string {
	return fmt.Sprintf("%s/%s", LibDirPath(), fileName)
}

// RunFilePath returns the path to a file inside the REX-Ray run directory
// with the provided file name.
func RunFilePath(fileName string) string {
	return fmt.Sprintf("%s/%s", RunDirPath(), fileName)
}

// BinDirPath returns the path to the REX-Ray bin directory.
func BinDirPath() string {
	if binDirPath == "" {
		binDirPath = fmt.Sprintf("%s%s", prefix, binDirPathSuffix)
		os.MkdirAll(binDirPath, 0755)
	}
	return binDirPath
}

// PidFilePath returns the path to the REX-Ray PID file.
func PidFilePath() string {
	if pidFilePath == "" {
		pidFilePath = fmt.Sprintf("%s/rexray.pid", RunDirPath())
	}
	return pidFilePath
}

// BinFilePath returns the path to the REX-Ray executable.
func BinFilePath() string {
	if binFilePath == "" {
		binFilePath = fmt.Sprintf("%s/rexray", BinDirPath())
	}
	return binFilePath
}

// EtcFilePath returns the path to a file inside the REX-Ray etc directory
// with the provided file name.
func EtcFilePath(fileName string) string {
	return fmt.Sprintf("%s/%s", EtcDirPath(), fileName)
}

// LogFilePath returns the path to a file inside the REX-Ray log directory
// with the provided file name.
func LogFilePath(fileName string) string {
	return fmt.Sprintf("%s/%s", LogDirPath(), fileName)
}

// LogFile returns a writer to a file inside the REX-Ray log directory
// with the provided file name.
func LogFile(fileName string) (io.Writer, error) {
	return os.OpenFile(
		LogFilePath(fileName), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
}

// StdOutAndLogFile returns a mutltiplexed writer for the current process's
// stdout descriptor and a REX-Ray log file with the provided name.
func StdOutAndLogFile(fileName string) (io.Writer, error) {
	lf, lfErr := LogFile(fileName)
	if lfErr != nil {
		return nil, lfErr
	}
	return io.MultiWriter(os.Stdout, lf), nil
}

// WritePidFile writes the current process ID to the REX-Ray PID file.
func WritePidFile(pid int) error {

	if pid < 0 {
		pid = os.Getpid()
	}

	return gotil.WriteStringToFile(fmt.Sprintf("%d", pid), PidFilePath())
}

// ReadPidFile reads the REX-Ray PID from the PID file.
func ReadPidFile() (int, error) {

	pidStr, pidStrErr := gotil.ReadFileToString(PidFilePath())
	if pidStrErr != nil {
		return -1, pidStrErr
	}

	pid, atoiErr := strconv.Atoi(pidStr)
	if atoiErr != nil {
		return -1, atoiErr
	}

	return pid, nil
}

// PrintVersion prints the current version information to the provided writer.
func PrintVersion(out io.Writer) {
	fmt.Fprintln(out, "REX-Ray")
	fmt.Fprintln(out, "-------")
	fmt.Fprintf(out, "Binary: %s\n", thisExeAbsPath)
	fmt.Fprintf(out, "SemVer: %s\n", core.Version.SemVer)
	fmt.Fprintf(out, "OsArch: %s\n", core.Version.Arch)
	fmt.Fprintf(out, "Branch: %s\n", core.Version.Branch)
	fmt.Fprintf(out, "Commit: %s\n", core.Version.ShaLong)
	fmt.Fprintf(out, "Formed: %s\n\n",
		core.Version.BuildTimestamp.Format(time.RFC1123))

	fmt.Fprintln(out, "libStorage")
	fmt.Fprintln(out, "----------")
	fmt.Fprintf(out, "SemVer: %s\n", apiversion.Version.SemVer)
	fmt.Fprintf(out, "OsArch: %s\n", apiversion.Version.Arch)
	fmt.Fprintf(out, "Branch: %s\n", apiversion.Version.Branch)
	fmt.Fprintf(out, "Commit: %s\n", apiversion.Version.ShaLong)

	timestamp := apiversion.Version.BuildTimestamp.Format(time.RFC1123)
	fmt.Fprintf(out, "Formed: %s\n", timestamp)
}

// WaitUntilLibStorageStopped blocks until libStorage is stopped.
func WaitUntilLibStorageStopped(ctx apitypes.Context, errs <-chan error) {
	ctx.Debug("waiting until libStorage is stopped")

	// if there is no err channel then do not wait until libStorage is stopped
	// as the absence of the err channel means libStorage was not started in
	// embedded mode
	if errs == nil {
		ctx.Debug("done waiting on err chan; err chan is nil")
		return
	}

	// in a goroutine, range over the apiserver.Close channel until it's closed
	for range apiserver.Close() {
	}
	ctx.Debug("done sending close signals to libStorage")

	// block until the err channel is closed
	for range errs {
	}
	ctx.Debug("done waiting on err chan")
}

var localHostRX = regexp.MustCompile(
	`(?i)^(localhost|(?:127\.0\.0\.1))(?::(\d+))?$`)

// IsLocalServerActive returns a flag indicating whether or not a local
// libStorage is already running.
func IsLocalServerActive(
	ctx apitypes.Context, config gofig.Config) (host string, running bool) {

	host = config.GetString(apitypes.ConfigHost)
	if host == "" {
		return "", false
	}

	proto, addr, err := gotil.ParseAddress(host)
	if err != nil {
		return "", false
	}

	switch proto {
	case "unix":
		ctx.WithField("sock", addr).Debug("is local unix server active")
		return host, gotil.FileExists(addr)
	case "tcp":
		m := localHostRX.FindStringSubmatch(addr)
		if len(m) < 3 {
			return "", false
		}
		port, err := strconv.Atoi(m[2])
		if err != nil {
			return "", false
		}
		ctx.WithField("port", port).Debug("is local tcp server active")
		return host, !gotil.IsTCPPortAvailable(port)
	}
	return "", false
}

// ActivateLibStorage activates a libStorage server if conditions are met and
// returns a possibly mutated context.
func ActivateLibStorage(
	ctx apitypes.Context,
	config gofig.Config) (apitypes.Context, gofig.Config, <-chan error, error) {

	config = config.Scope("rexray")

	if !config.IsSet(apitypes.ConfigIgVolOpsMountPath) {
		config.Set(apitypes.ConfigIgVolOpsMountPath, LibFilePath("volumes"))
	}

	var (
		host      string
		err       error
		isRunning bool
		errs      <-chan error
		server    apitypes.Server
	)

	if host = config.GetString(apitypes.ConfigHost); host != "" {
		if !config.GetBool(apitypes.ConfigEmbedded) {
			ctx.WithField(
				"host", host,
			).Debug("not starting embeddded server; embedded mode disabled")
			return ctx, config, nil, nil
		}
	}

	if host, isRunning = IsLocalServerActive(ctx, config); isRunning {
		ctx = ctx.WithValue(context.HostKey, host)
		ctx.WithField("host", host).Debug(
			"not starting embeddded server; already running")
		return ctx, config, nil, nil
	}

	// if no host was specified then see if a set of default services need to
	// be initialized
	if host == "" {
		if err = initDefaultLibStorageServices(ctx, config); err != nil {
			return ctx, config, nil, err
		}
	}

	ctx.Debug("starting embedded libStorage server")

	apiserver.CloseOnAbort()

	if server, errs, err = apiserver.Serve(ctx, config); err != nil {
		return ctx, config, nil, err
	}

	go func() {
		if err := <-errs; err != nil {
			ctx.Error(err)
		}
	}()

	if host == "" {
		config.Set(apitypes.ConfigHost, server.Addrs()[0])
	}

	return ctx, config, errs, nil
}
<commit_msg>REX-Ray Config StorageDrivers Backwards Compat<commit_after>package util

import (
	"fmt"
	"io"
	"os"
	"os/exec"
	"regexp"
	"strconv"
	"time"

	"github.com/akutz/gofig"
	"github.com/akutz/gotil"
	apiversion "github.com/emccode/libstorage/api"
	"github.com/emccode/libstorage/api/context"
	apiserver "github.com/emccode/libstorage/api/server"
	apitypes "github.com/emccode/libstorage/api/types"

	"github.com/emccode/rexray/core"
)

const (
	logDirPathSuffix = "/var/log/rexray"
	etcDirPathSuffix = "/etc/rexray"
	binDirPathSuffix = "/usr/bin"
	runDirPathSuffix = "/var/run/rexray"
	libDirPathSuffix = "/var/lib/rexray"

	// UnitFilePath is the path to the SystemD service's unit file.
	UnitFilePath = "/etc/systemd/system/rexray.service"

	// InitFilePath is the path to the SystemV Service's init script.
	InitFilePath = "/etc/init.d/rexray"

	// EnvFileName is the name of the environment file used by the SystemD
	// service.
	EnvFileName = "rexray.env"
)

var (
	thisExeDir     string
	thisExeName    string
	thisExeAbsPath string

	prefix string

	binDirPath  string
	binFilePath string
	logDirPath  string
	libDirPath  string
	runDirPath  string
	etcDirPath  string
	pidFilePath string
)

func init() {
	prefix = os.Getenv("REXRAY_HOME")

	thisExeDir, thisExeName, thisExeAbsPath = gotil.GetThisPathParts()
}

// GetPrefix gets the root path to the REX-Ray data.
func GetPrefix() string {
	return prefix
}

// Prefix sets the root path to the REX-Ray data.
func Prefix(p string) {
	if p == "" || p == "/" {
		return
	}

	binDirPath = ""
	binFilePath = ""
	logDirPath = ""
	libDirPath = ""
	runDirPath = ""
	etcDirPath = ""
	pidFilePath = ""

	prefix = p
}

// IsPrefixed returns a flag indicating whether or not a prefix value is set.
func IsPrefixed() bool {
	return !(prefix == "" || prefix == "/")
}

// Install executes the system install command.
func Install(args ...string) {
	exec.Command("install", args...).Run()
}

// InstallChownRoot executes the system install command and chowns the target
// to the root user and group.
func InstallChownRoot(args ...string) {
	a := []string{"-o", "0", "-g", "0"}
	for _, i := range args {
		a = append(a, i)
	}
	exec.Command("install", a...).Run()
}

// InstallDirChownRoot executes the system install command with a -d flag and
// chowns the target to the root user and group.
func InstallDirChownRoot(dirPath string) {
	InstallChownRoot("-d", dirPath)
}

// EtcDirPath returns the path to the REX-Ray etc directory.
func EtcDirPath() string {
	if etcDirPath == "" {
		etcDirPath = fmt.Sprintf("%s%s", prefix, etcDirPathSuffix)
		os.MkdirAll(etcDirPath, 0755)
	}
	return etcDirPath
}

// RunDirPath returns the path to the REX-Ray run directory.
func RunDirPath() string {
	if runDirPath == "" {
		runDirPath = fmt.Sprintf("%s%s", prefix, runDirPathSuffix)
		os.MkdirAll(runDirPath, 0755)
	}
	return runDirPath
}

// LogDirPath returns the path to the REX-Ray log directory.
func LogDirPath() string {
	if logDirPath == "" {
		logDirPath = fmt.Sprintf("%s%s", prefix, logDirPathSuffix)
		os.MkdirAll(logDirPath, 0755)
	}
	return logDirPath
}

// LibDirPath returns the path to the REX-Ray bin directory.
func LibDirPath() string {
	if libDirPath == "" {
		libDirPath = fmt.Sprintf("%s%s", prefix, libDirPathSuffix)
		os.MkdirAll(libDirPath, 0755)
	}
	return libDirPath
}

// LibFilePath returns the path to a file inside the REX-Ray lib directory
// with the provided file name.
func LibFilePath(fileName string) string {
	return fmt.Sprintf("%s/%s", LibDirPath(), fileName)
}

// RunFilePath returns the path to a file inside the REX-Ray run directory
// with the provided file name.
func RunFilePath(fileName string) string {
	return fmt.Sprintf("%s/%s", RunDirPath(), fileName)
}

// BinDirPath returns the path to the REX-Ray bin directory.
func BinDirPath() string {
	if binDirPath == "" {
		binDirPath = fmt.Sprintf("%s%s", prefix, binDirPathSuffix)
		os.MkdirAll(binDirPath, 0755)
	}
	return binDirPath
}

// PidFilePath returns the path to the REX-Ray PID file.
func PidFilePath() string {
	if pidFilePath == "" {
		pidFilePath = fmt.Sprintf("%s/rexray.pid", RunDirPath())
	}
	return pidFilePath
}

// BinFilePath returns the path to the REX-Ray executable.
func BinFilePath() string {
	if binFilePath == "" {
		binFilePath = fmt.Sprintf("%s/rexray", BinDirPath())
	}
	return binFilePath
}

// EtcFilePath returns the path to a file inside the REX-Ray etc directory
// with the provided file name.
func EtcFilePath(fileName string) string {
	return fmt.Sprintf("%s/%s", EtcDirPath(), fileName)
}

// LogFilePath returns the path to a file inside the REX-Ray log directory
// with the provided file name.
func LogFilePath(fileName string) string {
	return fmt.Sprintf("%s/%s", LogDirPath(), fileName)
}

// LogFile returns a writer to a file inside the REX-Ray log directory
// with the provided file name.
func LogFile(fileName string) (io.Writer, error) {
	return os.OpenFile(
		LogFilePath(fileName), os.O_CREATE|os.O_APPEND|os.O_RDWR, 0644)
}

// StdOutAndLogFile returns a mutltiplexed writer for the current process's
// stdout descriptor and a REX-Ray log file with the provided name.
func StdOutAndLogFile(fileName string) (io.Writer, error) {
	lf, lfErr := LogFile(fileName)
	if lfErr != nil {
		return nil, lfErr
	}
	return io.MultiWriter(os.Stdout, lf), nil
}

// WritePidFile writes the current process ID to the REX-Ray PID file.
func WritePidFile(pid int) error {

	if pid < 0 {
		pid = os.Getpid()
	}

	return gotil.WriteStringToFile(fmt.Sprintf("%d", pid), PidFilePath())
}

// ReadPidFile reads the REX-Ray PID from the PID file.
func ReadPidFile() (int, error) {

	pidStr, pidStrErr := gotil.ReadFileToString(PidFilePath())
	if pidStrErr != nil {
		return -1, pidStrErr
	}

	pid, atoiErr := strconv.Atoi(pidStr)
	if atoiErr != nil {
		return -1, atoiErr
	}

	return pid, nil
}

// PrintVersion prints the current version information to the provided writer.
func PrintVersion(out io.Writer) {
	fmt.Fprintln(out, "REX-Ray")
	fmt.Fprintln(out, "-------")
	fmt.Fprintf(out, "Binary: %s\n", thisExeAbsPath)
	fmt.Fprintf(out, "SemVer: %s\n", core.Version.SemVer)
	fmt.Fprintf(out, "OsArch: %s\n", core.Version.Arch)
	fmt.Fprintf(out, "Branch: %s\n", core.Version.Branch)
	fmt.Fprintf(out, "Commit: %s\n", core.Version.ShaLong)
	fmt.Fprintf(out, "Formed: %s\n\n",
		core.Version.BuildTimestamp.Format(time.RFC1123))

	fmt.Fprintln(out, "libStorage")
	fmt.Fprintln(out, "----------")
	fmt.Fprintf(out, "SemVer: %s\n", apiversion.Version.SemVer)
	fmt.Fprintf(out, "OsArch: %s\n", apiversion.Version.Arch)
	fmt.Fprintf(out, "Branch: %s\n", apiversion.Version.Branch)
	fmt.Fprintf(out, "Commit: %s\n", apiversion.Version.ShaLong)

	timestamp := apiversion.Version.BuildTimestamp.Format(time.RFC1123)
	fmt.Fprintf(out, "Formed: %s\n", timestamp)
}

// WaitUntilLibStorageStopped blocks until libStorage is stopped.
func WaitUntilLibStorageStopped(ctx apitypes.Context, errs <-chan error) {
	ctx.Debug("waiting until libStorage is stopped")

	// if there is no err channel then do not wait until libStorage is stopped
	// as the absence of the err channel means libStorage was not started in
	// embedded mode
	if errs == nil {
		ctx.Debug("done waiting on err chan; err chan is nil")
		return
	}

	// in a goroutine, range over the apiserver.Close channel until it's closed
	for range apiserver.Close() {
	}
	ctx.Debug("done sending close signals to libStorage")

	// block until the err channel is closed
	for range errs {
	}
	ctx.Debug("done waiting on err chan")
}

var localHostRX = regexp.MustCompile(
	`(?i)^(localhost|(?:127\.0\.0\.1))(?::(\d+))?$`)

// IsLocalServerActive returns a flag indicating whether or not a local
// libStorage is already running.
func IsLocalServerActive(
	ctx apitypes.Context, config gofig.Config) (host string, running bool) {

	host = config.GetString(apitypes.ConfigHost)
	if host == "" {
		return "", false
	}

	proto, addr, err := gotil.ParseAddress(host)
	if err != nil {
		return "", false
	}

	switch proto {
	case "unix":
		ctx.WithField("sock", addr).Debug("is local unix server active")
		return host, gotil.FileExists(addr)
	case "tcp":
		m := localHostRX.FindStringSubmatch(addr)
		if len(m) < 3 {
			return "", false
		}
		port, err := strconv.Atoi(m[2])
		if err != nil {
			return "", false
		}
		ctx.WithField("port", port).Debug("is local tcp server active")
		return host, !gotil.IsTCPPortAvailable(port)
	}
	return "", false
}

// ActivateLibStorage activates a libStorage server if conditions are met and
// returns a possibly mutated context.
func ActivateLibStorage(
	ctx apitypes.Context,
	config gofig.Config) (apitypes.Context, gofig.Config, <-chan error, error) {

	config = config.Scope("rexray")

	// set the `libstorage.service` property to the value of
	// `rexray.storageDrivers` if the former is not defined and the
	// latter is
	if !config.IsSet(apitypes.ConfigService) &&
		config.IsSet("rexray.storageDrivers") {

		if sd := config.GetStringSlice("rexray.storageDrivers"); len(sd) > 0 {
			config.Set(apitypes.ConfigService, sd[0])
		} else if sd := config.GetString("rexray.storageDrivers"); sd != "" {
			config.Set(apitypes.ConfigService, sd)
		}
	}

	if !config.IsSet(apitypes.ConfigIgVolOpsMountPath) {
		config.Set(apitypes.ConfigIgVolOpsMountPath, LibFilePath("volumes"))
	}

	var (
		host      string
		err       error
		isRunning bool
		errs      <-chan error
		server    apitypes.Server
	)

	if host = config.GetString(apitypes.ConfigHost); host != "" {
		if !config.GetBool(apitypes.ConfigEmbedded) {
			ctx.WithField(
				"host", host,
			).Debug("not starting embeddded server; embedded mode disabled")
			return ctx, config, nil, nil
		}
	}

	if host, isRunning = IsLocalServerActive(ctx, config); isRunning {
		ctx = ctx.WithValue(context.HostKey, host)
		ctx.WithField("host", host).Debug(
			"not starting embeddded server; already running")
		return ctx, config, nil, nil
	}

	// if no host was specified then see if a set of default services need to
	// be initialized
	if host == "" {
		if err = initDefaultLibStorageServices(ctx, config); err != nil {
			return ctx, config, nil, err
		}
	}

	ctx.Debug("starting embedded libStorage server")

	apiserver.CloseOnAbort()

	if server, errs, err = apiserver.Serve(ctx, config); err != nil {
		return ctx, config, nil, err
	}

	go func() {
		if err := <-errs; err != nil {
			ctx.Error(err)
		}
	}()

	if host == "" {
		config.Set(apitypes.ConfigHost, server.Addrs()[0])
	}

	return ctx, config, errs, nil
}
<|endoftext|>


<commit_before>package util

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"unicode/utf8"

	"github.com/Sirupsen/logrus"
)

// ExeDir is our starting location.
var ExeDir string

// NewLogger creates and returns an instance of logrus.Logger.
// If the `--debug` command flag was not provided, we set the level to Error.
func NewLogger() *logrus.Logger {
	log := logrus.New()
	if !CLConfig.Debug {
		log.Level = logrus.ErrorLevel
	}
	log.Out = os.Stdout
	return log
}

// FindLoc calculates the line and span of an Alert.
func FindLoc(count int, ctx string, s string, ext string, loc []int, pad int) (int, []int) {
	var pos int

	substring := s[loc[0]:loc[1]]
	meta := regexp.QuoteMeta(substring)
	diff := loc[0] - utf8.RuneCountInString(s[:loc[0]])
	r := regexp.MustCompile(fmt.Sprintf(`(\b%s|%s)`, meta, meta))
	offset := len(ctx) - len(ctx[loc[0]:])
	pos = r.FindAllStringIndex(ctx[loc[0]:], 1)[0][0] + 1 + offset

	counter := 0
	lines := strings.SplitAfter(ctx, "\n")
	for idx, l := range lines {
		if (counter + utf8.RuneCountInString(l)) >= pos {
			loc[0] = (pos - counter) + pad - diff
			loc[1] = loc[0] + utf8.RuneCountInString(substring) - 1
			return count - (len(lines) - (idx + 1)), loc
		}
		counter += utf8.RuneCountInString(l)
	}
	return count, loc
}

// PrepText prepares text for our check functions.
func PrepText(txt string) string {
	replacements := map[string]string{
		"\r\n":   "\n",
		"\u201c": `"`,
		"\u201d": `"`,
		"\u2018": "'",
		"\u2019": "'",
	}
	for old, new := range replacements {
		txt = strings.Replace(txt, old, new, -1)
	}
	return txt
}

// ExtFromSyntax takes a syntax's name (e.g., "Python") and returns its
// extension (if found).
func ExtFromSyntax(name string) string {
	for r, s := range LookupSyntaxName {
		if matched, _ := regexp.MatchString(r, name); matched {
			return s
		}
	}
	return name
}

// FormatFromExt takes a file extension and returns its [normExt, format]
// list, if supported.
func FormatFromExt(path string) (string, string) {
	ext := filepath.Ext(path)
	for r, f := range FormatByExtension {
		m, _ := regexp.MatchString(r, ext)
		if m {
			return f[0], f[1]
		}
	}
	return "unknown", "unknown"
}

// IsDir determines if the path given by `filename` is a directory.
func IsDir(filename string) bool {
	fi, err := os.Stat(filename)
	return err == nil && fi.IsDir()
}

// FileExists determines if the path given by `filename` exists.
func FileExists(filename string) bool {
	_, err := os.Stat(filename)
	return err == nil
}

// StringInSlice determines if `slice` contains the string `a`.
func StringInSlice(a string, slice []string) bool {
	for _, b := range slice {
		if a == b {
			return true
		}
	}
	return false
}

// AllStringsInSlice determines if `slice` contains the `strings`.
func AllStringsInSlice(strings []string, slice []string) bool {
	for _, s := range strings {
		if !StringInSlice(s, slice) {
			return false
		}
	}
	return true
}

// HasAnyPrefix determines if `text` has any prefix contained in `slice`.
func HasAnyPrefix(text string, slice []string) bool {
	for _, s := range slice {
		if strings.HasPrefix(text, s) {
			return true
		}
	}
	return false
}

// CheckError prints any errors to stdout. A return value of true => no error.
func CheckError(err error, context string) bool {
	if err != nil {
		fmt.Printf("%v (%s)\n", err, context)
	}
	return err == nil
}

// CheckAndClose closes `file` and prints any errors to stdout.
// A return value of true => no error.
func CheckAndClose(file *os.File) bool {
	err := file.Close()
	return CheckError(err, file.Name())
}
<commit_msg>fix: ensure `Span` always refers to `Line`<commit_after>package util

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"unicode/utf8"

	"github.com/Sirupsen/logrus"
)

// ExeDir is our starting location.
var ExeDir string

// NewLogger creates and returns an instance of logrus.Logger.
// If the `--debug` command flag was not provided, we set the level to Error.
func NewLogger() *logrus.Logger {
	log := logrus.New()
	if !CLConfig.Debug {
		log.Level = logrus.ErrorLevel
	}
	log.Out = os.Stdout
	return log
}

// FindLoc calculates the line and span of an Alert.
func FindLoc(count int, ctx string, s string, ext string, loc []int, pad int) (int, []int) {
	var length int

	substring := s[loc[0]:loc[1]]
	meta := regexp.QuoteMeta(substring)
	diff := loc[0] - utf8.RuneCountInString(s[:loc[0]])
	r := regexp.MustCompile(fmt.Sprintf(`(\b%s|%s)`, meta, meta))
	offset := len(ctx) - len(ctx[loc[0]:])
	pos := r.FindAllStringIndex(ctx[loc[0]:], 1)[0][0] + 1 + offset

	counter := 0
	lines := strings.SplitAfter(ctx, "\n")
	for idx, l := range lines {
		length = utf8.RuneCountInString(l)
		if (counter + length) >= pos {
			loc[0] = (pos - counter) + pad - diff
			loc[1] = loc[0] + utf8.RuneCountInString(substring) - 1
			extent := length + pad
			if loc[1] > extent {
				loc[1] = extent
			}
			return count - (len(lines) - (idx + 1)), loc
		}
		counter += length
	}

	return count, loc
}

// PrepText prepares text for our check functions.
func PrepText(txt string) string {
	replacements := map[string]string{
		"\r\n":   "\n",
		"\u201c": `"`,
		"\u201d": `"`,
		"\u2018": "'",
		"\u2019": "'",
	}
	for old, new := range replacements {
		txt = strings.Replace(txt, old, new, -1)
	}
	return txt
}

// ExtFromSyntax takes a syntax's name (e.g., "Python") and returns its
// extension (if found).
func ExtFromSyntax(name string) string {
	for r, s := range LookupSyntaxName {
		if matched, _ := regexp.MatchString(r, name); matched {
			return s
		}
	}
	return name
}

// FormatFromExt takes a file extension and returns its [normExt, format]
// list, if supported.
func FormatFromExt(path string) (string, string) {
	ext := filepath.Ext(path)
	for r, f := range FormatByExtension {
		m, _ := regexp.MatchString(r, ext)
		if m {
			return f[0], f[1]
		}
	}
	return "unknown", "unknown"
}

// IsDir determines if the path given by `filename` is a directory.
func IsDir(filename string) bool {
	fi, err := os.Stat(filename)
	return err == nil && fi.IsDir()
}

// FileExists determines if the path given by `filename` exists.
func FileExists(filename string) bool {
	_, err := os.Stat(filename)
	return err == nil
}

// StringInSlice determines if `slice` contains the string `a`.
func StringInSlice(a string, slice []string) bool {
	for _, b := range slice {
		if a == b {
			return true
		}
	}
	return false
}

// AllStringsInSlice determines if `slice` contains the `strings`.
func AllStringsInSlice(strings []string, slice []string) bool {
	for _, s := range strings {
		if !StringInSlice(s, slice) {
			return false
		}
	}
	return true
}

// HasAnyPrefix determines if `text` has any prefix contained in `slice`.
func HasAnyPrefix(text string, slice []string) bool {
	for _, s := range slice {
		if strings.HasPrefix(text, s) {
			return true
		}
	}
	return false
}

// CheckError prints any errors to stdout. A return value of true => no error.
func CheckError(err error, context string) bool {
	if err != nil {
		fmt.Printf("%v (%s)\n", err, context)
	}
	return err == nil
}

// CheckAndClose closes `file` and prints any errors to stdout.
// A return value of true => no error.
func CheckAndClose(file *os.File) bool {
	err := file.Close()
	return CheckError(err, file.Name())
}
<|endoftext|>


<commit_before>package vc

import (
	"net/http"
	"time"

	"github.com/julienschmidt/httprouter"
	"github.com/rcrowley/go-metrics"
	"github.com/snikch/api/ctx"
	"github.com/snikch/api/lynx"
	"github.com/snikch/api/sideload"
)

// EmptyResponse is used to determine if a response should be empty.
var EmptyResponse = &Response{}

type contextKey int

const (
	criteriaContextKey contextKey = iota
	paramsContextKey
)

// ActionProcessor handles an entire action lifecycle, from data retrieval
// through to unlocking, and responding.
type ActionProcessor struct {
	SideloadEnabled bool
}

// ActionHandler implementers are responsible for returning payload data for
// a request, along with a status code or error.
type ActionHandler interface {
	HandleAction(*ctx.Context) (interface{}, int, error)
}

// ActionHandlerFunc wraps a function with the HandleAction signature to a full
// ActionHandler interface.
type ActionHandlerFunc struct {
	Handler func(*ctx.Context) (interface{}, int, error)
}

// HandleAction implements the ActionHander interface and simply calls the
// underlying function.
func (fn ActionHandlerFunc) HandleAction(context *ctx.Context) (interface{}, int, error) {
	return fn.Handler(context)
}

// HandleActionFunc returns an http.Handler for the suppled action function.
// A type and action name are used in metrics and logging functions.
func (p *ActionProcessor) HandleActionFunc(typ, action string, fn func(*ctx.Context) (interface{}, int, error)) httprouter.Handle {
	return p.HTTPHandler(typ, action, ActionHandlerFunc{
		Handler: fn,
	})
}

var requestCriteriaTransformers = []func(*ctx.Context, *Criteria){}

func RegisterCriteriaTransformer(transformer func(*ctx.Context, *Criteria)) {
	requestCriteriaTransformers = append(requestCriteriaTransformers, transformer)
}

// HTTPHandler takes an ActionHandler and returns a http.Handler instance
// that can be used.
func (p *ActionProcessor) HTTPHandler(typ, action string, handler ActionHandler) httprouter.Handle {
	// Create a new timer for timing this handler.
	timer := metrics.NewTimer()
	metrics.DefaultRegistry.Register(typ+"-"+action, timer)
	sideloadTimer := metrics.NewTimer()
	metrics.DefaultRegistry.Register(typ+"-"+action+"-sideload", sideloadTimer)
	unlockTimer := metrics.NewTimer()
	metrics.DefaultRegistry.Register(typ+"-"+action+"-unlock", unlockTimer)

	return httprouter.Handle(func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
		// At the end of this function, add a time metric.
		defer timer.UpdateSince(time.Now())

		// Create a new context for this action.
		context := ctx.NewContext()
		context.Request = r
		context.EntityType = typ
		SetContextParams(context, params)

		// Get any criteria, and transform it if required.
		criteria := RequestCriteria(r)
		for _, transformer := range requestCriteriaTransformers {
			transformer(context, criteria)
		}
		// Make the criteria available on the content.
		SetContextCriteria(context, criteria)

		// Get the base payload back from the ActionHandler instance.
		payload, code, err := handler.HandleAction(context)
		if err != nil {
			RespondWithError(w, r, err)
			return
		}

		// If an empty response is required, return an empty response.
		if payload == EmptyResponse {
			RespondWithStatusCode(w, r, code)
			return
		}

		// Build up a response.
		response := Response{
			Payload: payload,
		}

		if p.SideloadEnabled {
			start := time.Now()
			// Retrieve any sideloaded entities.
			sideloaded, err := sideload.Load(context, payload, criteria.Sideload)

			response.Sideload = &sideloaded
			if err != nil {
				timer.UpdateSince(start)
				RespondWithError(w, r, err)
				return
			}
			timer.UpdateSince(start)
		}

		// Unlock any entities registered for this request.
		unlockStartTime := time.Now()
		err = lynx.ContextStore(context).Unlock()
		unlockTimer.UpdateSince(unlockStartTime)

		if err != nil {
			RespondWithError(w, r, err)
			return
		}

		RespondWithData(w, r, response, code)
	})
}
<commit_msg>Update comment<commit_after>package vc

import (
	"net/http"
	"time"

	"github.com/julienschmidt/httprouter"
	"github.com/rcrowley/go-metrics"
	"github.com/snikch/api/ctx"
	"github.com/snikch/api/lynx"
	"github.com/snikch/api/sideload"
)

// EmptyResponse is used to determine if a response should be empty.
var EmptyResponse = &Response{}

type contextKey int

const (
	criteriaContextKey contextKey = iota
	paramsContextKey
)

// ActionProcessor handles an entire action lifecycle, from data retrieval
// through to unlocking, and responding.
type ActionProcessor struct {
	SideloadEnabled bool
}

// ActionHandler implementers are responsible for returning payload data for
// a request, along with a status code or error.
type ActionHandler interface {
	HandleAction(*ctx.Context) (interface{}, int, error)
}

// ActionHandlerFunc wraps a function with the HandleAction signature to a full
// ActionHandler interface.
type ActionHandlerFunc struct {
	Handler func(*ctx.Context) (interface{}, int, error)
}

// HandleAction implements the ActionHander interface and simply calls the
// underlying function.
func (fn ActionHandlerFunc) HandleAction(context *ctx.Context) (interface{}, int, error) {
	return fn.Handler(context)
}

// HandleActionFunc returns an http.Handler for the suppled action function.
// A type and action name are used in metrics and logging functions.
func (p *ActionProcessor) HandleActionFunc(typ, action string, fn func(*ctx.Context) (interface{}, int, error)) httprouter.Handle {
	return p.HTTPHandler(typ, action, ActionHandlerFunc{
		Handler: fn,
	})
}

var requestCriteriaTransformers = []func(*ctx.Context, *Criteria){}

func RegisterCriteriaTransformer(transformer func(*ctx.Context, *Criteria)) {
	requestCriteriaTransformers = append(requestCriteriaTransformers, transformer)
}

// HTTPHandler takes an ActionHandler and returns a http.Handler instance
// that can be used. The type and action are used to determine the context in
// several areas, such as transformers and metrics.
func (p *ActionProcessor) HTTPHandler(typ, action string, handler ActionHandler) httprouter.Handle {
	// Create a new timer for timing this handler.
	timer := metrics.NewTimer()
	metrics.DefaultRegistry.Register(typ+"-"+action, timer)
	sideloadTimer := metrics.NewTimer()
	metrics.DefaultRegistry.Register(typ+"-"+action+"-sideload", sideloadTimer)
	unlockTimer := metrics.NewTimer()
	metrics.DefaultRegistry.Register(typ+"-"+action+"-unlock", unlockTimer)

	return httprouter.Handle(func(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
		// At the end of this function, add a time metric.
		defer timer.UpdateSince(time.Now())

		// Create a new context for this action.
		context := ctx.NewContext()
		context.Request = r
		context.EntityType = typ
		SetContextParams(context, params)

		// Get any criteria, and transform it if required.
		criteria := RequestCriteria(r)
		for _, transformer := range requestCriteriaTransformers {
			transformer(context, criteria)
		}
		// Make the criteria available on the content.
		SetContextCriteria(context, criteria)

		// Get the base payload back from the ActionHandler instance.
		payload, code, err := handler.HandleAction(context)
		if err != nil {
			RespondWithError(w, r, err)
			return
		}

		// If an empty response is required, return an empty response.
		if payload == EmptyResponse {
			RespondWithStatusCode(w, r, code)
			return
		}

		// Build up a response.
		response := Response{
			Payload: payload,
		}

		if p.SideloadEnabled {
			start := time.Now()
			// Retrieve any sideloaded entities.
			sideloaded, err := sideload.Load(context, payload, criteria.Sideload)

			response.Sideload = &sideloaded
			if err != nil {
				timer.UpdateSince(start)
				RespondWithError(w, r, err)
				return
			}
			timer.UpdateSince(start)
		}

		// Unlock any entities registered for this request.
		unlockStartTime := time.Now()
		err = lynx.ContextStore(context).Unlock()
		unlockTimer.UpdateSince(unlockStartTime)

		if err != nil {
			RespondWithError(w, r, err)
			return
		}

		RespondWithData(w, r, response, code)
	})
}
<|endoftext|>


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

package main

import (
	"flag"
	"fmt"
	"github.com/mbucc/vufs"
	"log"
)

var addr = flag.String("addr", ":5640", "network address")
var debug = flag.Int("debug", 0, "print debug messages")
var root = flag.String("root", "/", "root filesystem")

func main() {
	var err error
	flag.Parse()
	fs := new(vufs.VuFs)
	fs.Id = "vufs"
	fs.Root = *root
	fs.Debuglevel = *debug
	fs.Upool, err  = vufs.NewVusers(root)
	if err != nil {
		log.Println(err)
		os.exit(1)
	}

	fs.Start(fs)

	fmt.Print("vufs starting\n")
	// determined by build tags
	//extraFuncs()
	err := fs.StartNetListener("tcp", *addr)
	if err != nil {
		log.Println(err)
		os.exit(1)
	}
}
<commit_msg>zap commented out code I won't use<commit_after>// Copyright 2009 The go9p Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"flag"
	"fmt"
	"github.com/mbucc/vufs"
	"log"
)

var addr = flag.String("addr", ":5640", "network address")
var debug = flag.Int("debug", 0, "print debug messages")
var root = flag.String("root", "/", "root filesystem")

func main() {
	var err error
	flag.Parse()
	fs := new(vufs.VuFs)
	fs.Id = "vufs"
	fs.Root = *root
	fs.Debuglevel = *debug
	fs.Upool, err  = vufs.NewVusers(root)
	if err != nil {
		log.Println(err)
		os.exit(1)
	}

	fs.Start(fs)

	fmt.Print("vufs starting\n")
	err := fs.StartNetListener("tcp", *addr)
	if err != nil {
		log.Println(err)
		os.exit(1)
	}
}
<|endoftext|>