<commit_before>package cli

import (
	"errors"
	"fmt"
	"os"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/bitrise-io/bitrise-cli/bitrise"
	models "github.com/bitrise-io/bitrise-cli/models/models_1_0_0"
	"github.com/bitrise-io/go-pathutil/pathutil"
	stepmanModels "github.com/bitrise-io/stepman/models"
	"github.com/codegangsta/cli"
)

const (
	// DefaultBitriseConfigFileName ...
	DefaultBitriseConfigFileName = "bitrise.yml"
	// DefaultSecretsFileName ...
	DefaultSecretsFileName = ".bitrise.secrets.yml"
)

var (
	inventoryPath string
	startTime     time.Time
)

// FailedStepModel ...
type FailedStepModel struct {
	StepName string
	Error    error
}

// StepRunResultsModel ...
type StepRunResultsModel struct {
	TotalStepCount          int
	FailedSteps             []FailedStepModel
	FailedNotImportantSteps []FailedStepModel
	SkippedSteps            []FailedStepModel
}

func buildFailedFatal(err error) {
	runTime := time.Now().Sub(startTime)
	log.Fatal("Build failed error: " + err.Error() + " total run time: " + runTime.String())
}

func printStepStatus(stepRunResults StepRunResultsModel) {
	log.Infof("Out of %d steps, %d failed, %d failed but was marked as not important and %d was skipped",
		stepRunResults.TotalStepCount,
		len(stepRunResults.FailedSteps),
		len(stepRunResults.FailedNotImportantSteps),
		len(stepRunResults.SkippedSteps))

	printStepStatusList(fmt.Sprintf("%d step(s) failed:", len(stepRunResults.FailedSteps)), stepRunResults.FailedSteps)
	printStepStatusList(fmt.Sprintf("%d not important step(s) failed:", len(stepRunResults.FailedNotImportantSteps)), stepRunResults.FailedNotImportantSteps)
	printStepStatusList(fmt.Sprintf("%d step(s) skipped:", len(stepRunResults.SkippedSteps)), stepRunResults.SkippedSteps)
}

func printStepStatusList(header string, stepList []FailedStepModel) {
	if len(stepList) > 0 {
		log.Infof(header)
		for _, step := range stepList {
			if step.Error != nil {
				log.Infof(" * Step: (%s) | error: (%v)", step.StepName, step.Error)
			} else {
				log.Infof(" * Step: (%s)", step.StepName)
			}
		}
	}
}

func exportEnvironmentsList(envsList []stepmanModels.EnvironmentItemModel) error {
	log.Debugln("[BITRISE_CLI] - Exporting environments:", envsList)

	for _, env := range envsList {
		key, value, err := env.GetKeyValuePair()
		if err != nil {
			return err
		}

		opts, err := env.GetOptions()
		if err != nil {
			return err
		}

		if value != "" {
			if err := bitrise.RunEnvmanAdd(key, value, *opts.IsExpand); err != nil {
				log.Errorln("[BITRISE_CLI] - Failed to run envman add")
				return err
			}
		}
	}
	return nil
}

func cleanupStepWorkDir() error {
	stepYMLPth := bitrise.BitriseWorkDirPath + "/current_step.yml"
	if err := bitrise.RemoveFile(stepYMLPth); err != nil {
		return errors.New(fmt.Sprint("Failed to remove step yml: ", err))
	}

	stepDir := bitrise.BitriseWorkStepsDirPath
	if err := bitrise.RemoveDir(stepDir); err != nil {
		return errors.New(fmt.Sprint("Failed to remove step work dir: ", err))
	}
	return nil
}

func activateAndRunSteps(workflow models.WorkflowModel, defaultStepLibSource string) (stepRunResults StepRunResultsModel) {
	log.Debugln("[BITRISE_CLI] - Activating and running steps")

	stepRunResults = StepRunResultsModel{
		TotalStepCount:          0,
		FailedSteps:             []FailedStepModel{},
		FailedNotImportantSteps: []FailedStepModel{},
		SkippedSteps:            []FailedStepModel{},
	}

	registerFailedStepListItem := func(stepListItem models.StepListItemModel, err error) {
		name := ""
		for key := range stepListItem {
			name = key
			break
		}

		failedStep := FailedStepModel{
			StepName: name,
			Error:    err,
		}
		stepRunResults.FailedSteps = append(stepRunResults.FailedSteps, failedStep)
		log.Errorf("Failed to execute step: (%v) error: (%v)", name, err)
	}
	registerFailedStep := func(step stepmanModels.StepModel, err error) {
		failedStep := FailedStepModel{
			StepName: *step.Title,
			Error:    err,
		}

		if *step.IsNotImportant {
			stepRunResults.FailedNotImportantSteps = append(stepRunResults.FailedNotImportantSteps, failedStep)
			log.Errorf("Failed to execute step: (%v) error: (%v), but it's marked as not important", *step.Title, err)
		} else {
			stepRunResults.FailedSteps = append(stepRunResults.FailedSteps, failedStep)
			log.Errorf("Failed to execute step: (%v) error: (%v)", *step.Title, err)
		}
	}
	isBuildFailed := func() bool {
		return len(stepRunResults.FailedSteps) > 0
	}

	for idx, stepListItm := range workflow.Steps {
		stepRunResults.TotalStepCount++
		compositeStepIDStr, workflowStep, err := models.GetStepIDStepDataPair(stepListItm)
		if err != nil {
			registerFailedStepListItem(stepListItm, err)
			continue
		}
		stepIDData, err := models.CreateStepIDDataFromString(compositeStepIDStr, defaultStepLibSource)
		if err != nil {
			registerFailedStepListItem(stepListItm, err)
			continue
		}
		log.Debugf("[BITRISE_CLI] - Running Step: %#v", workflowStep)

		stepDir := bitrise.BitriseWorkStepsDirPath

		if err := bitrise.RunStepmanSetup(stepIDData.SteplibSource); err != nil {
			registerFailedStepListItem(stepListItm, err)
			continue
		}

		if err := cleanupStepWorkDir(); err != nil {
			registerFailedStepListItem(stepListItm, err)
			continue
		}

		stepYMLPth := bitrise.BitriseWorkDirPath + "/current_step.yml"
		if err := bitrise.RunStepmanActivate(stepIDData.SteplibSource, stepIDData.ID, stepIDData.Version, stepDir, stepYMLPth); err != nil {
			registerFailedStepListItem(stepListItm, err)
			continue
		} else {
			log.Debugf("[BITRISE_CLI] - Step activated: %s (%s)", stepIDData.ID, stepIDData.Version)

			specStep, err := bitrise.ReadSpecStep(stepYMLPth)
			log.Debugf("Spec read from YML: %#v\n", specStep)
			if err != nil {
				registerFailedStepListItem(stepListItm, err)
				continue
			}

			if err := models.MergeStepWith(specStep, workflowStep); err != nil {
				registerFailedStepListItem(stepListItm, err)
				continue
			}

			fmt.Println()
			log.Infof("========== (%d) %s ==========", idx, *specStep.Title)
			fmt.Println()

			if isBuildFailed() && !*specStep.IsAlwaysRun {
				log.Infof("A previous step failed and this step was not marked to IsAlwaysRun - skipping step (id:%s) (version:%s)", stepIDData.ID, stepIDData.Version)
				skippedStep := FailedStepModel{
					StepName: *specStep.Title,
				}
				stepRunResults.SkippedSteps = append(stepRunResults.SkippedSteps, skippedStep)
			} else {
				if err := runStep(specStep, stepIDData); err != nil {
					registerFailedStep(specStep, err)
					continue
				}
			}
		}
	}
	return stepRunResults
}

func runStep(step stepmanModels.StepModel, stepIDData models.StepIDData) error {
	log.Debugf("[BITRISE_CLI] - Try running step: %s (%s)", stepIDData.ID, stepIDData.Version)

	// Add step envs
	for _, input := range step.Inputs {
		key, value, err := input.GetKeyValuePair()
		if err != nil {
			return err
		}

		opts, err := input.GetOptions()
		if err != nil {
			return err
		}

		if value != "" {
			log.Debugf("Input: %#v\n", input)
			if err := bitrise.RunEnvmanAdd(key, value, *opts.IsExpand); err != nil {
				log.Errorln("[BITRISE_CLI] - Failed to run envman add")
				return err
			}
		}
	}

	stepDir := bitrise.BitriseWorkStepsDirPath
	stepCmd := stepDir + "/" + "step.sh"
	cmd := []string{"bash", stepCmd}
	fmt.Println("----------------------- OUTPUT ---------------------------")
	if err := bitrise.RunEnvmanRunInDir(bitrise.CurrentDir, cmd, "panic"); err != nil {
		fmt.Println("----------------------------------------------------------")
		log.Errorln("[BITRISE_CLI] - Failed to run envman run")
		return err
	}
	fmt.Println("----------------------------------------------------------")

	log.Debugf("[BITRISE_CLI] - Step executed: %s (%s)", stepIDData.ID, stepIDData.Version)
	return nil
}

func doRun(c *cli.Context) {
	PrintBitriseHeaderASCIIArt()
	log.Debugln("[BITRISE_CLI] - Run")

	startTime = time.Now()

	// Cleanup
	if err := bitrise.CleanupBitriseWorkPath(); err != nil {
		buildFailedFatal(errors.New("[BITRISE_CLI] - Failed to cleanup bitrise work dir: " + err.Error()))
	}

	// Input validation
	bitriseConfigPath := c.String(PathKey)
	if bitriseConfigPath == "" {
		log.Debugln("[BITRISE_CLI] - Workflow path not defined, searching for " + DefaultBitriseConfigFileName + " in current folder...")

		if exist, err := pathutil.IsPathExists("./" + DefaultBitriseConfigFileName); err != nil {
			buildFailedFatal(errors.New("[BITRISE_CLI] - Failed to check path:" + err.Error()))
		} else if !exist {
			log.Fatalln("[BITRISE_CLI] - No workflow yml found")
			buildFailedFatal(errors.New("[BITRISE_CLI] - No workflow yml found"))
		}
		bitriseConfigPath = "./" + DefaultBitriseConfigFileName
	}

	inventoryPath = c.String(InventoryKey)
	if inventoryPath == "" {
		log.Debugln("[BITRISE_CLI] - Inventory path not defined, searching for " + DefaultSecretsFileName + " in current folder...")
		inventoryPath = bitrise.CurrentDir + "/" + DefaultSecretsFileName

		if exist, err := pathutil.IsPathExists(inventoryPath); err != nil {
			buildFailedFatal(errors.New("[BITRISE_CLI] - Failed to check path: " + err.Error()))
		} else if !exist {
			log.Debugln("[BITRISE_CLI] - No inventory yml found")
			inventoryPath = ""
		}
	} else {
		if exist, err := pathutil.IsPathExists(inventoryPath); err != nil {
			buildFailedFatal(errors.New("[BITRISE_CLI] - Failed to check path: " + err.Error()))
		} else if !exist {
			buildFailedFatal(errors.New("[BITRISE_CLI] - No inventory yml found"))
		}
	}
	if inventoryPath != "" {
		if err := bitrise.RunEnvmanEnvstoreTest(inventoryPath); err != nil {
			buildFailedFatal(errors.New("Invalid invetory format: " + err.Error()))
		}

		if err := bitrise.RunCopy(inventoryPath, bitrise.EnvstorePath); err != nil {
			buildFailedFatal(errors.New("Failed to copy inventory: " + err.Error()))
		}
	}

	// Workflow selection
	workflowToRunName := ""
	if len(c.Args()) < 1 {
		log.Infoln("No workfow specified!")
	} else {
		workflowToRunName = c.Args()[0]
	}

	// Envman setup
	if err := os.Setenv(bitrise.EnvstorePathEnvKey, bitrise.EnvstorePath); err != nil {
		buildFailedFatal(errors.New("[BITRISE_CLI] - Failed to add env: " + err.Error()))
	}

	if err := os.Setenv(bitrise.FormattedOutputPathEnvKey, bitrise.FormattedOutputPath); err != nil {
		buildFailedFatal(errors.New("[BITRISE_CLI] - Failed to add env: " + err.Error()))
	}

	if inventoryPath == "" {
		if err := bitrise.RunEnvmanInit(); err != nil {
			buildFailedFatal(errors.New("[BITRISE_CLI] - Failed to run envman init"))
		}
	}

	// Run work flow
	bitriseConfig, err := bitrise.ReadBitriseConfig(bitriseConfigPath)
	if err != nil {
		buildFailedFatal(errors.New("[BITRISE_CLI] - Failed to read Workflow: " + err.Error()))
	}

	// check workflow
	if workflowToRunName == "" {
		// no workflow specified
		//  list all the available ones and then exit
		log.Infoln("The following workflows are available:")
		for wfName := range bitriseConfig.Workflows {
			log.Infoln(" * " + wfName)
		}
		os.Exit(1)
	}

	workflowToRun, exist := bitriseConfig.Workflows[workflowToRunName]
	if !exist {
		buildFailedFatal(errors.New("[BITRISE_CLI] - Specified Workflow (" + workflowToRunName + ") does not exist!"))
	}
	log.Infoln("[BITRISE_CLI] - Running Workflow:", workflowToRunName)

	// App level environment
	if err := exportEnvironmentsList(bitriseConfig.App.Environments); err != nil {
		buildFailedFatal(errors.New("[BITRISE_CLI] - Failed to export App environments: " + err.Error()))
	}

	// Workflow level environments
	if err := exportEnvironmentsList(workflowToRun.Environments); err != nil {
		buildFailedFatal(errors.New("[BITRISE_CLI] - Failed to export Workflow environments: " + err.Error()))
	}

	// Run the Workflow
	stepRunResults := activateAndRunSteps(workflowToRun, bitriseConfig.DefaultStepLibSource)

	// Build finished
	fmt.Println()
	log.Infoln("==> Summary:")
	runTime := time.Now().Sub(startTime)
	printStepStatus(stepRunResults)
	log.Info("Total run time: " + runTime.String())
	if len(stepRunResults.FailedSteps) > 0 {
		log.Fatal("FINISHED but a couple of steps failed - Ouch")
	} else {
		log.Info("DONE - Congrats!!")
		if len(stepRunResults.FailedNotImportantSteps) > 0 {
			log.Warn("P.S.: a couple of non imporatant steps failed")
		}
	}
}
<commit_msg>log success count<commit_after>package cli

import (
	"errors"
	"fmt"
	"os"
	"time"

	log "github.com/Sirupsen/logrus"
	"github.com/bitrise-io/bitrise-cli/bitrise"
	models "github.com/bitrise-io/bitrise-cli/models/models_1_0_0"
	"github.com/bitrise-io/go-pathutil/pathutil"
	stepmanModels "github.com/bitrise-io/stepman/models"
	"github.com/codegangsta/cli"
)

const (
	// DefaultBitriseConfigFileName ...
	DefaultBitriseConfigFileName = "bitrise.yml"
	// DefaultSecretsFileName ...
	DefaultSecretsFileName = ".bitrise.secrets.yml"
)

var (
	inventoryPath string
	startTime     time.Time
)

// FailedStepModel ...
type FailedStepModel struct {
	StepName string
	Error    error
}

// StepRunResultsModel ...
type StepRunResultsModel struct {
	TotalStepCount          int
	FailedSteps             []FailedStepModel
	FailedNotImportantSteps []FailedStepModel
	SkippedSteps            []FailedStepModel
}

func buildFailedFatal(err error) {
	runTime := time.Now().Sub(startTime)
	log.Fatal("Build failed error: " + err.Error() + " total run time: " + runTime.String())
}

func printStepStatus(stepRunResults StepRunResultsModel) {
	failedCount := len(stepRunResults.FailedSteps)
	failedNotImportantCount := len(stepRunResults.FailedNotImportantSteps)
	skippedCount := len(stepRunResults.SkippedSteps)
	successCount := stepRunResults.TotalStepCount - failedCount - failedNotImportantCount - skippedCount

	log.Infof("Out of %d steps, %d was successful, %d failed, %d failed but was marked as not important and %d was skipped",
		stepRunResults.TotalStepCount,
		successCount,
		failedCount,
		failedNotImportantCount,
		skippedCount)

	printStepStatusList("Failed steps:", stepRunResults.FailedSteps)
	printStepStatusList("Failed but not important steps:", stepRunResults.FailedNotImportantSteps)
	printStepStatusList("Skipped steps:", stepRunResults.SkippedSteps)
}

func printStepStatusList(header string, stepList []FailedStepModel) {
	if len(stepList) > 0 {
		log.Infof(header)
		for _, step := range stepList {
			if step.Error != nil {
				log.Infof(" * Step: (%s) | error: (%v)", step.StepName, step.Error)
			} else {
				log.Infof(" * Step: (%s)", step.StepName)
			}
		}
	}
}

func exportEnvironmentsList(envsList []stepmanModels.EnvironmentItemModel) error {
	log.Debugln("[BITRISE_CLI] - Exporting environments:", envsList)

	for _, env := range envsList {
		key, value, err := env.GetKeyValuePair()
		if err != nil {
			return err
		}

		opts, err := env.GetOptions()
		if err != nil {
			return err
		}

		if value != "" {
			if err := bitrise.RunEnvmanAdd(key, value, *opts.IsExpand); err != nil {
				log.Errorln("[BITRISE_CLI] - Failed to run envman add")
				return err
			}
		}
	}
	return nil
}

func cleanupStepWorkDir() error {
	stepYMLPth := bitrise.BitriseWorkDirPath + "/current_step.yml"
	if err := bitrise.RemoveFile(stepYMLPth); err != nil {
		return errors.New(fmt.Sprint("Failed to remove step yml: ", err))
	}

	stepDir := bitrise.BitriseWorkStepsDirPath
	if err := bitrise.RemoveDir(stepDir); err != nil {
		return errors.New(fmt.Sprint("Failed to remove step work dir: ", err))
	}
	return nil
}

func activateAndRunSteps(workflow models.WorkflowModel, defaultStepLibSource string) (stepRunResults StepRunResultsModel) {
	log.Debugln("[BITRISE_CLI] - Activating and running steps")

	stepRunResults = StepRunResultsModel{
		TotalStepCount:          0,
		FailedSteps:             []FailedStepModel{},
		FailedNotImportantSteps: []FailedStepModel{},
		SkippedSteps:            []FailedStepModel{},
	}

	registerFailedStepListItem := func(stepListItem models.StepListItemModel, err error) {
		name := ""
		for key := range stepListItem {
			name = key
			break
		}

		failedStep := FailedStepModel{
			StepName: name,
			Error:    err,
		}
		stepRunResults.FailedSteps = append(stepRunResults.FailedSteps, failedStep)
		log.Errorf("Failed to execute step: (%v) error: (%v)", name, err)
	}
	registerFailedStep := func(step stepmanModels.StepModel, err error) {
		failedStep := FailedStepModel{
			StepName: *step.Title,
			Error:    err,
		}

		if *step.IsNotImportant {
			stepRunResults.FailedNotImportantSteps = append(stepRunResults.FailedNotImportantSteps, failedStep)
			log.Errorf("Failed to execute step: (%v) error: (%v), but it's marked as not important", *step.Title, err)
		} else {
			stepRunResults.FailedSteps = append(stepRunResults.FailedSteps, failedStep)
			log.Errorf("Failed to execute step: (%v) error: (%v)", *step.Title, err)
		}
	}
	isBuildFailed := func() bool {
		return len(stepRunResults.FailedSteps) > 0
	}

	for idx, stepListItm := range workflow.Steps {
		stepRunResults.TotalStepCount++
		compositeStepIDStr, workflowStep, err := models.GetStepIDStepDataPair(stepListItm)
		if err != nil {
			registerFailedStepListItem(stepListItm, err)
			continue
		}
		stepIDData, err := models.CreateStepIDDataFromString(compositeStepIDStr, defaultStepLibSource)
		if err != nil {
			registerFailedStepListItem(stepListItm, err)
			continue
		}
		log.Debugf("[BITRISE_CLI] - Running Step: %#v", workflowStep)

		stepDir := bitrise.BitriseWorkStepsDirPath

		if err := bitrise.RunStepmanSetup(stepIDData.SteplibSource); err != nil {
			registerFailedStepListItem(stepListItm, err)
			continue
		}

		if err := cleanupStepWorkDir(); err != nil {
			registerFailedStepListItem(stepListItm, err)
			continue
		}

		stepYMLPth := bitrise.BitriseWorkDirPath + "/current_step.yml"
		if err := bitrise.RunStepmanActivate(stepIDData.SteplibSource, stepIDData.ID, stepIDData.Version, stepDir, stepYMLPth); err != nil {
			registerFailedStepListItem(stepListItm, err)
			continue
		} else {
			log.Debugf("[BITRISE_CLI] - Step activated: %s (%s)", stepIDData.ID, stepIDData.Version)

			specStep, err := bitrise.ReadSpecStep(stepYMLPth)
			log.Debugf("Spec read from YML: %#v\n", specStep)
			if err != nil {
				registerFailedStepListItem(stepListItm, err)
				continue
			}

			if err := models.MergeStepWith(specStep, workflowStep); err != nil {
				registerFailedStepListItem(stepListItm, err)
				continue
			}

			fmt.Println()
			log.Infof("========== (%d) %s ==========", idx, *specStep.Title)
			fmt.Println()

			if isBuildFailed() && !*specStep.IsAlwaysRun {
				log.Infof("A previous step failed and this step was not marked to IsAlwaysRun - skipping step (id:%s) (version:%s)", stepIDData.ID, stepIDData.Version)
				skippedStep := FailedStepModel{
					StepName: *specStep.Title,
				}
				stepRunResults.SkippedSteps = append(stepRunResults.SkippedSteps, skippedStep)
			} else {
				if err := runStep(specStep, stepIDData); err != nil {
					registerFailedStep(specStep, err)
					continue
				}
			}
		}
	}
	return stepRunResults
}

func runStep(step stepmanModels.StepModel, stepIDData models.StepIDData) error {
	log.Debugf("[BITRISE_CLI] - Try running step: %s (%s)", stepIDData.ID, stepIDData.Version)

	// Add step envs
	for _, input := range step.Inputs {
		key, value, err := input.GetKeyValuePair()
		if err != nil {
			return err
		}

		opts, err := input.GetOptions()
		if err != nil {
			return err
		}

		if value != "" {
			log.Debugf("Input: %#v\n", input)
			if err := bitrise.RunEnvmanAdd(key, value, *opts.IsExpand); err != nil {
				log.Errorln("[BITRISE_CLI] - Failed to run envman add")
				return err
			}
		}
	}

	stepDir := bitrise.BitriseWorkStepsDirPath
	stepCmd := stepDir + "/" + "step.sh"
	cmd := []string{"bash", stepCmd}
	fmt.Println("----------------------- OUTPUT ---------------------------")
	if err := bitrise.RunEnvmanRunInDir(bitrise.CurrentDir, cmd, "panic"); err != nil {
		fmt.Println("----------------------------------------------------------")
		log.Errorln("[BITRISE_CLI] - Failed to run envman run")
		return err
	}
	fmt.Println("----------------------------------------------------------")

	log.Debugf("[BITRISE_CLI] - Step executed: %s (%s)", stepIDData.ID, stepIDData.Version)
	return nil
}

func doRun(c *cli.Context) {
	PrintBitriseHeaderASCIIArt()
	log.Debugln("[BITRISE_CLI] - Run")

	startTime = time.Now()

	// Cleanup
	if err := bitrise.CleanupBitriseWorkPath(); err != nil {
		buildFailedFatal(errors.New("[BITRISE_CLI] - Failed to cleanup bitrise work dir: " + err.Error()))
	}

	// Input validation
	bitriseConfigPath := c.String(PathKey)
	if bitriseConfigPath == "" {
		log.Debugln("[BITRISE_CLI] - Workflow path not defined, searching for " + DefaultBitriseConfigFileName + " in current folder...")

		if exist, err := pathutil.IsPathExists("./" + DefaultBitriseConfigFileName); err != nil {
			buildFailedFatal(errors.New("[BITRISE_CLI] - Failed to check path:" + err.Error()))
		} else if !exist {
			log.Fatalln("[BITRISE_CLI] - No workflow yml found")
			buildFailedFatal(errors.New("[BITRISE_CLI] - No workflow yml found"))
		}
		bitriseConfigPath = "./" + DefaultBitriseConfigFileName
	}

	inventoryPath = c.String(InventoryKey)
	if inventoryPath == "" {
		log.Debugln("[BITRISE_CLI] - Inventory path not defined, searching for " + DefaultSecretsFileName + " in current folder...")
		inventoryPath = bitrise.CurrentDir + "/" + DefaultSecretsFileName

		if exist, err := pathutil.IsPathExists(inventoryPath); err != nil {
			buildFailedFatal(errors.New("[BITRISE_CLI] - Failed to check path: " + err.Error()))
		} else if !exist {
			log.Debugln("[BITRISE_CLI] - No inventory yml found")
			inventoryPath = ""
		}
	} else {
		if exist, err := pathutil.IsPathExists(inventoryPath); err != nil {
			buildFailedFatal(errors.New("[BITRISE_CLI] - Failed to check path: " + err.Error()))
		} else if !exist {
			buildFailedFatal(errors.New("[BITRISE_CLI] - No inventory yml found"))
		}
	}
	if inventoryPath != "" {
		if err := bitrise.RunEnvmanEnvstoreTest(inventoryPath); err != nil {
			buildFailedFatal(errors.New("Invalid invetory format: " + err.Error()))
		}

		if err := bitrise.RunCopy(inventoryPath, bitrise.EnvstorePath); err != nil {
			buildFailedFatal(errors.New("Failed to copy inventory: " + err.Error()))
		}
	}

	// Workflow selection
	workflowToRunName := ""
	if len(c.Args()) < 1 {
		log.Infoln("No workfow specified!")
	} else {
		workflowToRunName = c.Args()[0]
	}

	// Envman setup
	if err := os.Setenv(bitrise.EnvstorePathEnvKey, bitrise.EnvstorePath); err != nil {
		buildFailedFatal(errors.New("[BITRISE_CLI] - Failed to add env: " + err.Error()))
	}

	if err := os.Setenv(bitrise.FormattedOutputPathEnvKey, bitrise.FormattedOutputPath); err != nil {
		buildFailedFatal(errors.New("[BITRISE_CLI] - Failed to add env: " + err.Error()))
	}

	if inventoryPath == "" {
		if err := bitrise.RunEnvmanInit(); err != nil {
			buildFailedFatal(errors.New("[BITRISE_CLI] - Failed to run envman init"))
		}
	}

	// Run work flow
	bitriseConfig, err := bitrise.ReadBitriseConfig(bitriseConfigPath)
	if err != nil {
		buildFailedFatal(errors.New("[BITRISE_CLI] - Failed to read Workflow: " + err.Error()))
	}

	// check workflow
	if workflowToRunName == "" {
		// no workflow specified
		//  list all the available ones and then exit
		log.Infoln("The following workflows are available:")
		for wfName := range bitriseConfig.Workflows {
			log.Infoln(" * " + wfName)
		}
		os.Exit(1)
	}

	workflowToRun, exist := bitriseConfig.Workflows[workflowToRunName]
	if !exist {
		buildFailedFatal(errors.New("[BITRISE_CLI] - Specified Workflow (" + workflowToRunName + ") does not exist!"))
	}
	log.Infoln("[BITRISE_CLI] - Running Workflow:", workflowToRunName)

	// App level environment
	if err := exportEnvironmentsList(bitriseConfig.App.Environments); err != nil {
		buildFailedFatal(errors.New("[BITRISE_CLI] - Failed to export App environments: " + err.Error()))
	}

	// Workflow level environments
	if err := exportEnvironmentsList(workflowToRun.Environments); err != nil {
		buildFailedFatal(errors.New("[BITRISE_CLI] - Failed to export Workflow environments: " + err.Error()))
	}

	// Run the Workflow
	stepRunResults := activateAndRunSteps(workflowToRun, bitriseConfig.DefaultStepLibSource)

	// Build finished
	fmt.Println()
	log.Infoln("==> Summary:")
	runTime := time.Now().Sub(startTime)
	printStepStatus(stepRunResults)
	log.Info("Total run time: " + runTime.String())
	if len(stepRunResults.FailedSteps) > 0 {
		log.Fatal("FINISHED but a couple of steps failed - Ouch")
	} else {
		log.Info("DONE - Congrats!!")
		if len(stepRunResults.FailedNotImportantSteps) > 0 {
			log.Warn("P.S.: a couple of non imporatant steps failed")
		}
	}
}
<|endoftext|>


<commit_before>// Statistics tool
// @author Robin Verlangen

package main

import (
	"bytes"
	"log"
	"math"
	"math/rand"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

type Statistics struct {
	verticalSep    string
	horizontalSep  string
	colPad         int
	terminalWidth  int
	terminalHeight int
}

func (s *Statistics) loadTerminalDimensions() {
	cmd := exec.Command("stty", "size")
	cmd.Stdin = os.Stdin
	out, err := cmd.Output()
	if err != nil {
		log.Fatal(err)
	}
	str := strings.TrimSpace(string(out))
	split := strings.Split(str, " ")
	height, _ := strconv.ParseInt(split[0], 10, 0)
	width, _ := strconv.ParseInt(split[1], 10, 0)
	s.terminalHeight = int(height)
	s.terminalWidth = int(width)
	if verbose {
		log.Println("Terminal dimension %dx%d (WxH)", s.terminalWidth, s.terminalHeight)
	}
}

func (s *Statistics) GetChart(filter *Filter) string {
	maxHeight := s.terminalHeight - 4 // remove some for padding
	maxWidth := int(math.Max(24, float64(s.terminalWidth)))
	data := make([]int32, 0)
	rand.Seed(time.Now().UnixNano())
	for i := 0; i < 24; i++ {
		data = append(data, rand.Int31n(100))
	}

	// Scan for min and max
	minVal := int32(math.MaxInt32)
	maxVal := int32(math.MinInt32)
	for _, val := range data {
		if val < minVal {
			minVal = val
		}
		if val > maxVal {
			maxVal = val
		}
	}
	//log.Printf("min %d", minVal)
	//log.Printf("max %d", maxVal)
	//log.Printf("data %v", data)
	s.colPad = int((maxWidth - len(data)) / len(data))

	// Start to build chart (top to bottom)
	var buf bytes.Buffer
	for line := maxHeight; line >= 0; line-- {
		// Min line val (10/30)=0.3*10
		minLineVal := int32(float32(line) / (float32(maxHeight) / float32(maxVal)))

		// Iterate columns
		for col := 0; col < len(data); col++ {
			// Determine what to write
			if col == 0 && line != 0 {
				// Left axis
				buf.WriteString(s.verticalSep)
			} else if line == 0 {
				// Bottom axis
				buf.WriteString(s.horizontalSep)
			} else {
				// Data point
				colVal := data[col]

				// Normalize value (e.g. height max is 10, max value is 50, this value is 25, needs to be 5)
				//normalizedColVal := int((float32(maxHeight) / float32(maxVal)) * float32(colVal))

				// Print?
				if colVal >= minLineVal {
					buf.WriteString("o")
				} else {
					buf.WriteString(" ")
				}
			}

			// Padding
			if line != 0 {
				buf.WriteString(strings.Repeat(" ", s.colPad)) // Column padding
			} else {
				buf.WriteString(strings.Repeat(s.horizontalSep, s.colPad)) // Horizontal axis padding
			}
		}
		buf.WriteString("\n") // Close previous line
	}
	buf.WriteString("\n") // Final whiteline

	return buf.String()
}

func newStatistics() *Statistics {
	s := &Statistics{
		verticalSep:   "|",
		horizontalSep: "_",
		colPad:        3,
	}
	s.loadTerminalDimensions()
	return s
}
<commit_msg>Secondary data set, errors<commit_after>// Statistics tool
// @author Robin Verlangen

package main

import (
	"bytes"
	"log"
	"math"
	"math/rand"
	"os"
	"os/exec"
	"strconv"
	"strings"
	"time"
)

type Statistics struct {
	verticalSep    string
	horizontalSep  string
	colPad         int
	terminalWidth  int
	terminalHeight int
}

func (s *Statistics) loadTerminalDimensions() {
	cmd := exec.Command("stty", "size")
	cmd.Stdin = os.Stdin
	out, err := cmd.Output()
	if err != nil {
		log.Fatal(err)
	}
	str := strings.TrimSpace(string(out))
	split := strings.Split(str, " ")
	height, _ := strconv.ParseInt(split[0], 10, 0)
	width, _ := strconv.ParseInt(split[1], 10, 0)
	s.terminalHeight = int(height)
	s.terminalWidth = int(width)
	if verbose {
		log.Println("Terminal dimension %dx%d (WxH)", s.terminalWidth, s.terminalHeight)
	}
}

func (s *Statistics) GetChart(filter *Filter) string {
	maxHeight := s.terminalHeight - 4 // remove some for padding
	maxWidth := int(math.Max(24, float64(s.terminalWidth)))

	// Random data (primary is top, secondary is filled, e.g. errors)
	data := make([]int32, 0)
	dataSecondary := make([]int32, 0)
	rand.Seed(time.Now().UnixNano())
	for i := 0; i < 24; i++ {
		v := rand.Int31n(100)
		data = append(data, v)
		v2 := int32(math.Min(float64(v), float64(rand.Int31n(50))))
		dataSecondary = append(dataSecondary, v2)
	}

	// Scan for min and max
	minVal := int32(math.MaxInt32)
	maxVal := int32(math.MinInt32)
	for _, val := range data {
		if val < minVal {
			minVal = val
		}
		if val > maxVal {
			maxVal = val
		}
	}

	// Dynamic column padding
	s.colPad = int((maxWidth - len(data)) / len(data))

	// Start to build chart (top to bottom)
	var buf bytes.Buffer
	for line := maxHeight; line >= 0; line-- {
		// Min line val (10/30)=0.3*10
		minLineVal := int32(float32(line) / (float32(maxHeight) / float32(maxVal)))

		// Iterate columns
		for col := 0; col < len(data); col++ {
			// Determine what to write
			if col == 0 && line != 0 {
				// Left axis
				buf.WriteString(s.verticalSep)
			} else if line == 0 {
				// Bottom axis
				buf.WriteString(s.horizontalSep)
			} else {
				// Data point
				colVal := data[col]
				secondaryColVal := dataSecondary[col]

				// Normalize value (e.g. height max is 10, max value is 50, this value is 25, needs to be 5)
				//normalizedColVal := int((float32(maxHeight) / float32(maxVal)) * float32(colVal))

				// Print?
				if colVal >= minLineVal {
					if secondaryColVal >= minLineVal {
						buf.WriteString("*")
					} else {
						buf.WriteString("o")
					}
				} else {
					buf.WriteString(" ")
				}
			}

			// Padding
			if line != 0 {
				buf.WriteString(strings.Repeat(" ", s.colPad)) // Column padding
			} else {
				buf.WriteString(strings.Repeat(s.horizontalSep, s.colPad)) // Horizontal axis padding
			}
		}
		buf.WriteString("\n") // Close previous line
	}
	buf.WriteString("\n") // Final whiteline

	return buf.String()
}

func newStatistics() *Statistics {
	s := &Statistics{
		verticalSep:   "|",
		horizontalSep: "_",
		colPad:        3,
	}
	s.loadTerminalDimensions()
	return s
}
<|endoftext|>


<commit_before>/*
   Copyright (c) 2014, Percona LLC and/or its affiliates. All rights reserved.

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

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

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

package client

import (
	"code.google.com/p/go.net/websocket"
	"errors"
	"fmt"
	"github.com/percona/cloud-protocol/proto"
	"github.com/percona/cloud-tools/pct"
	"sync"
	"time"
)

const (
	SEND_BUFFER_SIZE = 10
	RECV_BUFFER_SIZE = 10
)

type WebsocketClient struct {
	logger *pct.Logger
	api    pct.APIConnector
	link   string
	// --
	conn        *websocket.Conn
	recvChan    chan *proto.Cmd
	sendChan    chan *proto.Reply
	connectChan chan bool
	errChan     chan error
	backoff     *pct.Backoff
	started     bool
	sendSync    *pct.SyncChan
	recvSync    *pct.SyncChan
	mux         *sync.Mutex
	name        string
	status      *pct.Status
}

func NewWebsocketClient(logger *pct.Logger, api pct.APIConnector, link string) (*WebsocketClient, error) {
	name := logger.Service()
	c := &WebsocketClient{
		logger: logger,
		api:    api,
		link:   link,
		// --
		conn:        nil,
		recvChan:    make(chan *proto.Cmd, RECV_BUFFER_SIZE),
		sendChan:    make(chan *proto.Reply, SEND_BUFFER_SIZE),
		connectChan: make(chan bool, 1),
		errChan:     make(chan error, 2),
		backoff:     pct.NewBackoff(5 * time.Minute),
		sendSync:    pct.NewSyncChan(),
		recvSync:    pct.NewSyncChan(),
		mux:         new(sync.Mutex),
		name:        name,
		status:      pct.NewStatus([]string{name, name + "-link"}),
	}
	return c, nil
}

func (c *WebsocketClient) Start() {
	// Start send() and recv() goroutines, but they wait for successful Connect().
	if !c.started {
		go c.send()
		go c.recv()
		c.started = true
	}
}

func (c *WebsocketClient) Stop() {
	if c.started {
		c.sendSync.Stop()
		c.recvSync.Stop()
		c.sendSync.Wait()
		c.recvSync.Wait()
		c.started = false
	}
}

func (c *WebsocketClient) Connect() {
	c.logger.Debug("Connect:call")
	defer c.logger.Debug("Connect:return")

	for {
		// Wait before attempt to avoid DDoS'ing the API
		// (there are many other agents in the world).
		c.logger.Debug("Connect:backoff.Wait")
		c.status.Update(c.name, "Connect wait")
		time.Sleep(c.backoff.Wait())

		if err := c.ConnectOnce(); err != nil {
			c.logger.Warn(err)
			continue
		}

		// Start/resume send() and recv() goroutines if Start() was called.
		if c.started {
			c.recvSync.Start()
			c.sendSync.Start()
		}

		c.backoff.Success()
		c.notifyConnect(true)
		return // success
	}
}

func (c *WebsocketClient) ConnectOnce() error {
	c.logger.Debug("ConnectOnce:call")
	defer c.logger.Debug("ConnectOnce:return")

	// Make websocket connection.  If this fails, either API is down or the ws
	// address is wrong.
	link := c.api.AgentLink(c.link)
	c.logger.Debug("ConnectOnce:link:" + link)
	config, err := websocket.NewConfig(link, c.api.Origin())
	if err != nil {
		return err
	}
	config.Header.Add("X-Percona-API-Key", c.api.ApiKey())

	c.logger.Debug("ConnectOnce:websocket.DialConfig")
	c.status.Update(c.name, "Connecting "+link)
	conn, err := websocket.DialConfig(config)
	if err != nil {
		return err
	}

	c.mux.Lock()
	defer c.mux.Unlock()
	c.conn = conn
	c.status.Update(c.name, "Connected "+link)

	return nil
}

func (c *WebsocketClient) Disconnect() error {
	c.logger.DebugOffline("Disconnect:call")
	defer c.logger.DebugOffline("Disconnect:return")

	/**
	 * Must guard c.conn here to prevent duplicate notifyConnect() because Close()
	 * causes recv() to error which calls Disconnect(), and normally we want this:
	 * to call Disconnect() on recv error so that notifyConnect(false) is called
	 * to let user know that remote end hung up.  However, when user hangs up
	 * the Disconnect() call from recv() is duplicate and not needed.
	 */
	c.mux.Lock()
	defer c.mux.Unlock()

	var err error
	if c.conn != nil {
		c.logger.DebugOffline("Disconnect:websocket.Conn.Close")
		if err = c.conn.Close(); err != nil {
			// Example: write tcp 127.0.0.1:8000: i/o timeout
			// That ^ can happen if remote end hangs up, then we call Close().
			// Since there's nothing we can do about errors here, we ignore them.
			c.logger.DebugOffline("Disconnect:websocket.Conn.Close:err:" + err.Error())
		}
		c.conn = nil
		c.logger.DebugOffline("Disconnect:disconnected")
		c.status.Update(c.name, "Disconnected")
		c.notifyConnect(false)
	}

	return nil
}

func (c *WebsocketClient) send() {
	/**
	 * Send Reply from agent to API.
	 */

	c.logger.DebugOffline("send:call")
	defer c.logger.DebugOffline("send:return")
	defer c.sendSync.Done()

	for {
		// Wait to start (connect) or be told to stop.
		c.logger.DebugOffline("send:start")
		select {
		case <-c.sendSync.StartChan:
			c.sendSync.StartChan <- true
		case <-c.sendSync.StopChan:
			return
		}

	SEND_LOOP:
		for {
			c.logger.DebugOffline("send:wait")
			select {
			case reply := <-c.sendChan:
				// Got Reply from agent, send to API.
				c.logger.DebugOffline("send:reply:", reply)
				if err := c.Send(reply, 20); err != nil {
					c.logger.DebugOffline("send:err:", err)
					select {
					case c.errChan <- err:
					default:
					}
					break SEND_LOOP
				}
			case <-c.sendSync.StopChan:
				c.logger.DebugOffline("send:stop")
				return
			}
		}

		c.logger.DebugOffline("send:Disconnect")
		c.Disconnect()
	}
}

func (c *WebsocketClient) recv() {
	/**
	 * Receive Cmd from API, forward to agent.
	 */

	c.logger.DebugOffline("recv:call")
	defer c.logger.DebugOffline("recv:return")
	defer c.recvSync.Done()

	for {
		// Wait to start (connect) or be told to stop.
		c.logger.DebugOffline("recv:start")
		select {
		case <-c.recvSync.StartChan:
			c.recvSync.StartChan <- true
		case <-c.recvSync.StopChan:
			return
		}

	RECV_LOOP:
		for {
			// Before blocking on Recv, see if we're supposed to stop.
			c.logger.DebugOffline("recv:wait")
			select {
			case <-c.recvSync.StopChan:
				c.logger.DebugOffline("recv:stop")
				return
			default:
			}

			// Wait for Cmd from API.
			cmd := &proto.Cmd{}
			if err := c.Recv(cmd, 0); err != nil {
				c.logger.DebugOffline("recv:err:", err)
				select {
				case c.errChan <- err:
				default:
				}
				break RECV_LOOP
			}

			// Forward Cmd to agent.
			c.logger.DebugOffline("recv:cmd:", cmd)
			c.recvChan <- cmd
		}

		c.logger.DebugOffline("recv:Disconnect")
		c.Disconnect()
	}
}

func (c *WebsocketClient) SendChan() chan *proto.Reply {
	return c.sendChan
}

func (c *WebsocketClient) RecvChan() chan *proto.Cmd {
	return c.recvChan
}

func (c *WebsocketClient) Send(data interface{}, timeout uint) error {
	c.logger.DebugOffline("Send:call")
	defer c.logger.DebugOffline("Send:return")

	/**
	 * I cannot provoke an EOF error on websocket.Send(), only Receive().
	 * Perhaps EOF errors are only reported on recv?  This only affects
	 * the logger since it's ws send-only: it will need a goroutine blocking
	 * on Recieve() that, upon error, notifies the sending goroutine
	 * to reconnect.
	 */
	if timeout > 0 {
		c.conn.SetWriteDeadline(time.Now().Add(time.Duration(timeout) * time.Second))
	} else {
		c.conn.SetWriteDeadline(time.Time{})
	}
	if err := websocket.JSON.Send(c.conn, data); err != nil {
		c.logger.DebugOffline("Send:err:", err)
		return errors.New(fmt.Sprint("Send:", err))
		return err
	}
	return nil
}

func (c *WebsocketClient) SendBytes(data []byte) error {
	c.logger.DebugOffline("SendBytes:call")
	defer c.logger.DebugOffline("SendBytes:return")
	c.conn.SetWriteDeadline(time.Now().Add(20 * time.Second))
	return websocket.Message.Send(c.conn, data)
}

func (c *WebsocketClient) Recv(data interface{}, timeout uint) error {
	c.logger.DebugOffline("Recv:call")
	defer c.logger.DebugOffline("Recv:return")
	if timeout > 0 {
		t := time.Now().Add(time.Duration(timeout) * time.Second)
		c.conn.SetReadDeadline(t)
	} else {
		c.conn.SetReadDeadline(time.Time{})
	}
	if err := websocket.JSON.Receive(c.conn, data); err != nil {
		c.logger.DebugOffline("Recv:err:", err)
		return errors.New(fmt.Sprint("Recv:", err))
	}
	return nil
}

func (c *WebsocketClient) ConnectChan() chan bool {
	return c.connectChan
}

func (c *WebsocketClient) ErrorChan() chan error {
	return c.errChan
}

func (c *WebsocketClient) Conn() *websocket.Conn {
	return c.conn
}

func (c *WebsocketClient) Status() map[string]string {
	c.status.Update(c.name+"-link", c.api.AgentLink(c.link))
	return c.status.All()
}

func (c *WebsocketClient) notifyConnect(state bool) {
	c.logger.DebugOffline(fmt.Sprintf("notifyConnect:call:%t", state))
	defer c.logger.DebugOffline("notifyConnect:return")
	select {
	case c.connectChan <- state:
	case <-time.After(20):
		c.logger.Error("notifyConnect timeout")
	}
}
<commit_msg>Disable dbg ws.Send: it's too verbose.<commit_after>/*
   Copyright (c) 2014, Percona LLC and/or its affiliates. All rights reserved.

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

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU Affero General Public License for more details.

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

package client

import (
	"code.google.com/p/go.net/websocket"
	"errors"
	"fmt"
	"github.com/percona/cloud-protocol/proto"
	"github.com/percona/cloud-tools/pct"
	"sync"
	"time"
)

const (
	SEND_BUFFER_SIZE = 10
	RECV_BUFFER_SIZE = 10
)

type WebsocketClient struct {
	logger *pct.Logger
	api    pct.APIConnector
	link   string
	// --
	conn        *websocket.Conn
	recvChan    chan *proto.Cmd
	sendChan    chan *proto.Reply
	connectChan chan bool
	errChan     chan error
	backoff     *pct.Backoff
	started     bool
	sendSync    *pct.SyncChan
	recvSync    *pct.SyncChan
	mux         *sync.Mutex
	name        string
	status      *pct.Status
}

func NewWebsocketClient(logger *pct.Logger, api pct.APIConnector, link string) (*WebsocketClient, error) {
	name := logger.Service()
	c := &WebsocketClient{
		logger: logger,
		api:    api,
		link:   link,
		// --
		conn:        nil,
		recvChan:    make(chan *proto.Cmd, RECV_BUFFER_SIZE),
		sendChan:    make(chan *proto.Reply, SEND_BUFFER_SIZE),
		connectChan: make(chan bool, 1),
		errChan:     make(chan error, 2),
		backoff:     pct.NewBackoff(5 * time.Minute),
		sendSync:    pct.NewSyncChan(),
		recvSync:    pct.NewSyncChan(),
		mux:         new(sync.Mutex),
		name:        name,
		status:      pct.NewStatus([]string{name, name + "-link"}),
	}
	return c, nil
}

func (c *WebsocketClient) Start() {
	// Start send() and recv() goroutines, but they wait for successful Connect().
	if !c.started {
		go c.send()
		go c.recv()
		c.started = true
	}
}

func (c *WebsocketClient) Stop() {
	if c.started {
		c.sendSync.Stop()
		c.recvSync.Stop()
		c.sendSync.Wait()
		c.recvSync.Wait()
		c.started = false
	}
}

func (c *WebsocketClient) Connect() {
	c.logger.Debug("Connect:call")
	defer c.logger.Debug("Connect:return")

	for {
		// Wait before attempt to avoid DDoS'ing the API
		// (there are many other agents in the world).
		c.logger.Debug("Connect:backoff.Wait")
		c.status.Update(c.name, "Connect wait")
		time.Sleep(c.backoff.Wait())

		if err := c.ConnectOnce(); err != nil {
			c.logger.Warn(err)
			continue
		}

		// Start/resume send() and recv() goroutines if Start() was called.
		if c.started {
			c.recvSync.Start()
			c.sendSync.Start()
		}

		c.backoff.Success()
		c.notifyConnect(true)
		return // success
	}
}

func (c *WebsocketClient) ConnectOnce() error {
	c.logger.Debug("ConnectOnce:call")
	defer c.logger.Debug("ConnectOnce:return")

	// Make websocket connection.  If this fails, either API is down or the ws
	// address is wrong.
	link := c.api.AgentLink(c.link)
	c.logger.Debug("ConnectOnce:link:" + link)
	config, err := websocket.NewConfig(link, c.api.Origin())
	if err != nil {
		return err
	}
	config.Header.Add("X-Percona-API-Key", c.api.ApiKey())

	c.logger.Debug("ConnectOnce:websocket.DialConfig")
	c.status.Update(c.name, "Connecting "+link)
	conn, err := websocket.DialConfig(config)
	if err != nil {
		return err
	}

	c.mux.Lock()
	defer c.mux.Unlock()
	c.conn = conn
	c.status.Update(c.name, "Connected "+link)

	return nil
}

func (c *WebsocketClient) Disconnect() error {
	c.logger.DebugOffline("Disconnect:call")
	defer c.logger.DebugOffline("Disconnect:return")

	/**
	 * Must guard c.conn here to prevent duplicate notifyConnect() because Close()
	 * causes recv() to error which calls Disconnect(), and normally we want this:
	 * to call Disconnect() on recv error so that notifyConnect(false) is called
	 * to let user know that remote end hung up.  However, when user hangs up
	 * the Disconnect() call from recv() is duplicate and not needed.
	 */
	c.mux.Lock()
	defer c.mux.Unlock()

	var err error
	if c.conn != nil {
		c.logger.DebugOffline("Disconnect:websocket.Conn.Close")
		if err = c.conn.Close(); err != nil {
			// Example: write tcp 127.0.0.1:8000: i/o timeout
			// That ^ can happen if remote end hangs up, then we call Close().
			// Since there's nothing we can do about errors here, we ignore them.
			c.logger.DebugOffline("Disconnect:websocket.Conn.Close:err:" + err.Error())
		}
		c.conn = nil
		c.logger.DebugOffline("Disconnect:disconnected")
		c.status.Update(c.name, "Disconnected")
		c.notifyConnect(false)
	}

	return nil
}

func (c *WebsocketClient) send() {
	/**
	 * Send Reply from agent to API.
	 */

	c.logger.DebugOffline("send:call")
	defer c.logger.DebugOffline("send:return")
	defer c.sendSync.Done()

	for {
		// Wait to start (connect) or be told to stop.
		c.logger.DebugOffline("send:start")
		select {
		case <-c.sendSync.StartChan:
			c.sendSync.StartChan <- true
		case <-c.sendSync.StopChan:
			return
		}

	SEND_LOOP:
		for {
			c.logger.DebugOffline("send:wait")
			select {
			case reply := <-c.sendChan:
				// Got Reply from agent, send to API.
				c.logger.DebugOffline("send:reply:", reply)
				if err := c.Send(reply, 20); err != nil {
					c.logger.DebugOffline("send:err:", err)
					select {
					case c.errChan <- err:
					default:
					}
					break SEND_LOOP
				}
			case <-c.sendSync.StopChan:
				c.logger.DebugOffline("send:stop")
				return
			}
		}

		c.logger.DebugOffline("send:Disconnect")
		c.Disconnect()
	}
}

func (c *WebsocketClient) recv() {
	/**
	 * Receive Cmd from API, forward to agent.
	 */

	c.logger.DebugOffline("recv:call")
	defer c.logger.DebugOffline("recv:return")
	defer c.recvSync.Done()

	for {
		// Wait to start (connect) or be told to stop.
		c.logger.DebugOffline("recv:start")
		select {
		case <-c.recvSync.StartChan:
			c.recvSync.StartChan <- true
		case <-c.recvSync.StopChan:
			return
		}

	RECV_LOOP:
		for {
			// Before blocking on Recv, see if we're supposed to stop.
			c.logger.DebugOffline("recv:wait")
			select {
			case <-c.recvSync.StopChan:
				c.logger.DebugOffline("recv:stop")
				return
			default:
			}

			// Wait for Cmd from API.
			cmd := &proto.Cmd{}
			if err := c.Recv(cmd, 0); err != nil {
				c.logger.DebugOffline("recv:err:", err)
				select {
				case c.errChan <- err:
				default:
				}
				break RECV_LOOP
			}

			// Forward Cmd to agent.
			c.logger.DebugOffline("recv:cmd:", cmd)
			c.recvChan <- cmd
		}

		c.logger.DebugOffline("recv:Disconnect")
		c.Disconnect()
	}
}

func (c *WebsocketClient) SendChan() chan *proto.Reply {
	return c.sendChan
}

func (c *WebsocketClient) RecvChan() chan *proto.Cmd {
	return c.recvChan
}

func (c *WebsocketClient) Send(data interface{}, timeout uint) error {
	// These make the debug output a little too verbose:
	// c.logger.DebugOffline("Send:call")
	// defer c.logger.DebugOffline("Send:return")

	/**
	 * I cannot provoke an EOF error on websocket.Send(), only Receive().
	 * Perhaps EOF errors are only reported on recv?  This only affects
	 * the logger since it's ws send-only: it will need a goroutine blocking
	 * on Recieve() that, upon error, notifies the sending goroutine
	 * to reconnect.
	 */
	if timeout > 0 {
		c.conn.SetWriteDeadline(time.Now().Add(time.Duration(timeout) * time.Second))
	} else {
		c.conn.SetWriteDeadline(time.Time{})
	}
	if err := websocket.JSON.Send(c.conn, data); err != nil {
		c.logger.DebugOffline("Send:err:", err)
		return errors.New(fmt.Sprint("Send:", err))
		return err
	}
	return nil
}

func (c *WebsocketClient) SendBytes(data []byte) error {
	c.logger.DebugOffline("SendBytes:call")
	defer c.logger.DebugOffline("SendBytes:return")
	c.conn.SetWriteDeadline(time.Now().Add(20 * time.Second))
	return websocket.Message.Send(c.conn, data)
}

func (c *WebsocketClient) Recv(data interface{}, timeout uint) error {
	c.logger.DebugOffline("Recv:call")
	defer c.logger.DebugOffline("Recv:return")
	if timeout > 0 {
		t := time.Now().Add(time.Duration(timeout) * time.Second)
		c.conn.SetReadDeadline(t)
	} else {
		c.conn.SetReadDeadline(time.Time{})
	}
	if err := websocket.JSON.Receive(c.conn, data); err != nil {
		c.logger.DebugOffline("Recv:err:", err)
		return errors.New(fmt.Sprint("Recv:", err))
	}
	return nil
}

func (c *WebsocketClient) ConnectChan() chan bool {
	return c.connectChan
}

func (c *WebsocketClient) ErrorChan() chan error {
	return c.errChan
}

func (c *WebsocketClient) Conn() *websocket.Conn {
	return c.conn
}

func (c *WebsocketClient) Status() map[string]string {
	c.status.Update(c.name+"-link", c.api.AgentLink(c.link))
	return c.status.All()
}

func (c *WebsocketClient) notifyConnect(state bool) {
	c.logger.DebugOffline(fmt.Sprintf("notifyConnect:call:%t", state))
	defer c.logger.DebugOffline("notifyConnect:return")
	select {
	case c.connectChan <- state:
	case <-time.After(20):
		c.logger.Error("notifyConnect timeout")
	}
}
<|endoftext|>


<commit_before>/*
Copyright (C) 2017 Ignasi Barrera

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/exec"
	"time"

	"github.com/gorilla/mux"
	mp3 "github.com/hajimehoshi/go-mp3"
	"github.com/hajimehoshi/oto"
	rpio "github.com/stianeikeland/go-rpio"
)

// Zuul is the Abiquo Gatekeeper that will take care of opening the door and welcoming
// all the people that comes into the office
type Zuul struct {
	WelcomeFile string
	DoorPin     int
}

// Init initializes the Zuul API and configures the HTTP routes for its commands
func (z *Zuul) Init() (*mux.Router, error) {
	if _, err := os.Stat(z.WelcomeFile); err != nil {
		return nil, fmt.Errorf("could not open welcome audio file: %v", err)
	}
	// if err := rpio.Open(); err != nil {
	// 	return nil, fmt.Errorf("error initializing GPIO system: %v", err)
	// }

	router := mux.NewRouter()
	router.HandleFunc("/puerta", z.Puerta)
	router.HandleFunc("/palante", z.PlayWelcomeFile)
	router.HandleFunc("/dimelo", z.Say).Methods("POST")
	return router, nil
}

// Puerta handles the requests to open the door and configures the GPIO pin accordingly
func (z *Zuul) Puerta(w http.ResponseWriter, r *http.Request) {
	log.Println("Received door open request")
	pin := rpio.Pin(z.DoorPin)
	pin.Low()
	time.Sleep(500 * time.Millisecond)
	pin.High()
}

// PlayWelcomeFile plays the welcome file in the speakers connected to the Raspberry Pi
func (z *Zuul) PlayWelcomeFile(w http.ResponseWriter, r *http.Request) {
	log.Println("Received welcome audio play request")
	f, err := os.Open(z.WelcomeFile)
	if err != nil {
		http.Error(w, err.Error(), 500)
	}
	defer f.Close()

	d, err := mp3.NewDecoder(f)
	if err != nil {
		http.Error(w, err.Error(), 500)
	}
	defer d.Close()

	p, err := oto.NewPlayer(d.SampleRate(), 2, 2, 8192)
	if err != nil {
		http.Error(w, err.Error(), 500)
	}
	defer p.Close()

	if _, err := io.Copy(p, d); err != nil {
		http.Error(w, err.Error(), 500)
	}
}

// Say plays the given audio text using the configured text to speech tool
func (z *Zuul) Say(w http.ResponseWriter, r *http.Request) {
	text := r.PostFormValue("text")
	log.Printf("Received text so speak: %v", text)
	cmd := exec.Command("espeak", "-ves+f4", "-s150", fmt.Sprintf("\"%v\"", text))
	if err := cmd.Run(); err != nil {
		http.Error(w, err.Error(), 500)
	}
}
<commit_msg>Initialize GPIO<commit_after>/*
Copyright (C) 2017 Ignasi Barrera

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"os/exec"
	"time"

	"github.com/gorilla/mux"
	mp3 "github.com/hajimehoshi/go-mp3"
	"github.com/hajimehoshi/oto"
	rpio "github.com/stianeikeland/go-rpio"
)

// Zuul is the Abiquo Gatekeeper that will take care of opening the door and welcoming
// all the people that comes into the office
type Zuul struct {
	WelcomeFile string
	DoorPin     int
}

// Init initializes the Zuul API and configures the HTTP routes for its commands
func (z *Zuul) Init() (*mux.Router, error) {
	if _, err := os.Stat(z.WelcomeFile); err != nil {
		return nil, fmt.Errorf("could not open welcome audio file: %v", err)
	}
	if err := rpio.Open(); err != nil {
		return nil, fmt.Errorf("error initializing GPIO system: %v", err)
	}

	router := mux.NewRouter()
	router.HandleFunc("/puerta", z.Puerta)
	router.HandleFunc("/palante", z.PlayWelcomeFile)
	router.HandleFunc("/dimelo", z.Say).Methods("POST")
	return router, nil
}

// Puerta handles the requests to open the door and configures the GPIO pin accordingly
func (z *Zuul) Puerta(w http.ResponseWriter, r *http.Request) {
	log.Println("Received door open request")
	pin := rpio.Pin(z.DoorPin)
	pin.Low()
	time.Sleep(500 * time.Millisecond)
	pin.High()
}

// PlayWelcomeFile plays the welcome file in the speakers connected to the Raspberry Pi
func (z *Zuul) PlayWelcomeFile(w http.ResponseWriter, r *http.Request) {
	log.Println("Received welcome audio play request")
	f, err := os.Open(z.WelcomeFile)
	if err != nil {
		http.Error(w, err.Error(), 500)
	}
	defer f.Close()

	d, err := mp3.NewDecoder(f)
	if err != nil {
		http.Error(w, err.Error(), 500)
	}
	defer d.Close()

	p, err := oto.NewPlayer(d.SampleRate(), 2, 2, 8192)
	if err != nil {
		http.Error(w, err.Error(), 500)
	}
	defer p.Close()

	if _, err := io.Copy(p, d); err != nil {
		http.Error(w, err.Error(), 500)
	}
}

// Say plays the given audio text using the configured text to speech tool
func (z *Zuul) Say(w http.ResponseWriter, r *http.Request) {
	text := r.PostFormValue("text")
	log.Printf("Received text so speak: %v", text)
	cmd := exec.Command("espeak", "-ves+f4", "-s150", fmt.Sprintf("\"%v\"", text))
	if err := cmd.Run(); err != nil {
		http.Error(w, err.Error(), 500)
	}
}
<|endoftext|>


<commit_before>package goapns

type Alert struct {
	title string
	body  string
}

func NewAlert() *Alert {
	a := &Alert{"title", "body"}
	return a
}
<commit_msg>Added JSON compatibility to Alert.go<commit_after>package goapns

type Alert struct {
	Title string `json:"title,omitempty"`
	Body  string `json:"body,omitempty"`
}

func NewAlert() *Alert {
	a := &Alert{"title", "body"}
	return a
}
<|endoftext|>


<commit_before>package arn

import (
	"encoding/json"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/animenotifier/shoboi"
	"github.com/animenotifier/twist"
)

// Anime ...
type Anime struct {
	ID            string           `json:"id"`
	Type          string           `json:"type"`
	Title         *AnimeTitle      `json:"title"`
	Image         *AnimeImageTypes `json:"image"`
	FirstChannel  string           `json:"firstChannel"`
	StartDate     string           `json:"startDate"`
	EndDate       string           `json:"endDate"`
	EpisodeCount  int              `json:"episodeCount"`
	EpisodeLength int              `json:"episodeLength"`
	Status        string           `json:"status"`
	NSFW          int              `json:"nsfw"`
	Rating        *AnimeRating     `json:"rating"`
	Summary       string           `json:"summary"`
	Trailers      []*ExternalMedia `json:"trailers"`
	Mappings      []*Mapping       `json:"mappings"`

	// Episodes      []*AnimeEpisode  `json:"episodes"`

	// Adult         bool            `json:"adult"`

	// Hashtag       string          `json:"hashtag"`
	// Source        string          `json:"source"`

	// PageGenerated string          `json:"pageGenerated"`
	// AnilistEdited uint64          `json:"anilistEdited"`
	// Genres        []string        `json:"genres"`
	// Tracks        *AnimeTrackList `json:"tracks"`
	// Links         []AnimeLink     `json:"links"`
	// Studios       []AnimeStudio   `json:"studios"`
	// Relations     []AnimeRelation `json:"relations"`
	// Created       string          `json:"created"`
	// CreatedBy     string          `json:"createdBy"`
	episodes        *AnimeEpisodes
	upcomingEpisode *UpcomingEpisode
}

// AnimeImageTypes ...
type AnimeImageTypes struct {
	Tiny     string `json:"tiny"`
	Small    string `json:"small"`
	Large    string `json:"large"`
	Original string `json:"original"`
}

// AnimeTitle ...
type AnimeTitle struct {
	Romaji    string   `json:"romaji"`
	English   string   `json:"english"`
	Japanese  string   `json:"japanese"`
	Hiragana  string   `json:"hiragana"`
	Canonical string   `json:"canonical"`
	Synonyms  []string `json:"synonyms"`
}

// GetAnime ...
func GetAnime(id string) (*Anime, error) {
	obj, err := DB.Get("Anime", id)

	if err != nil {
		return nil, err
	}

	return obj.(*Anime), nil
}

// Link returns the URI to the anime page.
func (anime *Anime) Link() string {
	return "/anime/" + anime.ID
}

// PrettyJSON ...
func (anime *Anime) PrettyJSON() (string, error) {
	data, err := json.MarshalIndent(anime, "", "    ")
	return string(data), err
}

// Watching ...
func (anime *Anime) Watching() int {
	return 0
}

// AddMapping adds the ID of an external site to the anime.
func (anime *Anime) AddMapping(serviceName string, serviceID string, userID string) {
	// Is the ID valid?
	if serviceID == "" {
		return
	}

	// If it already exists we don't need to add it
	for _, external := range anime.Mappings {
		if external.Service == serviceName && external.ServiceID == serviceID {
			return
		}
	}

	// Add the mapping
	anime.Mappings = append(anime.Mappings, &Mapping{
		Service:   serviceName,
		ServiceID: serviceID,
		Created:   DateTimeUTC(),
		CreatedBy: userID,
	})

	// Add the references
	switch serviceName {
	case "shoboi/anime":
		go anime.RefreshEpisodes()

	case "anilist/anime":
		DB.Set("AniListToAnime", serviceID, &AniListToAnime{
			AnimeID:   anime.ID,
			ServiceID: serviceID,
			Edited:    DateTimeUTC(),
			EditedBy:  userID,
		})

	case "myanimelist/anime":
		DB.Set("MyAnimeListToAnime", serviceID, &MyAnimeListToAnime{
			AnimeID:   anime.ID,
			ServiceID: serviceID,
			Edited:    DateTimeUTC(),
			EditedBy:  userID,
		})
	}
}

// GetMapping returns the external ID for the given service.
func (anime *Anime) GetMapping(name string) string {
	for _, external := range anime.Mappings {
		if external.Service == name {
			return external.ServiceID
		}
	}

	return ""
}

// RemoveMapping removes all mappings with the given service name and ID.
func (anime *Anime) RemoveMapping(name string, id string) bool {
	switch name {
	case "shoboi/anime":
		//
	case "anilist/anime":
		DB.Delete("AniListToAnime", id)
	case "myanimelist/anime":
		DB.Delete("MyAnimeListToAnime", id)
	}

	for index, external := range anime.Mappings {
		if external.Service == name && external.ServiceID == id {
			anime.Mappings = append(anime.Mappings[:index], anime.Mappings[index+1:]...)
			return true
		}
	}

	return false
}

// Episodes returns the anime episodes wrapper.
func (anime *Anime) Episodes() *AnimeEpisodes {
	if anime.episodes == nil {
		record, err := DB.Get("AnimeEpisodes", anime.ID)

		if err != nil {
			return nil
		}

		anime.episodes = record.(*AnimeEpisodes)
	}

	return anime.episodes
}

// RefreshEpisodes will refresh the episode data.
func (anime *Anime) RefreshEpisodes() error {
	// Create blank episode templates
	episodes := anime.Episodes()
	episodes.Items = make([]*AnimeEpisode, anime.EpisodeCount, anime.EpisodeCount)

	for i := 0; i < len(episodes.Items); i++ {
		episodes.Items[i] = NewAnimeEpisode()
	}

	// Shoboi
	episodes.Merge(anime.ShoboiEpisodes())

	// AnimeTwist
	episodes.Merge(anime.TwistEpisodes())

	// Number remaining episodes
	startNumber := 0

	for _, episode := range episodes.Items {
		if episode.Number != -1 {
			startNumber = episode.Number
			continue
		}

		startNumber++
		episode.Number = startNumber
	}

	// Guess airing dates
	oneWeek := 7 * 24 * time.Hour
	lastAiringDate := ""
	timeDifference := oneWeek

	for _, episode := range episodes.Items {
		if episode.AiringDate.Start != "" {
			if lastAiringDate != "" {
				a, _ := time.Parse(time.RFC3339, lastAiringDate)
				b, _ := time.Parse(time.RFC3339, episode.AiringDate.Start)
				timeDifference = b.Sub(a)
			}

			lastAiringDate = episode.AiringDate.Start
			continue
		}

		// Add 1 week to the last known airing date
		nextAiringDate, _ := time.Parse(time.RFC3339, lastAiringDate)
		nextAiringDate = nextAiringDate.Add(timeDifference)

		// Guess start and end time
		episode.AiringDate.Start = nextAiringDate.Format(time.RFC3339)
		episode.AiringDate.End = nextAiringDate.Add(30 * time.Minute).Format(time.RFC3339)

		// Set this date as the new last known airing date
		lastAiringDate = episode.AiringDate.Start
	}

	return episodes.Save()
}

// ShoboiEpisodes returns a slice of episode info from cal.syoboi.jp.
func (anime *Anime) ShoboiEpisodes() []*AnimeEpisode {
	shoboiID := anime.GetMapping("shoboi/anime")

	if shoboiID == "" {
		return nil
	}

	shoboiAnime, err := shoboi.GetAnime(shoboiID)

	if err != nil {
		return nil
	}

	arnEpisodes := []*AnimeEpisode{}
	shoboiEpisodes := shoboiAnime.Episodes()

	for _, shoboiEpisode := range shoboiEpisodes {
		airingDate := shoboiEpisode.AiringDate()

		episode := NewAnimeEpisode()
		episode.Number = shoboiEpisode.Number
		episode.Title = &EpisodeTitle{
			Japanese: shoboiEpisode.TitleJapanese,
		}
		episode.AiringDate = &AnimeAiringDate{
			Start: airingDate.Start,
			End:   airingDate.End,
		}

		arnEpisodes = append(arnEpisodes, episode)
	}

	return arnEpisodes
}

// TwistEpisodes returns a slice of episode info from twist.moe.
func (anime *Anime) TwistEpisodes() []*AnimeEpisode {
	// Get twist.moe feed
	feed, err := twist.GetFeedByKitsuID(anime.ID)

	if err != nil {
		return nil
	}

	episodes := feed.Episodes

	// Sort by episode number
	sort.Slice(episodes, func(a, b int) bool {
		return episodes[a].Number < episodes[b].Number
	})

	arnEpisodes := []*AnimeEpisode{}

	for _, episode := range episodes {
		arnEpisode := NewAnimeEpisode()
		arnEpisode.Number = episode.Number
		arnEpisode.Links = map[string]string{
			"twist.moe": strings.Replace(episode.Link, "https://test.twist.moe/", "https://twist.moe/", 1),
		}

		arnEpisodes = append(arnEpisodes, arnEpisode)
	}

	return arnEpisodes
}

// UpcomingEpisodes ...
func (anime *Anime) UpcomingEpisodes() []*UpcomingEpisode {
	var upcomingEpisodes []*UpcomingEpisode

	now := time.Now().UTC().Format(time.RFC3339)

	for _, episode := range anime.Episodes().Items {
		if episode.AiringDate.Start > now && episode.AiringDate.Start != invalidDate {
			upcomingEpisodes = append(upcomingEpisodes, &UpcomingEpisode{
				Anime:   anime,
				Episode: episode,
			})
		}
	}

	return upcomingEpisodes
}

// UpcomingEpisode ...
func (anime *Anime) UpcomingEpisode() *UpcomingEpisode {
	if anime.upcomingEpisode != nil {
		return anime.upcomingEpisode
	}

	now := time.Now().UTC().Format(time.RFC3339)

	for _, episode := range anime.Episodes().Items {
		if episode.AiringDate.Start > now && episode.AiringDate.Start != invalidDate {
			anime.upcomingEpisode = &UpcomingEpisode{
				Anime:   anime,
				Episode: episode,
			}

			return anime.upcomingEpisode
		}
	}

	return nil
}

// EpisodeCountString ...
func (anime *Anime) EpisodeCountString() string {
	if anime.EpisodeCount == 0 {
		return "?"
	}

	return strconv.Itoa(anime.EpisodeCount)
}

// EpisodeByNumber returns the episode with the given number.
func (anime *Anime) EpisodeByNumber(number int) *AnimeEpisode {
	for _, episode := range anime.Episodes().Items {
		if number == episode.Number {
			return episode
		}
	}

	return nil
}

// StreamAnime returns a stream of all anime.
func StreamAnime() (chan *Anime, error) {
	objects, err := DB.All("Anime")
	return objects.(chan *Anime), err
}

// MustStreamAnime returns a stream of all anime.
func MustStreamAnime() chan *Anime {
	stream, err := StreamAnime()
	PanicOnError(err)
	return stream
}

// AllAnime returns a slice of all anime.
func AllAnime() ([]*Anime, error) {
	var all []*Anime

	stream, err := StreamAnime()

	if err != nil {
		return nil, err
	}

	for obj := range stream {
		all = append(all, obj)
	}

	return all, nil
}

// FilterAnime filters all anime by a custom function.
func FilterAnime(filter func(*Anime) bool) ([]*Anime, error) {
	var filtered []*Anime

	channel := make(chan *Anime)
	err := DB.Scan("Anime", channel)

	if err != nil {
		return filtered, err
	}

	for obj := range channel {
		if filter(obj) {
			filtered = append(filtered, obj)
		}
	}

	return filtered, nil
}

// GetAiringAnime ...
func GetAiringAnime() ([]*Anime, error) {
	beforeTime := time.Now().Add(-6 * 30 * 24 * time.Hour)
	beforeTimeString := beforeTime.Format(time.RFC3339)

	return FilterAnime(func(anime *Anime) bool {
		if (anime.Type != "tv" && anime.Type != "movie") || anime.NSFW == 1 || anime.StartDate < beforeTimeString {
			return false
		}

		// return anime.UpcomingEpisode() != nil || anime.Status == "upcoming"
		return anime.Status == "current" || anime.Status == "upcoming"
	})
}

// MustSave saves the anime in the database.
func (anime *Anime) MustSave() {
	PanicOnError(anime.Save())
}
<commit_msg>Remove episodes<commit_after>package arn

import (
	"encoding/json"
	"sort"
	"strconv"
	"strings"
	"time"

	"github.com/animenotifier/shoboi"
	"github.com/animenotifier/twist"
)

// Anime ...
type Anime struct {
	ID            string           `json:"id"`
	Type          string           `json:"type"`
	Title         *AnimeTitle      `json:"title"`
	Image         *AnimeImageTypes `json:"image"`
	FirstChannel  string           `json:"firstChannel"`
	StartDate     string           `json:"startDate"`
	EndDate       string           `json:"endDate"`
	EpisodeCount  int              `json:"episodeCount"`
	EpisodeLength int              `json:"episodeLength"`
	Status        string           `json:"status"`
	NSFW          int              `json:"nsfw"`
	Rating        *AnimeRating     `json:"rating"`
	Summary       string           `json:"summary"`
	Trailers      []*ExternalMedia `json:"trailers"`
	Mappings      []*Mapping       `json:"mappings"`

	// Episodes      []*AnimeEpisode  `json:"episodes"`

	// Adult         bool            `json:"adult"`

	// Hashtag       string          `json:"hashtag"`
	// Source        string          `json:"source"`

	// PageGenerated string          `json:"pageGenerated"`
	// AnilistEdited uint64          `json:"anilistEdited"`
	// Genres        []string        `json:"genres"`
	// Tracks        *AnimeTrackList `json:"tracks"`
	// Links         []AnimeLink     `json:"links"`
	// Studios       []AnimeStudio   `json:"studios"`
	// Relations     []AnimeRelation `json:"relations"`
	// Created       string          `json:"created"`
	// CreatedBy     string          `json:"createdBy"`
	episodes        *AnimeEpisodes
	upcomingEpisode *UpcomingEpisode
}

// AnimeImageTypes ...
type AnimeImageTypes struct {
	Tiny     string `json:"tiny"`
	Small    string `json:"small"`
	Large    string `json:"large"`
	Original string `json:"original"`
}

// AnimeTitle ...
type AnimeTitle struct {
	Romaji    string   `json:"romaji"`
	English   string   `json:"english"`
	Japanese  string   `json:"japanese"`
	Hiragana  string   `json:"hiragana"`
	Canonical string   `json:"canonical"`
	Synonyms  []string `json:"synonyms"`
}

// GetAnime ...
func GetAnime(id string) (*Anime, error) {
	obj, err := DB.Get("Anime", id)

	if err != nil {
		return nil, err
	}

	return obj.(*Anime), nil
}

// Link returns the URI to the anime page.
func (anime *Anime) Link() string {
	return "/anime/" + anime.ID
}

// PrettyJSON ...
func (anime *Anime) PrettyJSON() (string, error) {
	data, err := json.MarshalIndent(anime, "", "    ")
	return string(data), err
}

// Watching ...
func (anime *Anime) Watching() int {
	return 0
}

// AddMapping adds the ID of an external site to the anime.
func (anime *Anime) AddMapping(serviceName string, serviceID string, userID string) {
	// Is the ID valid?
	if serviceID == "" {
		return
	}

	// If it already exists we don't need to add it
	for _, external := range anime.Mappings {
		if external.Service == serviceName && external.ServiceID == serviceID {
			return
		}
	}

	// Add the mapping
	anime.Mappings = append(anime.Mappings, &Mapping{
		Service:   serviceName,
		ServiceID: serviceID,
		Created:   DateTimeUTC(),
		CreatedBy: userID,
	})

	// Add the references
	switch serviceName {
	case "shoboi/anime":
		go anime.RefreshEpisodes()

	case "anilist/anime":
		DB.Set("AniListToAnime", serviceID, &AniListToAnime{
			AnimeID:   anime.ID,
			ServiceID: serviceID,
			Edited:    DateTimeUTC(),
			EditedBy:  userID,
		})

	case "myanimelist/anime":
		DB.Set("MyAnimeListToAnime", serviceID, &MyAnimeListToAnime{
			AnimeID:   anime.ID,
			ServiceID: serviceID,
			Edited:    DateTimeUTC(),
			EditedBy:  userID,
		})
	}
}

// GetMapping returns the external ID for the given service.
func (anime *Anime) GetMapping(name string) string {
	for _, external := range anime.Mappings {
		if external.Service == name {
			return external.ServiceID
		}
	}

	return ""
}

// RemoveMapping removes all mappings with the given service name and ID.
func (anime *Anime) RemoveMapping(name string, id string) bool {
	switch name {
	case "shoboi/anime":
		eps := anime.Episodes()

		if eps != nil {
			eps.Items = eps.Items[:0]
			eps.Save()
		}
	case "anilist/anime":
		DB.Delete("AniListToAnime", id)
	case "myanimelist/anime":
		DB.Delete("MyAnimeListToAnime", id)
	}

	for index, external := range anime.Mappings {
		if external.Service == name && external.ServiceID == id {
			anime.Mappings = append(anime.Mappings[:index], anime.Mappings[index+1:]...)
			return true
		}
	}

	return false
}

// Episodes returns the anime episodes wrapper.
func (anime *Anime) Episodes() *AnimeEpisodes {
	if anime.episodes == nil {
		record, err := DB.Get("AnimeEpisodes", anime.ID)

		if err != nil {
			return nil
		}

		anime.episodes = record.(*AnimeEpisodes)
	}

	return anime.episodes
}

// RefreshEpisodes will refresh the episode data.
func (anime *Anime) RefreshEpisodes() error {
	// Create blank episode templates
	episodes := anime.Episodes()
	episodes.Items = make([]*AnimeEpisode, anime.EpisodeCount, anime.EpisodeCount)

	for i := 0; i < len(episodes.Items); i++ {
		episodes.Items[i] = NewAnimeEpisode()
	}

	// Shoboi
	episodes.Merge(anime.ShoboiEpisodes())

	// AnimeTwist
	episodes.Merge(anime.TwistEpisodes())

	// Number remaining episodes
	startNumber := 0

	for _, episode := range episodes.Items {
		if episode.Number != -1 {
			startNumber = episode.Number
			continue
		}

		startNumber++
		episode.Number = startNumber
	}

	// Guess airing dates
	oneWeek := 7 * 24 * time.Hour
	lastAiringDate := ""
	timeDifference := oneWeek

	for _, episode := range episodes.Items {
		if episode.AiringDate.Start != "" {
			if lastAiringDate != "" {
				a, _ := time.Parse(time.RFC3339, lastAiringDate)
				b, _ := time.Parse(time.RFC3339, episode.AiringDate.Start)
				timeDifference = b.Sub(a)
			}

			lastAiringDate = episode.AiringDate.Start
			continue
		}

		// Add 1 week to the last known airing date
		nextAiringDate, _ := time.Parse(time.RFC3339, lastAiringDate)
		nextAiringDate = nextAiringDate.Add(timeDifference)

		// Guess start and end time
		episode.AiringDate.Start = nextAiringDate.Format(time.RFC3339)
		episode.AiringDate.End = nextAiringDate.Add(30 * time.Minute).Format(time.RFC3339)

		// Set this date as the new last known airing date
		lastAiringDate = episode.AiringDate.Start
	}

	return episodes.Save()
}

// ShoboiEpisodes returns a slice of episode info from cal.syoboi.jp.
func (anime *Anime) ShoboiEpisodes() []*AnimeEpisode {
	shoboiID := anime.GetMapping("shoboi/anime")

	if shoboiID == "" {
		return nil
	}

	shoboiAnime, err := shoboi.GetAnime(shoboiID)

	if err != nil {
		return nil
	}

	arnEpisodes := []*AnimeEpisode{}
	shoboiEpisodes := shoboiAnime.Episodes()

	for _, shoboiEpisode := range shoboiEpisodes {
		airingDate := shoboiEpisode.AiringDate()

		episode := NewAnimeEpisode()
		episode.Number = shoboiEpisode.Number
		episode.Title = &EpisodeTitle{
			Japanese: shoboiEpisode.TitleJapanese,
		}
		episode.AiringDate = &AnimeAiringDate{
			Start: airingDate.Start,
			End:   airingDate.End,
		}

		arnEpisodes = append(arnEpisodes, episode)
	}

	return arnEpisodes
}

// TwistEpisodes returns a slice of episode info from twist.moe.
func (anime *Anime) TwistEpisodes() []*AnimeEpisode {
	// Get twist.moe feed
	feed, err := twist.GetFeedByKitsuID(anime.ID)

	if err != nil {
		return nil
	}

	episodes := feed.Episodes

	// Sort by episode number
	sort.Slice(episodes, func(a, b int) bool {
		return episodes[a].Number < episodes[b].Number
	})

	arnEpisodes := []*AnimeEpisode{}

	for _, episode := range episodes {
		arnEpisode := NewAnimeEpisode()
		arnEpisode.Number = episode.Number
		arnEpisode.Links = map[string]string{
			"twist.moe": strings.Replace(episode.Link, "https://test.twist.moe/", "https://twist.moe/", 1),
		}

		arnEpisodes = append(arnEpisodes, arnEpisode)
	}

	return arnEpisodes
}

// UpcomingEpisodes ...
func (anime *Anime) UpcomingEpisodes() []*UpcomingEpisode {
	var upcomingEpisodes []*UpcomingEpisode

	now := time.Now().UTC().Format(time.RFC3339)

	for _, episode := range anime.Episodes().Items {
		if episode.AiringDate.Start > now && episode.AiringDate.Start != invalidDate {
			upcomingEpisodes = append(upcomingEpisodes, &UpcomingEpisode{
				Anime:   anime,
				Episode: episode,
			})
		}
	}

	return upcomingEpisodes
}

// UpcomingEpisode ...
func (anime *Anime) UpcomingEpisode() *UpcomingEpisode {
	if anime.upcomingEpisode != nil {
		return anime.upcomingEpisode
	}

	now := time.Now().UTC().Format(time.RFC3339)

	for _, episode := range anime.Episodes().Items {
		if episode.AiringDate.Start > now && episode.AiringDate.Start != invalidDate {
			anime.upcomingEpisode = &UpcomingEpisode{
				Anime:   anime,
				Episode: episode,
			}

			return anime.upcomingEpisode
		}
	}

	return nil
}

// EpisodeCountString ...
func (anime *Anime) EpisodeCountString() string {
	if anime.EpisodeCount == 0 {
		return "?"
	}

	return strconv.Itoa(anime.EpisodeCount)
}

// EpisodeByNumber returns the episode with the given number.
func (anime *Anime) EpisodeByNumber(number int) *AnimeEpisode {
	for _, episode := range anime.Episodes().Items {
		if number == episode.Number {
			return episode
		}
	}

	return nil
}

// StreamAnime returns a stream of all anime.
func StreamAnime() (chan *Anime, error) {
	objects, err := DB.All("Anime")
	return objects.(chan *Anime), err
}

// MustStreamAnime returns a stream of all anime.
func MustStreamAnime() chan *Anime {
	stream, err := StreamAnime()
	PanicOnError(err)
	return stream
}

// AllAnime returns a slice of all anime.
func AllAnime() ([]*Anime, error) {
	var all []*Anime

	stream, err := StreamAnime()

	if err != nil {
		return nil, err
	}

	for obj := range stream {
		all = append(all, obj)
	}

	return all, nil
}

// FilterAnime filters all anime by a custom function.
func FilterAnime(filter func(*Anime) bool) ([]*Anime, error) {
	var filtered []*Anime

	channel := make(chan *Anime)
	err := DB.Scan("Anime", channel)

	if err != nil {
		return filtered, err
	}

	for obj := range channel {
		if filter(obj) {
			filtered = append(filtered, obj)
		}
	}

	return filtered, nil
}

// GetAiringAnime ...
func GetAiringAnime() ([]*Anime, error) {
	beforeTime := time.Now().Add(-6 * 30 * 24 * time.Hour)
	beforeTimeString := beforeTime.Format(time.RFC3339)

	return FilterAnime(func(anime *Anime) bool {
		if (anime.Type != "tv" && anime.Type != "movie") || anime.NSFW == 1 || anime.StartDate < beforeTimeString {
			return false
		}

		// return anime.UpcomingEpisode() != nil || anime.Status == "upcoming"
		return anime.Status == "current" || anime.Status == "upcoming"
	})
}

// MustSave saves the anime in the database.
func (anime *Anime) MustSave() {
	PanicOnError(anime.Save())
}
<|endoftext|>


<commit_before>//package resp provides methods to parse and format resp(redis protocal) data
package resp

import (
	"io"
	"fmt"
	"errors"
	"bytes"
	"strconv"
)

const (
	T_SimpleString = '+'
	T_Error	   = '-'
	T_Integer	   = ':'
	T_BulkString  = '$'
	T_Array	   = '*'
)

type Data struct {
	T byte
	str []byte
	num int64
	array []*Data
	isNil bool
}

//string\bulkString
func (d *Data) String() string {
	return string(d.str)
}

func (d *Data) Byte() []byte {
	return d.str
}

func (d *Data) Error() string {
	return string(d.str)
}

func (d *Data) Integer() int64 {
	return d.num
}

func (d *Data) Array() []*Data {
	return d.array
}

func (d *Data) IsNil() bool {
	return d.isNil == true
}

//format *Data to []byte
func FormatData(d *Data) []byte {
	ret := new(bytes.Buffer)
	ret.WriteByte(d.T)
	switch d.T {
	case T_SimpleString, T_Error:
		fmt.Fprintf(ret, "%s\r\n", d.str)
	case T_Integer:
		fmt.Fprintf(ret, "%d\r\n", d.num)
	case T_BulkString:
		fmt.Fprintf(ret, "%d\r\n%s\r\n", len(d.str), string(d.str))
	case T_Array:
		fmt.Fprintf(ret, "%d\r\n", len(d.array))
		for index := range d.array {
			ret.Write(FormatData(d.array[index]))
		}
	}
	return ret.Bytes()
}

//read from io.Reader, and parse into *Data
func ReadData(r io.Reader) (*Data, error) {

	var buf []byte
	var err error

	buf = make([]byte, 1)
	_, err = io.ReadFull(r, buf)
	if nil != err {
		return nil, errors.New("err_first_byte " + err.Error())
	}

	ret := &Data{}
	switch buf[0] {
		case '+':
			ret.T = T_SimpleString
			ret.str, err = readRespLine(r)

		case '-':
			ret.T = T_Error
			ret.str, err = readRespLine(r)

		case ':':
			ret.T = T_Integer
			ret.num, err = readRespIntLine(r)

		case '$':
			var lenBulkString int64
			lenBulkString, err = readRespIntLine(r)

			ret.T = T_BulkString
			if -1 == lenBulkString {
				ret.isNil = true
			} else {
				ret.str, err = readRespN(r, lenBulkString)
				//read the followed \r\n
				_, err = readRespN(r, 2)
			}

		case '*':
			var lenArray int64
			var i int64
			lenArray, err = readRespIntLine(r)

			ret.T = T_Array
			if -1 == lenArray {
				ret.isNil = true
			} else if nil==err {
				ret.array = make([]*Data, lenArray)
				for i=0; i<lenArray; i++ {
					ret.array[i], err = ReadData(r)
				}
			}
		default:
			//Inline Commands
			tmp, err := readRespLine(r)
			if nil==err {
				tmpSlice := bytes.Fields(tmp)

				ret.T = T_Array
				ret.array = make([]*Data, len(tmpSlice))
				for index := range tmpSlice {
					t := &Data{}
					t.str = tmpSlice[index]
					t.T = T_SimpleString
					ret.array[index] = t
				}
			}

	}
	return ret, err
}

//读取当前行，并去掉最后的\r\n
func readRespLine(r io.Reader) ([]byte, error) {

	var n, i int
	var err error
	var buf []byte
	var ret *bytes.Buffer

	buf = make([]byte, 1)
	ret = &bytes.Buffer{}

	for {
		n, err = io.ReadFull(r, buf)
		if nil != err {
			return nil, err
		}

		if n==0 {
			continue
		}

		i++
		ret.WriteByte(buf[0])
		if '\n' == buf[0] {
			break
		}
	}

	return ret.Next(i-2), nil
}

//读取N个字节，并去掉最后的\r\n
func readRespN(r io.Reader, n int64) ([]byte, error) {
	var err error
	var ret []byte

	ret = make([]byte, n)
	_, err = io.ReadFull(r, ret)
	if nil!=err {
		ret = nil
	}
	return ret, err
}

//读取当前行的数字，并去掉最后的\r\n
func readRespIntLine(r io.Reader) (int64, error) {
	line, err := readRespLine(r)
	if nil!=err {
		return 0, err
	}
	return strconv.ParseInt(string(line), 10, 64)
}
<commit_msg>return EOF<commit_after>//package resp provides methods to parse and format resp(redis protocal) data
package resp

import (
	"io"
	"fmt"
	"errors"
	"bytes"
	"strconv"
)

const (
	T_SimpleString	= '+'
	T_Error		= '-'
	T_Integer	= ':'
	T_BulkString	= '$'
	T_Array		= '*'
)

type Data struct {
	T byte
	str []byte
	num int64
	array []*Data
	isNil bool
}

//string\bulkString
func (d *Data) String() string {
	return string(d.str)
}

func (d *Data) Byte() []byte {
	return d.str
}

func (d *Data) Error() string {
	return string(d.str)
}

func (d *Data) Integer() int64 {
	return d.num
}

func (d *Data) Array() []*Data {
	return d.array
}

func (d *Data) IsNil() bool {
	return d.isNil == true
}

//format *Data to []byte
func FormatData(d *Data) []byte {
	ret := new(bytes.Buffer)
	ret.WriteByte(d.T)
	switch d.T {
	case T_SimpleString, T_Error:
		fmt.Fprintf(ret, "%s\r\n", d.str)
	case T_Integer:
		fmt.Fprintf(ret, "%d\r\n", d.num)
	case T_BulkString:
		fmt.Fprintf(ret, "%d\r\n%s\r\n", len(d.str), string(d.str))
	case T_Array:
		fmt.Fprintf(ret, "%d\r\n", len(d.array))
		for index := range d.array {
			ret.Write(FormatData(d.array[index]))
		}
	}
	return ret.Bytes()
}

//read from io.Reader, and parse into *Data
func ReadData(r io.Reader) (*Data, error) {

	var buf []byte
	var err error

	buf = make([]byte, 1)
	_, err = io.ReadFull(r, buf)
	if nil != err {
		return nil, err
	}

	ret := &Data{}
	switch buf[0] {
		case '+':
			ret.T = T_SimpleString
			ret.str, err = readRespLine(r)

		case '-':
			ret.T = T_Error
			ret.str, err = readRespLine(r)

		case ':':
			ret.T = T_Integer
			ret.num, err = readRespIntLine(r)

		case '$':
			var lenBulkString int64
			lenBulkString, err = readRespIntLine(r)

			ret.T = T_BulkString
			if -1 == lenBulkString {
				ret.isNil = true
			} else {
				ret.str, err = readRespN(r, lenBulkString)
				//read the followed \r\n
				_, err = readRespN(r, 2)
			}

		case '*':
			var lenArray int64
			var i int64
			lenArray, err = readRespIntLine(r)

			ret.T = T_Array
			if -1 == lenArray {
				ret.isNil = true
			} else if nil==err {
				ret.array = make([]*Data, lenArray)
				for i=0; i<lenArray; i++ {
					ret.array[i], err = ReadData(r)
				}
			}
		default:
			//Inline Commands
			tmp, err := readRespLine(r)
			if nil==err {
				tmpSlice := bytes.Fields(tmp)

				ret.T = T_Array
				ret.array = make([]*Data, len(tmpSlice))
				for index := range tmpSlice {
					t := &Data{}
					t.str = tmpSlice[index]
					t.T = T_SimpleString
					ret.array[index] = t
				}
			}

	}
	return ret, err
}

//读取当前行，并去掉最后的\r\n
func readRespLine(r io.Reader) ([]byte, error) {

	var n, i int
	var err error
	var buf []byte
	var ret *bytes.Buffer

	buf = make([]byte, 1)
	ret = &bytes.Buffer{}

	for {
		n, err = io.ReadFull(r, buf)
		if nil != err {
			return nil, err
		}

		if n==0 {
			continue
		}

		i++
		ret.WriteByte(buf[0])
		if '\n' == buf[0] {
			break
		}
	}

	return ret.Next(i-2), nil
}

//读取N个字节，并去掉最后的\r\n
func readRespN(r io.Reader, n int64) ([]byte, error) {
	var err error
	var ret []byte

	ret = make([]byte, n)
	_, err = io.ReadFull(r, ret)
	if nil!=err {
		ret = nil
	}
	return ret, err
}

//读取当前行的数字，并去掉最后的\r\n
func readRespIntLine(r io.Reader) (int64, error) {
	line, err := readRespLine(r)
	if nil!=err {
		return 0, err
	}
	return strconv.ParseInt(string(line), 10, 64)
}
<|endoftext|>


<commit_before>package dashboard

import (
	con "github.com/Cepave/open-falcon-backend/modules/f2e-api/config"
)

// +-------+------------------+------+-----+-------------------+-----------------------------+
// | Field | Type             | Null | Key | Default           | Extra                       |
// +-------+------------------+------+-----+-------------------+-----------------------------+
// | id    | int(11) unsigned | NO   | PRI | NULL              | auto_increment              |
// | pid   | int(11) unsigned | NO   | MUL | 0                 |                             |
// | name  | char(128)        | NO   |     | NULL              |                             |
// | time  | timestamp        | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
// +-------+------------------+------+-----+-------------------+-----------------------------+

type DashboardScreen struct {
	ID      int64  `json:"id" gorm:"column:id"`
	PID     int64  `json:"pid" gorm:"column:pid"`
	Name    string `json:"name" gorm:"column:name"`
	Creator string `json:"creator" gorm:"column:creator"`
}

func (this DashboardScreen) TableName() string {
	return "dashboard_screen"
}

func (this DashboardScreen) Graphs() []DashboardGraph {
	db := con.Con()
	graphs := []DashboardGraph{}
	db.Dashboard.Model(&graphs).Where("screen_id = ?", this.ID).Scan(&graphs)
	return graphs
}

func (mine DashboardScreen) Exist() bool {
	db := con.Con()
	rcount := 0
	db.Dashboard.Model(&mine).Where("id = ?", mine.ID).Count(&rcount)
	if rcount != 0 {
		return true
	} else {
		return false
	}
}

func (mine DashboardScreen) ExistName() bool {
	db := con.Con()
	rcount := 0
	db.Dashboard.Model(&mine).Where("name = ?", mine.Name).Count(&rcount)
	if rcount != 0 {
		return true
	} else {
		return false
	}
}
<commit_msg>fix uniq name checking way of dashboard screen<commit_after>package dashboard

import (
	con "github.com/Cepave/open-falcon-backend/modules/f2e-api/config"
)

// +-------+------------------+------+-----+-------------------+-----------------------------+
// | Field | Type             | Null | Key | Default           | Extra                       |
// +-------+------------------+------+-----+-------------------+-----------------------------+
// | id    | int(11) unsigned | NO   | PRI | NULL              | auto_increment              |
// | pid   | int(11) unsigned | NO   | MUL | 0                 |                             |
// | name  | char(128)        | NO   |     | NULL              |                             |
// | time  | timestamp        | NO   |     | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
// +-------+------------------+------+-----+-------------------+-----------------------------+

type DashboardScreen struct {
	ID      int64  `json:"id" gorm:"column:id"`
	PID     int64  `json:"pid" gorm:"column:pid"`
	Name    string `json:"name" gorm:"column:name"`
	Creator string `json:"creator" gorm:"column:creator"`
}

func (this DashboardScreen) TableName() string {
	return "dashboard_screen"
}

func (this DashboardScreen) Graphs() []DashboardGraph {
	db := con.Con()
	graphs := []DashboardGraph{}
	db.Dashboard.Model(&graphs).Where("screen_id = ?", this.ID).Scan(&graphs)
	return graphs
}

func (mine DashboardScreen) Exist() bool {
	db := con.Con()
	rcount := 0
	db.Dashboard.Model(&mine).Where("id = ?", mine.ID).Count(&rcount)
	if rcount != 0 {
		return true
	} else {
		return false
	}
}

func (mine DashboardScreen) ExistName() bool {
	db := con.Con()
	rcount := 0
	if mine.ID == 0 {
		db.Dashboard.Model(&mine).Where("name = ?", mine.Name).Count(&rcount)
	} else {
		db.Dashboard.Table(mine.TableName()).Where("name = ? AND id != ?", mine.Name, mine.ID).Count(&rcount)
	}
	if rcount != 0 {
		return true
	} else {
		return false
	}
}
<|endoftext|>


<commit_before>/*
Package rest is a RESTful web-service framework. It make struct method to http.Handler automatically.

Define a service struct like this:

	type RestExample struct {
		rest.Service `prefix:"/prefix" mime:"application/json" charset:"utf-8"`

		CreateHello rest.Processor `method:"POST" path:"/hello"`
		GetHello    rest.Processor `method:"GET" path:"/hello/:to" func:"HandleHello"`
		Watch       rest.Streaming `method:"GET" path:"/hello/:to/streaming"`

		post  map[string]string
		watch map[string]chan string
	}

	type HelloArg struct {
		To   string `json:"to"`
		Post string `json:"post"`
	}

	// Post example:
	// > curl "http://127.0.0.1:8080/prefix/hello" -d '{"to":"rest", "post":"rest is powerful"}'
	//
	// No response
	func (r RestExample) HandleCreateHello(arg HelloArg) {
		r.post[arg.To] = arg.Post
		c, ok := r.watch[arg.To]
		if ok {
			select {
			case c <- arg.Post:
			default:
			}
		}
	}

	// Get example:
	// > curl "http://127.0.0.1:8080/prefix/hello/rest"
	//
	// Response:
	//   {"to":"rest","post":"rest is powerful"}
	func (r RestExample) HandleHello() HelloArg {
		to := r.Vars()["to"]
		post, ok := r.post[to]
		if !ok {
			r.Error(http.StatusNotFound, r.DetailError(2, "can't find hello to %s", to))
			return HelloArg{}
		}
		return HelloArg{
			To:   to,
			Post: post,
		}
	}

	// Streaming example:
	// > curl "http://127.0.0.1:8080/prefix/hello/rest/streaming"
	//
	// It create a long-live connection and will receive post content "rest is powerful"
	// when running post example.
	func (r RestExample) HandleWatch(s rest.Stream) {
		to := r.Vars()["to"]
		if to == "" {
			r.Error(http.StatusBadRequest, r.DetailError(3, "need to"))
			return
		}
		r.WriteHeader(http.StatusOK)
		c := make(chan string)
		r.watch[to] = c
		for {
			post := <-c
			s.SetDeadline(time.Now().Add(time.Second))
			err := s.Write(post)
			if err != nil {
				close(c)
				delete(r.watch, to)
				return
			}
		}
	}

The field tag of Service configure the parameters of processor, like method, path, or function which
will process the request.

The path of processor can capture arguments, which will pass to process function by order in path. Arguments
type can be string or int, or any type which kind is string or int.

The default name of handler is the name of field prefix with "Handle",
like Watch handelr correspond HandleWatch method.

Get the http.Handler from RestExample:

	handler, err := rest.New(&RestExample{
		post:  make(map[string]string),
		watch: make(map[string]chan string),
	})
	http.ListenAndServe("127.0.0.1:8080", handler)

Or use gorilla mux and work with other http handlers:

	// import "github.com/gorilla/mux"
	router := mux.NewRouter()
	handler, err := rest.New(&RestExample{
	    post:  make(map[string]string),
	    watch: make(map[string]chan string),
	})
	router.PathPrefix(handler.Prefix()).Handle(handler)
	http.ListenAndServe("127.0.0.1:8080", router)
*/
package rest

import (
	"fmt"
	"github.com/ant0ine/go-urlrouter"
	"net/http"
	"reflect"
)

// Rest handle the http request and call to correspond the handler(processor or streaming).
type Rest struct {
	instance       reflect.Value
	serviceIndex   int
	router         *urlrouter.Router
	prefix         string
	needCompress   bool
	defaultMime    string
	defaultCharset string
	ctxField       reflect.Value
}

// Create Rest instance from service instance
func New(s interface{}) (*Rest, error) {
	router := new(urlrouter.Router)

	instance := reflect.ValueOf(s)
	instance = reflect.Indirect(instance)
	t := instance.Type()
	serviceIndex, prefix, mime, charset := -1, "", "", ""
	needCompress := false
	for i, n := 0, instance.NumField(); i < n; i++ {
		field := instance.Field(i)
		if field.Type().String() == "rest.Service" {
			p, m, c, err := initService(field, t.Field(i).Tag)
			if err != nil {
				return nil, err
			}
			serviceIndex, prefix, mime, charset = i, p, m, c
			needCompress = t.Field(i).Tag.Get("compress") == "on"
		}
	}
	if serviceIndex < 0 {
		return nil, fmt.Errorf("%s doesn't contain rest.Service field.", t.Name())
	}
	for i, n := 0, instance.NumField(); i < n; i++ {
		node_ := instance.Field(i)
		field := t.Field(i)
		if !node_.CanAddr() {
			continue
		}
		if first := field.Name[0]; !('A' <= first && first <= 'Z') {
			continue
		}
		pNode, ok := node_.Addr().Interface().(node)
		if !ok {
			continue
		}

		method := field.Tag.Get("method")
		if method == "" {
			return nil, fmt.Errorf("%s node's tag must contain method", field.Name)
		}
		path := field.Tag.Get("path")

		formatter := pathToFormatter(prefix, path)
		handlers, paths, err := pNode.init(formatter, t, field.Name, field.Tag)
		if err != nil {
			return nil, err
		}
		for i := range handlers {
			router.Routes = append(router.Routes, urlrouter.Route{
				PathExp: fmt.Sprintf("/%s/%s", method, paths[i]),
				Dest:    handlers[i],
			})
		}
	}

	err := router.Start()
	if err != nil {
		return nil, err
	}

	return &Rest{
		instance:       instance,
		serviceIndex:   serviceIndex,
		router:         router,
		prefix:         prefix,
		needCompress:   needCompress,
		defaultMime:    mime,
		defaultCharset: charset,
		ctxField:       instance.Field(serviceIndex).FieldByName("context"),
	}, nil
}

// Get the url prefix of service.
func (r *Rest) Prefix() string {
	return r.prefix
}

// Serve the http request.
func (re *Rest) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path
	if method := r.URL.Query().Get("_method"); method != "" {
		r.Method = method
	}
	r.URL.Path = fmt.Sprintf("/%s/%s", r.Method, path)
	dest, vars := re.router.FindRouteFromURL(r.URL)
	if dest == nil {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	r.URL.Path = path

	handler := dest.Dest.(handler)

	if !re.needCompress {
		delete(r.Header, "Accept-Encoding")
	}

	ctx, err := newContext(w, r, vars, re.defaultMime, re.defaultCharset)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	ctx.name = handler.name()

	ctx.responseWriter.Header().Set("Content-Type", fmt.Sprintf("%s; charset=%s", ctx.mime, ctx.charset))

	re.ctxField.Set(reflect.ValueOf(ctx))

	handler.handle(re.instance, ctx)
}
<commit_msg>Revert "add method query"<commit_after>/*
Package rest is a RESTful web-service framework. It make struct method to http.Handler automatically.

Define a service struct like this:

	type RestExample struct {
		rest.Service `prefix:"/prefix" mime:"application/json" charset:"utf-8"`

		CreateHello rest.Processor `method:"POST" path:"/hello"`
		GetHello    rest.Processor `method:"GET" path:"/hello/:to" func:"HandleHello"`
		Watch       rest.Streaming `method:"GET" path:"/hello/:to/streaming"`

		post  map[string]string
		watch map[string]chan string
	}

	type HelloArg struct {
		To   string `json:"to"`
		Post string `json:"post"`
	}

	// Post example:
	// > curl "http://127.0.0.1:8080/prefix/hello" -d '{"to":"rest", "post":"rest is powerful"}'
	//
	// No response
	func (r RestExample) HandleCreateHello(arg HelloArg) {
		r.post[arg.To] = arg.Post
		c, ok := r.watch[arg.To]
		if ok {
			select {
			case c <- arg.Post:
			default:
			}
		}
	}

	// Get example:
	// > curl "http://127.0.0.1:8080/prefix/hello/rest"
	//
	// Response:
	//   {"to":"rest","post":"rest is powerful"}
	func (r RestExample) HandleHello() HelloArg {
		to := r.Vars()["to"]
		post, ok := r.post[to]
		if !ok {
			r.Error(http.StatusNotFound, r.DetailError(2, "can't find hello to %s", to))
			return HelloArg{}
		}
		return HelloArg{
			To:   to,
			Post: post,
		}
	}

	// Streaming example:
	// > curl "http://127.0.0.1:8080/prefix/hello/rest/streaming"
	//
	// It create a long-live connection and will receive post content "rest is powerful"
	// when running post example.
	func (r RestExample) HandleWatch(s rest.Stream) {
		to := r.Vars()["to"]
		if to == "" {
			r.Error(http.StatusBadRequest, r.DetailError(3, "need to"))
			return
		}
		r.WriteHeader(http.StatusOK)
		c := make(chan string)
		r.watch[to] = c
		for {
			post := <-c
			s.SetDeadline(time.Now().Add(time.Second))
			err := s.Write(post)
			if err != nil {
				close(c)
				delete(r.watch, to)
				return
			}
		}
	}

The field tag of Service configure the parameters of processor, like method, path, or function which
will process the request.

The path of processor can capture arguments, which will pass to process function by order in path. Arguments
type can be string or int, or any type which kind is string or int.

The default name of handler is the name of field prefix with "Handle",
like Watch handelr correspond HandleWatch method.

Get the http.Handler from RestExample:

	handler, err := rest.New(&RestExample{
		post:  make(map[string]string),
		watch: make(map[string]chan string),
	})
	http.ListenAndServe("127.0.0.1:8080", handler)

Or use gorilla mux and work with other http handlers:

	// import "github.com/gorilla/mux"
	router := mux.NewRouter()
	handler, err := rest.New(&RestExample{
	    post:  make(map[string]string),
	    watch: make(map[string]chan string),
	})
	router.PathPrefix(handler.Prefix()).Handle(handler)
	http.ListenAndServe("127.0.0.1:8080", router)
*/
package rest

import (
	"fmt"
	"github.com/ant0ine/go-urlrouter"
	"net/http"
	"reflect"
)

// Rest handle the http request and call to correspond the handler(processor or streaming).
type Rest struct {
	instance       reflect.Value
	serviceIndex   int
	router         *urlrouter.Router
	prefix         string
	needCompress   bool
	defaultMime    string
	defaultCharset string
	ctxField       reflect.Value
}

// Create Rest instance from service instance
func New(s interface{}) (*Rest, error) {
	router := new(urlrouter.Router)

	instance := reflect.ValueOf(s)
	instance = reflect.Indirect(instance)
	t := instance.Type()
	serviceIndex, prefix, mime, charset := -1, "", "", ""
	needCompress := false
	for i, n := 0, instance.NumField(); i < n; i++ {
		field := instance.Field(i)
		if field.Type().String() == "rest.Service" {
			p, m, c, err := initService(field, t.Field(i).Tag)
			if err != nil {
				return nil, err
			}
			serviceIndex, prefix, mime, charset = i, p, m, c
			needCompress = t.Field(i).Tag.Get("compress") == "on"
		}
	}
	if serviceIndex < 0 {
		return nil, fmt.Errorf("%s doesn't contain rest.Service field.", t.Name())
	}
	for i, n := 0, instance.NumField(); i < n; i++ {
		node_ := instance.Field(i)
		field := t.Field(i)
		if !node_.CanAddr() {
			continue
		}
		if first := field.Name[0]; !('A' <= first && first <= 'Z') {
			continue
		}
		pNode, ok := node_.Addr().Interface().(node)
		if !ok {
			continue
		}

		method := field.Tag.Get("method")
		if method == "" {
			return nil, fmt.Errorf("%s node's tag must contain method", field.Name)
		}
		path := field.Tag.Get("path")

		formatter := pathToFormatter(prefix, path)
		handlers, paths, err := pNode.init(formatter, t, field.Name, field.Tag)
		if err != nil {
			return nil, err
		}
		for i := range handlers {
			router.Routes = append(router.Routes, urlrouter.Route{
				PathExp: fmt.Sprintf("/%s/%s", method, paths[i]),
				Dest:    handlers[i],
			})
		}
	}

	err := router.Start()
	if err != nil {
		return nil, err
	}

	return &Rest{
		instance:       instance,
		serviceIndex:   serviceIndex,
		router:         router,
		prefix:         prefix,
		needCompress:   needCompress,
		defaultMime:    mime,
		defaultCharset: charset,
		ctxField:       instance.Field(serviceIndex).FieldByName("context"),
	}, nil
}

// Get the url prefix of service.
func (r *Rest) Prefix() string {
	return r.prefix
}

// Serve the http request.
func (re *Rest) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path
	r.URL.Path = fmt.Sprintf("/%s/%s", r.Method, path)
	dest, vars := re.router.FindRouteFromURL(r.URL)
	if dest == nil {
		w.WriteHeader(http.StatusNotFound)
		return
	}
	r.URL.Path = path

	handler := dest.Dest.(handler)

	if !re.needCompress {
		delete(r.Header, "Accept-Encoding")
	}

	ctx, err := newContext(w, r, vars, re.defaultMime, re.defaultCharset)
	if err != nil {
		http.Error(w, err.Error(), http.StatusBadRequest)
		return
	}
	ctx.name = handler.name()

	ctx.responseWriter.Header().Set("Content-Type", fmt.Sprintf("%s; charset=%s", ctx.mime, ctx.charset))

	re.ctxField.Set(reflect.ValueOf(ctx))

	handler.handle(re.instance, ctx)
}
<|endoftext|>