<commit_before>// MIT License
//
// Copyright (c) 2017 Stefan Wichmann
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main

import "io/ioutil"
import "encoding/json"
import "os"
import "errors"
import "time"
import "fmt"
import "crypto/sha256"
import log "github.com/Sirupsen/logrus"

// Bridge respresents the hue bridge in your system.
type Bridge struct {
	IP       string `json:"ip"`
	Username string `json:"username"`
}

// Location represents the geolocation for which sunrise and sunset will be calculated.
type Location struct {
	Latitude  float64 `json:"latitude"`
	Longitude float64 `json:"longitude"`
}

// WebInterface respresents the webinterface of Kelvin.
type WebInterface struct {
	Enabled bool `json:"enabled"`
	Port    int  `json:"port"`
}

// LightSchedule represents the schedule for any given day for the associated lights.
type LightSchedule struct {
	Name                    string                  `json:"name"`
	AssociatedDeviceIDs     []int                   `json:"associatedDeviceIDs"`
	DefaultColorTemperature int                     `json:"defaultColorTemperature"`
	DefaultBrightness       int                     `json:"defaultBrightness"`
	BeforeSunrise           []TimedColorTemperature `json:"beforeSunrise"`
	AfterSunset             []TimedColorTemperature `json:"afterSunset"`
}

// TimedColorTemperature represents a light configuration which will be
// reached at the given time.
type TimedColorTemperature struct {
	Time             string `json:"time"`
	ColorTemperature int    `json:"colorTemperature"`
	Brightness       int    `json:"brightness"`
}

// Configuration encapsulates all relevant parameters for Kelvin to operate.
type Configuration struct {
	ConfigurationFile string          `json:"-"`
	Hash              string          `json:"-"`
	Bridge            Bridge          `json:"bridge"`
	Location          Location        `json:"location"`
	WebInterface      WebInterface    `json:"webinterface"`
	Schedules         []LightSchedule `json:"schedules"`
}

// TimeStamp represents a parsed and validated TimedColorTemperature.
type TimeStamp struct {
	Time             time.Time
	ColorTemperature int
	Brightness       int
}

func (configuration *Configuration) initializeDefaults() {
	var bedTime TimedColorTemperature
	bedTime.Time = "22:00"
	bedTime.ColorTemperature = 2000
	bedTime.Brightness = 60

	var tvTime TimedColorTemperature
	tvTime.Time = "20:00"
	tvTime.ColorTemperature = 2300
	tvTime.Brightness = 80

	var wakeupTime TimedColorTemperature
	wakeupTime.Time = "4:00"
	wakeupTime.ColorTemperature = 2000
	wakeupTime.Brightness = 60

	var defaultSchedule LightSchedule
	defaultSchedule.Name = "default"
	defaultSchedule.AssociatedDeviceIDs = []int{}
	defaultSchedule.DefaultColorTemperature = 2750
	defaultSchedule.DefaultBrightness = 100
	defaultSchedule.AfterSunset = []TimedColorTemperature{tvTime, bedTime}
	defaultSchedule.BeforeSunrise = []TimedColorTemperature{wakeupTime}

	configuration.Schedules = []LightSchedule{defaultSchedule}

	var webinterface WebInterface
	webinterface.Enabled = false
	webinterface.Port = 8080
	configuration.WebInterface = webinterface
}

// InitializeConfiguration creates and returns an initialized
// configuration.
// If no configuration can be found on disk, one with default values
// will be created.
func InitializeConfiguration() (Configuration, error) {
	var configuration Configuration
	configuration.ConfigurationFile = "config.json"
	if configuration.Exists() {
		err := configuration.Read()
		if err != nil {
			return configuration, err
		}
		log.Printf("⚙ Configuration %v loaded", configuration.ConfigurationFile)
	} else {
		// write default config to disk
		configuration.initializeDefaults()
		err := configuration.Write()
		if err != nil {
			return configuration, err
		}
		log.Println("⚙ Default configuration generated")
	}
	return configuration, nil
}

// Write saves a configuration to disk.
func (configuration *Configuration) Write() error {
	if configuration.ConfigurationFile == "" {
		return errors.New("No configuration filename configured")
	}

	if !configuration.HasChanged() {
		log.Debugf("⚙ Configuration hasn't changed. Omitting write.")
		return nil
	}
	log.Debugf("⚙ Configuration changed. Saving...")
	json, err := json.MarshalIndent(configuration, "", "  ")
	if err != nil {
		return err
	}

	err = ioutil.WriteFile(configuration.ConfigurationFile, json, 0644)
	if err != nil {
		return err
	}

	configuration.Hash = configuration.HashValue()
	log.Debugf("⚙ Updated configuration hash")
	return nil
}

// Read loads a configuration from disk.
func (configuration *Configuration) Read() error {
	if configuration.ConfigurationFile == "" {
		return errors.New("No configuration filename configured")
	}

	raw, err := ioutil.ReadFile(configuration.ConfigurationFile)
	if err != nil {
		return err
	}

	err = json.Unmarshal(raw, configuration)
	if err != nil {
		return err
	}

	if len(configuration.Schedules) == 0 {
		log.Warningf("⚙ Your current configuration doesn't contain any schedules! Generating default schedule...")
		err := configuration.backup()
		if err != nil {
			log.Warningf("⚙ Could not create backup: %v", err)
		} else {
			log.Printf("⚙ Configuration backup created.")
			configuration.initializeDefaults()
			log.Printf("⚙ Default schedule created.")
			configuration.Write()
		}
	}
	configuration.Hash = configuration.HashValue()
	log.Debugf("⚙ Updated configuration hash.")

	// Migrate to new timestamp format
	for scheduleIndex := range configuration.Schedules {
		for beforeTimestampIndex := range configuration.Schedules[scheduleIndex].BeforeSunrise {
			t, err := migrateTimestampFormat(configuration.Schedules[scheduleIndex].BeforeSunrise[beforeTimestampIndex].Time)
			if err != nil {
				log.Warningf(err.Error())
			} else {
				configuration.Schedules[scheduleIndex].BeforeSunrise[beforeTimestampIndex].Time = t
			}
		}
		for afterTimestampIndex := range configuration.Schedules[scheduleIndex].AfterSunset {
			t, err := migrateTimestampFormat(configuration.Schedules[scheduleIndex].AfterSunset[afterTimestampIndex].Time)
			if err != nil {
				log.Warningf(err.Error())
			} else {
				configuration.Schedules[scheduleIndex].AfterSunset[afterTimestampIndex].Time = t
			}
		}
	}

	// Migration: Disable webinterface
	if configuration.WebInterface.Port == 0 {
		log.Printf("⚙ Migrating webinterface settings...")
		configuration.WebInterface.Enabled = false
		configuration.WebInterface.Port = 8080
	}
	configuration.Write()
	return nil
}

func (configuration *Configuration) lightScheduleForDay(light int, date time.Time) (Schedule, error) {
	// initialize schedule with end of day
	var schedule Schedule
	yr, mth, dy := date.Date()
	schedule.endOfDay = time.Date(yr, mth, dy, 23, 59, 59, 59, date.Location())

	var lightSchedule LightSchedule
	found := false
	for _, candidate := range configuration.Schedules {
		if containsInt(candidate.AssociatedDeviceIDs, light) {
			lightSchedule = candidate
			found = true
			break
		}
	}

	if !found {
		return schedule, fmt.Errorf("Light %d is not associated with any schedule in configuration", light)
	}

	schedule.sunrise = TimeStamp{CalculateSunrise(date, configuration.Location.Latitude, configuration.Location.Longitude), lightSchedule.DefaultColorTemperature, lightSchedule.DefaultBrightness}
	schedule.sunset = TimeStamp{CalculateSunset(date, configuration.Location.Latitude, configuration.Location.Longitude), lightSchedule.DefaultColorTemperature, lightSchedule.DefaultBrightness}

	// Before sunrise candidates
	schedule.beforeSunrise = []TimeStamp{}
	for _, candidate := range lightSchedule.BeforeSunrise {
		timestamp, err := candidate.AsTimestamp(date)
		if err != nil {
			log.Printf(err.Error())
			continue
		}
		schedule.beforeSunrise = append(schedule.beforeSunrise, timestamp)
	}

	// After sunset candidates
	schedule.afterSunset = []TimeStamp{}
	for _, candidate := range lightSchedule.AfterSunset {
		timestamp, err := candidate.AsTimestamp(date)
		if err != nil {
			log.Printf(err.Error())
			continue
		}
		schedule.afterSunset = append(schedule.afterSunset, timestamp)
	}

	return schedule, nil
}

// Exists return true if a configuration file is found on disk.
// False otherwise.
func (configuration *Configuration) Exists() bool {
	if configuration.ConfigurationFile == "" {
		return false
	}

	if _, err := os.Stat(configuration.ConfigurationFile); os.IsNotExist(err) {
		return false
	}
	return true
}

// HasChanged will detect changes to the configuration struct.
func (configuration *Configuration) HasChanged() bool {
	if configuration.Hash == "" {
		return true
	}
	return configuration.HashValue() != configuration.Hash
}

// HashValue will calculate a SHA256 hash of the configuration struct.
func (configuration *Configuration) HashValue() string {
	json, _ := json.Marshal(configuration)
	return fmt.Sprintf("%x", sha256.Sum256(json))
}

// AsTimestamp parses and validates a TimedColorTemperature and returns
// a corresponding TimeStamp.
func (color *TimedColorTemperature) AsTimestamp(referenceTime time.Time) (TimeStamp, error) {
	layout := "15:04"
	t, err := time.Parse(layout, color.Time)
	if err != nil {
		return TimeStamp{time.Now(), color.ColorTemperature, color.Brightness}, err
	}
	yr, mth, day := referenceTime.Date()
	targetTime := time.Date(yr, mth, day, t.Hour(), t.Minute(), t.Second(), 0, referenceTime.Location())

	return TimeStamp{targetTime, color.ColorTemperature, color.Brightness}, nil
}

func (configuration *Configuration) backup() error {
	backupFilename := configuration.ConfigurationFile + "_" + time.Now().Format("01022006")
	log.Debugf("⚙ Moving configuration to %s.", backupFilename)
	return os.Rename(configuration.ConfigurationFile, backupFilename)
}

func migrateTimestampFormat(timestamp string) (string, error) {
	// Check for old format and convert
	layout := "3:04PM"
	t, err := time.Parse(layout, timestamp)
	if err == nil {
		log.Debugf("⚙ Migrating old timestamp %s to %s", timestamp, t.Format("15:04"))
		return t.Format("15:04"), nil
	}

	// Already new format? Return unchanged
	layout = "15:04"
	t, err = time.Parse(layout, timestamp)
	if err == nil {
		return timestamp, nil
	}

	return "", fmt.Errorf("Invalid timestamp format: %s", timestamp)
}
<commit_msg>Introduce version number to configuration files. This enables a clean migration when new features get introduced.<commit_after>// MIT License
//
// Copyright (c) 2017 Stefan Wichmann
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main

import "io/ioutil"
import "encoding/json"
import "os"
import "errors"
import "time"
import "fmt"
import "crypto/sha256"
import log "github.com/Sirupsen/logrus"

// Bridge respresents the hue bridge in your system.
type Bridge struct {
	IP       string `json:"ip"`
	Username string `json:"username"`
}

// Location represents the geolocation for which sunrise and sunset will be calculated.
type Location struct {
	Latitude  float64 `json:"latitude"`
	Longitude float64 `json:"longitude"`
}

// WebInterface respresents the webinterface of Kelvin.
type WebInterface struct {
	Enabled bool `json:"enabled"`
	Port    int  `json:"port"`
}

// LightSchedule represents the schedule for any given day for the associated lights.
type LightSchedule struct {
	Name                    string                  `json:"name"`
	AssociatedDeviceIDs     []int                   `json:"associatedDeviceIDs"`
	DefaultColorTemperature int                     `json:"defaultColorTemperature"`
	DefaultBrightness       int                     `json:"defaultBrightness"`
	BeforeSunrise           []TimedColorTemperature `json:"beforeSunrise"`
	AfterSunset             []TimedColorTemperature `json:"afterSunset"`
}

// TimedColorTemperature represents a light configuration which will be
// reached at the given time.
type TimedColorTemperature struct {
	Time             string `json:"time"`
	ColorTemperature int    `json:"colorTemperature"`
	Brightness       int    `json:"brightness"`
}

// Configuration encapsulates all relevant parameters for Kelvin to operate.
type Configuration struct {
	ConfigurationFile string          `json:"-"`
	Hash              string          `json:"-"`
	Version           int             `json:"version"`
	Bridge            Bridge          `json:"bridge"`
	Location          Location        `json:"location"`
	WebInterface      WebInterface    `json:"webinterface"`
	Schedules         []LightSchedule `json:"schedules"`
}

// TimeStamp represents a parsed and validated TimedColorTemperature.
type TimeStamp struct {
	Time             time.Time
	ColorTemperature int
	Brightness       int
}

var latestConfigurationVersion = 0

func (configuration *Configuration) initializeDefaults() {
	configuration.Version = latestConfigurationVersion

	var bedTime TimedColorTemperature
	bedTime.Time = "22:00"
	bedTime.ColorTemperature = 2000
	bedTime.Brightness = 60

	var tvTime TimedColorTemperature
	tvTime.Time = "20:00"
	tvTime.ColorTemperature = 2300
	tvTime.Brightness = 80

	var wakeupTime TimedColorTemperature
	wakeupTime.Time = "4:00"
	wakeupTime.ColorTemperature = 2000
	wakeupTime.Brightness = 60

	var defaultSchedule LightSchedule
	defaultSchedule.Name = "default"
	defaultSchedule.AssociatedDeviceIDs = []int{}
	defaultSchedule.DefaultColorTemperature = 2750
	defaultSchedule.DefaultBrightness = 100
	defaultSchedule.AfterSunset = []TimedColorTemperature{tvTime, bedTime}
	defaultSchedule.BeforeSunrise = []TimedColorTemperature{wakeupTime}

	configuration.Schedules = []LightSchedule{defaultSchedule}

	var webinterface WebInterface
	webinterface.Enabled = false
	webinterface.Port = 8080
	configuration.WebInterface = webinterface
}

// InitializeConfiguration creates and returns an initialized
// configuration.
// If no configuration can be found on disk, one with default values
// will be created.
func InitializeConfiguration() (Configuration, error) {
	var configuration Configuration
	configuration.ConfigurationFile = "config.json"
	if configuration.Exists() {
		err := configuration.Read()
		if err != nil {
			return configuration, err
		}
		log.Printf("⚙ Configuration %v loaded", configuration.ConfigurationFile)
	} else {
		// write default config to disk
		configuration.initializeDefaults()
		err := configuration.Write()
		if err != nil {
			return configuration, err
		}
		log.Println("⚙ Default configuration generated")
	}
	return configuration, nil
}

// Write saves a configuration to disk.
func (configuration *Configuration) Write() error {
	if configuration.ConfigurationFile == "" {
		return errors.New("No configuration filename configured")
	}

	if !configuration.HasChanged() {
		log.Debugf("⚙ Configuration hasn't changed. Omitting write.")
		return nil
	}
	log.Debugf("⚙ Configuration changed. Saving...")
	json, err := json.MarshalIndent(configuration, "", "  ")
	if err != nil {
		return err
	}

	err = ioutil.WriteFile(configuration.ConfigurationFile, json, 0644)
	if err != nil {
		return err
	}

	configuration.Hash = configuration.HashValue()
	log.Debugf("⚙ Updated configuration hash")
	return nil
}

// Read loads a configuration from disk.
func (configuration *Configuration) Read() error {
	if configuration.ConfigurationFile == "" {
		return errors.New("No configuration filename configured")
	}

	raw, err := ioutil.ReadFile(configuration.ConfigurationFile)
	if err != nil {
		return err
	}

	err = json.Unmarshal(raw, configuration)
	if err != nil {
		return err
	}

	if len(configuration.Schedules) == 0 {
		log.Warningf("⚙ Your current configuration doesn't contain any schedules! Generating default schedule...")
		err := configuration.backup()
		if err != nil {
			log.Warningf("⚙ Could not create backup: %v", err)
		} else {
			log.Printf("⚙ Configuration backup created.")
			configuration.initializeDefaults()
			log.Printf("⚙ Default schedule created.")
			configuration.Write()
		}
	}
	configuration.Hash = configuration.HashValue()
	log.Debugf("⚙ Updated configuration hash.")

	// Migrate to new timestamp format
	for scheduleIndex := range configuration.Schedules {
		for beforeTimestampIndex := range configuration.Schedules[scheduleIndex].BeforeSunrise {
			t, err := migrateTimestampFormat(configuration.Schedules[scheduleIndex].BeforeSunrise[beforeTimestampIndex].Time)
			if err != nil {
				log.Warningf(err.Error())
			} else {
				configuration.Schedules[scheduleIndex].BeforeSunrise[beforeTimestampIndex].Time = t
			}
		}
		for afterTimestampIndex := range configuration.Schedules[scheduleIndex].AfterSunset {
			t, err := migrateTimestampFormat(configuration.Schedules[scheduleIndex].AfterSunset[afterTimestampIndex].Time)
			if err != nil {
				log.Warningf(err.Error())
			} else {
				configuration.Schedules[scheduleIndex].AfterSunset[afterTimestampIndex].Time = t
			}
		}
	}

	// Migration: Disable webinterface
	if configuration.WebInterface.Port == 0 {
		log.Printf("⚙ Migrating webinterface settings...")
		configuration.WebInterface.Enabled = false
		configuration.WebInterface.Port = 8080
	}

	configuration.Write()
	return nil
}

func (configuration *Configuration) lightScheduleForDay(light int, date time.Time) (Schedule, error) {
	// initialize schedule with end of day
	var schedule Schedule
	yr, mth, dy := date.Date()
	schedule.endOfDay = time.Date(yr, mth, dy, 23, 59, 59, 59, date.Location())

	var lightSchedule LightSchedule
	found := false
	for _, candidate := range configuration.Schedules {
		if containsInt(candidate.AssociatedDeviceIDs, light) {
			lightSchedule = candidate
			found = true
			break
		}
	}

	if !found {
		return schedule, fmt.Errorf("Light %d is not associated with any schedule in configuration", light)
	}

	schedule.sunrise = TimeStamp{CalculateSunrise(date, configuration.Location.Latitude, configuration.Location.Longitude), lightSchedule.DefaultColorTemperature, lightSchedule.DefaultBrightness}
	schedule.sunset = TimeStamp{CalculateSunset(date, configuration.Location.Latitude, configuration.Location.Longitude), lightSchedule.DefaultColorTemperature, lightSchedule.DefaultBrightness}

	// Before sunrise candidates
	schedule.beforeSunrise = []TimeStamp{}
	for _, candidate := range lightSchedule.BeforeSunrise {
		timestamp, err := candidate.AsTimestamp(date)
		if err != nil {
			log.Printf(err.Error())
			continue
		}
		schedule.beforeSunrise = append(schedule.beforeSunrise, timestamp)
	}

	// After sunset candidates
	schedule.afterSunset = []TimeStamp{}
	for _, candidate := range lightSchedule.AfterSunset {
		timestamp, err := candidate.AsTimestamp(date)
		if err != nil {
			log.Printf(err.Error())
			continue
		}
		schedule.afterSunset = append(schedule.afterSunset, timestamp)
	}

	return schedule, nil
}

// Exists return true if a configuration file is found on disk.
// False otherwise.
func (configuration *Configuration) Exists() bool {
	if configuration.ConfigurationFile == "" {
		return false
	}

	if _, err := os.Stat(configuration.ConfigurationFile); os.IsNotExist(err) {
		return false
	}
	return true
}

// HasChanged will detect changes to the configuration struct.
func (configuration *Configuration) HasChanged() bool {
	if configuration.Hash == "" {
		return true
	}
	return configuration.HashValue() != configuration.Hash
}

// HashValue will calculate a SHA256 hash of the configuration struct.
func (configuration *Configuration) HashValue() string {
	json, _ := json.Marshal(configuration)
	return fmt.Sprintf("%x", sha256.Sum256(json))
}

// AsTimestamp parses and validates a TimedColorTemperature and returns
// a corresponding TimeStamp.
func (color *TimedColorTemperature) AsTimestamp(referenceTime time.Time) (TimeStamp, error) {
	layout := "15:04"
	t, err := time.Parse(layout, color.Time)
	if err != nil {
		return TimeStamp{time.Now(), color.ColorTemperature, color.Brightness}, err
	}
	yr, mth, day := referenceTime.Date()
	targetTime := time.Date(yr, mth, day, t.Hour(), t.Minute(), t.Second(), 0, referenceTime.Location())

	return TimeStamp{targetTime, color.ColorTemperature, color.Brightness}, nil
}

func (configuration *Configuration) backup() error {
	backupFilename := configuration.ConfigurationFile + "_" + time.Now().Format("01022006")
	log.Debugf("⚙ Moving configuration to %s.", backupFilename)
	return os.Rename(configuration.ConfigurationFile, backupFilename)
}

func migrateTimestampFormat(timestamp string) (string, error) {
	// Check for old format and convert
	layout := "3:04PM"
	t, err := time.Parse(layout, timestamp)
	if err == nil {
		log.Debugf("⚙ Migrating old timestamp %s to %s", timestamp, t.Format("15:04"))
		return t.Format("15:04"), nil
	}

	// Already new format? Return unchanged
	layout = "15:04"
	t, err = time.Parse(layout, timestamp)
	if err == nil {
		return timestamp, nil
	}

	return "", fmt.Errorf("Invalid timestamp format: %s", timestamp)
}
<|endoftext|>


<commit_before>//go:generate go run ./cmd/generate-project-list
// THIS FILE IS AUTO-GENERATED WITH 'go generate .'
package dashboard

var defaultProjects = []*Project{
	{
		Name:          "jekyll",
		Nwo:           "jekyll/jekyll",
		Branch:        "master",
		GemName:       "jekyll",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnk2NTI1Mg==",
	},
	{
		Name:          "jekyll-admin",
		Nwo:           "jekyll/jekyll-admin",
		Branch:        "master",
		GemName:       "jekyll-admin",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnk1OTIyNzUyOA==",
	},
	{
		Name:          "minima",
		Nwo:           "jekyll/minima",
		Branch:        "master",
		GemName:       "minima",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnk1OTMzMTM5NQ==",
	},
	{
		Name:          "jekyll-seo-tag",
		Nwo:           "jekyll/jekyll-seo-tag",
		Branch:        "master",
		GemName:       "jekyll-seo-tag",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnk0NDkzMTMxMw==",
	},
	{
		Name:          "jekyll-sitemap",
		Nwo:           "jekyll/jekyll-sitemap",
		Branch:        "master",
		GemName:       "jekyll-sitemap",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkxNzMyNzUzNQ==",
	},
	{
		Name:          "jekyll-feed",
		Nwo:           "jekyll/jekyll-feed",
		Branch:        "master",
		GemName:       "jekyll-feed",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkzNTQ4OTU2Nw==",
	},
	{
		Name:          "jekyll-redirect-from",
		Nwo:           "jekyll/jekyll-redirect-from",
		Branch:        "master",
		GemName:       "jekyll-redirect-from",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkxNTIwODUxNA==",
	},
	{
		Name:          "jekyll-compose",
		Nwo:           "jekyll/jekyll-compose",
		Branch:        "master",
		GemName:       "jekyll-compose",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkxOTY1MDIwNA==",
	},
	{
		Name:          "classifier-reborn",
		Nwo:           "jekyll/classifier-reborn",
		Branch:        "master",
		GemName:       "classifier-reborn",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkyMjgxODQ0MQ==",
	},
	{
		Name:          "jemoji",
		Nwo:           "jekyll/jemoji",
		Branch:        "master",
		GemName:       "jemoji",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkxNTY1NzU3OA==",
	},
	{
		Name:          "jekyll-import",
		Nwo:           "jekyll/jekyll-import",
		Branch:        "master",
		GemName:       "jekyll-import",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnk3OTIzNDQz",
	},
	{
		Name:          "jekyll-archives",
		Nwo:           "jekyll/jekyll-archives",
		Branch:        "master",
		GemName:       "jekyll-archives",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkyMzA1MTM2OQ==",
	},
	{
		Name:          "jekyll-gist",
		Nwo:           "jekyll/jekyll-gist",
		Branch:        "master",
		GemName:       "jekyll-gist",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkyMDM4MTY4Nw==",
	},
	{
		Name:          "github-metadata",
		Nwo:           "jekyll/github-metadata",
		Branch:        "master",
		GemName:       "jekyll-github-metadata",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkyNDA4ODIxNA==",
	},
	{
		Name:          "jekyll-mentions",
		Nwo:           "jekyll/jekyll-mentions",
		Branch:        "master",
		GemName:       "jekyll-mentions",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkxNjA1Mzk5NA==",
	},
	{
		Name:          "mercenary",
		Nwo:           "jekyll/mercenary",
		Branch:        "master",
		GemName:       "mercenary",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkxNDE4MzUwNA==",
	},
	{
		Name:          "jekyll-sass-converter",
		Nwo:           "jekyll/jekyll-sass-converter",
		Branch:        "master",
		GemName:       "jekyll-sass-converter",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkxNjI0MzU3MA==",
	},
	{
		Name:          "jekyll-paginate",
		Nwo:           "jekyll/jekyll-paginate",
		Branch:        "master",
		GemName:       "jekyll-paginate",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkyMDE5MjY4Ng==",
	},
	{
		Name:          "jekyll-watch",
		Nwo:           "jekyll/jekyll-watch",
		Branch:        "master",
		GemName:       "jekyll-watch",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkyMTI1NzI4Mg==",
	},
	{
		Name:          "jekyll-avatar",
		Nwo:           "jekyll/jekyll-avatar",
		Branch:        "main",
		GemName:       "jekyll-avatar",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnk0OTM3ODE0Ng==",
	},
	{
		Name:          "jekyll-coffeescript",
		Nwo:           "jekyll/jekyll-coffeescript",
		Branch:        "master",
		GemName:       "jekyll-coffeescript",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkxNjI0MjcxOA==",
	},
	{
		Name:          "jekyll-opal",
		Nwo:           "jekyll/jekyll-opal",
		Branch:        "master",
		GemName:       "jekyll-opal",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkyMDg3MjQwMw==",
	},
	{
		Name:          "jekyll-docs",
		Nwo:           "jekyll/jekyll-docs",
		Branch:        "master",
		GemName:       "jekyll-docs",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkyODU3MDA4MA==",
	},
	{
		Name:          "directory",
		Nwo:           "jekyll/directory",
		Branch:        "master",
		GemName:       "directory",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkxMTI2NTcwOQ==",
	},
	{
		Name:          "jekyll-commonmark",
		Nwo:           "jekyll/jekyll-commonmark",
		Branch:        "master",
		GemName:       "jekyll-commonmark",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnk0ODQwMTA3Mw==",
	},
	{
		Name:          "jekyll-textile-converter",
		Nwo:           "jekyll/jekyll-textile-converter",
		Branch:        "master",
		GemName:       "jekyll-textile-converter",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkyOTQwMzM1Ng==",
	},
}
<commit_msg>Regenerate project list (sorted by stars)<commit_after>//go:generate go run ./cmd/generate-project-list
// THIS FILE IS AUTO-GENERATED WITH 'go generate .'
package dashboard

var defaultProjects = []*Project{
	{
		Name:          "jekyll",
		Nwo:           "jekyll/jekyll",
		Branch:        "master",
		GemName:       "jekyll",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnk2NTI1Mg==",
	},
	{
		Name:          "jekyll-admin",
		Nwo:           "jekyll/jekyll-admin",
		Branch:        "master",
		GemName:       "jekyll-admin",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnk1OTIyNzUyOA==",
	},
	{
		Name:          "minima",
		Nwo:           "jekyll/minima",
		Branch:        "master",
		GemName:       "minima",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnk1OTMzMTM5NQ==",
	},
	{
		Name:          "jekyll-seo-tag",
		Nwo:           "jekyll/jekyll-seo-tag",
		Branch:        "master",
		GemName:       "jekyll-seo-tag",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnk0NDkzMTMxMw==",
	},
	{
		Name:          "jekyll-sitemap",
		Nwo:           "jekyll/jekyll-sitemap",
		Branch:        "master",
		GemName:       "jekyll-sitemap",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkxNzMyNzUzNQ==",
	},
	{
		Name:          "jekyll-feed",
		Nwo:           "jekyll/jekyll-feed",
		Branch:        "master",
		GemName:       "jekyll-feed",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkzNTQ4OTU2Nw==",
	},
	{
		Name:          "jekyll-redirect-from",
		Nwo:           "jekyll/jekyll-redirect-from",
		Branch:        "master",
		GemName:       "jekyll-redirect-from",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkxNTIwODUxNA==",
	},
	{
		Name:          "jekyll-compose",
		Nwo:           "jekyll/jekyll-compose",
		Branch:        "master",
		GemName:       "jekyll-compose",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkxOTY1MDIwNA==",
	},
	{
		Name:          "classifier-reborn",
		Nwo:           "jekyll/classifier-reborn",
		Branch:        "master",
		GemName:       "classifier-reborn",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkyMjgxODQ0MQ==",
	},
	{
		Name:          "jemoji",
		Nwo:           "jekyll/jemoji",
		Branch:        "master",
		GemName:       "jemoji",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkxNTY1NzU3OA==",
	},
	{
		Name:          "jekyll-import",
		Nwo:           "jekyll/jekyll-import",
		Branch:        "master",
		GemName:       "jekyll-import",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnk3OTIzNDQz",
	},
	{
		Name:          "jekyll-archives",
		Nwo:           "jekyll/jekyll-archives",
		Branch:        "master",
		GemName:       "jekyll-archives",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkyMzA1MTM2OQ==",
	},
	{
		Name:          "jekyll-gist",
		Nwo:           "jekyll/jekyll-gist",
		Branch:        "master",
		GemName:       "jekyll-gist",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkyMDM4MTY4Nw==",
	},
	{
		Name:          "github-metadata",
		Nwo:           "jekyll/github-metadata",
		Branch:        "master",
		GemName:       "jekyll-github-metadata",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkyNDA4ODIxNA==",
	},
	{
		Name:          "jekyll-mentions",
		Nwo:           "jekyll/jekyll-mentions",
		Branch:        "master",
		GemName:       "jekyll-mentions",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkxNjA1Mzk5NA==",
	},
	{
		Name:          "jekyll-sass-converter",
		Nwo:           "jekyll/jekyll-sass-converter",
		Branch:        "master",
		GemName:       "jekyll-sass-converter",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkxNjI0MzU3MA==",
	},
	{
		Name:          "mercenary",
		Nwo:           "jekyll/mercenary",
		Branch:        "master",
		GemName:       "mercenary",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkxNDE4MzUwNA==",
	},
	{
		Name:          "jekyll-paginate",
		Nwo:           "jekyll/jekyll-paginate",
		Branch:        "master",
		GemName:       "jekyll-paginate",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkyMDE5MjY4Ng==",
	},
	{
		Name:          "jekyll-watch",
		Nwo:           "jekyll/jekyll-watch",
		Branch:        "master",
		GemName:       "jekyll-watch",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkyMTI1NzI4Mg==",
	},
	{
		Name:          "jekyll-avatar",
		Nwo:           "jekyll/jekyll-avatar",
		Branch:        "main",
		GemName:       "jekyll-avatar",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnk0OTM3ODE0Ng==",
	},
	{
		Name:          "jekyll-coffeescript",
		Nwo:           "jekyll/jekyll-coffeescript",
		Branch:        "master",
		GemName:       "jekyll-coffeescript",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkxNjI0MjcxOA==",
	},
	{
		Name:          "jekyll-opal",
		Nwo:           "jekyll/jekyll-opal",
		Branch:        "master",
		GemName:       "jekyll-opal",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkyMDg3MjQwMw==",
	},
	{
		Name:          "jekyll-docs",
		Nwo:           "jekyll/jekyll-docs",
		Branch:        "master",
		GemName:       "jekyll-docs",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkyODU3MDA4MA==",
	},
	{
		Name:          "directory",
		Nwo:           "jekyll/directory",
		Branch:        "master",
		GemName:       "directory",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkxMTI2NTcwOQ==",
	},
	{
		Name:          "jekyll-commonmark",
		Nwo:           "jekyll/jekyll-commonmark",
		Branch:        "master",
		GemName:       "jekyll-commonmark",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnk0ODQwMTA3Mw==",
	},
	{
		Name:          "jekyll-textile-converter",
		Nwo:           "jekyll/jekyll-textile-converter",
		Branch:        "master",
		GemName:       "jekyll-textile-converter",
		GlobalRelayID: "MDEwOlJlcG9zaXRvcnkyOTQwMzM1Ng==",
	},
}
<|endoftext|>


<commit_before>package gsm

import (
	"flag"
	"fmt"
)

type Configuration struct {
	DisplayVersion  bool
	DisplayRev      bool
	ExitImmediately bool
	UriFlag         string
}

var (
	revFlag     = flag.Bool("rev", false, "-rev\t\tPrint git revision and exit.")
	versionFlag = flag.Bool("version", false, "-version\tPrint version and exit.")
	uriFlag     = flag.String("uri", "amqp://guest:guest@localhost:5672", "-uri\t\tAMQP uri for consumer.")
)

func NewConfigurationFromFlags() *Configuration {
	flag.Usage = func() {
		fmt.Println("Usage: gsm-server [options]")
		printOptions()
	}
	flag.Parse()

	exitImmediately := *revFlag || *versionFlag

	return &Configuration{
		DisplayVersion:  *versionFlag,
		DisplayRev:      *revFlag,
		ExitImmediately: exitImmediately,
		UriFlag:         *uriFlag,
	}
}

func printOptions() {
	fmt.Println("Options:")
	flag.VisitAll(func(flag *flag.Flag) {
		fmt.Println(flag.Usage)
	})
}
<commit_msg>Adding cl option for gemdir<commit_after>package gsm

import (
	"flag"
	"fmt"
)

type Configuration struct {
	DisplayVersion  bool
	DisplayRev      bool
	ExitImmediately bool
	UriFlag         string
	GemDir          string
}

var (
	revFlag     = flag.Bool("rev", false, "-rev\t\tPrint git revision and exit.")
	versionFlag = flag.Bool("version", false, "-version\tPrint version and exit.")
	uriFlag     = flag.String("uri", "amqp://guest:guest@localhost:5672", "-uri\t\tAMQP uri for consumer.")
)

func NewConfigurationFromFlags() *Configuration {
	flag.Usage = func() {
		fmt.Println("Usage: gsm-server [options] <gemdir>")
		printOptions()
	}
	flag.Parse()

	var gemDir string

	exitImmediately := *revFlag || *versionFlag

	if flag.NArg() < 1 {
		gemDir = ""
		exitImmediately = true
	} else {
		gemDir = flag.Arg(0)
	}

	return &Configuration{
		DisplayVersion:  *versionFlag,
		DisplayRev:      *revFlag,
		ExitImmediately: exitImmediately,
		UriFlag:         *uriFlag,
		GemDir:          gemDir,
	}
}

func printOptions() {
	fmt.Println("Options:")
	flag.VisitAll(func(flag *flag.Flag) {
		fmt.Println(flag.Usage)
	})
}
<|endoftext|>


<commit_before>package libkbfs

import (
	"sync"
	"time"

	"github.com/keybase/client/go/client"
	"github.com/keybase/client/go/engine"
	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/logger"
	keybase1 "github.com/keybase/client/go/protocol"
	rpc "github.com/keybase/go-framed-msgpack-rpc"
	"golang.org/x/net/context"
)

// KeybaseDaemonRPC implements the KeybaseDaemon interface using RPC
// calls.
type KeybaseDaemonRPC struct {
	identifyClient keybase1.IdentifyInterface
	userClient     keybase1.UserInterface
	sessionClient  keybase1.SessionInterface
	favoriteClient keybase1.FavoriteInterface
	shutdownFn     func()
	log            logger.Logger

	sessionCacheLock sync.RWMutex
	// Set to the zero value when invalidated.
	cachedCurrentSession SessionInfo

	userCacheLock sync.RWMutex
	// Map entries are removed when invalidated.
	userCache map[keybase1.UID]UserInfo
}

var _ keybase1.NotifySessionInterface = (*KeybaseDaemonRPC)(nil)

var _ keybase1.NotifyUsersInterface = (*KeybaseDaemonRPC)(nil)

var _ ConnectionHandler = (*KeybaseDaemonRPC)(nil)

var _ KeybaseDaemon = (*KeybaseDaemonRPC)(nil)

// NewKeybaseDaemonRPC makes a new KeybaseDaemonRPC that makes RPC
// calls using the socket of the given Keybase context.
func NewKeybaseDaemonRPC(config Config, kbCtx *libkb.GlobalContext, log logger.Logger) *KeybaseDaemonRPC {
	k := newKeybaseDaemonRPC(log)
	conn := NewSharedKeybaseConnection(kbCtx, config, k)
	k.fillClients(conn.GetClient())
	k.shutdownFn = conn.Shutdown
	return k
}

// For testing.
func newKeybaseDaemonRPCWithClient(client keybase1.GenericClient,
	log logger.Logger) *KeybaseDaemonRPC {
	k := newKeybaseDaemonRPC(log)
	k.fillClients(client)
	return k
}

func newKeybaseDaemonRPC(log logger.Logger) *KeybaseDaemonRPC {
	k := KeybaseDaemonRPC{
		log:       log,
		userCache: make(map[keybase1.UID]UserInfo),
	}
	return &k
}

func (k *KeybaseDaemonRPC) fillClients(client keybase1.GenericClient) {
	k.identifyClient = keybase1.IdentifyClient{Cli: client}
	k.userClient = keybase1.UserClient{Cli: client}
	k.sessionClient = keybase1.SessionClient{Cli: client}
	k.favoriteClient = keybase1.FavoriteClient{Cli: client}
}

func (k *KeybaseDaemonRPC) filterKeys(ctx context.Context, uid keybase1.UID, keys []keybase1.PublicKey) ([]VerifyingKey, []CryptPublicKey, error) {
	var verifyingKeys []VerifyingKey
	var cryptPublicKeys []CryptPublicKey
	for _, publicKey := range keys {
		if len(publicKey.PGPFingerprint) > 0 {
			continue
		}
		// Import the KID to validate it.
		key, err := libkb.ImportKeypairFromKID(publicKey.KID)
		if err != nil {
			return nil, nil, err
		}
		if publicKey.IsSibkey {
			k.log.CDebugf(
				ctx, "got verifying key %s for user %s",
				key.VerboseDescription(), uid)
			verifyingKeys = append(
				verifyingKeys, VerifyingKey{key.GetKID()})
		} else {
			k.log.CDebugf(
				ctx, "got crypt public key %s for user %s",
				key.VerboseDescription(), uid)
			cryptPublicKeys = append(
				cryptPublicKeys, CryptPublicKey{key.GetKID()})
		}
	}
	return verifyingKeys, cryptPublicKeys, nil
}

func (k *KeybaseDaemonRPC) getCachedCurrentSession() SessionInfo {
	k.sessionCacheLock.RLock()
	defer k.sessionCacheLock.RUnlock()
	return k.cachedCurrentSession
}

func (k *KeybaseDaemonRPC) setCachedCurrentSession(s SessionInfo) {
	k.sessionCacheLock.Lock()
	defer k.sessionCacheLock.Unlock()
	k.cachedCurrentSession = s
}

func (k *KeybaseDaemonRPC) getCachedUserInfo(uid keybase1.UID) UserInfo {
	k.userCacheLock.RLock()
	defer k.userCacheLock.RUnlock()
	return k.userCache[uid]
}

func (k *KeybaseDaemonRPC) setCachedUserInfo(uid keybase1.UID, info UserInfo) {
	k.userCacheLock.Lock()
	defer k.userCacheLock.Unlock()
	if info.Name == libkb.NormalizedUsername("") {
		delete(k.userCache, uid)
	} else {
		k.userCache[uid] = info
	}
}

// LoggedOut implements keybase1.NotifySessionInterface.
func (k *KeybaseDaemonRPC) LoggedOut(ctx context.Context) error {
	k.log.CDebugf(ctx, "Current session logged out")
	k.setCachedCurrentSession(SessionInfo{})
	return nil
}

// UserChanged implements keybase1.NotifySessionInterface.
func (k *KeybaseDaemonRPC) UserChanged(ctx context.Context, uid keybase1.UID) error {
	k.log.CDebugf(ctx, "User %s changed", uid)
	k.setCachedUserInfo(uid, UserInfo{})
	return nil
}

// OnConnect implements the ConnectionHandler interface.
func (k *KeybaseDaemonRPC) OnConnect(ctx context.Context,
	conn *Connection, rawClient keybase1.GenericClient,
	server *rpc.Server) error {
	protocols := []rpc.Protocol{
		client.NewLogUIProtocol(),
		client.NewIdentifyUIProtocol(),
		keybase1.NotifySessionProtocol(k),
		keybase1.NotifyUsersProtocol(k),
	}
	for _, p := range protocols {
		err := server.Register(p)
		if err != nil {
			if _, ok := err.(rpc.AlreadyRegisteredError); !ok {
				return err
			}
		}
	}

	// Using conn.GetClient() here would cause problematic
	// recursion.
	c := keybase1.NotifyCtlClient{Cli: cancelableClient{rawClient}}
	err := c.SetNotifications(ctx, keybase1.NotificationChannels{
		Session: true,
		Users:   true,
	})
	if err != nil {
		return err
	}

	return nil
}

// OnConnectError implements the ConnectionHandler interface.
func (k *KeybaseDaemonRPC) OnConnectError(err error, wait time.Duration) {
	k.log.Warning("KeybaseDaemonRPC: connection error: %q; retrying in %s",
		err, wait)
}

// OnDoCommandError implements the ConnectionHandler interface.
func (k *KeybaseDaemonRPC) OnDoCommandError(err error, wait time.Duration) {
	k.log.Warning("KeybaseDaemonRPC: docommand error: %q; retrying in %s",
		err, wait)
}

// OnDisconnected implements the ConnectionHandler interface.
func (k *KeybaseDaemonRPC) OnDisconnected() {
	k.log.Warning("KeybaseDaemonRPC is disconnected")
}

// ShouldThrottle implements the ConnectionHandler interface.
func (k *KeybaseDaemonRPC) ShouldThrottle(err error) bool {
	return false
}

// Identify implements the KeybaseDaemon interface for KeybaseDaemonRPC.
func (k *KeybaseDaemonRPC) Identify(ctx context.Context, assertion string) (
	UserInfo, error) {
	arg := engine.IDEngineArg{UserAssertion: assertion}.Export()
	res, err := k.identifyClient.Identify(ctx, arg)
	if err != nil {
		return UserInfo{}, err
	}

	uid := res.User.Uid
	verifyingKeys, cryptPublicKeys, err := k.filterKeys(ctx, uid, res.PublicKeys)
	if err != nil {
		return UserInfo{}, err
	}

	u := UserInfo{
		Name:            libkb.NewNormalizedUsername(res.User.Username),
		UID:             uid,
		VerifyingKeys:   verifyingKeys,
		CryptPublicKeys: cryptPublicKeys,
	}

	k.setCachedUserInfo(uid, u)

	return u, nil
}

// LoadUserPlusKeys implements the KeybaseDaemon interface for KeybaseDaemonRPC.
func (k *KeybaseDaemonRPC) LoadUserPlusKeys(ctx context.Context, uid keybase1.UID) (
	UserInfo, error) {
	cachedUserInfo := k.getCachedUserInfo(uid)
	if cachedUserInfo.Name != libkb.NormalizedUsername("") {
		return cachedUserInfo, nil
	}

	arg := keybase1.LoadUserPlusKeysArg{Uid: uid, CacheOK: true}
	res, err := k.userClient.LoadUserPlusKeys(ctx, arg)
	if err != nil {
		return UserInfo{}, err
	}

	verifyingKeys, cryptPublicKeys, err := k.filterKeys(ctx, uid, res.DeviceKeys)
	if err != nil {
		return UserInfo{}, err
	}

	u := UserInfo{
		Name:            libkb.NewNormalizedUsername(res.Username),
		UID:             res.Uid,
		VerifyingKeys:   verifyingKeys,
		CryptPublicKeys: cryptPublicKeys,
	}

	k.setCachedUserInfo(uid, u)

	return u, nil
}

// CurrentSession implements the KeybaseDaemon interface for KeybaseDaemonRPC.
func (k *KeybaseDaemonRPC) CurrentSession(ctx context.Context, sessionID int) (
	SessionInfo, error) {
	cachedCurrentSession := k.getCachedCurrentSession()
	if cachedCurrentSession != (SessionInfo{}) {
		return cachedCurrentSession, nil
	}

	res, err := k.sessionClient.CurrentSession(ctx, sessionID)
	if err != nil {
		return SessionInfo{}, err
	}
	// Import the KID to validate it.
	deviceSubkey, err := libkb.ImportKeypairFromKID(res.DeviceSubkeyKid)
	if err != nil {
		return SessionInfo{}, err
	}
	k.log.CDebugf(ctx, "got device subkey %s",
		deviceSubkey.GetKID().ToShortIDString())
	cryptPublicKey := CryptPublicKey{deviceSubkey.GetKID()}
	s := SessionInfo{
		UID:            keybase1.UID(res.Uid),
		Token:          res.Token,
		CryptPublicKey: cryptPublicKey,
	}

	k.setCachedCurrentSession(s)

	return s, nil
}

// FavoriteAdd implements the KeybaseDaemon interface for KeybaseDaemonRPC.
func (k *KeybaseDaemonRPC) FavoriteAdd(ctx context.Context, folder keybase1.Folder) error {
	return k.favoriteClient.FavoriteAdd(ctx, keybase1.FavoriteAddArg{Folder: folder})
}

// FavoriteDelete implements the KeybaseDaemon interface for KeybaseDaemonRPC.
func (k *KeybaseDaemonRPC) FavoriteDelete(ctx context.Context, folder keybase1.Folder) error {
	return k.favoriteClient.FavoriteDelete(ctx, keybase1.FavoriteDeleteArg{Folder: folder})
}

// FavoriteList implements the KeybaseDaemon interface for KeybaseDaemonRPC.
func (k *KeybaseDaemonRPC) FavoriteList(ctx context.Context, sessionID int) ([]keybase1.Folder, error) {
	return k.favoriteClient.FavoriteList(ctx, sessionID)
}

// Shutdown implements the KeybaseDaemon interface for KeybaseDaemonRPC.
func (k *KeybaseDaemonRPC) Shutdown() {
	if k.shutdownFn != nil {
		k.shutdownFn()
	}
}
<commit_msg>fixes for new identify UI<commit_after>package libkbfs

import (
	"sync"
	"time"

	"github.com/keybase/client/go/client"
	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/logger"
	keybase1 "github.com/keybase/client/go/protocol"
	rpc "github.com/keybase/go-framed-msgpack-rpc"
	"golang.org/x/net/context"
)

// KeybaseDaemonRPC implements the KeybaseDaemon interface using RPC
// calls.
type KeybaseDaemonRPC struct {
	libkb.Contextified
	identifyClient keybase1.IdentifyInterface
	userClient     keybase1.UserInterface
	sessionClient  keybase1.SessionInterface
	favoriteClient keybase1.FavoriteInterface
	shutdownFn     func()
	log            logger.Logger

	sessionCacheLock sync.RWMutex
	// Set to the zero value when invalidated.
	cachedCurrentSession SessionInfo

	userCacheLock sync.RWMutex
	// Map entries are removed when invalidated.
	userCache map[keybase1.UID]UserInfo
}

var _ keybase1.NotifySessionInterface = (*KeybaseDaemonRPC)(nil)

var _ keybase1.NotifyUsersInterface = (*KeybaseDaemonRPC)(nil)

var _ ConnectionHandler = (*KeybaseDaemonRPC)(nil)

var _ KeybaseDaemon = (*KeybaseDaemonRPC)(nil)

// NewKeybaseDaemonRPC makes a new KeybaseDaemonRPC that makes RPC
// calls using the socket of the given Keybase context.
func NewKeybaseDaemonRPC(config Config, kbCtx *libkb.GlobalContext, log logger.Logger) *KeybaseDaemonRPC {
	k := newKeybaseDaemonRPC(kbCtx, log)
	conn := NewSharedKeybaseConnection(kbCtx, config, k)
	k.fillClients(conn.GetClient())
	k.shutdownFn = conn.Shutdown
	return k
}

// For testing.
func newKeybaseDaemonRPCWithClient(kbCtx *libkb.GlobalContext, client keybase1.GenericClient,
	log logger.Logger) *KeybaseDaemonRPC {
	k := newKeybaseDaemonRPC(kbCtx,log)
	k.fillClients(client)
	return k
}

func newKeybaseDaemonRPC(kbCtx *libkb.GlobalContext, log logger.Logger) *KeybaseDaemonRPC {
	k := KeybaseDaemonRPC{
		Contextified : libkb.NewContextified(kbCtx),
		log:       log,
		userCache: make(map[keybase1.UID]UserInfo),
	}
	return &k
}

func (k *KeybaseDaemonRPC) fillClients(client keybase1.GenericClient) {
	k.identifyClient = keybase1.IdentifyClient{Cli: client}
	k.userClient = keybase1.UserClient{Cli: client}
	k.sessionClient = keybase1.SessionClient{Cli: client}
	k.favoriteClient = keybase1.FavoriteClient{Cli: client}
}

func (k *KeybaseDaemonRPC) filterKeys(ctx context.Context, uid keybase1.UID, keys []keybase1.PublicKey) ([]VerifyingKey, []CryptPublicKey, error) {
	var verifyingKeys []VerifyingKey
	var cryptPublicKeys []CryptPublicKey
	for _, publicKey := range keys {
		if len(publicKey.PGPFingerprint) > 0 {
			continue
		}
		// Import the KID to validate it.
		key, err := libkb.ImportKeypairFromKID(publicKey.KID)
		if err != nil {
			return nil, nil, err
		}
		if publicKey.IsSibkey {
			k.log.CDebugf(
				ctx, "got verifying key %s for user %s",
				key.VerboseDescription(), uid)
			verifyingKeys = append(
				verifyingKeys, VerifyingKey{key.GetKID()})
		} else {
			k.log.CDebugf(
				ctx, "got crypt public key %s for user %s",
				key.VerboseDescription(), uid)
			cryptPublicKeys = append(
				cryptPublicKeys, CryptPublicKey{key.GetKID()})
		}
	}
	return verifyingKeys, cryptPublicKeys, nil
}

func (k *KeybaseDaemonRPC) getCachedCurrentSession() SessionInfo {
	k.sessionCacheLock.RLock()
	defer k.sessionCacheLock.RUnlock()
	return k.cachedCurrentSession
}

func (k *KeybaseDaemonRPC) setCachedCurrentSession(s SessionInfo) {
	k.sessionCacheLock.Lock()
	defer k.sessionCacheLock.Unlock()
	k.cachedCurrentSession = s
}

func (k *KeybaseDaemonRPC) getCachedUserInfo(uid keybase1.UID) UserInfo {
	k.userCacheLock.RLock()
	defer k.userCacheLock.RUnlock()
	return k.userCache[uid]
}

func (k *KeybaseDaemonRPC) setCachedUserInfo(uid keybase1.UID, info UserInfo) {
	k.userCacheLock.Lock()
	defer k.userCacheLock.Unlock()
	if info.Name == libkb.NormalizedUsername("") {
		delete(k.userCache, uid)
	} else {
		k.userCache[uid] = info
	}
}

// LoggedOut implements keybase1.NotifySessionInterface.
func (k *KeybaseDaemonRPC) LoggedOut(ctx context.Context) error {
	k.log.CDebugf(ctx, "Current session logged out")
	k.setCachedCurrentSession(SessionInfo{})
	return nil
}

// UserChanged implements keybase1.NotifySessionInterface.
func (k *KeybaseDaemonRPC) UserChanged(ctx context.Context, uid keybase1.UID) error {
	k.log.CDebugf(ctx, "User %s changed", uid)
	k.setCachedUserInfo(uid, UserInfo{})
	return nil
}

// OnConnect implements the ConnectionHandler interface.
func (k *KeybaseDaemonRPC) OnConnect(ctx context.Context,
	conn *Connection, rawClient keybase1.GenericClient,
	server *rpc.Server) error {
	protocols := []rpc.Protocol{
		client.NewLogUIProtocol(),
		client.NewIdentifyUIProtocol(k.G()),
		keybase1.NotifySessionProtocol(k),
		keybase1.NotifyUsersProtocol(k),
	}
	for _, p := range protocols {
		err := server.Register(p)
		if err != nil {
			if _, ok := err.(rpc.AlreadyRegisteredError); !ok {
				return err
			}
		}
	}

	// Using conn.GetClient() here would cause problematic
	// recursion.
	c := keybase1.NotifyCtlClient{Cli: cancelableClient{rawClient}}
	err := c.SetNotifications(ctx, keybase1.NotificationChannels{
		Session: true,
		Users:   true,
	})
	if err != nil {
		return err
	}

	return nil
}

// OnConnectError implements the ConnectionHandler interface.
func (k *KeybaseDaemonRPC) OnConnectError(err error, wait time.Duration) {
	k.log.Warning("KeybaseDaemonRPC: connection error: %q; retrying in %s",
		err, wait)
}

// OnDoCommandError implements the ConnectionHandler interface.
func (k *KeybaseDaemonRPC) OnDoCommandError(err error, wait time.Duration) {
	k.log.Warning("KeybaseDaemonRPC: docommand error: %q; retrying in %s",
		err, wait)
}

// OnDisconnected implements the ConnectionHandler interface.
func (k *KeybaseDaemonRPC) OnDisconnected() {
	k.log.Warning("KeybaseDaemonRPC is disconnected")
}

// ShouldThrottle implements the ConnectionHandler interface.
func (k *KeybaseDaemonRPC) ShouldThrottle(err error) bool {
	return false
}

// Identify implements the KeybaseDaemon interface for KeybaseDaemonRPC.
func (k *KeybaseDaemonRPC) Identify(ctx context.Context, assertion string) (
	UserInfo, error) {
	arg := keybase1.IdentifyArg{UserAssertion: assertion}
	res, err := k.identifyClient.Identify(ctx, arg)
	if err != nil {
		return UserInfo{}, err
	}

	uid := res.User.Uid
	verifyingKeys, cryptPublicKeys, err := k.filterKeys(ctx, uid, res.PublicKeys)
	if err != nil {
		return UserInfo{}, err
	}

	u := UserInfo{
		Name:            libkb.NewNormalizedUsername(res.User.Username),
		UID:             uid,
		VerifyingKeys:   verifyingKeys,
		CryptPublicKeys: cryptPublicKeys,
	}

	k.setCachedUserInfo(uid, u)

	return u, nil
}

// LoadUserPlusKeys implements the KeybaseDaemon interface for KeybaseDaemonRPC.
func (k *KeybaseDaemonRPC) LoadUserPlusKeys(ctx context.Context, uid keybase1.UID) (
	UserInfo, error) {
	cachedUserInfo := k.getCachedUserInfo(uid)
	if cachedUserInfo.Name != libkb.NormalizedUsername("") {
		return cachedUserInfo, nil
	}

	arg := keybase1.LoadUserPlusKeysArg{Uid: uid, CacheOK: true}
	res, err := k.userClient.LoadUserPlusKeys(ctx, arg)
	if err != nil {
		return UserInfo{}, err
	}

	verifyingKeys, cryptPublicKeys, err := k.filterKeys(ctx, uid, res.DeviceKeys)
	if err != nil {
		return UserInfo{}, err
	}

	u := UserInfo{
		Name:            libkb.NewNormalizedUsername(res.Username),
		UID:             res.Uid,
		VerifyingKeys:   verifyingKeys,
		CryptPublicKeys: cryptPublicKeys,
	}

	k.setCachedUserInfo(uid, u)

	return u, nil
}

// CurrentSession implements the KeybaseDaemon interface for KeybaseDaemonRPC.
func (k *KeybaseDaemonRPC) CurrentSession(ctx context.Context, sessionID int) (
	SessionInfo, error) {
	cachedCurrentSession := k.getCachedCurrentSession()
	if cachedCurrentSession != (SessionInfo{}) {
		return cachedCurrentSession, nil
	}

	res, err := k.sessionClient.CurrentSession(ctx, sessionID)
	if err != nil {
		return SessionInfo{}, err
	}
	// Import the KID to validate it.
	deviceSubkey, err := libkb.ImportKeypairFromKID(res.DeviceSubkeyKid)
	if err != nil {
		return SessionInfo{}, err
	}
	k.log.CDebugf(ctx, "got device subkey %s",
		deviceSubkey.GetKID().ToShortIDString())
	cryptPublicKey := CryptPublicKey{deviceSubkey.GetKID()}
	s := SessionInfo{
		UID:            keybase1.UID(res.Uid),
		Token:          res.Token,
		CryptPublicKey: cryptPublicKey,
	}

	k.setCachedCurrentSession(s)

	return s, nil
}

// FavoriteAdd implements the KeybaseDaemon interface for KeybaseDaemonRPC.
func (k *KeybaseDaemonRPC) FavoriteAdd(ctx context.Context, folder keybase1.Folder) error {
	return k.favoriteClient.FavoriteAdd(ctx, keybase1.FavoriteAddArg{Folder: folder})
}

// FavoriteDelete implements the KeybaseDaemon interface for KeybaseDaemonRPC.
func (k *KeybaseDaemonRPC) FavoriteDelete(ctx context.Context, folder keybase1.Folder) error {
	return k.favoriteClient.FavoriteDelete(ctx, keybase1.FavoriteDeleteArg{Folder: folder})
}

// FavoriteList implements the KeybaseDaemon interface for KeybaseDaemonRPC.
func (k *KeybaseDaemonRPC) FavoriteList(ctx context.Context, sessionID int) ([]keybase1.Folder, error) {
	return k.favoriteClient.FavoriteList(ctx, sessionID)
}

// Shutdown implements the KeybaseDaemon interface for KeybaseDaemonRPC.
func (k *KeybaseDaemonRPC) Shutdown() {
	if k.shutdownFn != nil {
		k.shutdownFn()
	}
}
<|endoftext|>


<commit_before>package builder

import (
	"time"

	"github.com/nicksnyder/go-i18n/i18n"

	"github.com/aymerick/kowa/models"
)

// Members node builder
type MembersBuilder struct {
	*NodeBuilderBase

	// loaded members
	membersVars []*MemberVars
}

// Members node content
type MembersContent struct {
	Node *Node

	Members []*MemberVars
}

// Member vars
type MemberVars struct {
	Date        time.Time
	Photo       *ImageVars
	Fullname    string
	Role        string
	Description string
}

func init() {
	RegisterNodeBuilder(KIND_MEMBERS, NewMembersBuilder)
}

func NewMembersBuilder(siteBuilder *SiteBuilder) NodeBuilder {
	return &MembersBuilder{
		NodeBuilderBase: &NodeBuilderBase{
			nodeKind:    KIND_MEMBERS,
			siteBuilder: siteBuilder,
		},
	}
}

// NodeBuilder
func (builder *MembersBuilder) Load() {
	// fetch members
	membersVars := builder.members()
	if len(membersVars) == 0 {
	}

	// get page settings
	title, tagline, cover, disabled := builder.pageSettings(models.PAGE_KIND_MEMBERS)
	if disabled {
		return
	}

	T := i18n.MustTfunc(builder.siteLang())
	slug := T("members")

	if title == "" {
		title = slug
	}

	// build node
	node := builder.newNode()
	node.fillUrl(slug)

	node.Title = title
	node.Tagline = tagline
	node.Cover = cover

	node.Meta = &NodeMeta{Description: tagline}

	node.InNavBar = true
	node.NavBarOrder = 15

	node.Content = &MembersContent{
		Node:    node,
		Members: membersVars,
	}

	builder.addNode(node)
}

// NodeBuilder
func (builder *MembersBuilder) Data(name string) interface{} {
	switch name {
	case "members":
		return builder.members()
	}

	return nil
}

// returns members contents
func (builder *MembersBuilder) members() []*MemberVars {
	if len(builder.membersVars) == 0 {
		// fetch members
		for _, member := range *builder.site().FindAllMembers() {
			memberVars := builder.NewMemberVars(member)

			builder.membersVars = append(builder.membersVars, memberVars)
		}
	}

	return builder.membersVars
}

func (builder *MembersBuilder) NewMemberVars(member *models.Member) *MemberVars {
	result := &MemberVars{
		Date:     member.CreatedAt,
		Fullname: member.Fullname,
		Role:     member.Role,
	}

	photo := member.FindPhoto()
	if photo != nil {
		result.Photo = builder.addImage(photo)
	}

	result.Description = member.Description

	return result
}
<commit_msg>builder: don't generate members page if there is no member<commit_after>package builder

import (
	"time"

	"github.com/nicksnyder/go-i18n/i18n"

	"github.com/aymerick/kowa/models"
)

// Members node builder
type MembersBuilder struct {
	*NodeBuilderBase

	// loaded members
	membersVars []*MemberVars
}

// Members node content
type MembersContent struct {
	Node *Node

	Members []*MemberVars
}

// Member vars
type MemberVars struct {
	Date        time.Time
	Photo       *ImageVars
	Fullname    string
	Role        string
	Description string
}

func init() {
	RegisterNodeBuilder(KIND_MEMBERS, NewMembersBuilder)
}

func NewMembersBuilder(siteBuilder *SiteBuilder) NodeBuilder {
	return &MembersBuilder{
		NodeBuilderBase: &NodeBuilderBase{
			nodeKind:    KIND_MEMBERS,
			siteBuilder: siteBuilder,
		},
	}
}

// NodeBuilder
func (builder *MembersBuilder) Load() {
	// fetch members
	membersVars := builder.members()
	if len(membersVars) == 0 {
		return
	}

	// get page settings
	title, tagline, cover, disabled := builder.pageSettings(models.PAGE_KIND_MEMBERS)
	if disabled {
		return
	}

	T := i18n.MustTfunc(builder.siteLang())
	slug := T("members")

	if title == "" {
		title = slug
	}

	// build node
	node := builder.newNode()
	node.fillUrl(slug)

	node.Title = title
	node.Tagline = tagline
	node.Cover = cover

	node.Meta = &NodeMeta{Description: tagline}

	node.InNavBar = true
	node.NavBarOrder = 15

	node.Content = &MembersContent{
		Node:    node,
		Members: membersVars,
	}

	builder.addNode(node)
}

// NodeBuilder
func (builder *MembersBuilder) Data(name string) interface{} {
	switch name {
	case "members":
		return builder.members()
	}

	return nil
}

// returns members contents
func (builder *MembersBuilder) members() []*MemberVars {
	if len(builder.membersVars) == 0 {
		// fetch members
		for _, member := range *builder.site().FindAllMembers() {
			memberVars := builder.NewMemberVars(member)

			builder.membersVars = append(builder.membersVars, memberVars)
		}
	}

	return builder.membersVars
}

func (builder *MembersBuilder) NewMemberVars(member *models.Member) *MemberVars {
	result := &MemberVars{
		Date:     member.CreatedAt,
		Fullname: member.Fullname,
		Role:     member.Role,
	}

	photo := member.FindPhoto()
	if photo != nil {
		result.Photo = builder.addImage(photo)
	}

	result.Description = member.Description

	return result
}
<|endoftext|>


<commit_before>package ergo

// Route

type Route struct {
	parent      *Route
	name        string
	path        string
	description string
	routes      map[string]*Route
}

func NewRoute(path string) *Route {
	return &Route{
		path:     path,
		routes:   map[string]*Route{},
	}
}

// GetParent returns the parent of the route.
func (r *Route) GetParent() *Route {
	return r.parent
}

// Name sets the name of the route.
// The route name is only used for documentation purposes.
func (r *Route) Name(name string) *Route {
	r.name = name
	return r
}

// GetName returns the name of the route.
func (r *Route) GetName() string {
	return r.name
}

// GetName returns the description of the route.
func (r *Route) GetDescription() string {
	return r.description
}

// Description sets the description of the route.
func (r *Route) Description(description string) *Route {
	r.description = description
	return r
}

// GetPath returns the relative path of the route to the
// parent route.
func (r *Route) GetPath() string {
	return r.path
}

// GetFullPath returns the absolute path of the route.
func (r *Route) GetFullPath() string {
	if r.parent != nil {
		return r.parent.GetFullPath() + r.path
	}
	return r.path
}

// GetRoutes returns the map of child routes.
func (r *Route) GetRoutes() map[string]*Route {
	return r.routes
}

// GetSchemes returns the default schemes passed from
// the parent.
func (r *Route) GetSchemes() []string {
	return r.schemes
}

// GetConsumes returns the consumable content types
// passed from the parent.
func (r *Route) GetConsumes() []string {
	return r.consumes
}

// GetProduces returns the producible content types
// passed from the parent.
func (r *Route) GetProduces() []string {
	return r.produces
}

func (r *Route) setSchemes(schemes []string) {
	r.schemes = schemes
}

func (r *Route) setConsumes(consumes []string) {
	r.consumes = consumes
}

func (r *Route) setProduces(produces []string) {
	r.produces = produces
}

<commit_msg>Setter and getter for notFoundHandler in Route<commit_after>package ergo

// Route

type Route struct {
	parent      *Route
	name        string
	path        string
	description string
	routes      map[string]*Route
	notFoundHandler Handler
}

func NewRoute(path string) *Route {
	return &Route{
		path:     path,
		routes:   map[string]*Route{},
	}
}

// GetParent returns the parent of the route.
func (r *Route) GetParent() *Route {
	return r.parent
}

// Name sets the name of the route.
// The route name is only used for documentation purposes.
func (r *Route) Name(name string) *Route {
	r.name = name
	return r
}

// GetName returns the name of the route.
func (r *Route) GetName() string {
	return r.name
}

// GetName returns the description of the route.
func (r *Route) GetDescription() string {
	return r.description
}

// Description sets the description of the route.
func (r *Route) Description(description string) *Route {
	r.description = description
	return r
}

// GetPath returns the relative path of the route to the
// parent route.
func (r *Route) GetPath() string {
	return r.path
}

// GetFullPath returns the absolute path of the route.
func (r *Route) GetFullPath() string {
	if r.parent != nil {
		return r.parent.GetFullPath() + r.path
	}
	return r.path
}

// GetRoutes returns the map of child routes.
func (r *Route) GetRoutes() map[string]*Route {
	return r.routes
}

// GetSchemes returns the default schemes passed from
// the parent.
func (r *Route) GetSchemes() []string {
	return r.schemes
}

// GetConsumes returns the consumable content types
// passed from the parent.
func (r *Route) GetConsumes() []string {
	return r.consumes
}

// GetProduces returns the producible content types
// passed from the parent.
func (r *Route) GetProduces() []string {
	return r.produces
}

// NotFoundHandler sets the handler used when an operation is not found
// in the route, when a subroute could not be found.
func (r *Route) NotFoundHandler(h Handler) *Route {
	r.notFoundHandler = h
	return r
}

// GetNotFoundHandler returns the handler set in the route.
// If it is nil and t is true then it will try and look for handler in a parent.
func (r *Route) GetNotFoundHandler(t bool) Handler {
	if r.notFoundHandler == nil {
		if t {
			return r.parent.GetNotFoundHandler(t)
		}
		return nil
	}
	return r.notFoundHandler
}

func (r *Route) setSchemes(schemes []string) {
	r.schemes = schemes
}

func (r *Route) setConsumes(consumes []string) {
	r.consumes = consumes
}

func (r *Route) setProduces(produces []string) {
	r.produces = produces
}

<|endoftext|>


<commit_before>package ergo

import (
	"strings"
)

type Router interface {
}

// Route

type Route struct {
	ergo       *Ergo
	parent     *Route
	path       string
	routes     []*Route
	params     map[string]*Param
	operations []*Operation
}

func NewRoute(path string) *Route {
	return &Route{
		path:   strings.ToLower(preparePath(path)),
		params: map[string]*Param{},
	}
}

// GetParent returns the parent of the route.
func (r *Route) GetParent() *Route {
	return r.parent
}

// GetPath returns the relative path of the route to the
// parent route.
func (r *Route) GetPath() string {
	if r.parent != nil {
		return "/" + r.path
	}
	return ""
}

// GetFullPath returns the absolute path of the route.
func (r *Route) GetFullPath() string {
	if r.parent != nil {
		return r.parent.GetFullPath() + r.GetPath()
	}
	return r.GetPath()
}

// New creates a route with the provided path and adds it
// to r then returns a pointer to it.
func (r *Route) New(path string) *Route {
	route := NewRoute(path)
	r.addRoute(route)
	return route
}

// AddRoute copies and add the given route then returns
// a pointer to the added one.
func (r *Route) AddRoute(route *Route) *Route {
	nroute := route.Copy()
	r.addRoute(nroute)
	return nroute
}

// AddRoutes copies and add every Route in routes.
func (r *Route) AddRoutes(routes ...*Route) *Route {
	for _, nr := range routes {
		r.AddRoute(nr)
	}
	return r
}

// GetRoutes returns the slice of child routes.
func (r *Route) GetRoutes() []*Route {
	return r.routes
}

// GetRoutes returns the slice of all nested routes under r.
func (r *Route) GetAllRoutes() []*Route {
	var routes []*Route
	for _, route := range r.routes {
		routes = append(routes, route.GetAllRoutes()...)
	}
	return routes
}

// SetRoutes replaces the routes slice with the given one.
func (r *Route) SetRoutes(routes []*Route) *Route {
	r.routes = routes
	return r
}

// Params add the given params to the params map in the route.
// No two params can have the same name, even if the were
// in different places.
func (r *Route) Params(params ...*Param) *Route {
	addParams(r, params...)
	return r
}

func (r *Route) GetParams() map[string]*Param {
	return r.params
}

func (r *Route) GetParamsSlice() []*Param {
	var params []*Param
	for _, p := range r.params {
		params = append(params, p)
	}
	return params
}

func (r *Route) ResetParams(params ...*Param) *Route {
	r.setParamsSlice(params...)
	return r
}

func (r *Route) SetParams(params map[string]*Param) *Route {
	r.setParams(params)
	return r
}

func (r *Route) IgnoreParams(params ...string) *Route {
	ignoreParams(r, params...)
	return r
}

func (r *Route) IgnoreParamsBut(params ...string) *Route {
	ignoreParamsBut(r, params...)
	return r
}

func (r *Route) GET(function HandlerFunc) *Operation {
	return r.HandleGET(HandlerFunc(function))
}

func (r *Route) HandleGET(handler Handler) *Operation {
	operation := HandleGET(handler)
	setOperation(r, operation)
	r.operations[constants.METHOD_GET] = operation
	return operation
}

func (r *Route) POST(function HandlerFunc) *Operation {
	return r.HandlePOST(HandlerFunc(function))
}

func (r *Route) HandlePOST(handler Handler) *Operation {
	operation := HandlePOST(handler)
	setOperation(r, operation)
	r.operations[constants.METHOD_POST] = operation
	return operation
}

func (r *Route) PUT(function HandlerFunc) *Operation {
	return r.HandlePUT(HandlerFunc(function))
}

func (r *Route) HandlePUT(handler Handler) *Operation {
	operation := HandlePUT(handler)
	setOperation(r, operation)
	r.operations[constants.METHOD_PUT] = operation
	return operation
}

func (r *Route) DELETE(function HandlerFunc) *Operation {
	return r.HandleDELETE(HandlerFunc(function))
}

func (r *Route) HandleDELETE(handler Handler) *Operation {
	operation := HandleDELETE(handler)
	setOperation(r, operation)
	r.operations[constants.METHOD_DELETE] = operation
	return operation
}

// Operations does not alter the given operations in any way,
// it does not even add route parameters.
func (r *Route) Operations(operations ...*Operation) *Route {
	for _, o := range operations {
		r.operations[o.method] = o
	}
	return r
}

func (r *Route) GetOperations() OperationMap {
	return r.operations
}

func (r *Route) GetOperationers() OperationerMap {
	ops := OperationerMap{}
	for m, o := range r.operations {
		ops[m] = o
	}
	return ops
}

// returns Ergo object with only a set of methods exposed
func (r *Route) Ergo() Ergoer {
	return r.ergo
}

func (r *Route) Match(path string) (*Route, string) {
	if r.parent != nil {
		mat, rem, par := r.match(path)
		if !mat {
			return nil, ""
		}
		if rem == "" {
			return r, par
		}
		return r.subMatch(rem)
	}
	return r.subMatch(path)
}

func (r *Route) MatchURL(u *url.URL) (*Route, string) {
	return r.Match(u.Path[:len(u.Path)+1])
}

func (r *Route) ServeHTTP(res *Response, req *Request) {
	// validate the params with all the matching routes
	o, ok := r.operations.GetOperation(req.Method)
	if !ok {
		r.ergo.MethodNotAllowed(r, res, req)
		return
	}
	req.operation = o
	o.ServeHTTP(res, req)
}

// Copy returns a pointer to a copy of the route.
// It does not copy parent, operations, nor deep-copy the params.
func (r *Route) Copy() *Route {
	route := NewRoute(r.path)
	for _, cr := range r.routes {
		route.AddRoute(cr)
	}
	route.params = r.params
	return route
}

func (r *Route) addRoute(route *Route) {
	_, ok := r.routes[route.path]
	if ok {
		panic(fmt.Sprintf("A route with the path \"%s\" already exists.", route.path))
	}
	route.ergo = r.ergo
	route.parent = r
	setParamer(r, route)
	r.routesSlice = append(r.routesSlice, route.path)
	r.routes[route.path] = route
}

func (r *Route) setParams(params map[string]*Param) {
	if params == nil {
		params = make(map[string]*Param)
	}
	r.params = params
}

func (r *Route) setParamsSlice(params ...*Param) {
	paramsMap := map[string]*Param{}
	for _, p := range params {
		r.params[p.name] = p
	}
	r.setParams(paramsMap)
}

func (r *Route) match(path string) (bool, string, string) {
	return match(r.path, path)
}

func (r *Route) subMatch(path string) (*Route, string) {
	for _, routepath := range r.routesSlice {
		nr, par := r.routes[routepath].Match(path)
		if nr != nil {
			return nr, par
		}
	}
	return nil, ""
}

<commit_msg>Needed changes for Route due to restructuring<commit_after>package ergo

import (
	"strings"
)

type Router interface {
}

// Route

type Route struct {
	ergo       *Ergo
	parent     *Route
	path       string
	routes     []*Route
	params     map[string]*Param
	operations []*Operation
}

func NewRoute(path string) *Route {
	return &Route{
		path:   strings.ToLower(preparePath(path)),
		params: map[string]*Param{},
	}
}

// GetParent returns the parent of the route.
func (r *Route) GetParent() *Route {
	return r.parent
}

// GetPath returns the relative path of the route to the
// parent route.
func (r *Route) GetPath() string {
	if r.parent != nil {
		return r.path
	}
	return ""
}

// GetFullPath returns the absolute path of the route.
func (r *Route) GetFullPath() string {
	if r.parent != nil {
		return r.parent.GetFullPath() + r.GetPath()
	}
	return r.GetPath()
}

// New creates a route with the provided path and adds it
// to r then returns a pointer to it.
func (r *Route) New(path string) *Route {
	route := NewRoute(path)
	r.addRoute(route)
	return route
}

// AddRoute copies and add the given route then returns
// a pointer to the added one.
func (r *Route) AddRoute(route *Route) *Route {
	nroute := route.Copy()
	r.addRoute(nroute)
	return nroute
}

// AddRoutes copies and add every Route in routes.
func (r *Route) AddRoutes(routes ...*Route) *Route {
	for _, nr := range routes {
		r.AddRoute(nr)
	}
	return r
}

// GetRoutes returns the slice of child routes.
func (r *Route) GetRoutes() []*Route {
	return r.routes
}

// GetRoutes returns the slice of all nested routes under r.
func (r *Route) GetAllRoutes() []*Route {
	var routes []*Route
	for _, route := range r.routes {
		routes = append(routes, route.GetAllRoutes()...)
	}
	return routes
}

// SetRoutes replaces the routes slice with the given one.
func (r *Route) SetRoutes(routes []*Route) *Route {
	r.routes = routes
	return r
}

// Params add the given params to the params map in the route.
// No two params can have the same name, even if the were
// in different places.
func (r *Route) Params(params ...*Param) *Route {
	addParams(r, params...)
	return r
}

func (r *Route) GetParams() map[string]*Param {
	return r.params
}

func (r *Route) GetParamsSlice() []*Param {
	var params []*Param
	for _, p := range r.params {
		params = append(params, p)
	}
	return params
}

func (r *Route) ResetParams(params ...*Param) *Route {
	r.setParamsSlice(params...)
	return r
}

func (r *Route) SetParams(params map[string]*Param) *Route {
	r.setParams(params)
	return r
}

func (r *Route) IgnoreParams(params ...string) *Route {
	ignoreParams(r, params...)
	return r
}

func (r *Route) IgnoreParamsBut(params ...string) *Route {
	ignoreParamsBut(r, params...)
	return r
}

func (r *Route) GET(function HandlerFunc) *Operation {
	return r.HandleGET(HandlerFunc(function))
}

func (r *Route) HandleGET(handler Handler) *Operation {
	operation := HandleGET(handler)
	r.addOperation(operation)
	return operation
}

func (r *Route) POST(function HandlerFunc) *Operation {
	return r.HandlePOST(HandlerFunc(function))
}

func (r *Route) HandlePOST(handler Handler) *Operation {
	operation := HandlePOST(handler)
	r.addOperation(operation)
	return operation
}

func (r *Route) PUT(function HandlerFunc) *Operation {
	return r.HandlePUT(HandlerFunc(function))
}

func (r *Route) HandlePUT(handler Handler) *Operation {
	operation := HandlePUT(handler)
	r.addOperation(operation)
	return operation
}

func (r *Route) DELETE(function HandlerFunc) *Operation {
	return r.HandleDELETE(HandlerFunc(function))
}

func (r *Route) HandleDELETE(handler Handler) *Operation {
	operation := HandleDELETE(handler)
	r.addOperation(operation)
	return operation
}

// Operations does not alter the given operations in any way,
// it does not even add route parameters.
func (r *Route) Operations(operations ...*Operation) *Route {
	r.operations = operations
	return r
}

func (r *Route) GetAllOperations() []*Operation {
	var ops []*Operation
	ops = append(ops, r.operations...)
	for _, route := range r.routes {
		ops = append(ops, route.GetAllOperations()...)
	}
	return ops
}

// returns Ergo object with only a set of methods exposed
func (r *Route) Ergo() Ergoer {
	return r.ergo
func (r *Route) GetOperations() []*Operation {
	return r.operations
}

func (r *Route) Match(path string) (*Route, string) {
	if r.parent != nil {
		mat, rem, par := r.match(path)
		if !mat {
			return nil, ""
		}
		if rem == "" {
			return r, par
		}
		return r.subMatch(rem)
	}
	return r.subMatch(path)
}

func (r *Route) MatchURL(u *url.URL) (*Route, string) {
	return r.Match(u.Path[:len(u.Path)+1])
}

func (r *Route) ServeHTTP(res *Response, req *Request) {
	// validate the params with all the matching routes
	o, ok := r.operations.GetOperation(req.Method)
	if !ok {
		r.ergo.MethodNotAllowed(r, res, req)
		return
	}
	req.operation = o
	o.ServeHTTP(res, req)
}

// Copy returns a pointer to a copy of the route.
// It does not copy parent, operations, nor deep-copy the params.
func (r *Route) Copy() *Route {
	route := NewRoute(r.path)
	for _, cr := range r.routes {
		route.AddRoute(cr)
	}
	route.params = r.params
	return route
}

func (r *Route) addOperation(o *Operation) {
	o.route = r
	o.setSchemes(r.ergo.GetSchemes())
	o.setConsumes(r.ergo.GetConsumes())
	o.setProduces(r.ergo.GetProduces())
	setParamer(r, o)
	r.operations = append(r.operations, o)

}

func (r *Route) addRoute(route *Route) {
	route.ergo = r.ergo
	route.parent = r
	setParamer(r, route)
	r.routes = append(r.routes, route)
}

func (r *Route) setParams(params map[string]*Param) {
	if params == nil {
		params = make(map[string]*Param)
	}
	r.params = params
}

func (r *Route) setParamsSlice(params ...*Param) {
	paramsMap := map[string]*Param{}
	for _, p := range params {
		r.params[p.name] = p
	}
	r.setParams(paramsMap)
}

func (r *Route) match(path string) (bool, string, string) {
	return match(r.path, path)
}

func (r *Route) subMatch(path string) (*Route, string) {
	for _, routepath := range r.routesSlice {
		nr, par := r.routes[routepath].Match(path)
		if nr != nil {
			return nr, par
		}
	}
	return nil, ""
}

<|endoftext|>


<commit_before>package view

import (
	"encoding/gob"
	"encoding/json"
	"fmt"
	"html/template"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"github.com/josephspurrier/gowebapp/shared/session"
)

func init() {
	// Magic goes here to allow serializing maps in securecookie
	// http://golang.org/pkg/encoding/gob/#Register
	// Source: http://stackoverflow.com/questions/21934730/gob-type-not-registered-for-interface-mapstringinterface
	gob.Register(Flash{})
}

var (
	FlashError   = "alert-danger"
	FlashSuccess = "alert-success"
	FlashNotice  = "alert-info"
	FlashWarning = "alert-warning"

	childTemplates     []string
	rootTemplate       string
	templateCollection = make(map[string]*template.Template)
	pluginCollection   = make(template.FuncMap)
	mutex              sync.RWMutex
	mutexPlugins       sync.RWMutex
	sessionName        string
	viewInfo           View
)

// Template root and children
type Template struct {
	Root     string   `json:"Root"`
	Children []string `json:"Children"`
}

// View attributes
type View struct {
	BaseURI   string
	Extension string
	Folder    string
	Name      string
	Caching   bool
	Vars      map[string]interface{}
	request   *http.Request
}

// Flash Message
type Flash struct {
	Message string
	Class   string
}

// Configure will set the view information
func Configure(vi View) {
	viewInfo = vi
}

// Read the configuration
func ReadConfig() View {
	return viewInfo
}

// LoadTemplates will set the root and child templates
func LoadTemplates(rootTemp string, childTemps []string) {
	rootTemplate = rootTemp
	childTemplates = childTemps
}

// LoadPlugins will set the plugins for the templates
func LoadPlugins(fm template.FuncMap) {
	// Load the plugins
	mutexPlugins.Lock()
	pluginCollection = fm
	mutexPlugins.Unlock()
}

// PrependBaseURI prepends the base URI to the string
func (v *View) PrependBaseURI(s string) string {
	return v.BaseURI + s
}

// New returns a new view
func New(req *http.Request) *View {
	v := &View{}
	v.Vars = make(map[string]interface{})
	v.Vars["AuthLevel"] = "anon"

	v.BaseURI = viewInfo.BaseURI
	v.Extension = viewInfo.Extension
	v.Folder = viewInfo.Folder
	v.Name = viewInfo.Name

	// Make sure BaseURI is available in the templates
	v.Vars["BaseURI"] = v.BaseURI

	// This is required for the view to access the request
	v.request = req

	// Get session
	session := session.Instance(v.request)

	// Set the AuthLevel to auth if the user is logged in
	if session.Values["id"] != nil {
		v.Vars["AuthLevel"] = "auth"
	}

	return v
}

// AssetTimePath returns a URL with the proper base uri and timestamp appended.
// Works for CSS and JS assets
// Determines if local or on the web
func (v *View) AssetTimePath(s string) (string, error) {
	if strings.HasPrefix(s, "//") {
		return s, nil
	}

	s = strings.TrimLeft(s, "/")
	abs, err := filepath.Abs(s)

	if err != nil {
		return "", err
	}

	time, err2 := FileTime(abs)
	if err2 != nil {
		return "", err2
	}

	return v.PrependBaseURI(s + "?" + time), nil
}

// Render a template to the screen
func (v *View) RenderSingle(w http.ResponseWriter) {

	// Get the template collection from cache
	/*mutex.RLock()
	tc, ok := templateCollection[v.Name]
	mutex.RUnlock()*/

	// Get the plugin collection
	mutexPlugins.RLock()
	pc := pluginCollection
	mutexPlugins.RUnlock()

	templateList := []string{v.Name}

	// List of template names
	/*templateList := make([]string, 0)
	templateList = append(templateList, rootTemplate)
	templateList = append(templateList, v.Name)
	templateList = append(templateList, childTemplates...)*/

	// Loop through each template and test the full path
	for i, name := range templateList {
		// Get the absolute path of the root template
		path, err := filepath.Abs(v.Folder + string(os.PathSeparator) + name + "." + v.Extension)
		if err != nil {
			http.Error(w, "Template Path Error: "+err.Error(), http.StatusInternalServerError)
			return
		}
		templateList[i] = path
	}

	// Determine if there is an error in the template syntax
	templates, err := template.New(v.Name).Funcs(pc).ParseFiles(templateList...)

	if err != nil {
		http.Error(w, "Template Parse Error: "+err.Error(), http.StatusInternalServerError)
		return
	}

	// Cache the template collection
	/*mutex.Lock()
	templateCollection[v.Name] = templates
	mutex.Unlock()*/

	// Save the template collection
	tc := templates

	// Get session
	sess := session.Instance(v.request)

	// Get the flashes for the template
	if flashes := sess.Flashes(); len(flashes) > 0 {
		v.Vars["flashes"] = make([]Flash, len(flashes))
		for i, f := range flashes {
			switch f.(type) {
			case Flash:
				v.Vars["flashes"].([]Flash)[i] = f.(Flash)
			default:
				v.Vars["flashes"].([]Flash)[i] = Flash{f.(string), "alert-box"}
			}

		}
		sess.Save(v.request, w)
	}

	// Display the content to the screen
	err = tc.Funcs(pc).ExecuteTemplate(w, v.Name+"."+v.Extension, v.Vars)

	if err != nil {
		http.Error(w, "Template File Error: "+err.Error(), http.StatusInternalServerError)
	}
}

// Render a template to the screen
func (v *View) Render(w http.ResponseWriter) {

	// Get the template collection from cache
	mutex.RLock()
	tc, ok := templateCollection[v.Name]
	mutex.RUnlock()

	// Get the plugin collection
	mutexPlugins.RLock()
	pc := pluginCollection
	mutexPlugins.RUnlock()

	// If the template collection is not cached or caching is disabled
	if !ok || !viewInfo.Caching {

		// List of template names
		templateList := make([]string, 0)
		templateList = append(templateList, rootTemplate)
		templateList = append(templateList, v.Name)
		templateList = append(templateList, childTemplates...)

		// Loop through each template and test the full path
		for i, name := range templateList {
			// Get the absolute path of the root template
			path, err := filepath.Abs(v.Folder + string(os.PathSeparator) + name + "." + v.Extension)
			if err != nil {
				http.Error(w, "Template Path Error: "+err.Error(), http.StatusInternalServerError)
				return
			}
			templateList[i] = path
		}

		// Determine if there is an error in the template syntax
		templates, err := template.New(v.Name).Funcs(pc).ParseFiles(templateList...)

		if err != nil {
			http.Error(w, "Template Parse Error: "+err.Error(), http.StatusInternalServerError)
			return
		}

		// Cache the template collection
		mutex.Lock()
		templateCollection[v.Name] = templates
		mutex.Unlock()

		// Save the template collection
		tc = templates
	}

	// Get session
	sess := session.Instance(v.request)

	// Get the flashes for the template
	if flashes := sess.Flashes(); len(flashes) > 0 {
		v.Vars["flashes"] = make([]Flash, len(flashes))
		for i, f := range flashes {
			switch f.(type) {
			case Flash:
				v.Vars["flashes"].([]Flash)[i] = f.(Flash)
			default:
				v.Vars["flashes"].([]Flash)[i] = Flash{f.(string), "alert-box"}
			}

		}
		sess.Save(v.request, w)
	}

	// Display the content to the screen
	err := tc.Funcs(pc).ExecuteTemplate(w, rootTemplate+"."+v.Extension, v.Vars)

	if err != nil {
		http.Error(w, "Template File Error: "+err.Error(), http.StatusInternalServerError)
	}
}

// Validate returns true if all the required form values are passed
func Validate(req *http.Request, required []string) (bool, string) {
	for _, v := range required {
		if req.FormValue(v) == "" {
			return false, v
		}
	}

	return true, ""
}

// SendFlashes allows retrieval of flash messages for using with Ajax
func (v *View) SendFlashes(w http.ResponseWriter) {
	// Get session
	sess := session.Instance(v.request)

	flashes := peekFlashes(w, v.request)
	sess.Save(v.request, w)

	js, err := json.Marshal(flashes)
	if err != nil {
		http.Error(w, "JSON Error: "+err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(js)
}

func peekFlashes(w http.ResponseWriter, r *http.Request) []Flash {
	// Get session
	sess := session.Instance(r)

	v := make([]Flash, 0)

	// Get the flashes for the template
	if flashes := sess.Flashes(); len(flashes) > 0 {
		v = make([]Flash, len(flashes))
		for i, f := range flashes {
			switch f.(type) {
			case Flash:
				v[i] = f.(Flash)
			default:
				v[i] = Flash{f.(string), "alert-box"}
			}

		}
	}

	return v
}

// Repopulate updates the dst map so the form fields can be refilled
func Repopulate(list []string, src url.Values, dst map[string]interface{}) {
	for _, v := range list {
		dst[v] = src.Get(v)
	}
}

// FileTime returns the modification time of the file
func FileTime(name string) (string, error) {
	fi, err := os.Stat(name)
	if err != nil {
		return "", err
	}
	mtime := fi.ModTime().Unix()
	return fmt.Sprintf("%v", mtime), nil
}
<commit_msg>Update view.go<commit_after>package view

import (
	"encoding/gob"
	"encoding/json"
	"fmt"
	"html/template"
	"net/http"
	"net/url"
	"os"
	"path/filepath"
	"strings"
	"sync"

	"github.com/josephspurrier/gowebapp/shared/session"
)

func init() {
	// Magic goes here to allow serializing maps in securecookie
	// http://golang.org/pkg/encoding/gob/#Register
	// Source: http://stackoverflow.com/questions/21934730/gob-type-not-registered-for-interface-mapstringinterface
	gob.Register(Flash{})
}

var (
	FlashError   = "alert-danger"
	FlashSuccess = "alert-success"
	FlashNotice  = "alert-info"
	FlashWarning = "alert-warning"

	childTemplates     []string
	rootTemplate       string
	templateCollection = make(map[string]*template.Template)
	pluginCollection   = make(template.FuncMap)
	mutex              sync.RWMutex
	mutexPlugins       sync.RWMutex
	sessionName        string
	viewInfo           View
)

// Template root and children
type Template struct {
	Root     string   `json:"Root"`
	Children []string `json:"Children"`
}

// View attributes
type View struct {
	BaseURI   string
	Extension string
	Folder    string
	Name      string
	Caching   bool
	Vars      map[string]interface{}
	request   *http.Request
}

// Flash Message
type Flash struct {
	Message string
	Class   string
}

// Configure will set the view information
func Configure(vi View) {
	viewInfo = vi
}

// Read the configuration
func ReadConfig() View {
	return viewInfo
}

// LoadTemplates will set the root and child templates
func LoadTemplates(rootTemp string, childTemps []string) {
	rootTemplate = rootTemp
	childTemplates = childTemps
}

// LoadPlugins will set the plugins for the templates
func LoadPlugins(fm template.FuncMap) {
	// Load the plugins
	mutexPlugins.Lock()
	pluginCollection = fm
	mutexPlugins.Unlock()
}

// PrependBaseURI prepends the base URI to the string
func (v *View) PrependBaseURI(s string) string {
	return v.BaseURI + s
}

// New returns a new view
func New(req *http.Request) *View {
	v := &View{}
	v.Vars = make(map[string]interface{})
	v.Vars["AuthLevel"] = "anon"

	v.BaseURI = viewInfo.BaseURI
	v.Extension = viewInfo.Extension
	v.Folder = viewInfo.Folder
	v.Name = viewInfo.Name

	// Make sure BaseURI is available in the templates
	v.Vars["BaseURI"] = v.BaseURI

	// This is required for the view to access the request
	v.request = req

	// Get session
	sess := session.Instance(v.request)

	// Set the AuthLevel to auth if the user is logged in
	if sess.Values["id"] != nil {
		v.Vars["AuthLevel"] = "auth"
	}

	return v
}

// AssetTimePath returns a URL with the proper base uri and timestamp appended.
// Works for CSS and JS assets
// Determines if local or on the web
func (v *View) AssetTimePath(s string) (string, error) {
	if strings.HasPrefix(s, "//") {
		return s, nil
	}

	s = strings.TrimLeft(s, "/")
	abs, err := filepath.Abs(s)

	if err != nil {
		return "", err
	}

	time, err2 := FileTime(abs)
	if err2 != nil {
		return "", err2
	}

	return v.PrependBaseURI(s + "?" + time), nil
}

// Render a template to the screen
func (v *View) RenderSingle(w http.ResponseWriter) {

	// Get the template collection from cache
	/*mutex.RLock()
	tc, ok := templateCollection[v.Name]
	mutex.RUnlock()*/

	// Get the plugin collection
	mutexPlugins.RLock()
	pc := pluginCollection
	mutexPlugins.RUnlock()

	templateList := []string{v.Name}

	// List of template names
	/*templateList := make([]string, 0)
	templateList = append(templateList, rootTemplate)
	templateList = append(templateList, v.Name)
	templateList = append(templateList, childTemplates...)*/

	// Loop through each template and test the full path
	for i, name := range templateList {
		// Get the absolute path of the root template
		path, err := filepath.Abs(v.Folder + string(os.PathSeparator) + name + "." + v.Extension)
		if err != nil {
			http.Error(w, "Template Path Error: "+err.Error(), http.StatusInternalServerError)
			return
		}
		templateList[i] = path
	}

	// Determine if there is an error in the template syntax
	templates, err := template.New(v.Name).Funcs(pc).ParseFiles(templateList...)

	if err != nil {
		http.Error(w, "Template Parse Error: "+err.Error(), http.StatusInternalServerError)
		return
	}

	// Cache the template collection
	/*mutex.Lock()
	templateCollection[v.Name] = templates
	mutex.Unlock()*/

	// Save the template collection
	tc := templates

	// Get session
	sess := session.Instance(v.request)

	// Get the flashes for the template
	if flashes := sess.Flashes(); len(flashes) > 0 {
		v.Vars["flashes"] = make([]Flash, len(flashes))
		for i, f := range flashes {
			switch f.(type) {
			case Flash:
				v.Vars["flashes"].([]Flash)[i] = f.(Flash)
			default:
				v.Vars["flashes"].([]Flash)[i] = Flash{f.(string), "alert-box"}
			}

		}
		sess.Save(v.request, w)
	}

	// Display the content to the screen
	err = tc.Funcs(pc).ExecuteTemplate(w, v.Name+"."+v.Extension, v.Vars)

	if err != nil {
		http.Error(w, "Template File Error: "+err.Error(), http.StatusInternalServerError)
	}
}

// Render a template to the screen
func (v *View) Render(w http.ResponseWriter) {

	// Get the template collection from cache
	mutex.RLock()
	tc, ok := templateCollection[v.Name]
	mutex.RUnlock()

	// Get the plugin collection
	mutexPlugins.RLock()
	pc := pluginCollection
	mutexPlugins.RUnlock()

	// If the template collection is not cached or caching is disabled
	if !ok || !viewInfo.Caching {

		// List of template names
		templateList := make([]string, 0)
		templateList = append(templateList, rootTemplate)
		templateList = append(templateList, v.Name)
		templateList = append(templateList, childTemplates...)

		// Loop through each template and test the full path
		for i, name := range templateList {
			// Get the absolute path of the root template
			path, err := filepath.Abs(v.Folder + string(os.PathSeparator) + name + "." + v.Extension)
			if err != nil {
				http.Error(w, "Template Path Error: "+err.Error(), http.StatusInternalServerError)
				return
			}
			templateList[i] = path
		}

		// Determine if there is an error in the template syntax
		templates, err := template.New(v.Name).Funcs(pc).ParseFiles(templateList...)

		if err != nil {
			http.Error(w, "Template Parse Error: "+err.Error(), http.StatusInternalServerError)
			return
		}

		// Cache the template collection
		mutex.Lock()
		templateCollection[v.Name] = templates
		mutex.Unlock()

		// Save the template collection
		tc = templates
	}

	// Get session
	sess := session.Instance(v.request)

	// Get the flashes for the template
	if flashes := sess.Flashes(); len(flashes) > 0 {
		v.Vars["flashes"] = make([]Flash, len(flashes))
		for i, f := range flashes {
			switch f.(type) {
			case Flash:
				v.Vars["flashes"].([]Flash)[i] = f.(Flash)
			default:
				v.Vars["flashes"].([]Flash)[i] = Flash{f.(string), "alert-box"}
			}

		}
		sess.Save(v.request, w)
	}

	// Display the content to the screen
	err := tc.Funcs(pc).ExecuteTemplate(w, rootTemplate+"."+v.Extension, v.Vars)

	if err != nil {
		http.Error(w, "Template File Error: "+err.Error(), http.StatusInternalServerError)
	}
}

// Validate returns true if all the required form values are passed
func Validate(req *http.Request, required []string) (bool, string) {
	for _, v := range required {
		if req.FormValue(v) == "" {
			return false, v
		}
	}

	return true, ""
}

// SendFlashes allows retrieval of flash messages for using with Ajax
func (v *View) SendFlashes(w http.ResponseWriter) {
	// Get session
	sess := session.Instance(v.request)

	flashes := peekFlashes(w, v.request)
	sess.Save(v.request, w)

	js, err := json.Marshal(flashes)
	if err != nil {
		http.Error(w, "JSON Error: "+err.Error(), http.StatusInternalServerError)
		return
	}

	w.Header().Set("Content-Type", "application/json")
	w.Write(js)
}

func peekFlashes(w http.ResponseWriter, r *http.Request) []Flash {
	// Get session
	sess := session.Instance(r)

	v := make([]Flash, 0)

	// Get the flashes for the template
	if flashes := sess.Flashes(); len(flashes) > 0 {
		v = make([]Flash, len(flashes))
		for i, f := range flashes {
			switch f.(type) {
			case Flash:
				v[i] = f.(Flash)
			default:
				v[i] = Flash{f.(string), "alert-box"}
			}

		}
	}

	return v
}

// Repopulate updates the dst map so the form fields can be refilled
func Repopulate(list []string, src url.Values, dst map[string]interface{}) {
	for _, v := range list {
		dst[v] = src.Get(v)
	}
}

// FileTime returns the modification time of the file
func FileTime(name string) (string, error) {
	fi, err := os.Stat(name)
	if err != nil {
		return "", err
	}
	mtime := fi.ModTime().Unix()
	return fmt.Sprintf("%v", mtime), nil
}
<|endoftext|>