<commit_before>// Copyright 2015 ThoughtWorks, Inc.

// This file is part of Gauge.

// Gauge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Gauge 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 General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Gauge.  If not, see <http://www.gnu.org/licenses/>.

package api

import (
	"fmt"
	"net"
	"os"
	"path"
	"strconv"
	"sync"

	"github.com/getgauge/common"
	"github.com/getgauge/gauge/api/infoGatherer"
	"github.com/getgauge/gauge/conceptExtractor"
	"github.com/getgauge/gauge/config"
	"github.com/getgauge/gauge/conn"
	"github.com/getgauge/gauge/formatter"
	"github.com/getgauge/gauge/gauge"
	"github.com/getgauge/gauge/gauge_messages"
	"github.com/getgauge/gauge/logger"
	"github.com/getgauge/gauge/manifest"
	"github.com/getgauge/gauge/parser"
	"github.com/getgauge/gauge/plugin"
	"github.com/getgauge/gauge/refactor"
	"github.com/getgauge/gauge/reporter"
	"github.com/getgauge/gauge/runner"
	"github.com/golang/protobuf/proto"
)

func StartAPI() *runner.StartChannels {
	startChan := &runner.StartChannels{RunnerChan: make(chan *runner.TestRunner), ErrorChan: make(chan error), KillChan: make(chan bool)}
	go StartAPIService(0, startChan)
	return startChan
}

func StartAPIService(port int, startChannels *runner.StartChannels) {
	specInfoGatherer := new(infoGatherer.SpecInfoGatherer)
	apiHandler := &gaugeApiMessageHandler{specInfoGatherer: specInfoGatherer}
	gaugeConnectionHandler, err := conn.NewGaugeConnectionHandler(port, apiHandler)
	if err != nil {
		startChannels.ErrorChan <- fmt.Errorf("Connection error. %s", err.Error())
		return
	}
	if port == 0 {
		if err := common.SetEnvVariable(common.APIPortEnvVariableName, strconv.Itoa(gaugeConnectionHandler.ConnectionPortNumber())); err != nil {
			startChannels.ErrorChan <- fmt.Errorf("Failed to set Env variable %s. %s", common.APIPortEnvVariableName, err.Error())
			return
		}
	}
	go gaugeConnectionHandler.HandleMultipleConnections()
	runner, err := connectToRunner(startChannels.KillChan)
	if err != nil {
		startChannels.ErrorChan <- err
		return
	}
	specInfoGatherer.MakeListOfAvailableSteps(runner)
	startChannels.RunnerChan <- runner
}

func connectToRunner(killChannel chan bool) (*runner.TestRunner, error) {
	manifest, err := manifest.ProjectManifest()
	if err != nil {
		return nil, err
	}

	runner, connErr := runner.StartRunnerAndMakeConnection(manifest, reporter.Current(), killChannel)
	if connErr != nil {
		return nil, connErr
	}

	return runner, nil
}

func runAPIServiceIndefinitely(port int, wg *sync.WaitGroup) {
	wg.Add(1)
	startChan := &runner.StartChannels{RunnerChan: make(chan *runner.TestRunner), ErrorChan: make(chan error), KillChan: make(chan bool)}
	go StartAPIService(port, startChan)
	select {
	case runner := <-startChan.RunnerChan:
		runner.Kill()
	case err := <-startChan.ErrorChan:
		logger.Fatalf(err.Error())
	}
}

func RunInBackground(apiPort string) {
	var port int
	var err error
	if apiPort != "" {
		port, err = strconv.Atoi(apiPort)
		if err != nil {
			logger.Fatalf(fmt.Sprintf("Invalid port number: %s", apiPort))
		}
		os.Setenv(common.APIPortEnvVariableName, apiPort)
	} else {
		port, err = conn.GetPortFromEnvironmentVariable(common.APIPortEnvVariableName)
		if err != nil {
			logger.Fatalf(fmt.Sprintf("Failed to start API Service. %s \n", err.Error()))
		}
	}
	var wg sync.WaitGroup
	runAPIServiceIndefinitely(port, &wg)
	wg.Wait()
}

type gaugeApiMessageHandler struct {
	specInfoGatherer *infoGatherer.SpecInfoGatherer
	Runner           *runner.TestRunner
}

func (handler *gaugeApiMessageHandler) MessageBytesReceived(bytesRead []byte, connection net.Conn) {
	apiMessage := &gauge_messages.APIMessage{}
	var responseMessage *gauge_messages.APIMessage
	err := proto.Unmarshal(bytesRead, apiMessage)
	if err != nil {
		logger.APILog.Error("Failed to read API proto message: %s\n", err.Error())
		responseMessage = handler.getErrorMessage(err)
	} else {
		logger.APILog.Debug("Api Request Received: %s", apiMessage)
		messageType := apiMessage.GetMessageType()
		switch messageType {
		case gauge_messages.APIMessage_GetProjectRootRequest:
			responseMessage = handler.projectRootRequestResponse(apiMessage)
			break
		case gauge_messages.APIMessage_GetInstallationRootRequest:
			responseMessage = handler.installationRootRequestResponse(apiMessage)
			break
		case gauge_messages.APIMessage_GetAllStepsRequest:
			responseMessage = handler.getAllStepsRequestResponse(apiMessage)
			break
		case gauge_messages.APIMessage_GetAllSpecsRequest:
			responseMessage = handler.getAllSpecsRequestResponse(apiMessage)
			break
		case gauge_messages.APIMessage_GetStepValueRequest:
			responseMessage = handler.getStepValueRequestResponse(apiMessage)
			break
		case gauge_messages.APIMessage_GetLanguagePluginLibPathRequest:
			responseMessage = handler.getLanguagePluginLibPath(apiMessage)
			break
		case gauge_messages.APIMessage_GetAllConceptsRequest:
			responseMessage = handler.getAllConceptsRequestResponse(apiMessage)
			break
		case gauge_messages.APIMessage_PerformRefactoringRequest:
			responseMessage = handler.performRefactoring(apiMessage)
			break
		case gauge_messages.APIMessage_ExtractConceptRequest:
			responseMessage = handler.extractConcept(apiMessage)
			break
		case gauge_messages.APIMessage_FormatSpecsRequest:
			responseMessage = handler.formatSpecs(apiMessage)
			break
		default:
			responseMessage = handler.createUnsupportedApiMessageResponse(apiMessage)
		}
	}
	handler.sendMessage(responseMessage, connection)
}

func (handler *gaugeApiMessageHandler) sendMessage(message *gauge_messages.APIMessage, connection net.Conn) {
	logger.APILog.Debug("Sending API response: %s", message)
	dataBytes, err := proto.Marshal(message)
	if err != nil {
		logger.APILog.Error("Failed to respond to API request. Could not Marshal response %s\n", err.Error())
	}
	if err := conn.Write(connection, dataBytes); err != nil {
		logger.APILog.Error("Failed to respond to API request. Could not write response %s\n", err.Error())
	}
}

func (handler *gaugeApiMessageHandler) projectRootRequestResponse(message *gauge_messages.APIMessage) *gauge_messages.APIMessage {
	projectRootResponse := &gauge_messages.GetProjectRootResponse{ProjectRoot: proto.String(config.ProjectRoot)}
	return &gauge_messages.APIMessage{MessageType: gauge_messages.APIMessage_GetProjectRootResponse.Enum(), MessageId: message.MessageId, ProjectRootResponse: projectRootResponse}
}

func (handler *gaugeApiMessageHandler) installationRootRequestResponse(message *gauge_messages.APIMessage) *gauge_messages.APIMessage {
	root, err := common.GetInstallationPrefix()
	if err != nil {
		logger.APILog.Error("Failed to find installation root while responding to API request. %s\n", err.Error())
		root = ""
	}
	installationRootResponse := &gauge_messages.GetInstallationRootResponse{InstallationRoot: proto.String(root)}
	return &gauge_messages.APIMessage{MessageType: gauge_messages.APIMessage_GetInstallationRootResponse.Enum(), MessageId: message.MessageId, InstallationRootResponse: installationRootResponse}
}

func (handler *gaugeApiMessageHandler) getAllStepsRequestResponse(message *gauge_messages.APIMessage) *gauge_messages.APIMessage {
	stepValues := handler.specInfoGatherer.GetAvailableSteps()
	stepValueResponses := make([]*gauge_messages.ProtoStepValue, 0)
	for _, stepValue := range stepValues {
		stepValueResponses = append(stepValueResponses, gauge.ConvertToProtoStepValue(stepValue))
	}
	getAllStepsResponse := &gauge_messages.GetAllStepsResponse{AllSteps: stepValueResponses}
	return &gauge_messages.APIMessage{MessageType: gauge_messages.APIMessage_GetAllStepResponse.Enum(), MessageId: message.MessageId, AllStepsResponse: getAllStepsResponse}
}

func (handler *gaugeApiMessageHandler) getAllSpecsRequestResponse(message *gauge_messages.APIMessage) *gauge_messages.APIMessage {
	getAllSpecsResponse := handler.createGetAllSpecsResponseMessageFor(handler.specInfoGatherer.GetAvailableSpecs())
	return &gauge_messages.APIMessage{MessageType: gauge_messages.APIMessage_GetAllSpecsResponse.Enum(), MessageId: message.MessageId, AllSpecsResponse: getAllSpecsResponse}
}

func (handler *gaugeApiMessageHandler) getStepValueRequestResponse(message *gauge_messages.APIMessage) *gauge_messages.APIMessage {
	request := message.GetStepValueRequest()
	stepText := request.GetStepText()
	hasInlineTable := request.GetHasInlineTable()
	stepValue, err := parser.ExtractStepValueAndParams(stepText, hasInlineTable)

	if err != nil {
		return handler.getErrorResponse(message, err)
	}
	stepValueResponse := &gauge_messages.GetStepValueResponse{StepValue: gauge.ConvertToProtoStepValue(stepValue)}
	return &gauge_messages.APIMessage{MessageType: gauge_messages.APIMessage_GetStepValueResponse.Enum(), MessageId: message.MessageId, StepValueResponse: stepValueResponse}

}

func (handler *gaugeApiMessageHandler) getAllConceptsRequestResponse(message *gauge_messages.APIMessage) *gauge_messages.APIMessage {
	allConceptsResponse := handler.createGetAllConceptsResponseMessageFor(handler.specInfoGatherer.GetConceptInfos())
	return &gauge_messages.APIMessage{MessageType: gauge_messages.APIMessage_GetAllConceptsResponse.Enum(), MessageId: message.MessageId, AllConceptsResponse: allConceptsResponse}
}

func (handler *gaugeApiMessageHandler) getLanguagePluginLibPath(message *gauge_messages.APIMessage) *gauge_messages.APIMessage {
	libPathRequest := message.GetLibPathRequest()
	language := libPathRequest.GetLanguage()
	languageInstallDir, err := plugin.GetInstallDir(language, "")
	if err != nil {
		return handler.getErrorMessage(err)
	}
	runnerInfo, err := runner.GetRunnerInfo(language)
	if err != nil {
		return handler.getErrorMessage(err)
	}
	relativeLibPath := runnerInfo.Lib
	libPath := path.Join(languageInstallDir, relativeLibPath)
	response := &gauge_messages.GetLanguagePluginLibPathResponse{Path: proto.String(libPath)}
	return &gauge_messages.APIMessage{MessageType: gauge_messages.APIMessage_GetLanguagePluginLibPathResponse.Enum(), MessageId: message.MessageId, LibPathResponse: response}
}

func (handler *gaugeApiMessageHandler) getErrorResponse(message *gauge_messages.APIMessage, err error) *gauge_messages.APIMessage {
	errorResponse := &gauge_messages.ErrorResponse{Error: proto.String(err.Error())}
	return &gauge_messages.APIMessage{MessageType: gauge_messages.APIMessage_ErrorResponse.Enum(), MessageId: message.MessageId, Error: errorResponse}

}

func (handler *gaugeApiMessageHandler) getErrorMessage(err error) *gauge_messages.APIMessage {
	id := common.GetUniqueID()
	errorResponse := &gauge_messages.ErrorResponse{Error: proto.String(err.Error())}
	return &gauge_messages.APIMessage{MessageType: gauge_messages.APIMessage_ErrorResponse.Enum(), MessageId: &id, Error: errorResponse}
}

func (handler *gaugeApiMessageHandler) createGetAllSpecsResponseMessageFor(specs []*gauge.Specification) *gauge_messages.GetAllSpecsResponse {
	protoSpecs := make([]*gauge_messages.ProtoSpec, 0)
	for _, spec := range specs {
		protoSpecs = append(protoSpecs, gauge.ConvertToProtoSpec(spec))
	}
	return &gauge_messages.GetAllSpecsResponse{Specs: protoSpecs}
}

func (handler *gaugeApiMessageHandler) createGetAllConceptsResponseMessageFor(conceptInfos []*gauge_messages.ConceptInfo) *gauge_messages.GetAllConceptsResponse {
	return &gauge_messages.GetAllConceptsResponse{Concepts: conceptInfos}
}

func (handler *gaugeApiMessageHandler) performRefactoring(message *gauge_messages.APIMessage) *gauge_messages.APIMessage {
	refactoringRequest := message.PerformRefactoringRequest
	startChan := StartAPI()
	refactoringResult := refactor.PerformRephraseRefactoring(refactoringRequest.GetOldStep(), refactoringRequest.GetNewStep(), startChan)
	if refactoringResult.Success {
		logger.APILog.Info("%s", refactoringResult.String())
	} else {
		logger.APILog.Error("Refactoring response from gauge. Errors : %s", refactoringResult.Errors)
	}
	response := &gauge_messages.PerformRefactoringResponse{Success: proto.Bool(refactoringResult.Success), Errors: refactoringResult.Errors, FilesChanged: refactoringResult.AllFilesChanges()}
	return &gauge_messages.APIMessage{MessageId: message.MessageId, MessageType: gauge_messages.APIMessage_PerformRefactoringResponse.Enum(), PerformRefactoringResponse: response}
}

func (handler *gaugeApiMessageHandler) extractConcept(message *gauge_messages.APIMessage) *gauge_messages.APIMessage {
	request := message.GetExtractConceptRequest()
	success, err, filesChanged := conceptExtractor.ExtractConcept(request.GetConceptName(), request.GetSteps(), request.GetConceptFileName(), request.GetChangeAcrossProject(), request.GetSelectedTextInfo())
	response := &gauge_messages.ExtractConceptResponse{IsSuccess: proto.Bool(success), Error: proto.String(err.Error()), FilesChanged: filesChanged}
	return &gauge_messages.APIMessage{MessageId: message.MessageId, MessageType: gauge_messages.APIMessage_ExtractConceptResponse.Enum(), ExtractConceptResponse: response}
}

func (handler *gaugeApiMessageHandler) formatSpecs(message *gauge_messages.APIMessage) *gauge_messages.APIMessage {
	request := message.GetFormatSpecsRequest()
	results := formatter.FormatSpecFiles(request.GetSpecs()...)
	warnings := make([]string, 0)
	errors := make([]string, 0)
	for _, result := range results {
		if result.ParseError != nil {
			errors = append(errors, result.ParseError.Error())
		}
		if result.Warnings != nil {
			warningTexts := make([]string, 0)
			for _, warning := range result.Warnings {
				warningTexts = append(warningTexts, warning.String())
			}
			warnings = append(warnings, warningTexts...)
		}
	}
	formatResponse := &gauge_messages.FormatSpecsResponse{Errors: errors, Warnings: warnings}
	return &gauge_messages.APIMessage{MessageId: message.MessageId, MessageType: gauge_messages.APIMessage_FormatSpecsResponse.Enum(), FormatSpecsResponse: formatResponse}
}

func (handler *gaugeApiMessageHandler) createUnsupportedApiMessageResponse(message *gauge_messages.APIMessage) *gauge_messages.APIMessage {
	return &gauge_messages.APIMessage{MessageId: message.MessageId,
		MessageType:                   gauge_messages.APIMessage_UnsupportedApiMessageResponse.Enum(),
		UnsupportedApiMessageResponse: &gauge_messages.UnsupportedApiMessageResponse{}}
}
<commit_msg>killing Gauge daemon if parent process is killed | #166<commit_after>// Copyright 2015 ThoughtWorks, Inc.

// This file is part of Gauge.

// Gauge is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.

// Gauge 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 General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with Gauge.  If not, see <http://www.gnu.org/licenses/>.

package api

import (
	"fmt"
	"net"
	"os"
	"path"
	"strconv"
	"time"

	"github.com/getgauge/common"
	"github.com/getgauge/gauge/api/infoGatherer"
	"github.com/getgauge/gauge/conceptExtractor"
	"github.com/getgauge/gauge/config"
	"github.com/getgauge/gauge/conn"
	"github.com/getgauge/gauge/formatter"
	"github.com/getgauge/gauge/gauge"
	"github.com/getgauge/gauge/gauge_messages"
	"github.com/getgauge/gauge/logger"
	"github.com/getgauge/gauge/manifest"
	"github.com/getgauge/gauge/parser"
	"github.com/getgauge/gauge/plugin"
	"github.com/getgauge/gauge/refactor"
	"github.com/getgauge/gauge/reporter"
	"github.com/getgauge/gauge/runner"
	"github.com/getgauge/gauge/util"
	"github.com/golang/protobuf/proto"
)

func StartAPI() *runner.StartChannels {
	startChan := &runner.StartChannels{RunnerChan: make(chan *runner.TestRunner), ErrorChan: make(chan error), KillChan: make(chan bool)}
	go StartAPIService(0, startChan)
	return startChan
}

func StartAPIService(port int, startChannels *runner.StartChannels) {
	specInfoGatherer := new(infoGatherer.SpecInfoGatherer)
	apiHandler := &gaugeApiMessageHandler{specInfoGatherer: specInfoGatherer}
	gaugeConnectionHandler, err := conn.NewGaugeConnectionHandler(port, apiHandler)
	if err != nil {
		startChannels.ErrorChan <- fmt.Errorf("Connection error. %s", err.Error())
		return
	}
	if port == 0 {
		if err := common.SetEnvVariable(common.APIPortEnvVariableName, strconv.Itoa(gaugeConnectionHandler.ConnectionPortNumber())); err != nil {
			startChannels.ErrorChan <- fmt.Errorf("Failed to set Env variable %s. %s", common.APIPortEnvVariableName, err.Error())
			return
		}
	}
	go gaugeConnectionHandler.HandleMultipleConnections()
	runner, err := connectToRunner(startChannels.KillChan)
	if err != nil {
		startChannels.ErrorChan <- err
		return
	}
	specInfoGatherer.MakeListOfAvailableSteps(runner)
	startChannels.RunnerChan <- runner
}

func connectToRunner(killChannel chan bool) (*runner.TestRunner, error) {
	manifest, err := manifest.ProjectManifest()
	if err != nil {
		return nil, err
	}

	runner, connErr := runner.StartRunnerAndMakeConnection(manifest, reporter.Current(), killChannel)
	if connErr != nil {
		return nil, connErr
	}

	return runner, nil
}

func runAPIServiceIndefinitely(port int) {
	startChan := &runner.StartChannels{RunnerChan: make(chan *runner.TestRunner), ErrorChan: make(chan error), KillChan: make(chan bool)}
	go StartAPIService(port, startChan)
	go checkParentIsAlive(startChan)

	for {
		select {
		case runner := <-startChan.RunnerChan:
			logger.Info("Got a kill message. Killing runner.")
			runner.Kill()
		case err := <-startChan.ErrorChan:
			logger.Fatalf("Killing Gauge daemon. %v", err.Error())
		}
	}
}

func checkParentIsAlive(startChannels *runner.StartChannels) {
	parentProcessID := os.Getppid()
	for {
		if !util.IsProcessRunning(parentProcessID) {
			startChannels.ErrorChan <- fmt.Errorf("Parent process with pid %d has terminated.", parentProcessID)
			return
		}
		time.Sleep(1 * time.Second)
	}
}

func RunInBackground(apiPort string) {
	var port int
	var err error
	if apiPort != "" {
		port, err = strconv.Atoi(apiPort)
		if err != nil {
			logger.Fatalf(fmt.Sprintf("Invalid port number: %s", apiPort))
		}
		os.Setenv(common.APIPortEnvVariableName, apiPort)
	} else {
		port, err = conn.GetPortFromEnvironmentVariable(common.APIPortEnvVariableName)
		if err != nil {
			logger.Fatalf(fmt.Sprintf("Failed to start API Service. %s \n", err.Error()))
		}
	}
	runAPIServiceIndefinitely(port)
}

type gaugeApiMessageHandler struct {
	specInfoGatherer *infoGatherer.SpecInfoGatherer
	Runner           *runner.TestRunner
}

func (handler *gaugeApiMessageHandler) MessageBytesReceived(bytesRead []byte, connection net.Conn) {
	apiMessage := &gauge_messages.APIMessage{}
	var responseMessage *gauge_messages.APIMessage
	err := proto.Unmarshal(bytesRead, apiMessage)
	if err != nil {
		logger.APILog.Error("Failed to read API proto message: %s\n", err.Error())
		responseMessage = handler.getErrorMessage(err)
	} else {
		logger.APILog.Debug("Api Request Received: %s", apiMessage)
		messageType := apiMessage.GetMessageType()
		switch messageType {
		case gauge_messages.APIMessage_GetProjectRootRequest:
			responseMessage = handler.projectRootRequestResponse(apiMessage)
			break
		case gauge_messages.APIMessage_GetInstallationRootRequest:
			responseMessage = handler.installationRootRequestResponse(apiMessage)
			break
		case gauge_messages.APIMessage_GetAllStepsRequest:
			responseMessage = handler.getAllStepsRequestResponse(apiMessage)
			break
		case gauge_messages.APIMessage_GetAllSpecsRequest:
			responseMessage = handler.getAllSpecsRequestResponse(apiMessage)
			break
		case gauge_messages.APIMessage_GetStepValueRequest:
			responseMessage = handler.getStepValueRequestResponse(apiMessage)
			break
		case gauge_messages.APIMessage_GetLanguagePluginLibPathRequest:
			responseMessage = handler.getLanguagePluginLibPath(apiMessage)
			break
		case gauge_messages.APIMessage_GetAllConceptsRequest:
			responseMessage = handler.getAllConceptsRequestResponse(apiMessage)
			break
		case gauge_messages.APIMessage_PerformRefactoringRequest:
			responseMessage = handler.performRefactoring(apiMessage)
			break
		case gauge_messages.APIMessage_ExtractConceptRequest:
			responseMessage = handler.extractConcept(apiMessage)
			break
		case gauge_messages.APIMessage_FormatSpecsRequest:
			responseMessage = handler.formatSpecs(apiMessage)
			break
		default:
			responseMessage = handler.createUnsupportedApiMessageResponse(apiMessage)
		}
	}
	handler.sendMessage(responseMessage, connection)
}

func (handler *gaugeApiMessageHandler) sendMessage(message *gauge_messages.APIMessage, connection net.Conn) {
	logger.APILog.Debug("Sending API response: %s", message)
	dataBytes, err := proto.Marshal(message)
	if err != nil {
		logger.APILog.Error("Failed to respond to API request. Could not Marshal response %s\n", err.Error())
	}
	if err := conn.Write(connection, dataBytes); err != nil {
		logger.APILog.Error("Failed to respond to API request. Could not write response %s\n", err.Error())
	}
}

func (handler *gaugeApiMessageHandler) projectRootRequestResponse(message *gauge_messages.APIMessage) *gauge_messages.APIMessage {
	projectRootResponse := &gauge_messages.GetProjectRootResponse{ProjectRoot: proto.String(config.ProjectRoot)}
	return &gauge_messages.APIMessage{MessageType: gauge_messages.APIMessage_GetProjectRootResponse.Enum(), MessageId: message.MessageId, ProjectRootResponse: projectRootResponse}
}

func (handler *gaugeApiMessageHandler) installationRootRequestResponse(message *gauge_messages.APIMessage) *gauge_messages.APIMessage {
	root, err := common.GetInstallationPrefix()
	if err != nil {
		logger.APILog.Error("Failed to find installation root while responding to API request. %s\n", err.Error())
		root = ""
	}
	installationRootResponse := &gauge_messages.GetInstallationRootResponse{InstallationRoot: proto.String(root)}
	return &gauge_messages.APIMessage{MessageType: gauge_messages.APIMessage_GetInstallationRootResponse.Enum(), MessageId: message.MessageId, InstallationRootResponse: installationRootResponse}
}

func (handler *gaugeApiMessageHandler) getAllStepsRequestResponse(message *gauge_messages.APIMessage) *gauge_messages.APIMessage {
	stepValues := handler.specInfoGatherer.GetAvailableSteps()
	stepValueResponses := make([]*gauge_messages.ProtoStepValue, 0)
	for _, stepValue := range stepValues {
		stepValueResponses = append(stepValueResponses, gauge.ConvertToProtoStepValue(stepValue))
	}
	getAllStepsResponse := &gauge_messages.GetAllStepsResponse{AllSteps: stepValueResponses}
	return &gauge_messages.APIMessage{MessageType: gauge_messages.APIMessage_GetAllStepResponse.Enum(), MessageId: message.MessageId, AllStepsResponse: getAllStepsResponse}
}

func (handler *gaugeApiMessageHandler) getAllSpecsRequestResponse(message *gauge_messages.APIMessage) *gauge_messages.APIMessage {
	getAllSpecsResponse := handler.createGetAllSpecsResponseMessageFor(handler.specInfoGatherer.GetAvailableSpecs())
	return &gauge_messages.APIMessage{MessageType: gauge_messages.APIMessage_GetAllSpecsResponse.Enum(), MessageId: message.MessageId, AllSpecsResponse: getAllSpecsResponse}
}

func (handler *gaugeApiMessageHandler) getStepValueRequestResponse(message *gauge_messages.APIMessage) *gauge_messages.APIMessage {
	request := message.GetStepValueRequest()
	stepText := request.GetStepText()
	hasInlineTable := request.GetHasInlineTable()
	stepValue, err := parser.ExtractStepValueAndParams(stepText, hasInlineTable)

	if err != nil {
		return handler.getErrorResponse(message, err)
	}
	stepValueResponse := &gauge_messages.GetStepValueResponse{StepValue: gauge.ConvertToProtoStepValue(stepValue)}
	return &gauge_messages.APIMessage{MessageType: gauge_messages.APIMessage_GetStepValueResponse.Enum(), MessageId: message.MessageId, StepValueResponse: stepValueResponse}

}

func (handler *gaugeApiMessageHandler) getAllConceptsRequestResponse(message *gauge_messages.APIMessage) *gauge_messages.APIMessage {
	allConceptsResponse := handler.createGetAllConceptsResponseMessageFor(handler.specInfoGatherer.GetConceptInfos())
	return &gauge_messages.APIMessage{MessageType: gauge_messages.APIMessage_GetAllConceptsResponse.Enum(), MessageId: message.MessageId, AllConceptsResponse: allConceptsResponse}
}

func (handler *gaugeApiMessageHandler) getLanguagePluginLibPath(message *gauge_messages.APIMessage) *gauge_messages.APIMessage {
	libPathRequest := message.GetLibPathRequest()
	language := libPathRequest.GetLanguage()
	languageInstallDir, err := plugin.GetInstallDir(language, "")
	if err != nil {
		return handler.getErrorMessage(err)
	}
	runnerInfo, err := runner.GetRunnerInfo(language)
	if err != nil {
		return handler.getErrorMessage(err)
	}
	relativeLibPath := runnerInfo.Lib
	libPath := path.Join(languageInstallDir, relativeLibPath)
	response := &gauge_messages.GetLanguagePluginLibPathResponse{Path: proto.String(libPath)}
	return &gauge_messages.APIMessage{MessageType: gauge_messages.APIMessage_GetLanguagePluginLibPathResponse.Enum(), MessageId: message.MessageId, LibPathResponse: response}
}

func (handler *gaugeApiMessageHandler) getErrorResponse(message *gauge_messages.APIMessage, err error) *gauge_messages.APIMessage {
	errorResponse := &gauge_messages.ErrorResponse{Error: proto.String(err.Error())}
	return &gauge_messages.APIMessage{MessageType: gauge_messages.APIMessage_ErrorResponse.Enum(), MessageId: message.MessageId, Error: errorResponse}

}

func (handler *gaugeApiMessageHandler) getErrorMessage(err error) *gauge_messages.APIMessage {
	id := common.GetUniqueID()
	errorResponse := &gauge_messages.ErrorResponse{Error: proto.String(err.Error())}
	return &gauge_messages.APIMessage{MessageType: gauge_messages.APIMessage_ErrorResponse.Enum(), MessageId: &id, Error: errorResponse}
}

func (handler *gaugeApiMessageHandler) createGetAllSpecsResponseMessageFor(specs []*gauge.Specification) *gauge_messages.GetAllSpecsResponse {
	protoSpecs := make([]*gauge_messages.ProtoSpec, 0)
	for _, spec := range specs {
		protoSpecs = append(protoSpecs, gauge.ConvertToProtoSpec(spec))
	}
	return &gauge_messages.GetAllSpecsResponse{Specs: protoSpecs}
}

func (handler *gaugeApiMessageHandler) createGetAllConceptsResponseMessageFor(conceptInfos []*gauge_messages.ConceptInfo) *gauge_messages.GetAllConceptsResponse {
	return &gauge_messages.GetAllConceptsResponse{Concepts: conceptInfos}
}

func (handler *gaugeApiMessageHandler) performRefactoring(message *gauge_messages.APIMessage) *gauge_messages.APIMessage {
	refactoringRequest := message.PerformRefactoringRequest
	startChan := StartAPI()
	refactoringResult := refactor.PerformRephraseRefactoring(refactoringRequest.GetOldStep(), refactoringRequest.GetNewStep(), startChan)
	if refactoringResult.Success {
		logger.APILog.Info("%s", refactoringResult.String())
	} else {
		logger.APILog.Error("Refactoring response from gauge. Errors : %s", refactoringResult.Errors)
	}
	response := &gauge_messages.PerformRefactoringResponse{Success: proto.Bool(refactoringResult.Success), Errors: refactoringResult.Errors, FilesChanged: refactoringResult.AllFilesChanges()}
	return &gauge_messages.APIMessage{MessageId: message.MessageId, MessageType: gauge_messages.APIMessage_PerformRefactoringResponse.Enum(), PerformRefactoringResponse: response}
}

func (handler *gaugeApiMessageHandler) extractConcept(message *gauge_messages.APIMessage) *gauge_messages.APIMessage {
	request := message.GetExtractConceptRequest()
	success, err, filesChanged := conceptExtractor.ExtractConcept(request.GetConceptName(), request.GetSteps(), request.GetConceptFileName(), request.GetChangeAcrossProject(), request.GetSelectedTextInfo())
	response := &gauge_messages.ExtractConceptResponse{IsSuccess: proto.Bool(success), Error: proto.String(err.Error()), FilesChanged: filesChanged}
	return &gauge_messages.APIMessage{MessageId: message.MessageId, MessageType: gauge_messages.APIMessage_ExtractConceptResponse.Enum(), ExtractConceptResponse: response}
}

func (handler *gaugeApiMessageHandler) formatSpecs(message *gauge_messages.APIMessage) *gauge_messages.APIMessage {
	request := message.GetFormatSpecsRequest()
	results := formatter.FormatSpecFiles(request.GetSpecs()...)
	warnings := make([]string, 0)
	errors := make([]string, 0)
	for _, result := range results {
		if result.ParseError != nil {
			errors = append(errors, result.ParseError.Error())
		}
		if result.Warnings != nil {
			warningTexts := make([]string, 0)
			for _, warning := range result.Warnings {
				warningTexts = append(warningTexts, warning.String())
			}
			warnings = append(warnings, warningTexts...)
		}
	}
	formatResponse := &gauge_messages.FormatSpecsResponse{Errors: errors, Warnings: warnings}
	return &gauge_messages.APIMessage{MessageId: message.MessageId, MessageType: gauge_messages.APIMessage_FormatSpecsResponse.Enum(), FormatSpecsResponse: formatResponse}
}

func (handler *gaugeApiMessageHandler) createUnsupportedApiMessageResponse(message *gauge_messages.APIMessage) *gauge_messages.APIMessage {
	return &gauge_messages.APIMessage{MessageId: message.MessageId,
		MessageType:                   gauge_messages.APIMessage_UnsupportedApiMessageResponse.Enum(),
		UnsupportedApiMessageResponse: &gauge_messages.UnsupportedApiMessageResponse{}}
}
<|endoftext|>


<commit_before>package api

import (
	"net/http"

	"github.com/NYTimes/gziphandler"
	"github.com/auth0/go-jwt-middleware"
	"github.com/codegangsta/negroni"
	"github.com/dgrijalva/jwt-go"
	"github.com/rabeesh/negroni-nocache"
	"github.com/rs/cors"
	"github.com/uber-go/zap"
)

// JWTSecret is the Secret used when signing JTW tokens
var JWTSecret string

var jwtSecretBytes []byte

var jMW = jwtmiddleware.New(jwtmiddleware.Options{
	ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
		return []byte(JWTSecret), nil
	},
	Extractor: func(r *http.Request) (string, error) {
		c, err := r.Cookie("Authorization")
		if err != nil {
			return "", err
		}
		return c.Value, nil
	},
	SigningMethod: jwt.SigningMethodHS256,
	ErrorHandler: func(w http.ResponseWriter, r *http.Request, err string) {
		logger.Info(
			"HTTP Request",
			zap.String("uri", r.RequestURI),
			zap.Int("http_status", -1),
			zap.String("username", getSlackUserName(r)),
			zap.String("remote_address", r.RemoteAddr),
			zap.String("method", r.Method),
			zap.Int64("content_length", r.ContentLength),
			zap.String("error", err),
		)
	},
})

func handlerFunc(fn func(w http.ResponseWriter, r *http.Request)) http.Handler {
	c := cors.New(cors.Options{
		AllowedOrigins:   []string{"http://localhost:*", "http://127.0.0.*", "http://*.fofgaming.com/"},
		AllowCredentials: true,
		AllowedMethods:   []string{"GET", "POST", "PUT", "OPTIONS", "DELETE"},
		AllowedHeaders:   []string{"*"},
	})
	return gziphandler.GzipHandler(
		c.Handler(
			negroni.New(
				&httpLogger{},
				negroni.NewRecovery(),
				nocache.New(true),
				negroni.Wrap(
					http.HandlerFunc(fn),
				),
			),
		),
	)
}

func jwtHandlerFunc(fn func(w http.ResponseWriter, r *http.Request)) http.Handler {
	c := cors.New(cors.Options{
		AllowedOrigins:   []string{"http://localhost:*", "http://127.0.0.*", "http://*.fofgaming.com/"},
		AllowCredentials: true,
		AllowedMethods:   []string{"GET", "POST", "PUT", "OPTIONS", "DELETE"},
		AllowedHeaders:   []string{"*"},
	})
	return gziphandler.GzipHandler(
		c.Handler(
			jMW.Handler(
				negroni.New(
					&httpLogger{},
					negroni.NewRecovery(),
					nocache.New(true),
					negroni.Wrap(
						http.HandlerFunc(fn),
					),
				),
			),
		),
	)
}
<commit_msg>try again...<commit_after>package api

import (
	"net/http"

	"github.com/NYTimes/gziphandler"
	"github.com/auth0/go-jwt-middleware"
	"github.com/codegangsta/negroni"
	"github.com/dgrijalva/jwt-go"
	"github.com/rabeesh/negroni-nocache"
	"github.com/rs/cors"
	"github.com/uber-go/zap"
)

// JWTSecret is the Secret used when signing JTW tokens
var JWTSecret string

var jwtSecretBytes []byte

var jMW = jwtmiddleware.New(jwtmiddleware.Options{
	ValidationKeyGetter: func(token *jwt.Token) (interface{}, error) {
		return []byte(JWTSecret), nil
	},
	Extractor: func(r *http.Request) (string, error) {
		c, err := r.Cookie("Authorization")
		if err != nil {
			return "", err
		}
		return c.Value, nil
	},
	SigningMethod: jwt.SigningMethodHS256,
	ErrorHandler: func(w http.ResponseWriter, r *http.Request, err string) {
		logger.Info(
			"HTTP Request",
			zap.String("uri", r.RequestURI),
			zap.Int("http_status", -1),
			zap.String("username", getSlackUserName(r)),
			zap.String("remote_address", r.RemoteAddr),
			zap.String("method", r.Method),
			zap.Int64("content_length", r.ContentLength),
			zap.String("error", err),
		)
	},
})

func handlerFunc(fn func(w http.ResponseWriter, r *http.Request)) http.Handler {
	c := cors.New(cors.Options{
		AllowedOrigins:   []string{"http://localhost:*", "http://127.0.0.*", "http://dev.fofgaming.com"},
		AllowCredentials: true,
		AllowedMethods:   []string{"GET", "POST", "PUT", "OPTIONS", "DELETE"},
		AllowedHeaders:   []string{"*"},
	})
	return gziphandler.GzipHandler(
		c.Handler(
			negroni.New(
				&httpLogger{},
				negroni.NewRecovery(),
				nocache.New(true),
				negroni.Wrap(
					http.HandlerFunc(fn),
				),
			),
		),
	)
}

func jwtHandlerFunc(fn func(w http.ResponseWriter, r *http.Request)) http.Handler {
	c := cors.New(cors.Options{
		AllowedOrigins:   []string{"http://localhost:*", "http://127.0.0.*", "http://dev.fofgaming.com"},
		AllowCredentials: true,
		AllowedMethods:   []string{"GET", "POST", "PUT", "OPTIONS", "DELETE"},
		AllowedHeaders:   []string{"*"},
	})
	return gziphandler.GzipHandler(
		c.Handler(
			jMW.Handler(
				negroni.New(
					&httpLogger{},
					negroni.NewRecovery(),
					nocache.New(true),
					negroni.Wrap(
						http.HandlerFunc(fn),
					),
				),
			),
		),
	)
}
<|endoftext|>


<commit_before>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
)

func main() {
	err := filepath.Walk(".", visit)
	if err != nil {
		log.Fatal(err)
	}
}

func visit(path string, info os.FileInfo, err error) error {
	if info.IsDir() {
		if strings.HasPrefix(path, "Externals") {
			return filepath.SkipDir
		}

		return nil
	}

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

	fileBytes, fileReadErr := ioutil.ReadFile(path)
	if fileReadErr != nil {
		return fileReadErr
	}

	fileReader := bytes.NewBuffer(fileBytes)

	fileScanner := bufio.NewScanner(fileReader)

	trimmedLines := []string{}
	for fileScanner.Scan() {
		line := fileScanner.Text()
		trimmedLine := strings.TrimRight(line, " \t\n")
		trimmedLines = append(trimmedLines, trimmedLine)
	}

	if fileScanner.Err() != nil {
		return fileScanner.Err()
	}

	tempFilePath := fmt.Sprintf("%s-tmp", path)
	trimmedTempFile, trimmedFileErr := os.Create(tempFilePath)
	if trimmedFileErr != nil {
		return trimmedFileErr
	}

	for _, trimmedLine := range trimmedLines {
		trimmedLineWithNewline := fmt.Sprintf("%s\n", trimmedLine)
		_, writeErr := trimmedTempFile.WriteString(trimmedLineWithNewline)
		if writeErr != nil {
			return writeErr
		}
	}

	swapContentErr := os.Rename(tempFilePath, path)
	if swapContentErr != nil {
		return swapContentErr
	}

	return nil
}
<commit_msg>Update trim script to handle `.m` files<commit_after>package main

import (
	"bufio"
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"
	"strings"
)

func main() {
	err := filepath.Walk(".", visit)
	if err != nil {
		log.Fatal(err)
	}
}

func visit(path string, info os.FileInfo, err error) error {
	if info.IsDir() {
		if strings.HasPrefix(path, "Externals") {
			return filepath.SkipDir
		}

		return nil
	}

	if !(strings.HasSuffix(path, ".swift") || strings.HasSuffix(path, ".h") || strings.HasSuffix(path, ".md") || strings.HasSuffix(path, ".m")) {
		return nil
	}

	fileBytes, fileReadErr := ioutil.ReadFile(path)
	if fileReadErr != nil {
		return fileReadErr
	}

	fileReader := bytes.NewBuffer(fileBytes)

	fileScanner := bufio.NewScanner(fileReader)

	trimmedLines := []string{}
	for fileScanner.Scan() {
		line := fileScanner.Text()
		trimmedLine := strings.TrimRight(line, " \t\n")
		trimmedLines = append(trimmedLines, trimmedLine)
	}

	if fileScanner.Err() != nil {
		return fileScanner.Err()
	}

	tempFilePath := fmt.Sprintf("%s-tmp", path)
	trimmedTempFile, trimmedFileErr := os.Create(tempFilePath)
	if trimmedFileErr != nil {
		return trimmedFileErr
	}

	for _, trimmedLine := range trimmedLines {
		trimmedLineWithNewline := fmt.Sprintf("%s\n", trimmedLine)
		_, writeErr := trimmedTempFile.WriteString(trimmedLineWithNewline)
		if writeErr != nil {
			return writeErr
		}
	}

	swapContentErr := os.Rename(tempFilePath, path)
	if swapContentErr != nil {
		return swapContentErr
	}

	return nil
}
<|endoftext|>


<commit_before>package app

import (
	"errors"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"unicode"

	"github.com/PuerkitoBio/goquery"
	"github.com/gorilla/context"
	"github.com/gorilla/mux"
	"github.com/gorilla/sessions"
	"github.com/justinas/alice"
	"github.com/kyleterry/sufr/config"
	"github.com/kyleterry/sufr/db"
)

var (
	router   = mux.NewRouter()
	store    = sessions.NewCookieStore([]byte("I gotta glock in my rari")) // TODO(kt): generate secret key instead of using Fetty Wap lyrics
	database *db.SufrDB
)

const (
	VisibilityPrivate = "private"
	VisibilityPublic  = "public"
)

type templatecontext int

const TemplateContext templatecontext = 0

type errorHandler func(http.ResponseWriter, *http.Request) error

func (fn errorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	err := fn(w, r)
	if err != nil {
		log.Printf("Got error while processing the request: %s\n", err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
	}
}

// Sufr is the main application struct. It also implements http.Handler so it can
// be passed directly into ListenAndServe
type Sufr struct {
}

// New created a new pointer to Sufr
func New() *Sufr {
	log.Println("Creating new Sufr instance")
	app := &Sufr{}

	database = db.New(config.DatabaseFile)
	if config.Debug {
		go database.Statsdumper()
	}
	err := database.Open()
	// Panic if we can't open the database
	if err != nil {
		log.Panic(err)
	}

	// This route is used to initially configure the instance
	router.Handle("/config", errorHandler(registrationHandler)).Methods("POST", "GET").Name("config")
	router.Handle("/login", errorHandler(loginHandler)).Methods("POST", "GET").Name("login")
	router.Handle("/logout", errorHandler(logoutHandler)).Methods("POST", "GET").Name("logout")

	all := alice.New(SetSettingsHandler, SetLoggedInHandler, SetActiveTabHandler, LoggingHandler)
	auth := alice.New(AuthHandler)
	auth = auth.Extend(all)

	router.Handle("/", all.Then(errorHandler(urlIndexHandler))).Name("url-index")
	router.Handle("/url/new", auth.Then(errorHandler(urlNewHandler))).Name("url-new")
	router.Handle("/url/submit", auth.Then(errorHandler(urlSubmitHandler))).Methods("POST").Name("url-submit")
	router.Handle("/url/{id:[0-9]+}", all.Then(errorHandler(urlViewHandler))).Name("url-view")
	router.Handle("/url/{id:[0-9]+}/edit", auth.Then(errorHandler(urlEditHandler))).Name("url-edit")
	router.Handle("/url/{id:[0-9]+}/save", auth.Then(errorHandler(urlSaveHandler))).Methods("POST").Name("url-save")
	router.Handle("/url/{id:[0-9]+}/delete", auth.Then(errorHandler(urlDeleteHandler))).Name("url-delete")
	router.Handle("/url/{id:[0-9]+}/toggle-fav", auth.Then(errorHandler(urlFavHandler))).Methods("POST").Name("url-fav-toggle")
	router.Handle("/tag", all.Then(errorHandler(tagIndexHandler))).Name("tag-index")
	router.Handle("/tag/{id:[0-9]+}", all.Then(errorHandler(tagViewHandler))).Name("tag-view")
	router.Handle("/import/shitbucket", auth.Then(errorHandler(shitbucketImportHandler))).Methods("POST", "GET").Name("shitbucket-import")
	router.Handle("/settings", auth.Then(errorHandler(settingsHandler))).Methods("POST", "GET").Name("settings")
	router.Handle("/database-backup", auth.Then(errorHandler(database.BackupHandler))).Methods("GET").Name("database-backup")
	router.PathPrefix("/static").Handler(staticHandler)

	router.NotFoundHandler = errorHandler(func(w http.ResponseWriter, r *http.Request) error {
		w.WriteHeader(http.StatusNotFound)
		return renderTemplate(w, "404", nil)
	})

	return app
}

func (s Sufr) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx := make(map[string]interface{})
	flashes := make(map[string][]interface{})
	session, err := store.Get(r, "flashes")
	if err != nil {
		log.Println(err)
	}
	flashes["danger"] = session.Flashes("danger")
	flashes["success"] = session.Flashes("success")
	flashes["warning"] = session.Flashes("warning")
	ctx["Flashes"] = flashes
	session.Save(r, w)

	context.Set(r, TemplateContext, ctx)

	// Have we configured?
	if !applicationConfigured() {
		if r.RequestURI != "/config" &&
			!strings.HasPrefix(r.RequestURI, "/static") {
			http.Redirect(w, r, reverse("config"), http.StatusSeeOther)
			return
		}
		router.ServeHTTP(w, r)
		return
	}

	// Is it a private only instance?
	if r.RequestURI != "/login" && instancePrivate() && !loggedIn(r) && !strings.HasPrefix(r.RequestURI, "/static") {
		http.Redirect(w, r, reverse("login"), http.StatusSeeOther)
		return
	}

	router.ServeHTTP(w, r)
}

// reverse Uses gorilla mux to give us a uri path by name. This is attached to template Funcs
func reverse(name string, params ...interface{}) string {
	s := make([]string, len(params))
	for _, param := range params {
		s = append(s, fmt.Sprint(param))
	}
	url, err := router.GetRoute(name).URL(s...)
	if err != nil {
		log.Println(err)
	}
	return url.Path
}

// Returns the page title or an error. If there is an error, the url is returned as well.
func getPageTitle(url string) (string, error) {
	doc, err := goquery.NewDocument(url)

	if err != nil {
		return url, err
	}

	title := doc.Find("title").Text()
	return title, nil
}

func parseTags(tagsstring string) []string {
	return strings.FieldsFunc(tagsstring, func(c rune) bool {
		return !unicode.IsLetter(c) && !unicode.IsNumber(c) && !unicode.IsPunct(c)
	})
}

func parseTagsMap(tagsstring string) map[string]struct{} {
	var m = make(map[string]struct{})
	for _, t := range parseTags(tagsstring) {
		m[t] = struct{}{}
	}
	return m
}

func ui64toa(v uint64) string {
	return strconv.FormatUint(v, 10)
}

func applicationConfigured() bool {
	settings, err := database.Get(uint64(1), config.BucketNameRoot)
	if err != nil {
		panic(err)
	}
	if settings != nil {
		return true
	}
	return false
}

func instancePrivate() bool {
	settingsbytes, err := database.Get(uint64(1), config.BucketNameRoot)
	if err != nil {
		panic(err)
	}
	settings := DeserializeSettings(settingsbytes)
	if settings.Visibility == VisibilityPrivate {
		return true
	}
	return false
}

func loggedIn(r *http.Request) bool {
	session, err := store.Get(r, "auth")
	if err != nil {
		panic(err)
	}

	val := session.Values["userID"]
	userID, ok := val.(uint64)

	if !ok || userID <= 0 {
		return false
	}

	user, err := database.Get(userID, config.BucketNameUser)
	if err != nil {
		panic(err)
	}

	if user == nil {
		return false
	}

	return true
}

func isYoutube(url string) bool {
	return strings.Contains(url, "youtube.com/watch")
}

func youtubevid(video string) string {
	u, _ := url.Parse(video)
	return u.Query()["v"][0]
}

func newcontext(values ...interface{}) (map[string]interface{}, error) {
	if len(values)%2 != 0 {
		return nil, errors.New("invalid dict call")
	}
	dict := make(map[string]interface{}, len(values)/2)
	for i := 0; i < len(values); i += 2 {
		key, ok := values[i].(string)
		if !ok {
			return nil, errors.New("dict keys must be strings")
		}
		dict[key] = values[i+1]
	}
	return dict, nil
}
<commit_msg>Adding custom user-agent to the http client for title lookups. Reddit was being problematic.<commit_after>package app

import (
	"errors"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"strconv"
	"strings"
	"unicode"

	"github.com/PuerkitoBio/goquery"
	"github.com/gorilla/context"
	"github.com/gorilla/mux"
	"github.com/gorilla/sessions"
	"github.com/justinas/alice"
	"github.com/kyleterry/sufr/config"
	"github.com/kyleterry/sufr/db"
)

var (
	router   = mux.NewRouter()
	store    = sessions.NewCookieStore([]byte("I gotta glock in my rari")) // TODO(kt): generate secret key instead of using Fetty Wap lyrics
	database *db.SufrDB
)

const (
	VisibilityPrivate = "private"
	VisibilityPublic  = "public"
	SUFRUserAgent     = "Linux:SUFR:v1.0 (by /u/found_dead)"
)

type templatecontext int

const TemplateContext templatecontext = 0

type errorHandler func(http.ResponseWriter, *http.Request) error

func (fn errorHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	err := fn(w, r)
	if err != nil {
		log.Printf("Got error while processing the request: %s\n", err)
		http.Error(w, "Internal Server Error", http.StatusInternalServerError)
	}
}

// Sufr is the main application struct. It also implements http.Handler so it can
// be passed directly into ListenAndServe
type Sufr struct {
}

// New created a new pointer to Sufr
func New() *Sufr {
	log.Println("Creating new Sufr instance")
	app := &Sufr{}

	database = db.New(config.DatabaseFile)
	if config.Debug {
		go database.Statsdumper()
	}
	err := database.Open()
	// Panic if we can't open the database
	if err != nil {
		log.Panic(err)
	}

	// This route is used to initially configure the instance
	router.Handle("/config", errorHandler(registrationHandler)).Methods("POST", "GET").Name("config")
	router.Handle("/login", errorHandler(loginHandler)).Methods("POST", "GET").Name("login")
	router.Handle("/logout", errorHandler(logoutHandler)).Methods("POST", "GET").Name("logout")

	all := alice.New(SetSettingsHandler, SetLoggedInHandler, SetActiveTabHandler, LoggingHandler)
	auth := alice.New(AuthHandler)
	auth = auth.Extend(all)

	router.Handle("/", all.Then(errorHandler(urlIndexHandler))).Name("url-index")
	router.Handle("/url/new", auth.Then(errorHandler(urlNewHandler))).Name("url-new")
	router.Handle("/url/submit", auth.Then(errorHandler(urlSubmitHandler))).Methods("POST").Name("url-submit")
	router.Handle("/url/{id:[0-9]+}", all.Then(errorHandler(urlViewHandler))).Name("url-view")
	router.Handle("/url/{id:[0-9]+}/edit", auth.Then(errorHandler(urlEditHandler))).Name("url-edit")
	router.Handle("/url/{id:[0-9]+}/save", auth.Then(errorHandler(urlSaveHandler))).Methods("POST").Name("url-save")
	router.Handle("/url/{id:[0-9]+}/delete", auth.Then(errorHandler(urlDeleteHandler))).Name("url-delete")
	router.Handle("/url/{id:[0-9]+}/toggle-fav", auth.Then(errorHandler(urlFavHandler))).Methods("POST").Name("url-fav-toggle")
	router.Handle("/tag", all.Then(errorHandler(tagIndexHandler))).Name("tag-index")
	router.Handle("/tag/{id:[0-9]+}", all.Then(errorHandler(tagViewHandler))).Name("tag-view")
	router.Handle("/import/shitbucket", auth.Then(errorHandler(shitbucketImportHandler))).Methods("POST", "GET").Name("shitbucket-import")
	router.Handle("/settings", auth.Then(errorHandler(settingsHandler))).Methods("POST", "GET").Name("settings")
	router.Handle("/database-backup", auth.Then(errorHandler(database.BackupHandler))).Methods("GET").Name("database-backup")
	router.PathPrefix("/static").Handler(staticHandler)

	router.NotFoundHandler = errorHandler(func(w http.ResponseWriter, r *http.Request) error {
		w.WriteHeader(http.StatusNotFound)
		return renderTemplate(w, "404", nil)
	})

	return app
}

func (s Sufr) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	ctx := make(map[string]interface{})
	flashes := make(map[string][]interface{})
	session, err := store.Get(r, "flashes")
	if err != nil {
		log.Println(err)
	}
	flashes["danger"] = session.Flashes("danger")
	flashes["success"] = session.Flashes("success")
	flashes["warning"] = session.Flashes("warning")
	ctx["Flashes"] = flashes
	session.Save(r, w)

	context.Set(r, TemplateContext, ctx)

	// Have we configured?
	if !applicationConfigured() {
		if r.RequestURI != "/config" &&
			!strings.HasPrefix(r.RequestURI, "/static") {
			http.Redirect(w, r, reverse("config"), http.StatusSeeOther)
			return
		}
		router.ServeHTTP(w, r)
		return
	}

	// Is it a private only instance?
	if r.RequestURI != "/login" && instancePrivate() && !loggedIn(r) && !strings.HasPrefix(r.RequestURI, "/static") {
		http.Redirect(w, r, reverse("login"), http.StatusSeeOther)
		return
	}

	router.ServeHTTP(w, r)
}

// reverse Uses gorilla mux to give us a uri path by name. This is attached to template Funcs
func reverse(name string, params ...interface{}) string {
	s := make([]string, len(params))
	for _, param := range params {
		s = append(s, fmt.Sprint(param))
	}
	url, err := router.GetRoute(name).URL(s...)
	if err != nil {
		log.Println(err)
	}
	return url.Path
}

// Returns the page title or an error. If there is an error, the url is returned as well.
func getPageTitle(url string) (string, error) {
	client := &http.Client{}
	req, err := http.NewRequest("GET", url, nil)
	if err != nil {
		return url, err
	}

	req.Header.Set("User-Agent", SUFRUserAgent)

	res, err := client.Do(req)
	if err != nil {
		return url, err
	}

	defer res.Body.Close()

	doc, err := goquery.NewDocumentFromReader(res.Body)

	if err != nil {
		return url, err
	}

	title := doc.Find("title").Text()
	return title, nil
}

func parseTags(tagsstring string) []string {
	return strings.FieldsFunc(tagsstring, func(c rune) bool {
		return !unicode.IsLetter(c) && !unicode.IsNumber(c) && !unicode.IsPunct(c)
	})
}

func parseTagsMap(tagsstring string) map[string]struct{} {
	var m = make(map[string]struct{})
	for _, t := range parseTags(tagsstring) {
		m[t] = struct{}{}
	}
	return m
}

func ui64toa(v uint64) string {
	return strconv.FormatUint(v, 10)
}

func applicationConfigured() bool {
	settings, err := database.Get(uint64(1), config.BucketNameRoot)
	if err != nil {
		panic(err)
	}
	if settings != nil {
		return true
	}
	return false
}

func instancePrivate() bool {
	settingsbytes, err := database.Get(uint64(1), config.BucketNameRoot)
	if err != nil {
		panic(err)
	}
	settings := DeserializeSettings(settingsbytes)
	if settings.Visibility == VisibilityPrivate {
		return true
	}
	return false
}

func loggedIn(r *http.Request) bool {
	session, err := store.Get(r, "auth")
	if err != nil {
		panic(err)
	}

	val := session.Values["userID"]
	userID, ok := val.(uint64)

	if !ok || userID <= 0 {
		return false
	}

	user, err := database.Get(userID, config.BucketNameUser)
	if err != nil {
		panic(err)
	}

	if user == nil {
		return false
	}

	return true
}

func isYoutube(url string) bool {
	return strings.Contains(url, "youtube.com/watch")
}

func youtubevid(video string) string {
	u, _ := url.Parse(video)
	return u.Query()["v"][0]
}

func newcontext(values ...interface{}) (map[string]interface{}, error) {
	if len(values)%2 != 0 {
		return nil, errors.New("invalid dict call")
	}
	dict := make(map[string]interface{}, len(values)/2)
	for i := 0; i < len(values); i += 2 {
		key, ok := values[i].(string)
		if !ok {
			return nil, errors.New("dict keys must be strings")
		}
		dict[key] = values[i+1]
	}
	return dict, nil
}
<|endoftext|>


<commit_before>package app

import (
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"errors"
	"io/ioutil"
	"log"
	"math/big"
	"net"
	"net/http"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"text/template"

	"github.com/elazarl/go-bindata-assetfs"
	"github.com/gorilla/websocket"
	"github.com/kr/pty"
)

type App struct {
	options Options

	upgrader *websocket.Upgrader

	preferences   map[string]interface{}
	titleTemplate *template.Template
}

type Options struct {
	Address     string
	Port        string
	PermitWrite bool
	Credential  string
	RandomUrl   bool
	ProfileFile string
	TitleFormat string
	Command     []string
}

const DefaultProfileFilePath = "~/.gotty"

func New(options Options) (*App, error) {
	titleTemplate, err := template.New("title").Parse(options.TitleFormat)
	if err != nil {
		return nil, errors.New("Title format string syntax error")
	}

	prefString := []byte{}
	prefPath := options.ProfileFile
	if options.ProfileFile == DefaultProfileFilePath {
		prefPath = os.Getenv("HOME") + "/.gotty"
	}
	if _, err = os.Stat(prefPath); os.IsNotExist(err) {
		if options.ProfileFile != DefaultProfileFilePath {
			return nil, err
		}
	} else {
		log.Printf("Loading profile path: %s", prefPath)
		prefString, _ = ioutil.ReadFile(prefPath)
	}
	if len(prefString) == 0 {
		prefString = []byte(("{}"))
	}
	var prefMap map[string]interface{}
	err = json.Unmarshal(prefString, &prefMap)
	if err != nil {
		return nil, err
	}

	return &App{
		options: options,

		upgrader: &websocket.Upgrader{
			ReadBufferSize:  1024,
			WriteBufferSize: 1024,
			Subprotocols:    []string{"gotty"},
		},

		preferences:   prefMap,
		titleTemplate: titleTemplate,
	}, nil
}

func (app *App) Run() error {
	if app.options.PermitWrite {
		log.Printf("Permitting clients to write input to the PTY.")
	}

	path := ""
	if app.options.RandomUrl {
		path += "/" + generateRandomString(8)
	}

	endpoint := app.options.Address + ":" + app.options.Port

	wsHandler := http.HandlerFunc(app.handleWS)
	staticHandler := http.FileServer(
		&assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: "static"},
	)

	var siteMux = http.NewServeMux()
	siteMux.Handle(path+"/", http.StripPrefix(path+"/", staticHandler))
	siteMux.Handle(path+"/ws", wsHandler)

	siteHandler := http.Handler(siteMux)

	if app.options.Credential != "" {
		log.Printf("Using Basic Authentication")
		siteHandler = wrapBasicAuth(siteHandler, app.options.Credential)
	}

	siteHandler = wrapLogger(siteHandler)

	log.Printf(
		"Server is starting with command: %s",
		strings.Join(app.options.Command, " "),
	)
	if app.options.Address != "" {
		log.Printf("URL: %s", "http://"+endpoint+path+"/")
	} else {
		for _, address := range listAddresses() {
			log.Printf("URL: %s", "http://"+address+":"+app.options.Port+path+"/")
		}
	}
	if err := http.ListenAndServe(endpoint, siteHandler); err != nil {
		return err
	}

	return nil
}

func (app *App) handleWS(w http.ResponseWriter, r *http.Request) {
	log.Printf("New client connected: %s", r.RemoteAddr)

	if r.Method != "GET" {
		http.Error(w, "Method not allowed", 405)
		return
	}

	conn, err := app.upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Print("Failed to upgrade connection")
		return
	}

	cmd := exec.Command(app.options.Command[0], app.options.Command[1:]...)
	ptyIo, err := pty.Start(cmd)
	if err != nil {
		log.Print("Failed to execute command")
		return
	}
	log.Printf("Command is running for client %s with PID %d", r.RemoteAddr, cmd.Process.Pid)

	context := &clientContext{
		app:        app,
		request:    r,
		connection: conn,
		command:    cmd,
		pty:        ptyIo,
	}

	context.goHandleClient()
}

func wrapLogger(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Printf("%s %s", r.Method, r.URL.Path)
		handler.ServeHTTP(w, r)
	})
}

func wrapBasicAuth(handler http.Handler, credential string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		token := strings.SplitN(r.Header.Get("Authorization"), " ", 2)

		if len(token) != 2 || strings.ToLower(token[0]) != "basic" {
			w.Header().Set("WWW-Authenticate", `Basic realm="GoTTY"`)
			http.Error(w, "Bad Request", http.StatusUnauthorized)
			return
		}

		payload, err := base64.StdEncoding.DecodeString(token[1])
		if err != nil {
			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
			return
		}

		if credential != string(payload) {
			w.Header().Set("WWW-Authenticate", `Basic realm="GoTTY"`)
			http.Error(w, "authorization failed", http.StatusUnauthorized)
			return
		}

		log.Printf("Basic Authentication Succeeded: %s", r.RemoteAddr)
		handler.ServeHTTP(w, r)
	})
}

func generateRandomString(length int) string {
	const base = 36
	size := big.NewInt(base)
	n := make([]byte, length)
	for i, _ := range n {
		c, _ := rand.Int(rand.Reader, size)
		n[i] = strconv.FormatInt(c.Int64(), base)[0]
	}
	return string(n)
}

func listAddresses() (addresses []string) {
	ifaces, _ := net.Interfaces()

	addresses = make([]string, 0, len(ifaces))

	for _, iface := range ifaces {
		ifAddrs, _ := iface.Addrs()
		for _, ifAddr := range ifAddrs {
			switch v := ifAddr.(type) {
			case *net.IPNet:
				addresses = append(addresses, v.IP.String())
			case *net.IPAddr:
				addresses = append(addresses, v.IP.To16().String())
				addresses = append(addresses, v.IP.To4().String())
			}
		}
	}

	return
}
<commit_msg>Show IPv6 addresses with `[` and `]`<commit_after>package app

import (
	"crypto/rand"
	"encoding/base64"
	"encoding/json"
	"errors"
	"io/ioutil"
	"log"
	"math/big"
	"net"
	"net/http"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"text/template"

	"github.com/elazarl/go-bindata-assetfs"
	"github.com/gorilla/websocket"
	"github.com/kr/pty"
)

type App struct {
	options Options

	upgrader *websocket.Upgrader

	preferences   map[string]interface{}
	titleTemplate *template.Template
}

type Options struct {
	Address     string
	Port        string
	PermitWrite bool
	Credential  string
	RandomUrl   bool
	ProfileFile string
	TitleFormat string
	Command     []string
}

const DefaultProfileFilePath = "~/.gotty"

func New(options Options) (*App, error) {
	titleTemplate, err := template.New("title").Parse(options.TitleFormat)
	if err != nil {
		return nil, errors.New("Title format string syntax error")
	}

	prefString := []byte{}
	prefPath := options.ProfileFile
	if options.ProfileFile == DefaultProfileFilePath {
		prefPath = os.Getenv("HOME") + "/.gotty"
	}
	if _, err = os.Stat(prefPath); os.IsNotExist(err) {
		if options.ProfileFile != DefaultProfileFilePath {
			return nil, err
		}
	} else {
		log.Printf("Loading profile path: %s", prefPath)
		prefString, _ = ioutil.ReadFile(prefPath)
	}
	if len(prefString) == 0 {
		prefString = []byte(("{}"))
	}
	var prefMap map[string]interface{}
	err = json.Unmarshal(prefString, &prefMap)
	if err != nil {
		return nil, err
	}

	return &App{
		options: options,

		upgrader: &websocket.Upgrader{
			ReadBufferSize:  1024,
			WriteBufferSize: 1024,
			Subprotocols:    []string{"gotty"},
		},

		preferences:   prefMap,
		titleTemplate: titleTemplate,
	}, nil
}

func (app *App) Run() error {
	if app.options.PermitWrite {
		log.Printf("Permitting clients to write input to the PTY.")
	}

	path := ""
	if app.options.RandomUrl {
		path += "/" + generateRandomString(8)
	}

	endpoint := app.options.Address + ":" + app.options.Port

	wsHandler := http.HandlerFunc(app.handleWS)
	staticHandler := http.FileServer(
		&assetfs.AssetFS{Asset: Asset, AssetDir: AssetDir, Prefix: "static"},
	)

	var siteMux = http.NewServeMux()
	siteMux.Handle(path+"/", http.StripPrefix(path+"/", staticHandler))
	siteMux.Handle(path+"/ws", wsHandler)

	siteHandler := http.Handler(siteMux)

	if app.options.Credential != "" {
		log.Printf("Using Basic Authentication")
		siteHandler = wrapBasicAuth(siteHandler, app.options.Credential)
	}

	siteHandler = wrapLogger(siteHandler)

	log.Printf(
		"Server is starting with command: %s",
		strings.Join(app.options.Command, " "),
	)
	if app.options.Address != "" {
		log.Printf("URL: %s", "http://"+endpoint+path+"/")
	} else {
		for _, address := range listAddresses() {
			log.Printf("URL: %s", "http://"+address+":"+app.options.Port+path+"/")
		}
	}
	if err := http.ListenAndServe(endpoint, siteHandler); err != nil {
		return err
	}

	return nil
}

func (app *App) handleWS(w http.ResponseWriter, r *http.Request) {
	log.Printf("New client connected: %s", r.RemoteAddr)

	if r.Method != "GET" {
		http.Error(w, "Method not allowed", 405)
		return
	}

	conn, err := app.upgrader.Upgrade(w, r, nil)
	if err != nil {
		log.Print("Failed to upgrade connection")
		return
	}

	cmd := exec.Command(app.options.Command[0], app.options.Command[1:]...)
	ptyIo, err := pty.Start(cmd)
	if err != nil {
		log.Print("Failed to execute command")
		return
	}
	log.Printf("Command is running for client %s with PID %d", r.RemoteAddr, cmd.Process.Pid)

	context := &clientContext{
		app:        app,
		request:    r,
		connection: conn,
		command:    cmd,
		pty:        ptyIo,
	}

	context.goHandleClient()
}

func wrapLogger(handler http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		log.Printf("%s %s", r.Method, r.URL.Path)
		handler.ServeHTTP(w, r)
	})
}

func wrapBasicAuth(handler http.Handler, credential string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		token := strings.SplitN(r.Header.Get("Authorization"), " ", 2)

		if len(token) != 2 || strings.ToLower(token[0]) != "basic" {
			w.Header().Set("WWW-Authenticate", `Basic realm="GoTTY"`)
			http.Error(w, "Bad Request", http.StatusUnauthorized)
			return
		}

		payload, err := base64.StdEncoding.DecodeString(token[1])
		if err != nil {
			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
			return
		}

		if credential != string(payload) {
			w.Header().Set("WWW-Authenticate", `Basic realm="GoTTY"`)
			http.Error(w, "authorization failed", http.StatusUnauthorized)
			return
		}

		log.Printf("Basic Authentication Succeeded: %s", r.RemoteAddr)
		handler.ServeHTTP(w, r)
	})
}

func generateRandomString(length int) string {
	const base = 36
	size := big.NewInt(base)
	n := make([]byte, length)
	for i, _ := range n {
		c, _ := rand.Int(rand.Reader, size)
		n[i] = strconv.FormatInt(c.Int64(), base)[0]
	}
	return string(n)
}

func listAddresses() (addresses []string) {
	ifaces, _ := net.Interfaces()

	addresses = make([]string, 0, len(ifaces))

	for _, iface := range ifaces {
		ifAddrs, _ := iface.Addrs()
		for _, ifAddr := range ifAddrs {
			switch v := ifAddr.(type) {
			case *net.IPNet:
				if v.IP.To4() == nil {
					addresses = append(addresses, "["+v.IP.String()+"]")
				} else {
					addresses = append(addresses, v.IP.String())
				}
			case *net.IPAddr:
				addresses = append(addresses, v.IP.String())
			}
		}
	}

	return
}
<|endoftext|>


<commit_before>package app

import (
	"log"
	"net/http"
	"fmt"

	"github.com/gorilla/mux"
	"github.com/steffen25/golang.zone/database"
	"github.com/steffen25/golang.zone/config"
)

type App struct {
	Config 		config.Config
	Database 	*database.MySQLDB
	Redis 	*database.RedisDB
}

func New(cfg config.Config) *App {
	db, err := database.NewMySQLDB(cfg.MySQL)
	if err != nil {
		log.Fatal(err)
	}
	redis, err := database.NewRedisDB(cfg.Redis)
	if err != nil {
		log.Fatal(err)
	}

	return &App{cfg, db, redis}
}

func (a *App) Run(r *mux.Router)  {
	port := a.Config.Port
	addr := fmt.Sprintf(":%v", port)
	fmt.Printf("APP is listening on port: %d\n", port)
	log.Fatal(http.ListenAndServe(addr, r))
}

func (a *App) IsProd() bool {
	return a.Config.Env == "prod"
}<commit_msg>Added CORS<commit_after>package app

import (
	"log"
	"net/http"
	"fmt"

	"github.com/gorilla/mux"
	"github.com/gorilla/handlers"
	"github.com/steffen25/golang.zone/database"
	"github.com/steffen25/golang.zone/config"
	"os"
)

type App struct {
	Config 		config.Config
	Database 	*database.MySQLDB
	Redis 	*database.RedisDB
}

func New(cfg config.Config) *App {
	db, err := database.NewMySQLDB(cfg.MySQL)
	if err != nil {
		log.Fatal(err)
	}
	redis, err := database.NewRedisDB(cfg.Redis)
	if err != nil {
		log.Fatal(err)
	}

	return &App{cfg, db, redis}
}

func (a *App) Run(r *mux.Router)  {
	port := a.Config.Port
	addr := fmt.Sprintf(":%v", port)
	fmt.Printf("APP is listening on port: %d\n", port)
	log.Fatal(http.ListenAndServe(addr, handlers.CORS()(r)))
}

func (a *App) IsProd() bool {
	return a.Config.Env == "prod"
}<|endoftext|>


<commit_before>package main

//Most code on this page is from http://reprage.com/post/using-golang-to-connect-raspberrypi-and-arduino/

import (
	"bytes"
	"encoding/binary"
	"errors"
	"github.com/huin/goserial"
	"io"
	"io/ioutil"
	"log"
	"strings"
)

var c = &goserial.Config{}
var s io.ReadWriteCloser

//We init the usb connection only once, at boot time.
func init() {
	// Find the device that represents the arduino serial
	// connection.
	c = &goserial.Config{Name: findArduino(), Baud: 9600}
	log.Printf("the USB port the Arduino service will use is %v\n", findArduino())
	s, _ = goserial.OpenPort(c)

}

//rduino converts the string command (on/off) to the one leter command
// the arduino board expects. And call sendArduinoCommand
func arduinoDo(command string, value int) error {
	return sendArduinoCommand(command, uint32(value), s) //u for up, d for down
}

// findArduino looks for the file that represents the Arduino
// serial connection. Returns the fully qualified path to the
// device if we are able to find a likely candidate for an
// Arduino, otherwise an empty string if unable to find
// something that 'looks' like an Arduino device.
func findArduino() string {
	contents, _ := ioutil.ReadDir("/dev")

	// Look for what is mostly likely the Arduino device
	for _, f := range contents {
		if strings.Contains(f.Name(), "tty.usbserial") ||
			strings.Contains(f.Name(), "ttyUSB") ||
			strings.Contains(f.Name(), "ttyACM") {
			return "/dev/" + f.Name()
		}
	}

	// Have not been able to find a USB device that 'looks'
	// like an Arduino.
	return ""
}

// sendArduinoCommand transmits a new command over the nominated serial
// port to the arduino. Returns an error on failure. Each command is
// identified by a single byte and may take one argument (a float).
func sendArduinoCommand(command string, argument uint32, serialPort io.ReadWriteCloser) error {
	if serialPort == nil {
		return errors.New("No serial port to talk to the Arduino.")
	}

	// Package argument for transmission
	value := new(bytes.Buffer)
	err := binary.Write(value, binary.LittleEndian, argument)
	if err != nil {
		return err
	}
	cmd := new(bytes.Buffer)
	err = binary.Write(cmd, binary.LittleEndian, command)
	if err != nil {
		return err
	}

	// Transmit command and argument down the pipe.
	for _, v := range [][]byte{cmd.Bytes(), value.Bytes()} {
		_, err = serialPort.Write(v)
		if err != nil {
			return err
		}
	}

	return nil
}
<commit_msg>proper way to convert string to []byte<commit_after>package main

//Most code on this page is from http://reprage.com/post/using-golang-to-connect-raspberrypi-and-arduino/

import (
	"bytes"
	"encoding/binary"
	"errors"
	"github.com/huin/goserial"
	"io"
	"io/ioutil"
	"log"
	"strings"
)

var c = &goserial.Config{}
var s io.ReadWriteCloser

//We init the usb connection only once, at boot time.
func init() {
	// Find the device that represents the arduino serial
	// connection.
	c = &goserial.Config{Name: findArduino(), Baud: 9600}
	log.Printf("the USB port the Arduino service will use is %v\n", findArduino())
	s, _ = goserial.OpenPort(c)

}

//rduino converts the string command (on/off) to the one leter command
// the arduino board expects. And call sendArduinoCommand
func arduinoDo(command string, value int) error {
	return sendArduinoCommand(command, uint32(value), s) //u for up, d for down
}

// findArduino looks for the file that represents the Arduino
// serial connection. Returns the fully qualified path to the
// device if we are able to find a likely candidate for an
// Arduino, otherwise an empty string if unable to find
// something that 'looks' like an Arduino device.
func findArduino() string {
	contents, _ := ioutil.ReadDir("/dev")

	// Look for what is mostly likely the Arduino device
	for _, f := range contents {
		if strings.Contains(f.Name(), "tty.usbserial") ||
			strings.Contains(f.Name(), "ttyUSB") ||
			strings.Contains(f.Name(), "ttyACM") {
			return "/dev/" + f.Name()
		}
	}

	// Have not been able to find a USB device that 'looks'
	// like an Arduino.
	return ""
}

// sendArduinoCommand transmits a new command over the nominated serial
// port to the arduino. Returns an error on failure. Each command is
// identified by a single byte and may take one argument (a float).
func sendArduinoCommand(command string, argument uint32, serialPort io.ReadWriteCloser) error {
	if serialPort == nil {
		return errors.New("No serial port to talk to the Arduino.")
	}

	// Package argument for transmission
	value := new(bytes.Buffer)
	err := binary.Write(value, binary.LittleEndian, argument)
	if err != nil {
		return err
	}

	// Transmit command and argument down the pipe.
	for _, v := range [][]byte{[]byte(command), value.Bytes()} {
		_, err = serialPort.Write(v)
		if err != nil {
			return err
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"iiif"
	"io/ioutil"
	"log"
	"mime"
	"net/http"
	"net/url"
	"regexp"
	"strings"
)

// AllFeatures is the complete list of everything supported by RAIS at this time
var AllFeatures = &iiif.FeatureSet{
	RegionByPx:  true,
	RegionByPct: true,

	SizeByWhListed: true,
	SizeByW:        true,
	SizeByH:        true,
	SizeByPct:      true,
	SizeByWh:       true,
	SizeByForcedWh: true,
	SizeAboveFull:  true,

	RotationBy90s:     true,
	RotationArbitrary: false,
	Mirroring:         true,

	Default: true,
	Color:   true,
	Gray:    true,
	Bitonal: true,

	Jpg:  true,
	Png:  true,
	Gif:  true,
	Tif:  true,
	Jp2:  false,
	Pdf:  false,
	Webp: false,

	BaseURIRedirect:     true,
	Cors:                true,
	JsonldMediaType:     true,
	ProfileLinkHeader:   false,
	CanonicalLinkHeader: false,
}

func acceptsLD(req *http.Request) bool {
	for _, h := range req.Header["Accept"] {
		for _, accept := range strings.Split(h, ",") {
			if accept == "application/ld+json" {
				return true
			}
		}
	}

	return false
}

// IIIFHandler responds to an IIIF URL request and parses the requested
// transformation within the limits of the handler's capabilities
type IIIFHandler struct {
	Base          *url.URL
	BaseRegex     *regexp.Regexp
	BaseOnlyRegex *regexp.Regexp
	FeatureSet    *iiif.FeatureSet
	InfoPathRegex *regexp.Regexp
	TilePath      string
}

// NewIIIFHandler sets up an IIIFHandler with all features RAIS can support,
// listening based on the given base URL
func NewIIIFHandler(u *url.URL, tp string) *IIIFHandler {
	rprefix := fmt.Sprintf(`^%s`, u.Path)
	return &IIIFHandler{
		Base:          u,
		BaseRegex:     regexp.MustCompile(rprefix + `/([^/]+)`),
		BaseOnlyRegex: regexp.MustCompile(rprefix + `/[^/]+$`),
		InfoPathRegex: regexp.MustCompile(rprefix + `/([^/]+)/info.json$`),
		TilePath:      tp,
		FeatureSet:    AllFeatures,
	}
}

// Route takes an HTTP request and parses it to see what (if any) IIIF
// translation is requested
func (ih *IIIFHandler) Route(w http.ResponseWriter, req *http.Request) {
	// Pull identifier from base so we know if we're even dealing with a valid
	// file in the first place
	p := req.RequestURI
	parts := ih.BaseRegex.FindStringSubmatch(p)

	// If it didn't even match the base, something weird happened, so we just
	// spit out a generic 404
	if parts == nil {
		http.NotFound(w, req)
		return
	}

	id := iiif.ID(parts[1])
	fp := ih.TilePath + "/" + id.Path()

	// Check for base path and redirect if that's all we have
	if ih.BaseOnlyRegex.MatchString(p) {
		http.Redirect(w, req, p+"/info.json", 303)
		return
	}

	// Handle info.json prior to reading the image, in case of cached info
	if ih.InfoPathRegex.MatchString(p) {
		ih.Info(w, req, id, fp)
		return
	}

	// No info path should mean a full command path - start reading the image
	res, err := NewImageResource(id, fp)
	if err != nil {
		e := newImageResError(err)
		http.Error(w, e.Message, e.Code)
		return
	}

	if u := iiif.NewURL(p); u.Valid() {
		ih.Command(w, req, u, res)
		return
	}

	// This means the URI was probably a command, but had an invalid syntax
	http.Error(w, "Invalid IIIF request", 400)
}

// Info responds to a IIIF info request with appropriate JSON based on the
// image's data and the handler's capabilities
func (ih *IIIFHandler) Info(w http.ResponseWriter, req *http.Request, id iiif.ID, fp string) {
	// Check for cached image data first, and use that to create JSON
	json, e := ih.loadInfoJSONFromCache(id)
	if e != nil {
		http.Error(w, e.Message, e.Code)
		return
	}

	// Next, check for an overridden info.json file, and just spit that out
	// directly if it exists
	if json == nil {
		json = ih.loadInfoJSONOverride(id, fp)
	}

	if json == nil {
		json, e = ih.loadInfoJSONFromImageResource(id, fp)
		if e != nil {
			http.Error(w, e.Message, e.Code)
			return
		}
	}

	// Set headers - content type is dependent on client
	ct := "application/json"
	if acceptsLD(req) {
		ct = "application/ld+json"
	}
	w.Header().Set("Content-Type", ct)
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Write(json)
}

func newImageResError(err error) *HandlerError {
	switch err {
	case ErrImageDoesNotExist:
		return NewError("image resource does not exist", 404)
	default:
		return NewError(err.Error(), 500)
	}
}

func (ih *IIIFHandler) loadInfoJSONFromCache(id iiif.ID) ([]byte, *HandlerError) {
	if infoCache == nil {
		return nil, nil
	}

	data, ok := infoCache.Get(id)
	if !ok {
		return nil, nil
	}

	return ih.buildInfoJSON(id, data.(ImageInfo))
}

func (ih *IIIFHandler) loadInfoJSONOverride(id iiif.ID, fp string) []byte {
	// If an override file isn't found or has an error, just skip it
	json, err := ioutil.ReadFile(fp + "-info.json")
	if err != nil {
		return nil
	}

	// If an override file *is* found, replace the id
	fullid := ih.Base.String() + "/" + id.String()
	return bytes.Replace(json, []byte("%ID%"), []byte(fullid), 1)
}

func (ih *IIIFHandler) loadInfoJSONFromImageResource(id iiif.ID, fp string) ([]byte, *HandlerError) {
	log.Printf("Loading image data from image resource (id: %s)", id)
	res, err := NewImageResource(id, fp)
	if err != nil {
		return nil, newImageResError(err)
	}

	d := res.Decoder
	imageInfo := ImageInfo{
		Width:      d.GetWidth(),
		Height:     d.GetHeight(),
		TileWidth:  d.GetTileWidth(),
		TileHeight: d.GetTileHeight(),
		Levels:     d.GetLevels(),
	}

	if infoCache != nil {
		infoCache.Add(id, imageInfo)
	}
	return ih.buildInfoJSON(id, imageInfo)
}

func (ih *IIIFHandler) buildInfoJSON(id iiif.ID, i ImageInfo) ([]byte, *HandlerError) {
	info := ih.FeatureSet.Info()
	info.Width = i.Width
	info.Height = i.Height

	// Set up tile sizes
	if i.TileWidth > 0 {
		var sf []int
		scale := 1
		for x := 0; x < i.Levels; x++ {
			// For sanity's sake, let's not tell viewers they can get at absurdly
			// small sizes
			if info.Width/scale < 16 {
				break
			}
			if info.Height/scale < 16 {
				break
			}
			sf = append(sf, scale)
			scale <<= 1
		}
		info.Tiles = make([]iiif.TileSize, 1)
		info.Tiles[0] = iiif.TileSize{
			Width:        i.TileWidth,
			ScaleFactors: sf,
		}
		if i.TileHeight > 0 {
			info.Tiles[0].Height = i.TileHeight
		}
	}

	// The info id is actually the full URL to the resource, not just its ID
	info.ID = ih.Base.String() + "/" + id.String()

	json, err := json.Marshal(info)
	if err != nil {
		log.Printf("ERROR!  Unable to marshal IIIFInfo response: %s", err)
		return nil, NewError("server error", 500)
	}

	return json, nil
}

// Command handles image processing operations
func (ih *IIIFHandler) Command(w http.ResponseWriter, req *http.Request, u *iiif.URL, res *ImageResource) {
	// Send last modified time
	if err := sendHeaders(w, req, res.FilePath); err != nil {
		return
	}

	// Do we support this request?  If not, return a 501
	if !ih.FeatureSet.Supported(u) {
		http.Error(w, "Feature not supported", 501)
		return
	}

	img, err := res.Apply(u)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	w.Header().Set("Content-Type", mime.TypeByExtension("."+string(u.Format)))
	if err = EncodeImage(w, img, u.Format); err != nil {
		http.Error(w, "Unable to encode", 500)
		log.Printf("Unable to encode to %s: %s", u.Format, err)
		return
	}
}
<commit_msg>Invert validity check for IIIF URL<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"iiif"
	"io/ioutil"
	"log"
	"mime"
	"net/http"
	"net/url"
	"regexp"
	"strings"
)

// AllFeatures is the complete list of everything supported by RAIS at this time
var AllFeatures = &iiif.FeatureSet{
	RegionByPx:  true,
	RegionByPct: true,

	SizeByWhListed: true,
	SizeByW:        true,
	SizeByH:        true,
	SizeByPct:      true,
	SizeByWh:       true,
	SizeByForcedWh: true,
	SizeAboveFull:  true,

	RotationBy90s:     true,
	RotationArbitrary: false,
	Mirroring:         true,

	Default: true,
	Color:   true,
	Gray:    true,
	Bitonal: true,

	Jpg:  true,
	Png:  true,
	Gif:  true,
	Tif:  true,
	Jp2:  false,
	Pdf:  false,
	Webp: false,

	BaseURIRedirect:     true,
	Cors:                true,
	JsonldMediaType:     true,
	ProfileLinkHeader:   false,
	CanonicalLinkHeader: false,
}

func acceptsLD(req *http.Request) bool {
	for _, h := range req.Header["Accept"] {
		for _, accept := range strings.Split(h, ",") {
			if accept == "application/ld+json" {
				return true
			}
		}
	}

	return false
}

// IIIFHandler responds to an IIIF URL request and parses the requested
// transformation within the limits of the handler's capabilities
type IIIFHandler struct {
	Base          *url.URL
	BaseRegex     *regexp.Regexp
	BaseOnlyRegex *regexp.Regexp
	FeatureSet    *iiif.FeatureSet
	InfoPathRegex *regexp.Regexp
	TilePath      string
}

// NewIIIFHandler sets up an IIIFHandler with all features RAIS can support,
// listening based on the given base URL
func NewIIIFHandler(u *url.URL, tp string) *IIIFHandler {
	rprefix := fmt.Sprintf(`^%s`, u.Path)
	return &IIIFHandler{
		Base:          u,
		BaseRegex:     regexp.MustCompile(rprefix + `/([^/]+)`),
		BaseOnlyRegex: regexp.MustCompile(rprefix + `/[^/]+$`),
		InfoPathRegex: regexp.MustCompile(rprefix + `/([^/]+)/info.json$`),
		TilePath:      tp,
		FeatureSet:    AllFeatures,
	}
}

// Route takes an HTTP request and parses it to see what (if any) IIIF
// translation is requested
func (ih *IIIFHandler) Route(w http.ResponseWriter, req *http.Request) {
	// Pull identifier from base so we know if we're even dealing with a valid
	// file in the first place
	p := req.RequestURI
	parts := ih.BaseRegex.FindStringSubmatch(p)

	// If it didn't even match the base, something weird happened, so we just
	// spit out a generic 404
	if parts == nil {
		http.NotFound(w, req)
		return
	}

	id := iiif.ID(parts[1])
	fp := ih.TilePath + "/" + id.Path()

	// Check for base path and redirect if that's all we have
	if ih.BaseOnlyRegex.MatchString(p) {
		http.Redirect(w, req, p+"/info.json", 303)
		return
	}

	// Handle info.json prior to reading the image, in case of cached info
	if ih.InfoPathRegex.MatchString(p) {
		ih.Info(w, req, id, fp)
		return
	}

	// No info path should mean a full command path - start reading the image
	res, err := NewImageResource(id, fp)
	if err != nil {
		e := newImageResError(err)
		http.Error(w, e.Message, e.Code)
		return
	}

	u := iiif.NewURL(p)
	if !u.Valid() {
		// This means the URI was probably a command, but had an invalid syntax
		http.Error(w, "Invalid IIIF request", 400)
		return
	}

	// Attempt to run the command
	ih.Command(w, req, u, res)
}

// Info responds to a IIIF info request with appropriate JSON based on the
// image's data and the handler's capabilities
func (ih *IIIFHandler) Info(w http.ResponseWriter, req *http.Request, id iiif.ID, fp string) {
	// Check for cached image data first, and use that to create JSON
	json, e := ih.loadInfoJSONFromCache(id)
	if e != nil {
		http.Error(w, e.Message, e.Code)
		return
	}

	// Next, check for an overridden info.json file, and just spit that out
	// directly if it exists
	if json == nil {
		json = ih.loadInfoJSONOverride(id, fp)
	}

	if json == nil {
		json, e = ih.loadInfoJSONFromImageResource(id, fp)
		if e != nil {
			http.Error(w, e.Message, e.Code)
			return
		}
	}

	// Set headers - content type is dependent on client
	ct := "application/json"
	if acceptsLD(req) {
		ct = "application/ld+json"
	}
	w.Header().Set("Content-Type", ct)
	w.Header().Set("Access-Control-Allow-Origin", "*")
	w.Write(json)
}

func newImageResError(err error) *HandlerError {
	switch err {
	case ErrImageDoesNotExist:
		return NewError("image resource does not exist", 404)
	default:
		return NewError(err.Error(), 500)
	}
}

func (ih *IIIFHandler) loadInfoJSONFromCache(id iiif.ID) ([]byte, *HandlerError) {
	if infoCache == nil {
		return nil, nil
	}

	data, ok := infoCache.Get(id)
	if !ok {
		return nil, nil
	}

	return ih.buildInfoJSON(id, data.(ImageInfo))
}

func (ih *IIIFHandler) loadInfoJSONOverride(id iiif.ID, fp string) []byte {
	// If an override file isn't found or has an error, just skip it
	json, err := ioutil.ReadFile(fp + "-info.json")
	if err != nil {
		return nil
	}

	// If an override file *is* found, replace the id
	fullid := ih.Base.String() + "/" + id.String()
	return bytes.Replace(json, []byte("%ID%"), []byte(fullid), 1)
}

func (ih *IIIFHandler) loadInfoJSONFromImageResource(id iiif.ID, fp string) ([]byte, *HandlerError) {
	log.Printf("Loading image data from image resource (id: %s)", id)
	res, err := NewImageResource(id, fp)
	if err != nil {
		return nil, newImageResError(err)
	}

	d := res.Decoder
	imageInfo := ImageInfo{
		Width:      d.GetWidth(),
		Height:     d.GetHeight(),
		TileWidth:  d.GetTileWidth(),
		TileHeight: d.GetTileHeight(),
		Levels:     d.GetLevels(),
	}

	if infoCache != nil {
		infoCache.Add(id, imageInfo)
	}
	return ih.buildInfoJSON(id, imageInfo)
}

func (ih *IIIFHandler) buildInfoJSON(id iiif.ID, i ImageInfo) ([]byte, *HandlerError) {
	info := ih.FeatureSet.Info()
	info.Width = i.Width
	info.Height = i.Height

	// Set up tile sizes
	if i.TileWidth > 0 {
		var sf []int
		scale := 1
		for x := 0; x < i.Levels; x++ {
			// For sanity's sake, let's not tell viewers they can get at absurdly
			// small sizes
			if info.Width/scale < 16 {
				break
			}
			if info.Height/scale < 16 {
				break
			}
			sf = append(sf, scale)
			scale <<= 1
		}
		info.Tiles = make([]iiif.TileSize, 1)
		info.Tiles[0] = iiif.TileSize{
			Width:        i.TileWidth,
			ScaleFactors: sf,
		}
		if i.TileHeight > 0 {
			info.Tiles[0].Height = i.TileHeight
		}
	}

	// The info id is actually the full URL to the resource, not just its ID
	info.ID = ih.Base.String() + "/" + id.String()

	json, err := json.Marshal(info)
	if err != nil {
		log.Printf("ERROR!  Unable to marshal IIIFInfo response: %s", err)
		return nil, NewError("server error", 500)
	}

	return json, nil
}

// Command handles image processing operations
func (ih *IIIFHandler) Command(w http.ResponseWriter, req *http.Request, u *iiif.URL, res *ImageResource) {
	// Send last modified time
	if err := sendHeaders(w, req, res.FilePath); err != nil {
		return
	}

	// Do we support this request?  If not, return a 501
	if !ih.FeatureSet.Supported(u) {
		http.Error(w, "Feature not supported", 501)
		return
	}

	img, err := res.Apply(u)
	if err != nil {
		http.Error(w, err.Error(), 500)
		return
	}

	w.Header().Set("Content-Type", mime.TypeByExtension("."+string(u.Format)))
	if err = EncodeImage(w, img, u.Format); err != nil {
		http.Error(w, "Unable to encode", 500)
		log.Printf("Unable to encode to %s: %s", u.Format, err)
		return
	}
}
<|endoftext|>


<commit_before>package sdp

import (
	"io"
	"strconv"
	"time"
)

// An Encoder writes a session description to a buffer.
type Encoder struct {
	w io.Writer
	b writer
}

// NewEncoder returns a new encoder that writes to w.
func NewEncoder(w io.Writer) *Encoder {
	return NewEncoderSize(w, 1024)
}

// NewEncoderSize returns a new encoder that writes to w.
func NewEncoderSize(w io.Writer, size int) *Encoder {
	return &Encoder{w: w, b: make([]byte, 0, size)}
}

// Encode encodes the session description.
func (e *Encoder) Encode(s *Session) error {
	e.Reset()
	e.b = e.b.session(s)
	if e.w != nil {
		return e.Flush()
	}
	return nil
}

// Flush writes encoded bytes to w.
func (e *Encoder) Flush() error {
	if b := e.b; len(b) > 0 {
		n, err := e.w.Write(b)
		e.b = b[:copy(b, b[n:])]
		return err
	}
	return nil
}

// Reset resets encoder state to be empty.
func (e *Encoder) Reset() {
	e.b = e.b[:0]
	//	e.pos, e.newline = 0, false
}

// Bytes returns encoded bytes of the last session description.
// The bytes stop being valid at the next encoder call.
func (e *Encoder) Bytes() []byte {
	return e.b
}

// Bytes returns the encoded session description as str.
func (e *Encoder) String() string {
	return string(e.b)
}

type writer []byte

func (w writer) char(v byte) writer {
	return append(w, v)
}

func (w writer) add(v byte) writer {
	return append(w, '\r', '\n', v, '=')
}

func (w writer) str(v string) writer {
	if v == "" {
		return append(w, '-')
	}
	return append(w, v...)
}

func (w writer) int(v int64) writer {
	return strconv.AppendInt(w, v, 10)
}

func (w writer) sp() writer {
	return append(w, ' ')
}

func (w writer) rn() writer {
	return append(w, '\r', '\n')
}

func (w writer) session(s *Session) writer {
	w = w.str("v=").int(int64(s.Version))
	if s.Origin != nil {
		w = w.add('o').origin(s.Origin)
	}
	w = w.add('s').str(s.Name)
	if s.Information != "" {
		w = w.add('i').str(s.Information)
	}
	if s.URI != "" {
		w = w.add('u').str(s.URI)
	}
	for _, it := range s.Email {
		w = w.add('e').str(it)
	}
	for _, it := range s.Phone {
		w = w.add('p').str(it)
	}
	if s.Connection != nil {
		w = w.add('c').connection(s.Connection)
	}
	for _, b := range s.Bandwidth {
		w = w.add('b').bandwidth(b)
	}
	if len(s.TimeZone) > 0 {
		w = w.add('z').timezone(s.TimeZone)
	}
	for _, it := range s.Key {
		w = w.add('k').key(it)
	}
	w = w.add('t').timing(s.Timing)
	for _, it := range s.Repeat {
		w = w.add('r').repeat(it)
	}
	if s.Mode != "" {
		w = w.add('a').str(s.Mode)
	}
	for _, it := range s.Attributes {
		w = w.add('a').attr(it)
	}
	for _, it := range s.Media {
		w = w.media(it)
	}
	w = w.rn()
	return w
}

func (w writer) origin(o *Origin) writer {
	return w.str(strdef(o.Username, "-")).sp().int(o.SessionID).sp().int(o.SessionVersion).sp().transport(o.Network, o.Type, o.Address)
}

func (w writer) media(m *Media) writer {
	w = w.add('m').str(m.Type).sp().int(int64(m.Port))
	if m.PortNum > 0 {
		w = w.char('/').int(int64(m.PortNum))
	}
	w = w.sp().str(m.Proto)
	if f := m.FormatDescr; f != "" {
		w = w.sp().str(f)
	}
	for _, it := range m.Format {
		w = w.sp().int(int64(it.Payload))
	}
	if m.Information != "" {
		w = w.add('i').str(m.Information)
	}
	for _, it := range m.Connection {
		w = w.add('c').connection(it)
	}
	for _, b := range m.Bandwidth {
		w = w.add('b').bandwidth(b)
	}
	for _, it := range m.Key {
		w = w.add('k').key(it)
	}
	for _, it := range m.Format {
		w = w.format(it)
	}
	if m.Mode != "" {
		w = w.add('a').str(m.Mode)
	}
	for _, it := range m.Attributes {
		w = w.add('a').attr(it)
	}
	return w
}

func (w writer) format(f *Format) writer {
	p := int64(f.Payload)
	if f.Name != "" {
		w = w.add('a').str("rtpmap:").int(p).sp().str(f.Name).char('/').int(int64(f.ClockRate))
		if f.Channels > 0 {
			w = w.char('/').int(int64(f.Channels))
		}
	}
	for _, it := range f.Feedback {
		w = w.add('a').str("rtcp-fb:").int(p).sp().str(it)
	}
	for _, it := range f.Params {
		w = w.add('a').str("fmtp:").int(p).sp().str(it)
	}
	return w
}

func (w writer) attr(a *Attr) writer {
	if a.Value == "" {
		return w.str(a.Name)
	}
	return w.str(a.Name).char(':').str(a.Value)
}

func (w writer) timezone(z []*TimeZone) writer {
	for i, it := range z {
		if i > 0 {
			w = w.char(' ')
		}
		w = w.time(it.Time).sp().duration(it.Offset)
	}
	return w
}

func (w writer) timing(t *Timing) writer {
	if t == nil {
		return w.str("0 0")
	}
	return w.time(t.Start).sp().time(t.Stop)
}

func (w writer) repeat(r *Repeat) writer {
	w.duration(r.Interval).sp().duration(r.Duration)
	for _, it := range r.Offsets {
		w = w.sp().duration(it)
	}
	return w
}

func (w writer) time(t time.Time) writer {
	if t.IsZero() {
		return w.char('0')
	}
	return w.int(int64(t.Sub(epoch).Seconds()))
}

func (w writer) duration(d time.Duration) writer {
	v := int64(d.Seconds())
	switch {
	case v == 0:
		return w.char('0')
	case v%86400 == 0:
		return w.int(v / 86400).char('d')
	case v%3600 == 0:
		return w.int(v / 3600).char('h')
	case v%60 == 0:
		return w.int(v / 60).char('m')
	default:
		return w.int(v)
	}
}

func (w writer) bandwidth(b *Bandwidth) writer {
	return w.str(b.Type).char(':').int(int64(b.Value))
}

func (w writer) key(k *Key) writer {
	if k.Value == "" {
		return w.str(k.Method)
	}
	return w.str(k.Method).char(':').str(k.Value)
}

func (w writer) connection(c *Connection) writer {
	w = w.transport(c.Network, c.Type, c.Address)
	if c.TTL > 0 {
		w = w.char('/').int(int64(c.TTL))
	}
	if c.AddressNum > 1 {
		w = w.char('/').int(int64(c.AddressNum))
	}
	return w
}

func (w writer) transport(network, typ, addr string) writer {
	return w.fields(strdef(network, "IN"), strdef(typ, "IP4"), strdef(addr, "127.0.0.1"))
}

func strdef(v, def string) string {
	if v == "" {
		return def
	}
	return v
}

func (w writer) fields(v ...string) writer {
	for i, s := range v {
		if i > 0 {
			w = append(w, ' ')
		}
		w = append(w, s...)
	}
	return w
}
<commit_msg>Rename rn to crlf<commit_after>package sdp

import (
	"io"
	"strconv"
	"time"
)

// An Encoder writes a session description to a buffer.
type Encoder struct {
	w io.Writer
	b writer
}

// NewEncoder returns a new encoder that writes to w.
func NewEncoder(w io.Writer) *Encoder {
	return NewEncoderSize(w, 1024)
}

// NewEncoderSize returns a new encoder that writes to w.
func NewEncoderSize(w io.Writer, size int) *Encoder {
	return &Encoder{w: w, b: make([]byte, 0, size)}
}

// Encode encodes the session description.
func (e *Encoder) Encode(s *Session) error {
	e.Reset()
	e.b = e.b.session(s)
	if e.w != nil {
		return e.Flush()
	}
	return nil
}

// Flush writes encoded bytes to w.
func (e *Encoder) Flush() error {
	if b := e.b; len(b) > 0 {
		n, err := e.w.Write(b)
		e.b = b[:copy(b, b[n:])]
		return err
	}
	return nil
}

// Reset resets encoder state to be empty.
func (e *Encoder) Reset() {
	e.b = e.b[:0]
	//	e.pos, e.newline = 0, false
}

// Bytes returns encoded bytes of the last session description.
// The bytes stop being valid at the next encoder call.
func (e *Encoder) Bytes() []byte {
	return e.b
}

// Bytes returns the encoded session description as str.
func (e *Encoder) String() string {
	return string(e.b)
}

type writer []byte

func (w writer) char(v byte) writer {
	return append(w, v)
}

func (w writer) add(v byte) writer {
	return append(w, '\r', '\n', v, '=')
}

func (w writer) str(v string) writer {
	if v == "" {
		return append(w, '-')
	}
	return append(w, v...)
}

func (w writer) int(v int64) writer {
	return strconv.AppendInt(w, v, 10)
}

func (w writer) sp() writer {
	return append(w, ' ')
}

func (w writer) crlf() writer {
	return append(w, '\r', '\n')
}

func (w writer) session(s *Session) writer {
	w = w.str("v=").int(int64(s.Version))
	if s.Origin != nil {
		w = w.add('o').origin(s.Origin)
	}
	w = w.add('s').str(s.Name)
	if s.Information != "" {
		w = w.add('i').str(s.Information)
	}
	if s.URI != "" {
		w = w.add('u').str(s.URI)
	}
	for _, it := range s.Email {
		w = w.add('e').str(it)
	}
	for _, it := range s.Phone {
		w = w.add('p').str(it)
	}
	if s.Connection != nil {
		w = w.add('c').connection(s.Connection)
	}
	for _, b := range s.Bandwidth {
		w = w.add('b').bandwidth(b)
	}
	if len(s.TimeZone) > 0 {
		w = w.add('z').timezone(s.TimeZone)
	}
	for _, it := range s.Key {
		w = w.add('k').key(it)
	}
	w = w.add('t').timing(s.Timing)
	for _, it := range s.Repeat {
		w = w.add('r').repeat(it)
	}
	if s.Mode != "" {
		w = w.add('a').str(s.Mode)
	}
	for _, it := range s.Attributes {
		w = w.add('a').attr(it)
	}
	for _, it := range s.Media {
		w = w.media(it)
	}
	return w.crlf()
}

func (w writer) origin(o *Origin) writer {
	return w.str(strdef(o.Username, "-")).sp().int(o.SessionID).sp().int(o.SessionVersion).sp().transport(o.Network, o.Type, o.Address)
}

func (w writer) media(m *Media) writer {
	w = w.add('m').str(m.Type).sp().int(int64(m.Port))
	if m.PortNum > 0 {
		w = w.char('/').int(int64(m.PortNum))
	}
	w = w.sp().str(m.Proto)
	if f := m.FormatDescr; f != "" {
		w = w.sp().str(f)
	}
	for _, it := range m.Format {
		w = w.sp().int(int64(it.Payload))
	}
	if m.Information != "" {
		w = w.add('i').str(m.Information)
	}
	for _, it := range m.Connection {
		w = w.add('c').connection(it)
	}
	for _, b := range m.Bandwidth {
		w = w.add('b').bandwidth(b)
	}
	for _, it := range m.Key {
		w = w.add('k').key(it)
	}
	for _, it := range m.Format {
		w = w.format(it)
	}
	if m.Mode != "" {
		w = w.add('a').str(m.Mode)
	}
	for _, it := range m.Attributes {
		w = w.add('a').attr(it)
	}
	return w
}

func (w writer) format(f *Format) writer {
	p := int64(f.Payload)
	if f.Name != "" {
		w = w.add('a').str("rtpmap:").int(p).sp().str(f.Name).char('/').int(int64(f.ClockRate))
		if f.Channels > 0 {
			w = w.char('/').int(int64(f.Channels))
		}
	}
	for _, it := range f.Feedback {
		w = w.add('a').str("rtcp-fb:").int(p).sp().str(it)
	}
	for _, it := range f.Params {
		w = w.add('a').str("fmtp:").int(p).sp().str(it)
	}
	return w
}

func (w writer) attr(a *Attr) writer {
	if a.Value == "" {
		return w.str(a.Name)
	}
	return w.str(a.Name).char(':').str(a.Value)
}

func (w writer) timezone(z []*TimeZone) writer {
	for i, it := range z {
		if i > 0 {
			w = w.char(' ')
		}
		w = w.time(it.Time).sp().duration(it.Offset)
	}
	return w
}

func (w writer) timing(t *Timing) writer {
	if t == nil {
		return w.str("0 0")
	}
	return w.time(t.Start).sp().time(t.Stop)
}

func (w writer) repeat(r *Repeat) writer {
	w.duration(r.Interval).sp().duration(r.Duration)
	for _, it := range r.Offsets {
		w = w.sp().duration(it)
	}
	return w
}

func (w writer) time(t time.Time) writer {
	if t.IsZero() {
		return w.char('0')
	}
	return w.int(int64(t.Sub(epoch).Seconds()))
}

func (w writer) duration(d time.Duration) writer {
	v := int64(d.Seconds())
	switch {
	case v == 0:
		return w.char('0')
	case v%86400 == 0:
		return w.int(v / 86400).char('d')
	case v%3600 == 0:
		return w.int(v / 3600).char('h')
	case v%60 == 0:
		return w.int(v / 60).char('m')
	default:
		return w.int(v)
	}
}

func (w writer) bandwidth(b *Bandwidth) writer {
	return w.str(b.Type).char(':').int(int64(b.Value))
}

func (w writer) key(k *Key) writer {
	if k.Value == "" {
		return w.str(k.Method)
	}
	return w.str(k.Method).char(':').str(k.Value)
}

func (w writer) connection(c *Connection) writer {
	w = w.transport(c.Network, c.Type, c.Address)
	if c.TTL > 0 {
		w = w.char('/').int(int64(c.TTL))
	}
	if c.AddressNum > 1 {
		w = w.char('/').int(int64(c.AddressNum))
	}
	return w
}

func (w writer) transport(network, typ, addr string) writer {
	return w.fields(strdef(network, "IN"), strdef(typ, "IP4"), strdef(addr, "127.0.0.1"))
}

func strdef(v, def string) string {
	if v == "" {
		return def
	}
	return v
}

func (w writer) fields(v ...string) writer {
	for i, s := range v {
		if i > 0 {
			w = append(w, ' ')
		}
		w = append(w, s...)
	}
	return w
}
<|endoftext|>