<commit_before>package appstore

// https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Subscriptions.html#//apple_ref/doc/uid/TP40008267-CH7-SW16
type NotificationType string

const (
	// Initial purchase of the subscription.
	NotificationTypeInitialBuy NotificationType = "INITIAL_BUY"
	// Subscription was canceled by Apple customer support.
	NotificationTypeCancel NotificationType = "CANCEL"
	// Automatic renewal was successful for an expired subscription.
	NotificationTypeRenewal NotificationType = "RENEWAL"
	// Customer renewed a subscription interactively after it lapsed.
	NotificationTypeInteractiveRenewal NotificationType = "INTERACTIVE_RENEWAL"
	// Customer changed the plan that takes affect at the next subscription renewal. Current active plan is not affected.
	NotificationTypeDidChangeRenewalPreference NotificationType = "DID_CHANGE_RENEWAL_PREFERENCE"
)

type NotificationEnvironment string

const (
	NotificationSandbox    NotificationEnvironment = "Sandbox"
	NotificationProduction NotificationEnvironment = "PROD"
)

type NotificationExpiresDate struct {
	ExpiresDateMS  string `json:"expires_date"`
	ExpiresDateUTC string `json:"expires_date_formatted"`
	ExpiresDatePST string `json:"expires_date_formatted_pst"`
}

type NotificationReceipt struct {
	UniqueIdentifier          string `json:"unique_identifier"`
	AppItemID                 string `json:"app_item_id"`
	Quantity                  string `json:"quantity"`
	VersionExternalIdentifier string `json:"version_external_identifier"`
	UniqueVendorIdentifier    string `json:"unique_vendor_identifier"`
	WebOrderLineItemID        string `json:"web_order_line_item_id"`
	ItemID                    string `json:"item_id"`
	ProductID                 string `json:"product_id"`
	BID                       string `json:"bid"`
	BVRS                      string `json:"bvrs"`
	TransactionID             string `json:"transaction_id"`
	OriginalTransactionID     string `json:"original_transaction_id"`
	IsTrialPeriod             string `json:"is_trial_period"`

	PurchaseDate
	OriginalPurchaseDate
	NotificationExpiresDate
	CancellationDate
}

type SubscriptionNotification struct {
	Environment      NotificationEnvironment `json:"environment"`
	NotificationType NotificationType        `json:"notification_type"`

	// Not show in raw notify body
	Password              string `json:"password"`
	OriginalTransactionID string `json:"original_transaction_id"`
	AutoRenewAdamID       string `json:"auto_renew_adam_id"`

	// The primary key for identifying a subscription purchase.
	// Posted only if the notification_type is CANCEL.
	WebOrderLineItemID string `json:"web_order_line_item_id"`

	// This is the same as the Subscription Expiration Intent in the receipt.
	// Posted only if notification_type is RENEWAL or INTERACTIVE_RENEWAL.
	ExpirationIntent string `json:"expiration_intent"`

	// Auto renew info
	AutoRenewStatus    string `json:"auto_renew_status"` // false or true
	AutoRenewProductID string `json:"auto_renew_product_id"`

	// Posted if the notification_type is RENEWAL or INTERACTIVE_RENEWAL, and only if the renewal is successful.
	// Posted also if the notification_type is INITIAL_BUY.
	// Not posted for notification_type CANCEL.
	LatestReceipt     string              `json:"latest_receipt"`
	LatestReceiptInfo NotificationReceipt `json:"latest_receipt_info"`

	// Posted only if the notification_type is RENEWAL or CANCEL or if renewal failed and subscription expired.
	LatestExpiredReceipt     string              `json:"latest_expired_receipt"`
	LatestExpiredReceiptInfo NotificationReceipt `json:"latest_expired_receipt_info"`

	// Posted only if the notification_type is CANCEL.
	CancellationDate
}
<commit_msg>Update subscription notification type for IAP<commit_after>package appstore

// https://developer.apple.com/library/content/documentation/NetworkingInternet/Conceptual/StoreKitGuide/Chapters/Subscriptions.html#//apple_ref/doc/uid/TP40008267-CH7-SW16
type NotificationType string

const (
	// Initial purchase of the subscription.
	NotificationTypeInitialBuy NotificationType = "INITIAL_BUY"
	// Subscription was canceled by Apple customer support.
	NotificationTypeCancel NotificationType = "CANCEL"
	// Automatic renewal was successful for an expired subscription.
	NotificationTypeRenewal NotificationType = "RENEWAL"
	// Customer renewed a subscription interactively after it lapsed.
	NotificationTypeInteractiveRenewal NotificationType = "INTERACTIVE_RENEWAL"
	// Customer changed the plan that takes affect at the next subscription renewal. Current active plan is not affected.
	NotificationTypeDidChangeRenewalPreference NotificationType = "DID_CHANGE_RENEWAL_PREF"
)

type NotificationEnvironment string

const (
	NotificationSandbox    NotificationEnvironment = "Sandbox"
	NotificationProduction NotificationEnvironment = "PROD"
)

type NotificationExpiresDate struct {
	ExpiresDateMS  string `json:"expires_date"`
	ExpiresDateUTC string `json:"expires_date_formatted"`
	ExpiresDatePST string `json:"expires_date_formatted_pst"`
}

type NotificationReceipt struct {
	UniqueIdentifier          string `json:"unique_identifier"`
	AppItemID                 string `json:"app_item_id"`
	Quantity                  string `json:"quantity"`
	VersionExternalIdentifier string `json:"version_external_identifier"`
	UniqueVendorIdentifier    string `json:"unique_vendor_identifier"`
	WebOrderLineItemID        string `json:"web_order_line_item_id"`
	ItemID                    string `json:"item_id"`
	ProductID                 string `json:"product_id"`
	BID                       string `json:"bid"`
	BVRS                      string `json:"bvrs"`
	TransactionID             string `json:"transaction_id"`
	OriginalTransactionID     string `json:"original_transaction_id"`
	IsTrialPeriod             string `json:"is_trial_period"`

	PurchaseDate
	OriginalPurchaseDate
	NotificationExpiresDate
	CancellationDate
}

type SubscriptionNotification struct {
	Environment      NotificationEnvironment `json:"environment"`
	NotificationType NotificationType        `json:"notification_type"`

	// Not show in raw notify body
	Password              string `json:"password"`
	OriginalTransactionID string `json:"original_transaction_id"`
	AutoRenewAdamID       string `json:"auto_renew_adam_id"`

	// The primary key for identifying a subscription purchase.
	// Posted only if the notification_type is CANCEL.
	WebOrderLineItemID string `json:"web_order_line_item_id"`

	// This is the same as the Subscription Expiration Intent in the receipt.
	// Posted only if notification_type is RENEWAL or INTERACTIVE_RENEWAL.
	ExpirationIntent string `json:"expiration_intent"`

	// Auto renew info
	AutoRenewStatus    string `json:"auto_renew_status"` // false or true
	AutoRenewProductID string `json:"auto_renew_product_id"`

	// Posted if the notification_type is RENEWAL or INTERACTIVE_RENEWAL, and only if the renewal is successful.
	// Posted also if the notification_type is INITIAL_BUY.
	// Not posted for notification_type CANCEL.
	LatestReceipt     string              `json:"latest_receipt"`
	LatestReceiptInfo NotificationReceipt `json:"latest_receipt_info"`

	// Posted only if the notification_type is RENEWAL or CANCEL or if renewal failed and subscription expired.
	LatestExpiredReceipt     string              `json:"latest_expired_receipt"`
	LatestExpiredReceiptInfo NotificationReceipt `json:"latest_expired_receipt_info"`

	// Posted only if the notification_type is CANCEL.
	CancellationDate
}
<|endoftext|>


<commit_before>package connector

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"strings"
	"time"

	"github.com/facette/facette/pkg/catalog"
	"github.com/facette/facette/pkg/config"
	"github.com/facette/facette/pkg/library"
	"github.com/facette/facette/pkg/types"
	"github.com/facette/facette/pkg/utils"
)

type facettePlotRequest struct {
	Time        time.Time     `json:"time"`
	Range       string        `json:"range"`
	Sample      int           `json:"sample"`
	Percentiles []float64     `json:"percentiles"`
	Graph       library.Graph `json:"graph"`
}

type facettePlotResponse struct {
	ID          string          `json:"id"`
	Start       string          `json:"start"`
	End         string          `json:"end"`
	Step        float64         `json:"step"`
	Name        string          `json:"name"`
	Description string          `json:"description"`
	Type        int             `json:"type"`
	StackMode   int             `json:"stack_mode"`
	Series      []*facetteSerie `json:"series"`
	Modified    time.Time       `json:"modified"`
}

type facetteSerie struct {
	Name    string                     `json:"name"`
	StackID int                        `json:"stack_id"`
	Plots   []types.PlotValue          `json:"plots"`
	Info    map[string]types.PlotValue `json:"info"`
	Options map[string]interface{}     `json:"options"`
}

// FacetteConnector represents the main structure of the Facette connector.
type FacetteConnector struct {
	upstream string
}

const (
	facetteURLCatalog            string = "/api/v1/catalog/"
	facetteURLLibraryGraphsPlots string = "/api/v1/library/graphs/plots"
)

func init() {
	Connectors["facette"] = func(settings map[string]interface{}) (Connector, error) {
		var err error

		connector := &FacetteConnector{}

		if connector.upstream, err = config.GetString(settings, "upstream", true); err != nil {
			return nil, err
		}

		return connector, nil
	}
}

// GetPlots retrieves time series data from origin based on a query and a time interval.
func (connector *FacetteConnector) GetPlots(query *types.PlotQuery) (map[string]*types.PlotResult, error) {
	// Convert plotQuery into plotRequest-like to forward query to upstream Facette API
	plotRequest := facettePlotRequest{
		Time:  query.StartTime,
		Range: utils.DurationToRange(query.EndTime.Sub(query.StartTime)),
		Graph: library.Graph{
			Item: library.Item{
				Name: "facette",
			},
			Groups: []*library.OperGroup{
				&library.OperGroup{
					Name: query.Group.Name,
					Type: query.Group.Type,
					Series: func(series []*types.SerieQuery) []*library.Serie {
						requestSeries := make([]*library.Serie, len(series))

						for i, serie := range series {
							requestSeries[i] = &library.Serie{
								Name:   serie.Name,
								Origin: serie.Metric.Origin,
								Source: serie.Metric.Source,
								Metric: serie.Metric.Name,
								Scale:  serie.Scale,
							}
						}

						return requestSeries
					}(query.Group.Series),
					Scale: query.Group.Scale,
				},
			},
		},
	}

	if query.Step != 0 {
		plotRequest.Sample = int((query.EndTime.Sub(query.StartTime) / query.Step).Seconds())
	}

	requestBody, err := json.Marshal(plotRequest)
	if err != nil {
		return nil, fmt.Errorf("unable to marshal plot request: %s", err)
	}

	httpClient := http.Client{}

	request, err := http.NewRequest(
		"POST",
		strings.TrimSuffix(connector.upstream, "/")+facetteURLLibraryGraphsPlots,
		bytes.NewReader(requestBody))
	if err != nil {
		return nil, fmt.Errorf("unable to set up HTTP request: %s", err)
	}

	request.Header.Add("Content-Type", "application/json")
	request.Header.Add("User-Agent", "Facette")
	request.Header.Add("X-Requested-With", "FacetteConnector")

	response, err := httpClient.Do(request)
	if err != nil {
		return nil, err
	}

	if err := facetteCheckConnectorResponse(response); err != nil {
		return nil, fmt.Errorf("invalid upstream HTTP response: %s", err)
	}

	data, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, fmt.Errorf("unable to read HTTP response body: %s", err)
	}

	plotResponse := facettePlotResponse{}

	if err := json.Unmarshal(data, &plotResponse); err != nil {
		return nil, fmt.Errorf("unable to unmarshal upstream response: %s", err)
	}

	result := make(map[string]*types.PlotResult)

	for _, serie := range plotResponse.Series {
		result[serie.Name] = &types.PlotResult{
			Plots: serie.Plots,
			Info:  serie.Info,
		}
	}

	return result, nil
}

// Refresh triggers a full connector data update.
func (connector *FacetteConnector) Refresh(originName string, outputChan chan *catalog.CatalogRecord) error {
	httpTransport := &http.Transport{
		Dial: (&net.Dialer{
			// Enable dual IPv4/IPv6 stack connectivity:
			DualStack: true,
		}).Dial,
	}

	httpClient := http.Client{Transport: httpTransport}

	response, err := httpClient.Get(strings.TrimSuffix(connector.upstream, "/") + facetteURLCatalog)
	if err != nil {
		return err
	}

	if err = facetteCheckConnectorResponse(response); err != nil {
		return fmt.Errorf("invalid HTTP backend response: %s", err)
	}

	data, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return fmt.Errorf("unable to read HTTP response body: %s", err)
	}

	upstreamCatalog := make(map[string]map[string][]string)
	if err = json.Unmarshal(data, &upstreamCatalog); err != nil {
		return fmt.Errorf("unable to unmarshal JSON data: %s", err)
	}

	// Parse the upstream catalog entries and append them to our local catalog
	for upstreamOriginName, upstreamOrigin := range upstreamCatalog {
		for sourceName, metrics := range upstreamOrigin {
			for _, metric := range metrics {
				outputChan <- &catalog.CatalogRecord{
					Origin:    upstreamOriginName,
					Source:    sourceName,
					Metric:    metric,
					Connector: connector,
				}
			}
		}
	}

	return nil
}

func facetteCheckConnectorResponse(response *http.Response) error {
	if response.StatusCode != 200 {
		return fmt.Errorf("got HTTP status code %d, expected 200", response.StatusCode)
	}

	if !strings.Contains(response.Header.Get("Content-Type"), "application/json") {
		return fmt.Errorf("got HTTP content type `%s', expected `application/json'", response.Header["Content-Type"])
	}

	return nil
}
<commit_msg>Add context to error messages in facette connector<commit_after>package connector

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io/ioutil"
	"net"
	"net/http"
	"strings"
	"time"

	"github.com/facette/facette/pkg/catalog"
	"github.com/facette/facette/pkg/config"
	"github.com/facette/facette/pkg/library"
	"github.com/facette/facette/pkg/types"
	"github.com/facette/facette/pkg/utils"
)

type facettePlotRequest struct {
	Time        time.Time     `json:"time"`
	Range       string        `json:"range"`
	Sample      int           `json:"sample"`
	Percentiles []float64     `json:"percentiles"`
	Graph       library.Graph `json:"graph"`
}

type facettePlotResponse struct {
	ID          string          `json:"id"`
	Start       string          `json:"start"`
	End         string          `json:"end"`
	Step        float64         `json:"step"`
	Name        string          `json:"name"`
	Description string          `json:"description"`
	Type        int             `json:"type"`
	StackMode   int             `json:"stack_mode"`
	Series      []*facetteSerie `json:"series"`
	Modified    time.Time       `json:"modified"`
}

type facetteSerie struct {
	Name    string                     `json:"name"`
	StackID int                        `json:"stack_id"`
	Plots   []types.PlotValue          `json:"plots"`
	Info    map[string]types.PlotValue `json:"info"`
	Options map[string]interface{}     `json:"options"`
}

// FacetteConnector represents the main structure of the Facette connector.
type FacetteConnector struct {
	upstream string
}

const (
	facetteURLCatalog            string = "/api/v1/catalog/"
	facetteURLLibraryGraphsPlots string = "/api/v1/library/graphs/plots"
)

func init() {
	Connectors["facette"] = func(settings map[string]interface{}) (Connector, error) {
		var err error

		connector := &FacetteConnector{}

		if connector.upstream, err = config.GetString(settings, "upstream", true); err != nil {
			return nil, err
		}

		return connector, nil
	}
}

// GetPlots retrieves time series data from origin based on a query and a time interval.
func (connector *FacetteConnector) GetPlots(query *types.PlotQuery) (map[string]*types.PlotResult, error) {
	// Convert plotQuery into plotRequest-like to forward query to upstream Facette API
	plotRequest := facettePlotRequest{
		Time:  query.StartTime,
		Range: utils.DurationToRange(query.EndTime.Sub(query.StartTime)),
		Graph: library.Graph{
			Item: library.Item{
				Name: "facette",
			},
			Groups: []*library.OperGroup{
				&library.OperGroup{
					Name: query.Group.Name,
					Type: query.Group.Type,
					Series: func(series []*types.SerieQuery) []*library.Serie {
						requestSeries := make([]*library.Serie, len(series))

						for i, serie := range series {
							requestSeries[i] = &library.Serie{
								Name:   serie.Name,
								Origin: serie.Metric.Origin,
								Source: serie.Metric.Source,
								Metric: serie.Metric.Name,
								Scale:  serie.Scale,
							}
						}

						return requestSeries
					}(query.Group.Series),
					Scale: query.Group.Scale,
				},
			},
		},
	}

	if query.Step != 0 {
		plotRequest.Sample = int((query.EndTime.Sub(query.StartTime) / query.Step).Seconds())
	}

	requestBody, err := json.Marshal(plotRequest)
	if err != nil {
		return nil, fmt.Errorf("unable to marshal plot request: %s", err)
	}

	httpClient := http.Client{}

	request, err := http.NewRequest(
		"POST",
		strings.TrimSuffix(connector.upstream, "/")+facetteURLLibraryGraphsPlots,
		bytes.NewReader(requestBody))
	if err != nil {
		return nil, fmt.Errorf("unable to set up HTTP request: %s", err)
	}

	request.Header.Add("Content-Type", "application/json")
	request.Header.Add("User-Agent", "Facette")
	request.Header.Add("X-Requested-With", "FacetteConnector")

	response, err := httpClient.Do(request)
	if err != nil {
		return nil, fmt.Errorf("unable to perform HTTP request: %s", err)
	}

	if err := facetteCheckConnectorResponse(response); err != nil {
		return nil, fmt.Errorf("invalid upstream HTTP response: %s", err)
	}

	data, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return nil, fmt.Errorf("unable to read HTTP response body: %s", err)
	}

	plotResponse := facettePlotResponse{}

	if err := json.Unmarshal(data, &plotResponse); err != nil {
		return nil, fmt.Errorf("unable to unmarshal upstream response: %s", err)
	}

	result := make(map[string]*types.PlotResult)

	for _, serie := range plotResponse.Series {
		result[serie.Name] = &types.PlotResult{
			Plots: serie.Plots,
			Info:  serie.Info,
		}
	}

	return result, nil
}

// Refresh triggers a full connector data update.
func (connector *FacetteConnector) Refresh(originName string, outputChan chan *catalog.CatalogRecord) error {
	httpTransport := &http.Transport{
		Dial: (&net.Dialer{
			// Enable dual IPv4/IPv6 stack connectivity:
			DualStack: true,
		}).Dial,
	}

	httpClient := http.Client{Transport: httpTransport}

	response, err := httpClient.Get(strings.TrimSuffix(connector.upstream, "/") + facetteURLCatalog)
	if err != nil {
		return fmt.Errorf("unable to perform HTTP request: %s", err)
	}

	if err = facetteCheckConnectorResponse(response); err != nil {
		return fmt.Errorf("invalid HTTP backend response: %s", err)
	}

	data, err := ioutil.ReadAll(response.Body)
	if err != nil {
		return fmt.Errorf("unable to read HTTP response body: %s", err)
	}

	upstreamCatalog := make(map[string]map[string][]string)
	if err = json.Unmarshal(data, &upstreamCatalog); err != nil {
		return fmt.Errorf("unable to unmarshal JSON data: %s", err)
	}

	// Parse the upstream catalog entries and append them to our local catalog
	for upstreamOriginName, upstreamOrigin := range upstreamCatalog {
		for sourceName, metrics := range upstreamOrigin {
			for _, metric := range metrics {
				outputChan <- &catalog.CatalogRecord{
					Origin:    upstreamOriginName,
					Source:    sourceName,
					Metric:    metric,
					Connector: connector,
				}
			}
		}
	}

	return nil
}

func facetteCheckConnectorResponse(response *http.Response) error {
	if response.StatusCode != 200 {
		return fmt.Errorf("got HTTP status code %d, expected 200", response.StatusCode)
	}

	if !strings.Contains(response.Header.Get("Content-Type"), "application/json") {
		return fmt.Errorf("got HTTP content type `%s', expected `application/json'", response.Header["Content-Type"])
	}

	return nil
}
<|endoftext|>


<commit_before>package server

import (
    "path/filepath"

    flag "github.com/ogier/pflag"

    "github.com/jaypipes/procession/pkg/cfg"
    "github.com/jaypipes/procession/pkg/env"
    "github.com/jaypipes/procession/pkg/util"
)

const (
    cfgPath = "/etc/procession/iam"
    defaultUseTLS = false
    defaultBindPort = 10000
)

var (
    defaultCertPath = filepath.Join(cfgPath, "server.pem")
    defaultKeyPath = filepath.Join(cfgPath, "server.key")
    defaultBindHost = util.BindHost()
)

type Config struct {
    UseTLS bool
    CertPath string
    KeyPath string
    BindHost string
    BindPort int
}

func configFromOpts() *Config {
    optUseTLS := flag.Bool(
        "use-tls",
        env.EnvOrDefaultBool(
            "PROCESSION_USE_TLS", defaultUseTLS,
        ),
        "Connection uses TLS if true, else plain TCP",
    )
    optCertPath := flag.String(
        "cert-path",
        env.EnvOrDefaultStr(
            "PROCESSION_CERT_PATH", defaultCertPath,
        ),
        "Path to the TLS cert file",
    )
    optKeyPath := flag.String(
        "key-path",
        env.EnvOrDefaultStr(
            "PROCESSION_KEY_PATH", defaultKeyPath,
        ),
        "Path to the TLS key file",
    )
    optHost := flag.String(
        "bind-address",
        env.EnvOrDefaultStr(
            "PROCESSION_BIND_HOST", defaultBindHost,
        ),
        "The host address the server will listen on",
    )
    optPort := flag.Int(
        "bind-port",
        env.EnvOrDefaultInt(
            "PROCESSION_BIND_PORT", defaultBindPort,
        ),
        "The port the server will listen on",
    )

    cfg.ParseCliOpts()

    return &Config{
        UseTLS: *optUseTLS,
        CertPath: *optCertPath,
        KeyPath: *optKeyPath,
        BindHost: *optHost,
        BindPort: *optPort,
    }
}
<commit_msg>Add DSN to IAM server configuration<commit_after>package server

import (
    "path/filepath"

    flag "github.com/ogier/pflag"

    "github.com/jaypipes/procession/pkg/cfg"
    "github.com/jaypipes/procession/pkg/env"
    "github.com/jaypipes/procession/pkg/util"
)

const (
    cfgPath = "/etc/procession/iam"
    defaultUseTLS = false
    defaultBindPort = 10000
    defaultDSN = "user:password@/dbname"
)

var (
    defaultCertPath = filepath.Join(cfgPath, "server.pem")
    defaultKeyPath = filepath.Join(cfgPath, "server.key")
    defaultBindHost = util.BindHost()
)

type Config struct {
    UseTLS bool
    CertPath string
    KeyPath string
    BindHost string
    BindPort int
    DSN string
}

func configFromOpts() *Config {
    optUseTLS := flag.Bool(
        "use-tls",
        env.EnvOrDefaultBool(
            "PROCESSION_USE_TLS", defaultUseTLS,
        ),
        "Connection uses TLS if true, else plain TCP",
    )
    optCertPath := flag.String(
        "cert-path",
        env.EnvOrDefaultStr(
            "PROCESSION_CERT_PATH", defaultCertPath,
        ),
        "Path to the TLS cert file",
    )
    optKeyPath := flag.String(
        "key-path",
        env.EnvOrDefaultStr(
            "PROCESSION_KEY_PATH", defaultKeyPath,
        ),
        "Path to the TLS key file",
    )
    optHost := flag.String(
        "bind-address",
        env.EnvOrDefaultStr(
            "PROCESSION_BIND_HOST", defaultBindHost,
        ),
        "The host address the server will listen on",
    )
    optPort := flag.Int(
        "bind-port",
        env.EnvOrDefaultInt(
            "PROCESSION_BIND_PORT", defaultBindPort,
        ),
        "The port the server will listen on",
    )
    optDSN := flag.String(
        "dsn",
        env.EnvOrDefaultStr(
            "PROCESSION_DSN", defaultDSN,
        ),
        "Data Source Name (DSN) for connecting to the IAM data store",
    )

    cfg.ParseCliOpts()

    return &Config{
        UseTLS: *optUseTLS,
        CertPath: *optCertPath,
        KeyPath: *optKeyPath,
        BindHost: *optHost,
        BindPort: *optPort,
        DSN: *optDSN,
    }
}
<|endoftext|>


<commit_before><commit_msg>Minor improvement catching up<commit_after><|endoftext|>


<commit_before>package acl

import (
	"fmt"
	"github.com/Aptomi/aptomi/pkg/lang"
	"github.com/Aptomi/aptomi/pkg/lang/expression"
	"sync"
)

type Resolver struct {
	rules     []*Rule
	cache     *expression.Cache
	roleCache sync.Map
}

func NewResolver(rules []*Rule) *Resolver {
	return &Resolver{
		rules:     rules,
		cache:     expression.NewCache(),
		roleCache: sync.Map{},
	}
}

func (resolver *Resolver) GetUserRole(user *lang.User) (*Role, error) {
	resultCached, ok := resolver.roleCache.Load(user.ID)
	if ok {
		return resultCached.(*Role), nil
	}

	result := lang.NewRuleActionResult(lang.NewLabelSet(make(map[string]string)))
	params := expression.NewParams(user.Labels, nil)
	for _, rule := range resolver.rules {
		matched, err := rule.Matches(params, resolver.cache)
		if err != nil {
			return nil, fmt.Errorf("unable to resolve role for user '%s': %s", user.ID, err)
		}
		if matched {
			rule.ApplyActions(result)
			if rule.Actions.Stop {
				break
			}
		}
	}

	roleID := result.Labels.Labels[LabelRole]
	role, ok := Roles[roleID]
	if !ok {
		return Nobody, nil
	}

	resolver.roleCache.Store(user.ID, role)
	return role, nil
}

func (resolver *Resolver) GetUserPrivileges(user *lang.User) (*Role, error) {
}
<commit_msg>Fix compilation error in acl PoC<commit_after>package acl

import (
	"fmt"
	"github.com/Aptomi/aptomi/pkg/lang"
	"github.com/Aptomi/aptomi/pkg/lang/expression"
	"sync"
)

type Resolver struct {
	rules     []*Rule
	cache     *expression.Cache
	roleCache sync.Map
}

func NewResolver(rules []*Rule) *Resolver {
	return &Resolver{
		rules:     rules,
		cache:     expression.NewCache(),
		roleCache: sync.Map{},
	}
}

func (resolver *Resolver) GetUserRole(user *lang.User) (*Role, error) {
	resultCached, ok := resolver.roleCache.Load(user.ID)
	if ok {
		return resultCached.(*Role), nil
	}

	result := lang.NewRuleActionResult(lang.NewLabelSet(make(map[string]string)))
	params := expression.NewParams(user.Labels, nil)
	for _, rule := range resolver.rules {
		matched, err := rule.Matches(params, resolver.cache)
		if err != nil {
			return nil, fmt.Errorf("unable to resolve role for user '%s': %s", user.ID, err)
		}
		if matched {
			rule.ApplyActions(result)
			if rule.Actions.Stop {
				break
			}
		}
	}

	roleID := result.Labels.Labels[LabelRole]
	role, ok := Roles[roleID]
	if !ok {
		return Nobody, nil
	}

	resolver.roleCache.Store(user.ID, role)
	return role, nil
}

func (resolver *Resolver) GetUserPrivileges(user *lang.User) (*Role, error) {
	// todo implement
	return nil, nil
}
<|endoftext|>


<commit_before>package middleware

import (
	"strings"

	"github.com/go-macaron/gzip"
	"github.com/grafana/grafana/pkg/infra/log"
	"gopkg.in/macaron.v1"
)

const resourcesPath = "/resources"

var gzipIgnoredPathPrefixes = []string{
	"/api/datasources/proxy", // Ignore datasource proxy requests.
	"/api/plugin-proxy/",
	"/metrics",
	"/live/ws", // WebSocket does not support gzip compression.
}

func Gziper() macaron.Handler {
	gziperLogger := log.New("gziper")
	gziper := gzip.Gziper()

	return func(ctx *macaron.Context) {
		requestPath := ctx.Req.URL.RequestURI()

		for _, pathPrefix := range gzipIgnoredPathPrefixes {
			if strings.HasPrefix(requestPath, pathPrefix) {
				return
			}
		}

		// ignore resources
		if (strings.HasPrefix(requestPath, "/api/datasources/") || strings.HasPrefix(requestPath, "/api/plugins/")) && strings.Contains(requestPath, resourcesPath) {
			return
		}

		if _, err := ctx.Invoke(gziper); err != nil {
			gziperLogger.Error("Invoking gzip handler failed", "err", err)
		}
	}
}
<commit_msg>live: disable gzip for ws endpoints (#34015)<commit_after>package middleware

import (
	"strings"

	"github.com/go-macaron/gzip"
	"github.com/grafana/grafana/pkg/infra/log"
	"gopkg.in/macaron.v1"
)

const resourcesPath = "/resources"

var gzipIgnoredPathPrefixes = []string{
	"/api/datasources/proxy", // Ignore datasource proxy requests.
	"/api/plugin-proxy/",
	"/metrics",
	"/api/live/ws",   // WebSocket does not support gzip compression.
	"/api/live/push", // WebSocket does not support gzip compression.
}

func Gziper() macaron.Handler {
	gziperLogger := log.New("gziper")
	gziper := gzip.Gziper()

	return func(ctx *macaron.Context) {
		requestPath := ctx.Req.URL.RequestURI()

		for _, pathPrefix := range gzipIgnoredPathPrefixes {
			if strings.HasPrefix(requestPath, pathPrefix) {
				return
			}
		}

		// ignore resources
		if (strings.HasPrefix(requestPath, "/api/datasources/") || strings.HasPrefix(requestPath, "/api/plugins/")) && strings.Contains(requestPath, resourcesPath) {
			return
		}

		if _, err := ctx.Invoke(gziper); err != nil {
			gziperLogger.Error("Invoking gzip handler failed", "err", err)
		}
	}
}
<|endoftext|>


<commit_before>package registry

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/labstack/echo"
)

const defaultLimit = 50

// A Version describes a specific release of an application.
type Version struct {
	Name      string          `json:"name"`
	Version   string          `json:"version"`
	URL       string          `json:"url"`
	Sha256    string          `json:"sha256"`
	CreatedAt time.Time       `json:"created_at"`
	Size      string          `json:"size"`
	Manifest  json.RawMessage `json:"manifest"`
	TarPrefix string          `json:"tar_prefix"`
}

var errVersionNotFound = errors.New("Version not found")

var proxyClient = &http.Client{
	Timeout: 10 * time.Second,
}

// GetLatestVersion returns the latest version available from the list of
// registries by resolving them in sequence using the specified application
// name and channel name.
func GetLatestVersion(appName, channel string, registries []*url.URL) (*Version, error) {
	requestURI := fmt.Sprintf("/registry/%s/%s/latest",
		url.PathEscape(appName),
		url.PathEscape(channel))
	rc, ok, err := fetchUntilFound(registries, requestURI)
	if err != nil {
		return nil, err
	}
	if !ok {
		return nil, errVersionNotFound
	}
	defer rc.Close()
	var v *Version
	if err = json.NewDecoder(rc).Decode(&v); err != nil {
		return nil, err
	}
	return v, nil
}

// Proxy will proxy the given request to the registries in sequence and return
// the response as io.ReadCloser when finding a registry returning a HTTP 200OK
// response.
func Proxy(req *http.Request, registries []*url.URL) (io.ReadCloser, error) {
	rc, ok, err := fetchUntilFound(registries, req.RequestURI)
	if err != nil {
		return nil, err
	}
	if !ok {
		return nil, echo.NewHTTPError(http.StatusNotFound)
	}
	return rc, nil
}

func printCursor(c []int) string {
	sum := 0
	for _, i := range c {
		sum += i
	}
	if sum == -len(c) {
		return ""
	}
	var a []string
	for _, i := range c {
		a = append(a, strconv.Itoa(i))
	}
	return strings.Join(a, "|")
}

type jsonObject map[string]interface{}

type appsList struct {
	ref        *url.URL
	list       []jsonObject
	registries []*registryFetchState
	names      map[string]struct {
		*registryFetchState
		int
	}
	limit int
}

type pageInfo struct {
	Count      int    `json:"count"`
	NextCursor string `json:"next_cursor,omitempty"`
}

type appsPaginated struct {
	List     []jsonObject `json:"list"`
	PageInfo pageInfo     `json:"page_info"`
}

type registryFetchState struct {
	url    *url.URL
	index  int
	cursor int
	ended  int
}

func newAppsList(ref *url.URL, registries []*url.URL, cursors []int, limit int) *appsList {
	if len(registries) != len(cursors) {
		panic("should have same length")
	}
	regStates := make([]*registryFetchState, len(registries))
	for i := range regStates {
		regStates[i] = &registryFetchState{
			index:  i,
			url:    registries[i],
			cursor: cursors[i],
			ended:  -1,
		}
	}
	return &appsList{
		ref:   ref,
		limit: limit,
		list:  make([]jsonObject, 0),
		names: make(map[string]struct {
			*registryFetchState
			int
		}),
		registries: regStates,
	}
}

func (a *appsList) FetchAll() error {
	for _, r := range a.registries {
		if err := a.fetch(r); err != nil {
			return err
		}
	}
	return nil
}

func (a *appsList) fetch(r *registryFetchState) error {
	limit := a.limit

	cursor := r.cursor
	// A negative dimension of the cursor means we already reached the end of the
	// list. There is no need to fetch anymore in that case.
	if cursor < 0 {
		return nil
	}

	for {
		ref := addQueries(removeQueries(a.ref, "cursor", "limit"),
			"cursor", strconv.Itoa(cursor),
			"limit", strconv.Itoa(limit),
		)
		rc, ok, err := fetch(r.url, ref)
		if err != nil {
			return err
		}
		if !ok {
			return nil
		}
		defer rc.Close()
		var resp appsPaginated
		if err = json.NewDecoder(rc).Decode(&resp); err != nil {
			return err
		}
		added := 0
		for i, obj := range resp.List {
			name := obj["name"].(string)
			if _, ok := a.names[name]; ok {
				continue
			}
			a.list = append(a.list, obj)
			a.names[name] = struct {
				*registryFetchState
				int
			}{r, cursor + i}
			added++
		}
		if added >= limit {
			break
		}
		nextCursor := resp.PageInfo.NextCursor
		if len(resp.List) < limit || nextCursor == "" {
			r.ended = cursor + len(resp.List) - 1
			break
		}
		cursor, _ = strconv.Atoi(nextCursor)
		limit -= added
	}

	return nil
}

func (a *appsList) Paginated(sortBy string, reverse bool, limit int) *appsPaginated {
	sort.Slice(a.list, func(i, j int) bool {
		vi := a.list[i]
		vj := a.list[j]
		var less bool
		switch valA := vi[sortBy].(type) {
		case string:
			valB := vj[sortBy].(string)
			less = valA < valB
		case int:
			valB := vj[sortBy].(int)
			less = valA < valB
		}
		if reverse {
			return !less
		}
		return less
	})

	if limit > len(a.list) {
		limit = len(a.list)
	}

	list := a.list[:limit]

	// calculation of the next cursor by iterating through the sorted and
	// truncated list and incrementing the dimension of the cursor associated
	// with the objects registry.
	//
	// In the end, we also check if the end value of each dimensions of the
	// cursor reached the end of the list. If so, the dimension is set to -1.
	cursors := make([]int, len(a.registries))
	for i, reg := range a.registries {
		cursors[i] = reg.cursor
	}
	for _, o := range list {
		name := o["name"].(string)
		reg := a.names[name]
		cursors[reg.index] = reg.int + 1
	}
	for i, reg := range a.registries {
		if e := reg.ended; e >= 0 && cursors[i] >= e {
			cursors[i] = -1
		}
	}

	return &appsPaginated{
		List: list,
		PageInfo: pageInfo{
			Count:      len(list),
			NextCursor: printCursor(cursors),
		},
	}
}

// ProxyList will proxy the given request to the registries by aggregating the
// results along the way. It should be used for list endpoints.
func ProxyList(req *http.Request, registries []*url.URL) (json.RawMessage, error) {
	ref, err := url.Parse(req.RequestURI)
	if err != nil {
		return nil, err
	}

	var sortBy string
	var sortReverse bool
	var limit int

	cursors := make([]int, len(registries))

	q := ref.Query()
	if v, ok := q["cursor"]; ok {
		splits := strings.Split(v[0], "|")
		for i, s := range splits {
			if i >= len(registries) {
				break
			}
			cursors[i], _ = strconv.Atoi(s)
		}
	}
	if v, ok := q["sort"]; ok {
		sortBy = v[0]
	}
	if len(sortBy) > 0 && sortBy[0] == '-' {
		sortReverse = true
		sortBy = sortBy[1:]
	}
	if sortBy == "" {
		sortBy = "name"
	}
	if v, ok := q["limit"]; ok {
		limit, _ = strconv.Atoi(v[0])
	}
	if limit <= 0 {
		limit = defaultLimit
	}

	list := newAppsList(ref, registries, cursors, limit)
	if err := list.FetchAll(); err != nil {
		return nil, err
	}
	return json.Marshal(list.Paginated(sortBy, sortReverse, limit))
}

func fetchUntilFound(registries []*url.URL, requestURI string) (rc io.ReadCloser, ok bool, err error) {
	ref, err := url.Parse(requestURI)
	if err != nil {
		return
	}
	for _, registry := range registries {
		rc, ok, err = fetch(registry, ref)
		if err != nil {
			return
		}
		if !ok {
			continue
		}
		return
	}
	return nil, false, nil
}

func fetch(registry, ref *url.URL) (rc io.ReadCloser, ok bool, err error) {
	u := registry.ResolveReference(ref)
	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
	if err != nil {
		return
	}
	resp, err := proxyClient.Do(req)
	if err != nil {
		return
	}
	defer func() {
		if err != nil {
			resp.Body.Close()
		}
	}()
	if resp.StatusCode == 404 {
		return
	}
	if resp.StatusCode != 200 {
		var msg struct {
			Message string `json:"message"`
		}
		if err = json.NewDecoder(resp.Body).Decode(&msg); err != nil {
			err = echo.NewHTTPError(resp.StatusCode)
		} else {
			err = echo.NewHTTPError(resp.StatusCode, msg.Message)
		}
		return
	}
	return resp.Body, true, nil
}

func removeQueries(u *url.URL, filter ...string) *url.URL {
	u, _ = url.Parse(u.String())
	q1 := u.Query()
	q2 := make(url.Values)
	for k, v := range q1 {
		if len(v) == 0 {
			continue
		}
		var remove bool
		for _, f := range filter {
			if f == k {
				remove = true
				break
			}
		}
		if !remove {
			q2.Add(k, v[0])
		}
	}
	u.RawQuery = q2.Encode()
	return u
}

func addQueries(u *url.URL, queries ...string) *url.URL {
	u, _ = url.Parse(u.String())
	q := u.Query()
	for i := 0; i < len(queries); i += 2 {
		q.Add(queries[i], queries[i+1])
	}
	u.RawQuery = q.Encode()
	return u
}
<commit_msg>Fix end offset calculation<commit_after>package registry

import (
	"encoding/json"
	"errors"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/labstack/echo"
)

const defaultLimit = 50

// A Version describes a specific release of an application.
type Version struct {
	Name      string          `json:"name"`
	Version   string          `json:"version"`
	URL       string          `json:"url"`
	Sha256    string          `json:"sha256"`
	CreatedAt time.Time       `json:"created_at"`
	Size      string          `json:"size"`
	Manifest  json.RawMessage `json:"manifest"`
	TarPrefix string          `json:"tar_prefix"`
}

var errVersionNotFound = errors.New("Version not found")

var proxyClient = &http.Client{
	Timeout: 10 * time.Second,
}

// GetLatestVersion returns the latest version available from the list of
// registries by resolving them in sequence using the specified application
// name and channel name.
func GetLatestVersion(appName, channel string, registries []*url.URL) (*Version, error) {
	requestURI := fmt.Sprintf("/registry/%s/%s/latest",
		url.PathEscape(appName),
		url.PathEscape(channel))
	rc, ok, err := fetchUntilFound(registries, requestURI)
	if err != nil {
		return nil, err
	}
	if !ok {
		return nil, errVersionNotFound
	}
	defer rc.Close()
	var v *Version
	if err = json.NewDecoder(rc).Decode(&v); err != nil {
		return nil, err
	}
	return v, nil
}

// Proxy will proxy the given request to the registries in sequence and return
// the response as io.ReadCloser when finding a registry returning a HTTP 200OK
// response.
func Proxy(req *http.Request, registries []*url.URL) (io.ReadCloser, error) {
	rc, ok, err := fetchUntilFound(registries, req.RequestURI)
	if err != nil {
		return nil, err
	}
	if !ok {
		return nil, echo.NewHTTPError(http.StatusNotFound)
	}
	return rc, nil
}

type jsonObject map[string]interface{}

type appsList struct {
	ref        *url.URL
	list       []jsonObject
	registries []*registryFetchState
	names      map[string]struct {
		*registryFetchState
		int
	}
	limit int
}

type pageInfo struct {
	Count      int    `json:"count"`
	NextCursor string `json:"next_cursor,omitempty"`
}

type appsPaginated struct {
	List     []jsonObject `json:"list"`
	PageInfo pageInfo     `json:"page_info"`
}

type registryFetchState struct {
	url    *url.URL
	index  int
	cursor int
	ended  int
}

func newAppsList(ref *url.URL, registries []*url.URL, cursors []int, limit int) *appsList {
	if len(registries) != len(cursors) {
		panic("should have same length")
	}
	regStates := make([]*registryFetchState, len(registries))
	for i := range regStates {
		regStates[i] = &registryFetchState{
			index:  i,
			url:    registries[i],
			cursor: cursors[i],
			ended:  -1,
		}
	}
	return &appsList{
		ref:   ref,
		limit: limit,
		list:  make([]jsonObject, 0),
		names: make(map[string]struct {
			*registryFetchState
			int
		}),
		registries: regStates,
	}
}

func (a *appsList) FetchAll() error {
	for _, r := range a.registries {
		if err := a.fetch(r); err != nil {
			return err
		}
	}
	return nil
}

func (a *appsList) fetch(r *registryFetchState) error {
	limit := a.limit

	cursor := r.cursor
	// A negative dimension of the cursor means we already reached the end of the
	// list. There is no need to fetch anymore in that case.
	if cursor < 0 {
		return nil
	}

	for {
		ref := addQueries(removeQueries(a.ref, "cursor", "limit"),
			"cursor", strconv.Itoa(cursor),
			"limit", strconv.Itoa(limit),
		)
		rc, ok, err := fetch(r.url, ref)
		if err != nil {
			return err
		}
		if !ok {
			return nil
		}
		defer rc.Close()
		var resp appsPaginated
		if err = json.NewDecoder(rc).Decode(&resp); err != nil {
			return err
		}
		added := 0
		for i, obj := range resp.List {
			name := obj["name"].(string)
			if _, ok := a.names[name]; ok {
				continue
			}
			a.list = append(a.list, obj)
			a.names[name] = struct {
				*registryFetchState
				int
			}{r, cursor + i}
			added++
		}
		if added >= limit {
			break
		}
		nextCursor := resp.PageInfo.NextCursor
		if len(resp.List) < limit || nextCursor == "" {
			r.ended = cursor + len(resp.List)
			break
		}
		cursor, _ = strconv.Atoi(nextCursor)
		limit -= added
	}

	return nil
}

func (a *appsList) Paginated(sortBy string, reverse bool, limit int) *appsPaginated {
	sort.Slice(a.list, func(i, j int) bool {
		vi := a.list[i]
		vj := a.list[j]
		var less bool
		switch valA := vi[sortBy].(type) {
		case string:
			valB := vj[sortBy].(string)
			less = valA < valB
		case int:
			valB := vj[sortBy].(int)
			less = valA < valB
		}
		if reverse {
			return !less
		}
		return less
	})

	if limit > len(a.list) {
		limit = len(a.list)
	}

	list := a.list[:limit]

	// Calculation of the next multi-cursor by iterating through the sorted and
	// truncated list and incrementing the dimension of the multi-cursor
	// associated with the objects registry.
	//
	// In the end, we also check if the end value of each dimensions of the
	// cursor reached the end of the list. If so, the dimension is set to -1.
	cursors := make([]int, len(a.registries))
	for i, reg := range a.registries {
		cursors[i] = reg.cursor
	}
	for _, o := range list {
		name := o["name"].(string)
		reg := a.names[name]
		cursors[reg.index] = reg.int + 1
	}
	for i, reg := range a.registries {
		if e := reg.ended; e >= 0 && cursors[i] >= e {
			cursors[i] = -1
		}
	}

	return &appsPaginated{
		List: list,
		PageInfo: pageInfo{
			Count:      len(list),
			NextCursor: printMutliCursor(cursors),
		},
	}
}

// ProxyList will proxy the given request to the registries by aggregating the
// results along the way. It should be used for list endpoints.
func ProxyList(req *http.Request, registries []*url.URL) (json.RawMessage, error) {
	ref, err := url.Parse(req.RequestURI)
	if err != nil {
		return nil, err
	}

	var sortBy string
	var sortReverse bool
	var limit int

	cursors := make([]int, len(registries))

	q := ref.Query()
	if v, ok := q["cursor"]; ok {
		splits := strings.Split(v[0], "|")
		for i, s := range splits {
			if i >= len(registries) {
				break
			}
			cursors[i], _ = strconv.Atoi(s)
		}
	}
	if v, ok := q["sort"]; ok {
		sortBy = v[0]
	}
	if len(sortBy) > 0 && sortBy[0] == '-' {
		sortReverse = true
		sortBy = sortBy[1:]
	}
	if sortBy == "" {
		sortBy = "name"
	}
	if v, ok := q["limit"]; ok {
		limit, _ = strconv.Atoi(v[0])
	}
	if limit <= 0 {
		limit = defaultLimit
	}

	list := newAppsList(ref, registries, cursors, limit)
	if err := list.FetchAll(); err != nil {
		return nil, err
	}
	return json.Marshal(list.Paginated(sortBy, sortReverse, limit))
}

func fetchUntilFound(registries []*url.URL, requestURI string) (rc io.ReadCloser, ok bool, err error) {
	ref, err := url.Parse(requestURI)
	if err != nil {
		return
	}
	for _, registry := range registries {
		rc, ok, err = fetch(registry, ref)
		if err != nil {
			return
		}
		if !ok {
			continue
		}
		return
	}
	return nil, false, nil
}

func fetch(registry, ref *url.URL) (rc io.ReadCloser, ok bool, err error) {
	u := registry.ResolveReference(ref)
	req, err := http.NewRequest(http.MethodGet, u.String(), nil)
	if err != nil {
		return
	}
	resp, err := proxyClient.Do(req)
	if err != nil {
		return
	}
	defer func() {
		if err != nil {
			resp.Body.Close()
		}
	}()
	if resp.StatusCode == 404 {
		return
	}
	if resp.StatusCode != 200 {
		var msg struct {
			Message string `json:"message"`
		}
		if err = json.NewDecoder(resp.Body).Decode(&msg); err != nil {
			err = echo.NewHTTPError(resp.StatusCode)
		} else {
			err = echo.NewHTTPError(resp.StatusCode, msg.Message)
		}
		return
	}
	return resp.Body, true, nil
}

func printMutliCursor(c []int) string {
	// if all dimensions of the multi-cursor are -1, we print the empty string
	sum := 0
	for _, i := range c {
		sum += i
	}
	if sum == -len(c) {
		return ""
	}
	var a []string
	for _, i := range c {
		a = append(a, strconv.Itoa(i))
	}
	return strings.Join(a, "|")
}

func removeQueries(u *url.URL, filter ...string) *url.URL {
	u, _ = url.Parse(u.String())
	q1 := u.Query()
	q2 := make(url.Values)
	for k, v := range q1 {
		if len(v) == 0 {
			continue
		}
		var remove bool
		for _, f := range filter {
			if f == k {
				remove = true
				break
			}
		}
		if !remove {
			q2.Add(k, v[0])
		}
	}
	u.RawQuery = q2.Encode()
	return u
}

func addQueries(u *url.URL, queries ...string) *url.URL {
	u, _ = url.Parse(u.String())
	q := u.Query()
	for i := 0; i < len(queries); i += 2 {
		q.Add(queries[i], queries[i+1])
	}
	u.RawQuery = q.Encode()
	return u
}
<|endoftext|>


<commit_before>// +build windows
package term

import (
	"io"
	"os"

	"github.com/docker/docker/pkg/term/winconsole"
)

// State holds the console mode for the terminal.
type State struct {
	mode uint32
}

// Winsize is used for window size.
type Winsize struct {
	Height uint16
	Width  uint16
	x      uint16
	y      uint16
}

func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
	switch {
	case os.Getenv("ConEmuANSI") == "ON":
		// The ConEmu shell emulates ANSI well by default.
		return os.Stdin, os.Stdout, os.Stderr
	case os.Getenv("MSYSTEM") != "":
		// MSYS (mingw) does not emulate ANSI well.
		return winconsole.WinConsoleStreams()
	default:
		return winconsole.WinConsoleStreams()
	}
}

// GetFdInfo returns file descriptor and bool indicating whether the file is a terminal.
func GetFdInfo(in interface{}) (uintptr, bool) {
	return winconsole.GetHandleInfo(in)
}

// GetWinsize retrieves the window size of the terminal connected to the passed file descriptor.
func GetWinsize(fd uintptr) (*Winsize, error) {
	info, err := winconsole.GetConsoleScreenBufferInfo(fd)
	if err != nil {
		return nil, err
	}

	// TODO(azlinux): Set the pixel width / height of the console (currently unused by any caller)
	return &Winsize{
		Width:  uint16(info.Window.Bottom - info.Window.Top + 1),
		Height: uint16(info.Window.Right - info.Window.Left + 1),
		x:      0,
		y:      0}, nil
}

// SetWinsize sets the size of the given terminal connected to the passed file descriptor.
func SetWinsize(fd uintptr, ws *Winsize) error {
	// TODO(azlinux): Implement SetWinsize
	return nil
}

// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd uintptr) bool {
	return winconsole.IsConsole(fd)
}

// RestoreTerminal restores the terminal connected to the given file descriptor to a
// previous state.
func RestoreTerminal(fd uintptr, state *State) error {
	return winconsole.SetConsoleMode(fd, state.mode)
}

// SaveState saves the state of the terminal connected to the given file descriptor.
func SaveState(fd uintptr) (*State, error) {
	mode, e := winconsole.GetConsoleMode(fd)
	if e != nil {
		return nil, e
	}
	return &State{mode}, nil
}

// DisableEcho disables echo for the terminal connected to the given file descriptor.
// -- See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
func DisableEcho(fd uintptr, state *State) error {
	mode := state.mode
	mode &^= winconsole.ENABLE_ECHO_INPUT
	mode |= winconsole.ENABLE_PROCESSED_INPUT | winconsole.ENABLE_LINE_INPUT
	// TODO(azlinux): Core code registers a goroutine to catch os.Interrupt and reset the terminal state.
	return winconsole.SetConsoleMode(fd, mode)
}

// SetRawTerminal puts the terminal connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be
// restored.
func SetRawTerminal(fd uintptr) (*State, error) {
	state, err := MakeRaw(fd)
	if err != nil {
		return nil, err
	}
	// TODO(azlinux): Core code registers a goroutine to catch os.Interrupt and reset the terminal state.
	return state, err
}

// MakeRaw puts the terminal connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be
// restored.
func MakeRaw(fd uintptr) (*State, error) {
	state, err := SaveState(fd)
	if err != nil {
		return nil, err
	}

	// See
	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
	mode := state.mode

	// Disable these modes
	mode &^= winconsole.ENABLE_ECHO_INPUT
	mode &^= winconsole.ENABLE_LINE_INPUT
	mode &^= winconsole.ENABLE_MOUSE_INPUT
	// TODO(azlinux): Enable window input to handle window resizing
	mode |= winconsole.ENABLE_WINDOW_INPUT

	// Enable these modes
	mode |= winconsole.ENABLE_PROCESSED_INPUT
	mode |= winconsole.ENABLE_EXTENDED_FLAGS
	mode |= winconsole.ENABLE_INSERT_MODE
	mode |= winconsole.ENABLE_QUICK_EDIT_MODE

	err = winconsole.SetConsoleMode(fd, mode)
	if err != nil {
		return nil, err
	}
	return state, nil
}
<commit_msg>Swap width/height in GetWinsize and monitorTtySize<commit_after>// +build windows
package term

import (
	"io"
	"os"

	"github.com/docker/docker/pkg/term/winconsole"
)

// State holds the console mode for the terminal.
type State struct {
	mode uint32
}

// Winsize is used for window size.
type Winsize struct {
	Height uint16
	Width  uint16
	x      uint16
	y      uint16
}

func StdStreams() (stdIn io.ReadCloser, stdOut, stdErr io.Writer) {
	switch {
	case os.Getenv("ConEmuANSI") == "ON":
		// The ConEmu shell emulates ANSI well by default.
		return os.Stdin, os.Stdout, os.Stderr
	case os.Getenv("MSYSTEM") != "":
		// MSYS (mingw) does not emulate ANSI well.
		return winconsole.WinConsoleStreams()
	default:
		return winconsole.WinConsoleStreams()
	}
}

// GetFdInfo returns file descriptor and bool indicating whether the file is a terminal.
func GetFdInfo(in interface{}) (uintptr, bool) {
	return winconsole.GetHandleInfo(in)
}

// GetWinsize retrieves the window size of the terminal connected to the passed file descriptor.
func GetWinsize(fd uintptr) (*Winsize, error) {
	info, err := winconsole.GetConsoleScreenBufferInfo(fd)
	if err != nil {
		return nil, err
	}

	// TODO(azlinux): Set the pixel width / height of the console (currently unused by any caller)
	return &Winsize{
		Width:  uint16(info.Window.Right - info.Window.Left + 1),
		Height: uint16(info.Window.Bottom - info.Window.Top + 1),
		x:      0,
		y:      0}, nil
}

// SetWinsize sets the size of the given terminal connected to the passed file descriptor.
func SetWinsize(fd uintptr, ws *Winsize) error {
	// TODO(azlinux): Implement SetWinsize
	return nil
}

// IsTerminal returns true if the given file descriptor is a terminal.
func IsTerminal(fd uintptr) bool {
	return winconsole.IsConsole(fd)
}

// RestoreTerminal restores the terminal connected to the given file descriptor to a
// previous state.
func RestoreTerminal(fd uintptr, state *State) error {
	return winconsole.SetConsoleMode(fd, state.mode)
}

// SaveState saves the state of the terminal connected to the given file descriptor.
func SaveState(fd uintptr) (*State, error) {
	mode, e := winconsole.GetConsoleMode(fd)
	if e != nil {
		return nil, e
	}
	return &State{mode}, nil
}

// DisableEcho disables echo for the terminal connected to the given file descriptor.
// -- See http://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
func DisableEcho(fd uintptr, state *State) error {
	mode := state.mode
	mode &^= winconsole.ENABLE_ECHO_INPUT
	mode |= winconsole.ENABLE_PROCESSED_INPUT | winconsole.ENABLE_LINE_INPUT
	// TODO(azlinux): Core code registers a goroutine to catch os.Interrupt and reset the terminal state.
	return winconsole.SetConsoleMode(fd, mode)
}

// SetRawTerminal puts the terminal connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be
// restored.
func SetRawTerminal(fd uintptr) (*State, error) {
	state, err := MakeRaw(fd)
	if err != nil {
		return nil, err
	}
	// TODO(azlinux): Core code registers a goroutine to catch os.Interrupt and reset the terminal state.
	return state, err
}

// MakeRaw puts the terminal connected to the given file descriptor into raw
// mode and returns the previous state of the terminal so that it can be
// restored.
func MakeRaw(fd uintptr) (*State, error) {
	state, err := SaveState(fd)
	if err != nil {
		return nil, err
	}

	// See
	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms686033(v=vs.85).aspx
	// -- https://msdn.microsoft.com/en-us/library/windows/desktop/ms683462(v=vs.85).aspx
	mode := state.mode

	// Disable these modes
	mode &^= winconsole.ENABLE_ECHO_INPUT
	mode &^= winconsole.ENABLE_LINE_INPUT
	mode &^= winconsole.ENABLE_MOUSE_INPUT
	// TODO(azlinux): Enable window input to handle window resizing
	mode |= winconsole.ENABLE_WINDOW_INPUT

	// Enable these modes
	mode |= winconsole.ENABLE_PROCESSED_INPUT
	mode |= winconsole.ENABLE_EXTENDED_FLAGS
	mode |= winconsole.ENABLE_INSERT_MODE
	mode |= winconsole.ENABLE_QUICK_EDIT_MODE

	err = winconsole.SetConsoleMode(fd, mode)
	if err != nil {
		return nil, err
	}
	return state, nil
}
<|endoftext|>


<commit_before>package Golf

import (
	"net/http"
	"os"
	"path"
	"strings"
)

// Application is an abstraction of a Golf application, can be used for
// configuration, etc.
type Application struct {
	router *router

	// A map of string slices as value to indicate the static files.
	staticRouter map[string][]string

	// The View model of the application. View handles the templating and page
	// rendering.
	View *View

	// Config provides configuration management.
	Config *Config

	// NotFoundHandler handles requests when no route is matched.
	NotFoundHandler Handler

	// MiddlewareChain is the default middlewares that Golf uses.
	MiddlewareChain *Chain

	errorHandler map[int]Handler

	// The default error handler, if the corresponding error code is not specified
	// in the `errorHandler` map, this handler will be called.
	DefaultErrorHandler Handler
}

// New is used for creating a new Golf Application instance.
func New() *Application {
	app := new(Application)
	app.router = newRouter()
	app.staticRouter = make(map[string][]string)
	app.View = NewView()
	app.Config = NewConfig(app)
	// debug, _ := app.Config.GetBool("debug", false)
	app.errorHandler = make(map[int]Handler)
	app.MiddlewareChain = NewChain(defaultMiddlewares...)
	app.DefaultErrorHandler = defaultErrorHandler
	return app
}

// First search if any of the static route matches the request.
// If not, look up the URL in the router.
func (app *Application) handler(ctx *Context) {
	for prefix, staticPathSlice := range app.staticRouter {
		if strings.HasPrefix(ctx.Request.URL.Path, prefix) {
			for _, staticPath := range staticPathSlice {
				filePath := path.Join(staticPath, ctx.Request.URL.Path[len(prefix):])
				fileInfo, err := os.Stat(filePath)
				if err == nil && !fileInfo.IsDir() {
					staticHandler(ctx, filePath)
					return
				}
			}
		}
	}

	params, handler := app.router.match(ctx.Request.URL.Path, ctx.Request.Method)
	if handler != nil {
		ctx.Params = params
		handler(ctx)
	} else {
		app.handleError(ctx, 404)
	}
	ctx.Send()
}

// Serve a static file
func staticHandler(ctx *Context, filePath string) {
	http.ServeFile(ctx.Response, ctx.Request, filePath)
}

// Basic entrance of an `http.ResponseWriter` and an `http.Request`.
func (app *Application) ServeHTTP(res http.ResponseWriter, req *http.Request) {
	ctx := NewContext(req, res, app)
	app.MiddlewareChain.Final(app.handler)(ctx)
}

// Run the Golf Application.
func (app *Application) Run(addr string) {
	err := http.ListenAndServe(addr, app)
	if err != nil {
		panic(err)
	}
}

// Run app with TLS.
func (app *Application) RunTLS(addr, certFile, keyFile string) {
	err := http.ListenAndServeTLS(addr, certFile, keyFile, app)
	if err != nil {
		panic(err)
	}
}

// Static is used for registering a static folder
func (app *Application) Static(url string, path string) {
	url = strings.TrimRight(url, "/")
	app.staticRouter[url] = append(app.staticRouter[url], path)
}

// Get method is used for registering a Get method route
func (app *Application) Get(pattern string, handler Handler) {
	app.router.get(pattern, handler)
}

// Post method is used for registering a Post method route
func (app *Application) Post(pattern string, handler Handler) {
	app.router.post(pattern, handler)
}

// Put method is used for registering a Put method route
func (app *Application) Put(pattern string, handler Handler) {
	app.router.put(pattern, handler)
}

// Delete method is used for registering a Delete method route
func (app *Application) Delete(pattern string, handler Handler) {
	app.router.delete(pattern, handler)
}

// Error method is used for registering an handler for a specified HTTP error code.
func (app *Application) Error(statusCode int, handler Handler) {
	app.errorHandler[statusCode] = handler
}

// Handles a HTTP Error, if there is a corresponding handler set in the map
// `errorHandler`, then call it. Otherwise call the `defaultErrorHandler`.
func (app *Application) handleError(ctx *Context, statusCode int) {
	ctx.StatusCode = statusCode
	handler, ok := app.errorHandler[ctx.StatusCode]
	if !ok {
		defaultErrorHandler(ctx)
		return
	}
	handler(ctx)
}
<commit_msg>[docs] Improved comment for RunTLS<commit_after>package Golf

import (
	"net/http"
	"os"
	"path"
	"strings"
)

// Application is an abstraction of a Golf application, can be used for
// configuration, etc.
type Application struct {
	router *router

	// A map of string slices as value to indicate the static files.
	staticRouter map[string][]string

	// The View model of the application. View handles the templating and page
	// rendering.
	View *View

	// Config provides configuration management.
	Config *Config

	// NotFoundHandler handles requests when no route is matched.
	NotFoundHandler Handler

	// MiddlewareChain is the default middlewares that Golf uses.
	MiddlewareChain *Chain

	errorHandler map[int]Handler

	// The default error handler, if the corresponding error code is not specified
	// in the `errorHandler` map, this handler will be called.
	DefaultErrorHandler Handler
}

// New is used for creating a new Golf Application instance.
func New() *Application {
	app := new(Application)
	app.router = newRouter()
	app.staticRouter = make(map[string][]string)
	app.View = NewView()
	app.Config = NewConfig(app)
	// debug, _ := app.Config.GetBool("debug", false)
	app.errorHandler = make(map[int]Handler)
	app.MiddlewareChain = NewChain(defaultMiddlewares...)
	app.DefaultErrorHandler = defaultErrorHandler
	return app
}

// First search if any of the static route matches the request.
// If not, look up the URL in the router.
func (app *Application) handler(ctx *Context) {
	for prefix, staticPathSlice := range app.staticRouter {
		if strings.HasPrefix(ctx.Request.URL.Path, prefix) {
			for _, staticPath := range staticPathSlice {
				filePath := path.Join(staticPath, ctx.Request.URL.Path[len(prefix):])
				fileInfo, err := os.Stat(filePath)
				if err == nil && !fileInfo.IsDir() {
					staticHandler(ctx, filePath)
					return
				}
			}
		}
	}

	params, handler := app.router.match(ctx.Request.URL.Path, ctx.Request.Method)
	if handler != nil {
		ctx.Params = params
		handler(ctx)
	} else {
		app.handleError(ctx, 404)
	}
	ctx.Send()
}

// Serve a static file
func staticHandler(ctx *Context, filePath string) {
	http.ServeFile(ctx.Response, ctx.Request, filePath)
}

// Basic entrance of an `http.ResponseWriter` and an `http.Request`.
func (app *Application) ServeHTTP(res http.ResponseWriter, req *http.Request) {
	ctx := NewContext(req, res, app)
	app.MiddlewareChain.Final(app.handler)(ctx)
}

// Run the Golf Application.
func (app *Application) Run(addr string) {
	err := http.ListenAndServe(addr, app)
	if err != nil {
		panic(err)
	}
}

// RunTLS runs the app with TLS support.
func (app *Application) RunTLS(addr, certFile, keyFile string) {
	err := http.ListenAndServeTLS(addr, certFile, keyFile, app)
	if err != nil {
		panic(err)
	}
}

// Static is used for registering a static folder
func (app *Application) Static(url string, path string) {
	url = strings.TrimRight(url, "/")
	app.staticRouter[url] = append(app.staticRouter[url], path)
}

// Get method is used for registering a Get method route
func (app *Application) Get(pattern string, handler Handler) {
	app.router.get(pattern, handler)
}

// Post method is used for registering a Post method route
func (app *Application) Post(pattern string, handler Handler) {
	app.router.post(pattern, handler)
}

// Put method is used for registering a Put method route
func (app *Application) Put(pattern string, handler Handler) {
	app.router.put(pattern, handler)
}

// Delete method is used for registering a Delete method route
func (app *Application) Delete(pattern string, handler Handler) {
	app.router.delete(pattern, handler)
}

// Error method is used for registering an handler for a specified HTTP error code.
func (app *Application) Error(statusCode int, handler Handler) {
	app.errorHandler[statusCode] = handler
}

// Handles a HTTP Error, if there is a corresponding handler set in the map
// `errorHandler`, then call it. Otherwise call the `defaultErrorHandler`.
func (app *Application) handleError(ctx *Context, statusCode int) {
	ctx.StatusCode = statusCode
	handler, ok := app.errorHandler[ctx.StatusCode]
	if !ok {
		defaultErrorHandler(ctx)
		return
	}
	handler(ctx)
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"github.com/gdamore/tcell"
)

type cmdItem struct {
	prefix string
	value  string
}

type app struct {
	ui            *ui
	nav           *nav
	ticker        *time.Ticker
	quitChan      chan bool
	cmd           *exec.Cmd
	cmdIn         io.WriteCloser
	cmdOutBuf     []byte
	cmdHistory    []cmdItem
	cmdHistoryBeg int
	cmdHistoryInd int
}

func newApp(screen tcell.Screen) *app {
	ui := newUI(screen)
	nav := newNav(ui.wins[0].h)

	quitChan := make(chan bool, 1)

	osChan := make(chan os.Signal, 1)
	signal.Notify(osChan, os.Interrupt, syscall.SIGHUP, syscall.SIGTERM)
	go func() {
		<-osChan
		nav.copyTotalChan <- -nav.copyTotal
		nav.moveTotalChan <- -nav.moveTotal
		nav.deleteTotalChan <- -nav.deleteTotal
		quitChan <- true
		return
	}()

	return &app{
		ui:       ui,
		nav:      nav,
		ticker:   new(time.Ticker),
		quitChan: quitChan,
	}
}

func (app *app) readFile(path string) {
	log.Printf("reading file: %s", path)

	f, err := os.Open(path)
	if err != nil {
		app.ui.echoerrf("opening file: %s", err)
		return
	}
	defer f.Close()

	p := newParser(f)

	for p.parse() {
		p.expr.eval(app, nil)
	}

	if p.err != nil {
		app.ui.echoerrf("%s", p.err)
	}
}

func (app *app) readHistory() error {
	f, err := os.Open(gHistoryPath)
	if os.IsNotExist(err) {
		return nil
	}
	if err != nil {
		return fmt.Errorf("opening history file: %s", err)
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		toks := strings.SplitN(scanner.Text(), " ", 2)
		if toks[0] != ":" && toks[0] != "$" && toks[0] != "%" && toks[0] != "!" && toks[0] != "&" {
			continue
		}
		if len(toks) < 2 {
			continue
		}
		app.cmdHistory = append(app.cmdHistory, cmdItem{toks[0], toks[1]})
	}

	app.cmdHistoryBeg = len(app.cmdHistory)

	if err := scanner.Err(); err != nil {
		return fmt.Errorf("reading history file: %s", err)
	}

	return nil
}

func (app *app) writeHistory() error {
	if len(app.cmdHistory) == 0 {
		return nil
	}

	local := make([]cmdItem, len(app.cmdHistory)-app.cmdHistoryBeg)
	copy(local, app.cmdHistory[app.cmdHistoryBeg:])
	app.cmdHistory = nil

	if err := app.readHistory(); err != nil {
		return fmt.Errorf("reading history file: %s", err)
	}

	app.cmdHistory = append(app.cmdHistory, local...)

	if err := os.MkdirAll(filepath.Dir(gHistoryPath), os.ModePerm); err != nil {
		return fmt.Errorf("creating data directory: %s", err)
	}

	f, err := os.Create(gHistoryPath)
	if err != nil {
		return fmt.Errorf("creating history file: %s", err)
	}
	defer f.Close()

	if len(app.cmdHistory) > 1000 {
		app.cmdHistory = app.cmdHistory[len(app.cmdHistory)-1000:]
	}

	for _, cmd := range app.cmdHistory {
		_, err = f.WriteString(fmt.Sprintf("%s %s\n", cmd.prefix, cmd.value))
		if err != nil {
			return fmt.Errorf("writing history file: %s", err)
		}
	}

	return nil
}

// This is the main event loop of the application. Expressions are read from
// the client and the server on separate goroutines and sent here over channels
// for evaluation. Similarly directories and regular files are also read in
// separate goroutines and sent here for update.
func (app *app) loop() {
	serverChan := readExpr()

	app.ui.readExpr()

	if gSelect != "" {
		go func() {
			lstat, err := os.Lstat(gSelect)
			if err != nil {
				app.ui.exprChan <- &callExpr{"echoerr", []string{err.Error()}, 1}
			} else if lstat.IsDir() {
				app.ui.exprChan <- &callExpr{"cd", []string{gSelect}, 1}
			} else {
				app.ui.exprChan <- &callExpr{"select", []string{gSelect}, 1}
			}
		}()
	}

	for _, path := range gConfigPaths {
		if _, err := os.Stat(path); !os.IsNotExist(err) {
			app.readFile(path)
		}
	}

	if gCommand != "" {
		p := newParser(strings.NewReader(gCommand))

		for p.parse() {
			p.expr.eval(app, nil)
		}

		if p.err != nil {
			app.ui.echoerrf("%s", p.err)
		}
	}

	for {
		select {
		case <-app.quitChan:
			if app.nav.copyTotal > 0 {
				app.ui.echoerr("quit: copy operation in progress")
				continue
			}

			if app.nav.moveTotal > 0 {
				app.ui.echoerr("quit: move operation in progress")
				continue
			}

			if app.nav.deleteTotal > 0 {
				app.ui.echoerr("quit: delete operation in progress")
				continue
			}

			log.Print("bye!")

			if err := app.writeHistory(); err != nil {
				log.Printf("writing history file: %s", err)
			}

			if gLastDirPath != "" {
				f, err := os.Create(gLastDirPath)
				if err != nil {
					log.Printf("opening last dir file: %s", err)
				}
				defer f.Close()

				_, err = f.WriteString(app.nav.currDir().path)
				if err != nil {
					log.Printf("writing last dir file: %s", err)
				}
			}

			return
		case n := <-app.nav.copyBytesChan:
			app.nav.copyBytes += n
			// n is usually 4096B so update roughly per 4096B x 1024 = 4MB copied
			if app.nav.copyUpdate++; app.nav.copyUpdate >= 1024 {
				app.nav.copyUpdate = 0
				app.ui.draw(app.nav)
			}
		case n := <-app.nav.copyTotalChan:
			app.nav.copyTotal += n
			if n < 0 {
				app.nav.copyBytes += n
			}
			if app.nav.copyTotal == 0 {
				app.nav.copyUpdate = 0
			}
			app.ui.draw(app.nav)
		case n := <-app.nav.moveCountChan:
			app.nav.moveCount += n
			if app.nav.moveUpdate++; app.nav.moveUpdate >= 1000 {
				app.nav.moveUpdate = 0
				app.ui.draw(app.nav)
			}
		case n := <-app.nav.moveTotalChan:
			app.nav.moveTotal += n
			if n < 0 {
				app.nav.moveCount += n
			}
			if app.nav.moveTotal == 0 {
				app.nav.moveUpdate = 0
			}
			app.ui.draw(app.nav)
		case n := <-app.nav.deleteCountChan:
			app.nav.deleteCount += n
			if app.nav.deleteUpdate++; app.nav.deleteUpdate >= 1000 {
				app.nav.deleteUpdate = 0
				app.ui.draw(app.nav)
			}
		case n := <-app.nav.deleteTotalChan:
			app.nav.deleteTotal += n
			if n < 0 {
				app.nav.deleteCount += n
			}
			if app.nav.deleteTotal == 0 {
				app.nav.deleteUpdate = 0
			}
			app.ui.draw(app.nav)
		case d := <-app.nav.dirChan:
			app.nav.checkDir(d)

			prev, ok := app.nav.dirCache[d.path]
			if ok {
				d.ind = prev.ind
				d.sel(prev.name(), app.nav.height)
			}

			app.nav.dirCache[d.path] = d

			for i := range app.nav.dirs {
				if app.nav.dirs[i].path == d.path {
					app.nav.dirs[i] = d
				}
			}

			app.nav.position()

			curr, err := app.nav.currFile()
			if err == nil {
				if d.path == app.nav.currDir().path {
					app.ui.loadFile(app.nav)
				}
				if d.path == curr.path {
					app.ui.dirPrev = d
				}
			}

			app.ui.draw(app.nav)
		case r := <-app.nav.regChan:
			app.nav.checkReg(r)

			app.nav.regCache[r.path] = r

			curr, err := app.nav.currFile()
			if err == nil {
				if r.path == curr.path {
					app.ui.regPrev = r
				}
			}

			app.ui.draw(app.nav)
		case ev := <-app.ui.evChan:
			e := app.ui.readEvent(ev)
			if e == nil {
				continue
			}
			e.eval(app, nil)
		loop:
			for {
				select {
				case ev := <-app.ui.evChan:
					e = app.ui.readEvent(ev)
					if e == nil {
						continue
					}
					e.eval(app, nil)
				default:
					break loop
				}
			}
			app.ui.draw(app.nav)
		case e := <-app.ui.exprChan:
			e.eval(app, nil)
			app.ui.draw(app.nav)
		case e := <-serverChan:
			e.eval(app, nil)
			app.ui.draw(app.nav)
		case <-app.ticker.C:
			app.nav.renew()
			app.ui.loadFile(app.nav)
			app.ui.draw(app.nav)
		}
	}
}

func (app *app) exportFiles() {
	var currFile string
	if curr, err := app.nav.currFile(); err == nil {
		currFile = curr.path
	}

	currSelections := app.nav.currSelections()

	exportFiles(currFile, currSelections)
}

func waitKey() error {
	cmd := pauseCommand()

	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		return fmt.Errorf("waiting key: %s", err)
	}

	return nil
}

// This function is used to run a shell command. Modes are as follows:
//
//     Prefix  Wait  Async  Stdin  Stdout  Stderr  UI action
//     $       No    No     Yes    Yes     Yes     Pause and then resume
//     %       No    No     Yes    Yes     Yes     Statline for input/output
//     !       Yes   No     Yes    Yes     Yes     Pause and then resume
//     &       No    Yes    No     No      No      Do nothing
func (app *app) runShell(s string, args []string, prefix string) {
	app.exportFiles()
	exportOpts()

	cmd := shellCommand(s, args)

	var out io.Reader
	var err error
	switch prefix {
	case "$", "!":
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr

		app.ui.pause()
		defer app.ui.resume()
		defer app.nav.renew()

		err = cmd.Run()
	case "%":
		stdin, err := cmd.StdinPipe()
		if err != nil {
			log.Printf("writing stdin: %s", err)
		}
		app.cmdIn = stdin
		stdout, err := cmd.StdoutPipe()
		if err != nil {
			log.Printf("reading stdout: %s", err)
		}
		out = stdout
		cmd.Stderr = cmd.Stdout
		fallthrough
	case "&":
		err = cmd.Start()
	}

	if err != nil {
		app.ui.echoerrf("running shell: %s", err)
	}

	switch prefix {
	case "!":
		if err := waitKey(); err != nil {
			app.ui.echoerrf("waiting key: %s", err)
		}
	}

	app.ui.loadFile(app.nav)

	switch prefix {
	case "%":
		app.cmd = cmd
		app.cmdOutBuf = nil
		app.ui.msg = ""
		app.ui.cmdPrefix = ">"

		go func() {
			reader := bufio.NewReader(out)
			for {
				b, err := reader.ReadByte()
				if err == io.EOF {
					break
				}
				app.cmdOutBuf = append(app.cmdOutBuf, b)
				app.ui.exprChan <- &callExpr{"echo", []string{string(app.cmdOutBuf)}, 1}
				if b == '\n' || b == '\r' {
					app.cmdOutBuf = nil
				}
			}

			if err := cmd.Wait(); err != nil {
				log.Printf("running shell: %s", err)
			}
			app.cmd = nil
			app.ui.cmdPrefix = ""
			app.ui.exprChan <- &callExpr{"load", nil, 1}
		}()
	case "&":
		go func() {
			if err := cmd.Wait(); err != nil {
				log.Printf("running shell: %s", err)
			}
		}()
	}
}
<commit_msg>handle signals properly<commit_after>package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"time"

	"github.com/gdamore/tcell"
)

type cmdItem struct {
	prefix string
	value  string
}

type app struct {
	ui            *ui
	nav           *nav
	ticker        *time.Ticker
	quitChan      chan bool
	cmd           *exec.Cmd
	cmdIn         io.WriteCloser
	cmdOutBuf     []byte
	cmdHistory    []cmdItem
	cmdHistoryBeg int
	cmdHistoryInd int
}

func newApp(screen tcell.Screen) *app {
	ui := newUI(screen)
	nav := newNav(ui.wins[0].h)

	quitChan := make(chan bool, 1)

	app := &app{
		ui:       ui,
		nav:      nav,
		ticker:   new(time.Ticker),
		quitChan: quitChan,
	}

	sigChan := make(chan os.Signal, 1)
	signal.Notify(sigChan, os.Interrupt, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM)
	go func() {
		switch <-sigChan {
		case os.Interrupt:
			return
		case syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM:
			app.writeHistory()
			os.Remove(gLogPath)
			os.Exit(3)
			return
		}
	}()

	return app
}

func (app *app) readFile(path string) {
	log.Printf("reading file: %s", path)

	f, err := os.Open(path)
	if err != nil {
		app.ui.echoerrf("opening file: %s", err)
		return
	}
	defer f.Close()

	p := newParser(f)

	for p.parse() {
		p.expr.eval(app, nil)
	}

	if p.err != nil {
		app.ui.echoerrf("%s", p.err)
	}
}

func (app *app) readHistory() error {
	f, err := os.Open(gHistoryPath)
	if os.IsNotExist(err) {
		return nil
	}
	if err != nil {
		return fmt.Errorf("opening history file: %s", err)
	}
	defer f.Close()

	scanner := bufio.NewScanner(f)
	for scanner.Scan() {
		toks := strings.SplitN(scanner.Text(), " ", 2)
		if toks[0] != ":" && toks[0] != "$" && toks[0] != "%" && toks[0] != "!" && toks[0] != "&" {
			continue
		}
		if len(toks) < 2 {
			continue
		}
		app.cmdHistory = append(app.cmdHistory, cmdItem{toks[0], toks[1]})
	}

	app.cmdHistoryBeg = len(app.cmdHistory)

	if err := scanner.Err(); err != nil {
		return fmt.Errorf("reading history file: %s", err)
	}

	return nil
}

func (app *app) writeHistory() error {
	if len(app.cmdHistory) == 0 {
		return nil
	}

	local := make([]cmdItem, len(app.cmdHistory)-app.cmdHistoryBeg)
	copy(local, app.cmdHistory[app.cmdHistoryBeg:])
	app.cmdHistory = nil

	if err := app.readHistory(); err != nil {
		return fmt.Errorf("reading history file: %s", err)
	}

	app.cmdHistory = append(app.cmdHistory, local...)

	if err := os.MkdirAll(filepath.Dir(gHistoryPath), os.ModePerm); err != nil {
		return fmt.Errorf("creating data directory: %s", err)
	}

	f, err := os.Create(gHistoryPath)
	if err != nil {
		return fmt.Errorf("creating history file: %s", err)
	}
	defer f.Close()

	if len(app.cmdHistory) > 1000 {
		app.cmdHistory = app.cmdHistory[len(app.cmdHistory)-1000:]
	}

	for _, cmd := range app.cmdHistory {
		_, err = f.WriteString(fmt.Sprintf("%s %s\n", cmd.prefix, cmd.value))
		if err != nil {
			return fmt.Errorf("writing history file: %s", err)
		}
	}

	return nil
}

// This is the main event loop of the application. Expressions are read from
// the client and the server on separate goroutines and sent here over channels
// for evaluation. Similarly directories and regular files are also read in
// separate goroutines and sent here for update.
func (app *app) loop() {
	serverChan := readExpr()

	app.ui.readExpr()

	if gSelect != "" {
		go func() {
			lstat, err := os.Lstat(gSelect)
			if err != nil {
				app.ui.exprChan <- &callExpr{"echoerr", []string{err.Error()}, 1}
			} else if lstat.IsDir() {
				app.ui.exprChan <- &callExpr{"cd", []string{gSelect}, 1}
			} else {
				app.ui.exprChan <- &callExpr{"select", []string{gSelect}, 1}
			}
		}()
	}

	for _, path := range gConfigPaths {
		if _, err := os.Stat(path); !os.IsNotExist(err) {
			app.readFile(path)
		}
	}

	if gCommand != "" {
		p := newParser(strings.NewReader(gCommand))

		for p.parse() {
			p.expr.eval(app, nil)
		}

		if p.err != nil {
			app.ui.echoerrf("%s", p.err)
		}
	}

	for {
		select {
		case <-app.quitChan:
			if app.nav.copyTotal > 0 {
				app.ui.echoerr("quit: copy operation in progress")
				continue
			}

			if app.nav.moveTotal > 0 {
				app.ui.echoerr("quit: move operation in progress")
				continue
			}

			if app.nav.deleteTotal > 0 {
				app.ui.echoerr("quit: delete operation in progress")
				continue
			}

			log.Print("bye!")

			if err := app.writeHistory(); err != nil {
				log.Printf("writing history file: %s", err)
			}

			if gLastDirPath != "" {
				f, err := os.Create(gLastDirPath)
				if err != nil {
					log.Printf("opening last dir file: %s", err)
				}
				defer f.Close()

				_, err = f.WriteString(app.nav.currDir().path)
				if err != nil {
					log.Printf("writing last dir file: %s", err)
				}
			}

			return
		case n := <-app.nav.copyBytesChan:
			app.nav.copyBytes += n
			// n is usually 4096B so update roughly per 4096B x 1024 = 4MB copied
			if app.nav.copyUpdate++; app.nav.copyUpdate >= 1024 {
				app.nav.copyUpdate = 0
				app.ui.draw(app.nav)
			}
		case n := <-app.nav.copyTotalChan:
			app.nav.copyTotal += n
			if n < 0 {
				app.nav.copyBytes += n
			}
			if app.nav.copyTotal == 0 {
				app.nav.copyUpdate = 0
			}
			app.ui.draw(app.nav)
		case n := <-app.nav.moveCountChan:
			app.nav.moveCount += n
			if app.nav.moveUpdate++; app.nav.moveUpdate >= 1000 {
				app.nav.moveUpdate = 0
				app.ui.draw(app.nav)
			}
		case n := <-app.nav.moveTotalChan:
			app.nav.moveTotal += n
			if n < 0 {
				app.nav.moveCount += n
			}
			if app.nav.moveTotal == 0 {
				app.nav.moveUpdate = 0
			}
			app.ui.draw(app.nav)
		case n := <-app.nav.deleteCountChan:
			app.nav.deleteCount += n
			if app.nav.deleteUpdate++; app.nav.deleteUpdate >= 1000 {
				app.nav.deleteUpdate = 0
				app.ui.draw(app.nav)
			}
		case n := <-app.nav.deleteTotalChan:
			app.nav.deleteTotal += n
			if n < 0 {
				app.nav.deleteCount += n
			}
			if app.nav.deleteTotal == 0 {
				app.nav.deleteUpdate = 0
			}
			app.ui.draw(app.nav)
		case d := <-app.nav.dirChan:
			app.nav.checkDir(d)

			prev, ok := app.nav.dirCache[d.path]
			if ok {
				d.ind = prev.ind
				d.sel(prev.name(), app.nav.height)
			}

			app.nav.dirCache[d.path] = d

			for i := range app.nav.dirs {
				if app.nav.dirs[i].path == d.path {
					app.nav.dirs[i] = d
				}
			}

			app.nav.position()

			curr, err := app.nav.currFile()
			if err == nil {
				if d.path == app.nav.currDir().path {
					app.ui.loadFile(app.nav)
				}
				if d.path == curr.path {
					app.ui.dirPrev = d
				}
			}

			app.ui.draw(app.nav)
		case r := <-app.nav.regChan:
			app.nav.checkReg(r)

			app.nav.regCache[r.path] = r

			curr, err := app.nav.currFile()
			if err == nil {
				if r.path == curr.path {
					app.ui.regPrev = r
				}
			}

			app.ui.draw(app.nav)
		case ev := <-app.ui.evChan:
			e := app.ui.readEvent(ev)
			if e == nil {
				continue
			}
			e.eval(app, nil)
		loop:
			for {
				select {
				case ev := <-app.ui.evChan:
					e = app.ui.readEvent(ev)
					if e == nil {
						continue
					}
					e.eval(app, nil)
				default:
					break loop
				}
			}
			app.ui.draw(app.nav)
		case e := <-app.ui.exprChan:
			e.eval(app, nil)
			app.ui.draw(app.nav)
		case e := <-serverChan:
			e.eval(app, nil)
			app.ui.draw(app.nav)
		case <-app.ticker.C:
			app.nav.renew()
			app.ui.loadFile(app.nav)
			app.ui.draw(app.nav)
		}
	}
}

func (app *app) exportFiles() {
	var currFile string
	if curr, err := app.nav.currFile(); err == nil {
		currFile = curr.path
	}

	currSelections := app.nav.currSelections()

	exportFiles(currFile, currSelections)
}

func waitKey() error {
	cmd := pauseCommand()

	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		return fmt.Errorf("waiting key: %s", err)
	}

	return nil
}

// This function is used to run a shell command. Modes are as follows:
//
//     Prefix  Wait  Async  Stdin  Stdout  Stderr  UI action
//     $       No    No     Yes    Yes     Yes     Pause and then resume
//     %       No    No     Yes    Yes     Yes     Statline for input/output
//     !       Yes   No     Yes    Yes     Yes     Pause and then resume
//     &       No    Yes    No     No      No      Do nothing
func (app *app) runShell(s string, args []string, prefix string) {
	app.exportFiles()
	exportOpts()

	cmd := shellCommand(s, args)

	var out io.Reader
	var err error
	switch prefix {
	case "$", "!":
		cmd.Stdin = os.Stdin
		cmd.Stdout = os.Stdout
		cmd.Stderr = os.Stderr

		app.ui.pause()
		defer app.ui.resume()
		defer app.nav.renew()

		err = cmd.Run()
	case "%":
		stdin, err := cmd.StdinPipe()
		if err != nil {
			log.Printf("writing stdin: %s", err)
		}
		app.cmdIn = stdin
		stdout, err := cmd.StdoutPipe()
		if err != nil {
			log.Printf("reading stdout: %s", err)
		}
		out = stdout
		cmd.Stderr = cmd.Stdout
		fallthrough
	case "&":
		err = cmd.Start()
	}

	if err != nil {
		app.ui.echoerrf("running shell: %s", err)
	}

	switch prefix {
	case "!":
		if err := waitKey(); err != nil {
			app.ui.echoerrf("waiting key: %s", err)
		}
	}

	app.ui.loadFile(app.nav)

	switch prefix {
	case "%":
		app.cmd = cmd
		app.cmdOutBuf = nil
		app.ui.msg = ""
		app.ui.cmdPrefix = ">"

		go func() {
			reader := bufio.NewReader(out)
			for {
				b, err := reader.ReadByte()
				if err == io.EOF {
					break
				}
				app.cmdOutBuf = append(app.cmdOutBuf, b)
				app.ui.exprChan <- &callExpr{"echo", []string{string(app.cmdOutBuf)}, 1}
				if b == '\n' || b == '\r' {
					app.cmdOutBuf = nil
				}
			}

			if err := cmd.Wait(); err != nil {
				log.Printf("running shell: %s", err)
			}
			app.cmd = nil
			app.ui.cmdPrefix = ""
			app.ui.exprChan <- &callExpr{"load", nil, 1}
		}()
	case "&":
		go func() {
			if err := cmd.Wait(); err != nil {
				log.Printf("running shell: %s", err)
			}
		}()
	}
}
<|endoftext|>