<commit_before>package mdata

import (
	"strconv"
	"sync"
	"time"

	"github.com/grafana/metrictank/mdata/cache"
	"github.com/raintank/schema"
	log "github.com/sirupsen/logrus"
)

// AggMetrics is an in-memory store of AggMetric objects
// note: they are keyed by MKey here because each
// AggMetric manages access to, and references of,
// their rollup archives themselves
type AggMetrics struct {
	store          Store
	cachePusher    cache.CachePusher
	dropFirstChunk bool
	sync.RWMutex
	Metrics        map[uint32]map[schema.MKey]*AggMetric
	chunkMaxStale  uint32
	metricMaxStale uint32
	gcInterval     time.Duration
}

func NewAggMetrics(store Store, cachePusher cache.CachePusher, dropFirstChunk bool, chunkMaxStale, metricMaxStale uint32, gcInterval time.Duration) *AggMetrics {
	ms := AggMetrics{
		store:          store,
		cachePusher:    cachePusher,
		dropFirstChunk: dropFirstChunk,
		Metrics:        make(map[uint32]map[schema.MKey]*AggMetric),
		chunkMaxStale:  chunkMaxStale,
		metricMaxStale: metricMaxStale,
		gcInterval:     gcInterval,
	}

	// gcInterval = 0 can be useful in tests
	if gcInterval > 0 {
		go ms.GC()
	}
	return &ms
}

// periodically scan chunks and close any that have not received data in a while
func (ms *AggMetrics) GC() {
	for {
		unix := time.Duration(time.Now().UnixNano())
		diff := ms.gcInterval - (unix % ms.gcInterval)
		time.Sleep(diff + time.Minute)
		log.Info("checking for stale chunks that need persisting.")
		now := uint32(time.Now().Unix())
		chunkMinTs := now - uint32(ms.chunkMaxStale)
		metricMinTs := now - uint32(ms.metricMaxStale)

		ms.RLock()
		orgs := make([]uint32, 0, len(ms.Metrics))
		for o := range ms.Metrics {
			orgs = append(orgs, o)
		}
		ms.RUnlock()
		totalActive := 0
		for _, org := range orgs {
			// as this is the only goroutine that can delete from ms.Metrics
			// we only need to lock long enough to get the list of active metrics.
			// it doesn't matter if new metrics are added while we iterate this list.
			keys := make([]schema.MKey, 0, len(ms.Metrics[org]))
			for k := range ms.Metrics[org] {
				keys = append(keys, k)
			}
			ms.RUnlock()
			for _, key := range keys {
				gcMetric.Inc()
				ms.RLock()
				a := ms.Metrics[org][key]
				ms.RUnlock()
				if a.GC(now, chunkMinTs, metricMinTs) {
					log.Debugf("metric %s is stale. Purging data from memory.", key)
					ms.Lock()
					delete(ms.Metrics[org], key)
					ms.Unlock()
				}
			}
			ms.RLock()
			totalActive += len(ms.Metrics[org])
			promActiveMetrics.WithLabelValues(strconv.Itoa(int(org))).Set(float64(len(ms.Metrics[org])))
			ms.RUnlock()
		}
		metricsActive.Set(totalActive)
	}
}

func (ms *AggMetrics) Get(key schema.MKey) (Metric, bool) {
	var m *AggMetric
	ms.RLock()
	_, ok := ms.Metrics[key.Org]
	if ok {
		m, ok = ms.Metrics[key.Org][key]
	}
	ms.RUnlock()
	return m, ok
}

func (ms *AggMetrics) GetOrCreate(key schema.MKey, schemaId, aggId uint16) Metric {
	var m *AggMetric
	// in the most common case, it's already there and an Rlock is all we need
	ms.RLock()
	_, ok := ms.Metrics[key.Org]
	if ok {
		m, ok = ms.Metrics[key.Org][key]
	}
	ms.RUnlock()
	if ok {
		return m
	}

	k := schema.AMKey{
		MKey: key,
	}

	agg := Aggregations.Get(aggId)
	confSchema := Schemas.Get(schemaId)

	// if it wasn't there, get the write lock and prepare to add it
	// but first we need to check again if someone has added it in
	// the meantime (quite rare, but anyway)
	ms.Lock()
	if _, ok := ms.Metrics[key.Org]; !ok {
		ms.Metrics[key.Org] = make(map[schema.MKey]*AggMetric)
	}
	m, ok = ms.Metrics[key.Org][key]
	if ok {
		ms.Unlock()
		return m
	}
	m = NewAggMetric(ms.store, ms.cachePusher, k, confSchema.Retentions, confSchema.ReorderWindow, &agg, ms.dropFirstChunk)
	ms.Metrics[key.Org][key] = m
	active := len(ms.Metrics[key.Org])
	ms.Unlock()
	metricsActive.Inc()
	promActiveMetrics.WithLabelValues(strconv.Itoa(int(key.Org))).Set(float64(active))
	return m
}
<commit_msg>don't needlessly key by orgid twice<commit_after>package mdata

import (
	"strconv"
	"sync"
	"time"

	"github.com/grafana/metrictank/mdata/cache"
	"github.com/raintank/schema"
	log "github.com/sirupsen/logrus"
)

// AggMetrics is an in-memory store of AggMetric objects
// note: they are keyed by MKey here because each
// AggMetric manages access to, and references of,
// their rollup archives themselves
type AggMetrics struct {
	store          Store
	cachePusher    cache.CachePusher
	dropFirstChunk bool
	sync.RWMutex
	Metrics        map[uint32]map[schema.Key]*AggMetric
	chunkMaxStale  uint32
	metricMaxStale uint32
	gcInterval     time.Duration
}

func NewAggMetrics(store Store, cachePusher cache.CachePusher, dropFirstChunk bool, chunkMaxStale, metricMaxStale uint32, gcInterval time.Duration) *AggMetrics {
	ms := AggMetrics{
		store:          store,
		cachePusher:    cachePusher,
		dropFirstChunk: dropFirstChunk,
		Metrics:        make(map[uint32]map[schema.Key]*AggMetric),
		chunkMaxStale:  chunkMaxStale,
		metricMaxStale: metricMaxStale,
		gcInterval:     gcInterval,
	}

	// gcInterval = 0 can be useful in tests
	if gcInterval > 0 {
		go ms.GC()
	}
	return &ms
}

// periodically scan chunks and close any that have not received data in a while
func (ms *AggMetrics) GC() {
	for {
		unix := time.Duration(time.Now().UnixNano())
		diff := ms.gcInterval - (unix % ms.gcInterval)
		time.Sleep(diff + time.Minute)
		log.Info("checking for stale chunks that need persisting.")
		now := uint32(time.Now().Unix())
		chunkMinTs := now - uint32(ms.chunkMaxStale)
		metricMinTs := now - uint32(ms.metricMaxStale)

		ms.RLock()
		orgs := make([]uint32, 0, len(ms.Metrics))
		for o := range ms.Metrics {
			orgs = append(orgs, o)
		}
		ms.RUnlock()
		totalActive := 0
		for _, org := range orgs {
			// as this is the only goroutine that can delete from ms.Metrics
			// we only need to lock long enough to get the list of active metrics.
			// it doesn't matter if new metrics are added while we iterate this list.
			keys := make([]schema.Key, 0, len(ms.Metrics[org]))
			for k := range ms.Metrics[org] {
				keys = append(keys, k)
			}
			ms.RUnlock()
			for _, key := range keys {
				gcMetric.Inc()
				ms.RLock()
				a := ms.Metrics[org][key]
				ms.RUnlock()
				if a.GC(now, chunkMinTs, metricMinTs) {
					log.Debugf("metric %s is stale. Purging data from memory.", key)
					ms.Lock()
					delete(ms.Metrics[org], key)
					ms.Unlock()
				}
			}
			ms.RLock()
			totalActive += len(ms.Metrics[org])
			promActiveMetrics.WithLabelValues(strconv.Itoa(int(org))).Set(float64(len(ms.Metrics[org])))
			ms.RUnlock()
		}
		metricsActive.Set(totalActive)
	}
}

func (ms *AggMetrics) Get(key schema.MKey) (Metric, bool) {
	var m *AggMetric
	ms.RLock()
	_, ok := ms.Metrics[key.Org]
	if ok {
		m, ok = ms.Metrics[key.Org][key.Key]
	}
	ms.RUnlock()
	return m, ok
}

func (ms *AggMetrics) GetOrCreate(key schema.MKey, schemaId, aggId uint16) Metric {
	var m *AggMetric
	// in the most common case, it's already there and an Rlock is all we need
	ms.RLock()
	_, ok := ms.Metrics[key.Org]
	if ok {
		m, ok = ms.Metrics[key.Org][key.Key]
	}
	ms.RUnlock()
	if ok {
		return m
	}

	k := schema.AMKey{
		MKey: key,
	}

	agg := Aggregations.Get(aggId)
	confSchema := Schemas.Get(schemaId)

	// if it wasn't there, get the write lock and prepare to add it
	// but first we need to check again if someone has added it in
	// the meantime (quite rare, but anyway)
	ms.Lock()
	if _, ok := ms.Metrics[key.Org]; !ok {
		ms.Metrics[key.Org] = make(map[schema.Key]*AggMetric)
	}
	m, ok = ms.Metrics[key.Org][key.Key]
	if ok {
		ms.Unlock()
		return m
	}
	m = NewAggMetric(ms.store, ms.cachePusher, k, confSchema.Retentions, confSchema.ReorderWindow, &agg, ms.dropFirstChunk)
	ms.Metrics[key.Org][key.Key] = m
	active := len(ms.Metrics[key.Org])
	ms.Unlock()
	metricsActive.Inc()
	promActiveMetrics.WithLabelValues(strconv.Itoa(int(key.Org))).Set(float64(active))
	return m
}
<|endoftext|>


<commit_before>package main

import (
	"math/big"
	"testing"
)

func TestParseMagnitude(t *testing.T) {
	verifyParseMagnitude(t, "1/2", big.NewRat(1, 2))
	verifyParseMagnitude(t, "1 1/2", big.NewRat(3, 2))
	verifyParseMagnitude(t, "2.5", big.NewRat(5, 2))
	verifyParseMagnitude(t, "3", big.NewRat(3, 1))
}

func TestMul(t *testing.T) {
	if v, err := mul(20, big.NewRat(3, 4)); err != nil {
		t.Fatal(err)
	} else {
		if v != 15 {
			t.Errorf("Error multiplying 20 and 3/4: expected 15 and got %v.", v)
		}
	}
	if _, err := mul(20, big.NewRat(1, 7)); err == nil {
		t.Errorf("Expected error muliplying 20 and 1/7 and did not get it.")
	}
}

func TestParse(t *testing.T) {
	verifyParse(t, "1/2 tsp", HalfTeaspoon)
	verifyParse(t, "1 1/2 tsp", Volume(Teaspoon+HalfTeaspoon))
	verifyParse(t, "3/4 gallons", Volume(Quart*3))
}

func verifyParseMagnitude(t *testing.T, s string, expected *big.Rat) {
	if value, err := parseMagnitude(s); err != nil {
		t.Fatal(err)
	} else {
		if value.Num().Int64() != expected.Num().Int64() ||
			value.Denom().Int64() != expected.Denom().Int64() {
			t.Errorf("Error parsing magnitude %v: expected %v but got %v", s, expected, value)
		}
	}
}

func verifyParse(t *testing.T, s string, expected Measurement) {
	if value, err := Parse(s); err != nil {
		t.Fatal(err)
	} else {
		if value != expected {
			t.Errorf("Error parsing %v: expected %v but got %v", s, expected, value)
		}
	}
}
<commit_msg>Add weight tests<commit_after>package main

import (
	"math/big"
	"testing"
)

func TestParseMagnitude(t *testing.T) {
	verifyParseMagnitude(t, "1/2", big.NewRat(1, 2))
	verifyParseMagnitude(t, "1 1/2", big.NewRat(3, 2))
	verifyParseMagnitude(t, "2.5", big.NewRat(5, 2))
	verifyParseMagnitude(t, "3", big.NewRat(3, 1))
}

func TestMul(t *testing.T) {
	if v, err := mul(20, big.NewRat(3, 4)); err != nil {
		t.Fatal(err)
	} else {
		if v != 15 {
			t.Errorf("Error multiplying 20 and 3/4: expected 15 and got %v.", v)
		}
	}
	if _, err := mul(20, big.NewRat(1, 7)); err == nil {
		t.Errorf("Expected error muliplying 20 and 1/7 and did not get it.")
	}
}

func TestParse(t *testing.T) {
	verifyParse(t, "1/2 tsp", HalfTeaspoon)
	verifyParse(t, "1 1/2 tsp", Volume(Teaspoon+HalfTeaspoon))
	verifyParse(t, "3/4 gallons", Volume(Quart*3))
	verifyParse(t, "16 oz", Pound)
	verifyParse(t, "1/2 lb", Weight(Ounce*8))
}

func verifyParseMagnitude(t *testing.T, s string, expected *big.Rat) {
	if value, err := parseMagnitude(s); err != nil {
		t.Fatal(err)
	} else {
		if value.Num().Int64() != expected.Num().Int64() ||
			value.Denom().Int64() != expected.Denom().Int64() {
			t.Errorf("Error parsing magnitude %v: expected %v but got %v", s, expected, value)
		}
	}
}

func verifyParse(t *testing.T, s string, expected Measurement) {
	if value, err := Parse(s); err != nil {
		t.Fatal(err)
	} else {
		if value != expected {
			t.Errorf("Error parsing %v: expected %v but got %v", s, expected, value)
		}
	}
}
<|endoftext|>


<commit_before>package core

import (
	"encoding/json"
	"fmt"
	"github.com/kardianos/osext"
	"github.com/mitchellh/go-homedir"
	"github.com/projectjane/jane/models"
	"github.com/projectjane/jane/parse"
	"io/ioutil"
	"log"
	"os"
)

func LoadConfig(params models.Params) (config models.Config) {
	configFile := locateConfig(params)
	if checkConfig(configFile) {
		config = readConfig(configFile)
		subConfig(&config)
		if params.Validate {
			fmt.Println("SUCCESS - Config file is valid: " + configFile)
			os.Exit(0)
		}
	} else {
		os.Exit(1)
	}
	return config
}

func locateConfig(params models.Params) (configFile string) {
	file := "jane.json"

	zero := params.ConfigFile
	if FileExists(zero) {
		return zero
	}

	first, _ := osext.ExecutableFolder()
	first += "/" + file
	if FileExists(first) {
		return first
	}

	second, _ := homedir.Dir()
	second += "/" + file
	if FileExists(second) {
		return second
	}

	third := "/etc/" + file
	if FileExists(third) {
		return third
	}

	return file

}

func readConfig(location string) (config models.Config) {
	file, err := ioutil.ReadFile(location)
	if err != nil {
		log.Print(err)
	}
	err = json.Unmarshal(file, &config)
	if err != nil {
		log.Print(err)
	}
	return config
}

func subConfig(config *models.Config) {
	for i := 0; i < len(config.Connectors); i++ {
		config.Connectors[i].Server = parse.SubstituteInputs(config.Connectors[i].Server)
		config.Connectors[i].Port = parse.SubstituteInputs(config.Connectors[i].Port)
		config.Connectors[i].Login = parse.SubstituteInputs(config.Connectors[i].Login)
		config.Connectors[i].Pass = parse.SubstituteInputs(config.Connectors[i].Pass)
	}
}

func checkConfig(location string) (exists bool) {
	exists = true
	if _, err := os.Stat(location); os.IsNotExist(err) {
		fmt.Println("Error - Missing a config file")
		exists = false
	}
	if exists {
		file, err := ioutil.ReadFile(location)
		if err != nil {
			log.Print(err)
		}
		var js interface{}
		exists = json.Unmarshal(file, &js) == nil
		if !exists {
			fmt.Println("Error - Config file does not appear to be valid json")
		}
	}
	return exists
}
<commit_msg>Adding Key to the ENV vars<commit_after>package core

import (
	"encoding/json"
	"fmt"
	"github.com/kardianos/osext"
	"github.com/mitchellh/go-homedir"
	"github.com/projectjane/jane/models"
	"github.com/projectjane/jane/parse"
	"io/ioutil"
	"log"
	"os"
)

func LoadConfig(params models.Params) (config models.Config) {
	configFile := locateConfig(params)
	if checkConfig(configFile) {
		config = readConfig(configFile)
		subConfig(&config)
		if params.Validate {
			fmt.Println("SUCCESS - Config file is valid: " + configFile)
			os.Exit(0)
		}
	} else {
		os.Exit(1)
	}
	return config
}

func locateConfig(params models.Params) (configFile string) {
	file := "jane.json"

	zero := params.ConfigFile
	if FileExists(zero) {
		return zero
	}

	first, _ := osext.ExecutableFolder()
	first += "/" + file
	if FileExists(first) {
		return first
	}

	second, _ := homedir.Dir()
	second += "/" + file
	if FileExists(second) {
		return second
	}

	third := "/etc/" + file
	if FileExists(third) {
		return third
	}

	return file

}

func readConfig(location string) (config models.Config) {
	file, err := ioutil.ReadFile(location)
	if err != nil {
		log.Print(err)
	}
	err = json.Unmarshal(file, &config)
	if err != nil {
		log.Print(err)
	}
	return config
}

func subConfig(config *models.Config) {
	for i := 0; i < len(config.Connectors); i++ {
		config.Connectors[i].Server = parse.SubstituteInputs(config.Connectors[i].Server)
		config.Connectors[i].Port = parse.SubstituteInputs(config.Connectors[i].Port)
		config.Connectors[i].Login = parse.SubstituteInputs(config.Connectors[i].Login)
		config.Connectors[i].Pass = parse.SubstituteInputs(config.Connectors[i].Pass)
		config.Connectors[i].Key = parse.SubstituteInputs(config.Connectors[i].Key)
	}
}

func checkConfig(location string) (exists bool) {
	exists = true
	if _, err := os.Stat(location); os.IsNotExist(err) {
		fmt.Println("Error - Missing a config file")
		exists = false
	}
	if exists {
		file, err := ioutil.ReadFile(location)
		if err != nil {
			log.Print(err)
		}
		var js interface{}
		exists = json.Unmarshal(file, &js) == nil
		if !exists {
			fmt.Println("Error - Config file does not appear to be valid json")
		}
	}
	return exists
}
<|endoftext|>


<commit_before>// Respell a text file phonetically by looking up available words.
package main

import (
	"bufio"
	"flag"
	"fmt"
	"github.com/BluntSporks/cmudict"
	"log"
	"os"
	"strings"
)

func main() {
	// Parse flags.
	dictFile := flag.String("d", cmudict.DefaultDictPath(), "Name of CMU-formatted file to modify")
	spellFile := flag.String("s", defaultSpellPath(), "Name of spelling file that maps phonemes to spellings")
	textFile := flag.String("t", "", "Name of text file to respell")
	flag.Parse()

	if len(*textFile) == 0 {
		log.Fatal("Missing -t argument")
	}

	// Load CMUDict file.
	cmuDict := cmudict.LoadDict(*dictFile)

	// Load the spelling file.
	spellings := loadSpellings(*spellFile)

	// Open dict file.
	handle, err := os.Open(*textFile)
	if err != nil {
		log.Fatal(err)
	}
	defer handle.Close()

	// Scan file line by line.
	scanner := bufio.NewScanner(handle)
	for scanner.Scan() {
		line := scanner.Text()
		if err := scanner.Err(); err != nil {
			log.Fatal(err)
		}
		words := matchWords(line)
		for _, word := range words {
			upword := strings.ToUpper(word)
			pron := cmuDict[upword]
			if pron == "" {
				fmt.Print(word)
			} else {
				phonemes := cmudict.GetPhonemes(pron, true)
				fixed := fixPhonemes(phonemes)
				for _, phoneme := range fixed {
					bare := cmudict.StripAccent(phoneme)
					spelling := spellings[bare]
					output := phoneme
					if spelling != "" {
						output = spelling
					}
					fmt.Print(output)
				}
			}
		}
		fmt.Println()
	}
}
<commit_msg>Adding capitalization<commit_after>// Respell a text file phonetically by looking up available words.
package main

import (
	"bufio"
	"flag"
	"fmt"
	"github.com/BluntSporks/cmudict"
	"log"
	"os"
	"strings"
)

func main() {
	// Parse flags.
	dictFile := flag.String("d", cmudict.DefaultDictPath(), "Name of CMU-formatted file to modify")
	spellFile := flag.String("s", defaultSpellPath(), "Name of spelling file that maps phonemes to spellings")
	textFile := flag.String("t", "", "Name of text file to respell")
	flag.Parse()

	if len(*textFile) == 0 {
		log.Fatal("Missing -t argument")
	}

	// Load CMUDict file.
	cmuDict := cmudict.LoadDict(*dictFile)

	// Load the spelling file.
	spellings := loadSpellings(*spellFile)

	// Open dict file.
	handle, err := os.Open(*textFile)
	if err != nil {
		log.Fatal(err)
	}
	defer handle.Close()

	// Scan file line by line.
	scanner := bufio.NewScanner(handle)
	for scanner.Scan() {
		line := scanner.Text()
		if err := scanner.Err(); err != nil {
			log.Fatal(err)
		}
		words := matchWords(line)
		for _, word := range words {
			upword := strings.ToUpper(word)
			isUpper := word != strings.ToLower(word)
			pron := cmuDict[upword]
			if pron == "" {
				fmt.Print(word)
			} else {
				phonemes := cmudict.GetPhonemes(pron, true)
				fixed := fixPhonemes(phonemes)
				for i, phoneme := range fixed {
					bare := cmudict.StripAccent(phoneme)
					spelling := spellings[bare]
					output := phoneme
					if spelling != "" {
						output = spelling
					}

					// Capitalize the first letter of output if the original word was not
					// lowercase.
					if i == 0 && isUpper {
						capd := strings.ToUpper(output[0:1])
						if len(output) > 1 {
							capd += output[1:]
						}
						output = capd
					}
					fmt.Print(output)
				}
			}
		}
		fmt.Println()
	}
}
<|endoftext|>


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

package main

import (
	"fmt"
	"github.com/nu7hatch/gouuid"
	"github.com/uniqush/log"
	. "github.com/uniqush/uniqush-push/push"
	"io"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"sync"
)

type RestAPI struct {
	psm       *PushServiceManager
	loggers   []log.Logger
	backend   *PushBackEnd
	version   string
	waitGroup *sync.WaitGroup
	stopChan  chan<- bool
}

// loggers: sequence is web, add
func NewRestAPI(psm *PushServiceManager, loggers []log.Logger, version string, backend *PushBackEnd) *RestAPI {
	ret := new(RestAPI)
	ret.psm = psm
	ret.loggers = loggers
	ret.version = version
	ret.backend = backend
	ret.waitGroup = new(sync.WaitGroup)
	return ret
}

const (
	ADD_PUSH_SERVICE_PROVIDER_TO_SERVICE_URL    = "/addpsp"
	REMOVE_PUSH_SERVICE_PROVIDER_TO_SERVICE_URL = "/rmpsp"
	ADD_DELIVERY_POINT_TO_SERVICE_URL           = "/subscribe"
	REMOVE_DELIVERY_POINT_FROM_SERVICE_URL      = "/unsubscribe"
	PUSH_NOTIFICATION_URL                       = "/push"
	STOP_PROGRAM_URL                            = "/stop"
	VERSION_INFO_URL                            = "/version"
	QUERY_NUMBER_OF_DELIVERY_POINTS_URL         = "/nrdp"
)

var validServicePattern *regexp.Regexp
var validSubscriberPattern *regexp.Regexp

func init() {
	var err error
	validServicePattern, err = regexp.Compile("^[a-zA-z\\.0-9-_]+$")
	if err != nil {
		validServicePattern = nil
	}
	validSubscriberPattern, err = regexp.Compile("^[a-zA-z\\.0-9-_]+$")
	if err != nil {
		validSubscriberPattern = nil
	}
}

func validateSubscribers(subs []string) error {
	if validSubscriberPattern != nil {
		for _, sub := range subs {
			if !validSubscriberPattern.MatchString(sub) {
				return fmt.Errorf("invalid subscriber name: %s. Accept charaters: a-z, A-Z, 0-9, -, _ or .", sub)
			}
		}
	}
	return nil
}

func validateService(service string) error {
	if validServicePattern != nil {
		if !validServicePattern.MatchString(service) {
			return fmt.Errorf("invalid service name: %s. Accept charaters: a-z, A-Z, 0-9, -, _ or .", service)
		}
	}
	return nil
}

func getSubscribersFromMap(kv map[string]string, validate bool) (subs []string, err error) {
	var v string
	var ok bool
	if v, ok = kv["subscriber"]; !ok {
		if v, ok = kv["subscribers"]; !ok {
			err = fmt.Errorf("NoSubscriber")
			return
		}
	}
	subs = strings.Split(v, ",")
	if validate {
		err = validateSubscribers(subs)
		if err != nil {
			subs = nil
			return
		}
	}
	return
}

func getServiceFromMap(kv map[string]string, validate bool) (service string, err error) {
	var ok bool
	if service, ok = kv["service"]; !ok {
		err = fmt.Errorf("NoService")
		return
	}
	if validate {
		err = validateService(service)
		if err != nil {
			service = ""
			return
		}
	}
	return
}

func (self *RestAPI) changePushServiceProvider(kv map[string]string, logger log.Logger, remoteAddr string, add bool) {
	psp, err := self.psm.BuildPushServiceProviderFromMap(kv)
	if err != nil {
		logger.Errorf("From=%v Cannot build push service provider: %v", remoteAddr, err)
		return
	}
	service, err := getServiceFromMap(kv, true)
	if err != nil {
		logger.Errorf("From=%v Cannot get service name: %v; %v", remoteAddr, service, err)
		return
	}
	if add {
		err = self.backend.AddPushServiceProvider(service, psp)
	} else {
		err = self.backend.RemovePushServiceProvider(service, psp)
	}
	if err != nil {
		logger.Errorf("From=%v Failed: %v", remoteAddr, err)
		return
	}
	logger.Infof("From=%v Service=%v PushServiceProvider=%v Success!", remoteAddr, service, psp.Name())
	return
}

func (self *RestAPI) changeSubscription(kv map[string]string, logger log.Logger, remoteAddr string, issub bool) {
	dp, err := self.psm.BuildDeliveryPointFromMap(kv)
	if err != nil {
		logger.Errorf("Cannot build delivery point: %v", err)
		return
	}
	service, err := getServiceFromMap(kv, true)
	if err != nil {
		logger.Errorf("From=%v Cannot get service name: %v; %v", remoteAddr, service, err)
		return
	}
	subs, err := getSubscribersFromMap(kv, true)
	if err != nil {
		logger.Errorf("From=%v Service=%v Cannot get subscriber: %v", remoteAddr, service, err)
		return
	}

	var psp *PushServiceProvider
	if issub {
		psp, err = self.backend.Subscribe(service, subs[0], dp)
	} else {
		err = self.backend.Unsubscribe(service, subs[0], dp)
	}
	if err != nil {
		logger.Errorf("From=%v Failed: %v", remoteAddr, err)
		return
	}
	if psp == nil {
		logger.Infof("From=%v Service=%v Subscriber=%v DeliveryPoint=%v Success!", remoteAddr, service, subs[0], dp.Name())
	} else {
		logger.Infof("From=%v Service=%v Subscriber=%v PushServiceProvider=%v DeliveryPoint=%v Success!", remoteAddr, service, subs[0], psp.Name(), dp.Name())
	}
}

func (self *RestAPI) pushNotification(reqId string, kv map[string]string, perdp map[string][]string, logger log.Logger, remoteAddr string) {
	service, err := getServiceFromMap(kv, true)
	if err != nil {
		logger.Errorf("RequestId=%v From=%v Cannot get service name: %v; %v", reqId, remoteAddr, service, err)
		return
	}
	subs, err := getSubscribersFromMap(kv, false)
	if err != nil {
		logger.Errorf("RequestId=%v From=%v Service=%v Cannot get subscriber: %v", reqId, remoteAddr, service, err)
		return
	}
	if len(subs) == 0 {
		logger.Errorf("RequestId=%v From=%v Service=%v NoSubscriber", reqId, remoteAddr, service)
		return
	}

	notif := NewEmptyNotification()

	for k, v := range kv {
		if len(v) <= 0 {
			continue
		}
		switch k {
		case "subscriber":
		case "subscribers":
		case "service":
			// three keys need to be ignored
		case "badge":
			if v != "" {
				var e error
				_, e = strconv.Atoi(v)
				if e == nil {
					notif.Data["badge"] = v
				} else {
					notif.Data["badge"] = "0"
				}
			}
		default:
			notif.Data[k] = v
		}
	}

	if notif.IsEmpty() {
		logger.Errorf("RequestId=%v From=%v Service=%v EmptyNotification", reqId, remoteAddr, service)
		return
	}

	logger.Infof("RequestId=%v From=%v Service=%v Subscribers=\"%v\"", reqId, remoteAddr, service, subs)

	self.backend.Push(reqId, service, subs, notif, perdp, logger)
	return
}

func (self *RestAPI) stop(w io.Writer, remoteAddr string) {
	self.waitGroup.Wait()
	self.backend.Finalize()
	self.loggers[LOGGER_WEB].Infof("stopped by %v", remoteAddr)
	if w != nil {
		fmt.Fprintf(w, "Stopped\r\n")
	}
	self.stopChan <- true
	return
}

func (self *RestAPI) numberOfDeliveryPoints(kv map[string][]string, logger log.Logger, remoteAddr string) int {
	ret := 0
	ss, ok := kv["service"]
	if !ok {
		return ret
	}
	if len(ss) == 0 {
		return ret
	}
	service := ss[0]
	subs, ok := kv["subscriber"]
	if !ok {
		return ret
	}
	if len(subs) == 0 {
		return ret
	}
	sub := subs[0]
	ret = self.backend.NumberOfDeliveryPoints(service, sub, logger)
	return ret
}

func (self *RestAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	remoteAddr := r.RemoteAddr
	switch r.URL.Path {
	case QUERY_NUMBER_OF_DELIVERY_POINTS_URL:
		r.ParseForm()
		n := self.numberOfDeliveryPoints(r.Form, self.loggers[LOGGER_WEB], remoteAddr)
		fmt.Fprintf(w, "%v\r\n", n)
		return
	case VERSION_INFO_URL:
		fmt.Fprintf(w, "%v\r\n", self.version)
		self.loggers[LOGGER_WEB].Infof("Checked version from %v", remoteAddr)
		return
	case STOP_PROGRAM_URL:
		self.stop(w, remoteAddr)
		return
	}
	r.ParseForm()
	kv := make(map[string]string, len(r.Form))
	perdp := make(map[string][]string, 3)
	perdpPrefix := "uniqush.perdp."
	for k, v := range r.Form {
		if len(k) > len(perdpPrefix) {
			if k[:len(perdpPrefix)] == perdpPrefix {
				key := k[len(perdpPrefix):]
				perdp[key] = v
				continue
			}
		}
		if len(v) > 0 {
			kv[k] = v[0]
		}
	}
	writer := w
	logLevel := log.LOGLEVEL_INFO

	self.waitGroup.Add(1)
	defer self.waitGroup.Done()
	switch r.URL.Path {
	case ADD_PUSH_SERVICE_PROVIDER_TO_SERVICE_URL:
		weblogger := log.NewLogger(writer, "[AddPushServiceProvider]", logLevel)
		logger := log.MultiLogger(weblogger, self.loggers[LOGGER_ADDPSP])
		self.changePushServiceProvider(kv, logger, remoteAddr, true)
	case REMOVE_PUSH_SERVICE_PROVIDER_TO_SERVICE_URL:
		weblogger := log.NewLogger(writer, "[RemovePushServiceProvider]", logLevel)
		logger := log.MultiLogger(weblogger, self.loggers[LOGGER_RMPSP])
		self.changePushServiceProvider(kv, logger, remoteAddr, false)
	case ADD_DELIVERY_POINT_TO_SERVICE_URL:
		weblogger := log.NewLogger(writer, "[Subscribe]", logLevel)
		logger := log.MultiLogger(weblogger, self.loggers[LOGGER_SUB])
		self.changeSubscription(kv, logger, remoteAddr, true)
	case REMOVE_DELIVERY_POINT_FROM_SERVICE_URL:
		weblogger := log.NewLogger(writer, "[Unsubscribe]", logLevel)
		logger := log.MultiLogger(weblogger, self.loggers[LOGGER_UNSUB])
		self.changeSubscription(kv, logger, remoteAddr, false)
	case PUSH_NOTIFICATION_URL:
		weblogger := log.NewLogger(writer, "[Push]", logLevel)
		logger := log.MultiLogger(weblogger, self.loggers[LOGGER_PUSH])
		rid, _ := uuid.NewV4()
		self.pushNotification(rid.String(), kv, perdp, logger, remoteAddr)
	}
}

func (self *RestAPI) Run(addr string, stopChan chan<- bool) {
	self.loggers[LOGGER_WEB].Configf("[Start] %s", addr)
	self.loggers[LOGGER_WEB].Debugf("[Version] %s", self.version)
	http.Handle(STOP_PROGRAM_URL, self)
	http.Handle(VERSION_INFO_URL, self)
	http.Handle(ADD_PUSH_SERVICE_PROVIDER_TO_SERVICE_URL, self)
	http.Handle(ADD_DELIVERY_POINT_TO_SERVICE_URL, self)
	http.Handle(REMOVE_DELIVERY_POINT_FROM_SERVICE_URL, self)
	http.Handle(REMOVE_PUSH_SERVICE_PROVIDER_TO_SERVICE_URL, self)
	http.Handle(PUSH_NOTIFICATION_URL, self)
	http.Handle(QUERY_NUMBER_OF_DELIVERY_POINTS_URL, self)
	self.stopChan = stopChan
	err := http.ListenAndServe(addr, nil)
	if err != nil {
		self.loggers[LOGGER_WEB].Fatalf("HTTPServerError \"%v\"", err)
	}
	return
}
<commit_msg>fix #39.<commit_after>/*
 * Copyright 2011 Nan Deng
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

package main

import (
	"fmt"

	"github.com/uniqush/log"
	. "github.com/uniqush/uniqush-push/push"
	"io"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"sync"
)

type RestAPI struct {
	psm       *PushServiceManager
	loggers   []log.Logger
	backend   *PushBackEnd
	version   string
	waitGroup *sync.WaitGroup
	stopChan  chan<- bool
}

// loggers: sequence is web, add
func NewRestAPI(psm *PushServiceManager, loggers []log.Logger, version string, backend *PushBackEnd) *RestAPI {
	ret := new(RestAPI)
	ret.psm = psm
	ret.loggers = loggers
	ret.version = version
	ret.backend = backend
	ret.waitGroup = new(sync.WaitGroup)
	return ret
}

const (
	ADD_PUSH_SERVICE_PROVIDER_TO_SERVICE_URL    = "/addpsp"
	REMOVE_PUSH_SERVICE_PROVIDER_TO_SERVICE_URL = "/rmpsp"
	ADD_DELIVERY_POINT_TO_SERVICE_URL           = "/subscribe"
	REMOVE_DELIVERY_POINT_FROM_SERVICE_URL      = "/unsubscribe"
	PUSH_NOTIFICATION_URL                       = "/push"
	STOP_PROGRAM_URL                            = "/stop"
	VERSION_INFO_URL                            = "/version"
	QUERY_NUMBER_OF_DELIVERY_POINTS_URL         = "/nrdp"
)

var validServicePattern *regexp.Regexp
var validSubscriberPattern *regexp.Regexp

func init() {
	var err error
	validServicePattern, err = regexp.Compile("^[a-zA-z\\.0-9-_@]+$")
	if err != nil {
		validServicePattern = nil
	}
	validSubscriberPattern, err = regexp.Compile("^[a-zA-z\\.0-9-_@]+$")
	if err != nil {
		validSubscriberPattern = nil
	}
}

func validateSubscribers(subs []string) error {
	if validSubscriberPattern != nil {
		for _, sub := range subs {
			if !validSubscriberPattern.MatchString(sub) {
				return fmt.Errorf("invalid subscriber name: %s. Accept charaters: a-z, A-Z, 0-9, -, _, @ or .", sub)
			}
		}
	}
	return nil
}

func validateService(service string) error {
	if validServicePattern != nil {
		if !validServicePattern.MatchString(service) {
			return fmt.Errorf("invalid service name: %s. Accept charaters: a-z, A-Z, 0-9, -, _, @ or .", service)
		}
	}
	return nil
}

func getSubscribersFromMap(kv map[string]string, validate bool) (subs []string, err error) {
	var v string
	var ok bool
	if v, ok = kv["subscriber"]; !ok {
		if v, ok = kv["subscribers"]; !ok {
			err = fmt.Errorf("NoSubscriber")
			return
		}
	}
	subs = strings.Split(v, ",")
	if validate {
		err = validateSubscribers(subs)
		if err != nil {
			subs = nil
			return
		}
	}
	return
}

func getServiceFromMap(kv map[string]string, validate bool) (service string, err error) {
	var ok bool
	if service, ok = kv["service"]; !ok {
		err = fmt.Errorf("NoService")
		return
	}
	if validate {
		err = validateService(service)
		if err != nil {
			service = ""
			return
		}
	}
	return
}

func (self *RestAPI) changePushServiceProvider(kv map[string]string, logger log.Logger, remoteAddr string, add bool) {
	psp, err := self.psm.BuildPushServiceProviderFromMap(kv)
	if err != nil {
		logger.Errorf("From=%v Cannot build push service provider: %v", remoteAddr, err)
		return
	}
	service, err := getServiceFromMap(kv, true)
	if err != nil {
		logger.Errorf("From=%v Cannot get service name: %v; %v", remoteAddr, service, err)
		return
	}
	if add {
		err = self.backend.AddPushServiceProvider(service, psp)
	} else {
		err = self.backend.RemovePushServiceProvider(service, psp)
	}
	if err != nil {
		logger.Errorf("From=%v Failed: %v", remoteAddr, err)
		return
	}
	logger.Infof("From=%v Service=%v PushServiceProvider=%v Success!", remoteAddr, service, psp.Name())
	return
}

func (self *RestAPI) changeSubscription(kv map[string]string, logger log.Logger, remoteAddr string, issub bool) {
	dp, err := self.psm.BuildDeliveryPointFromMap(kv)
	if err != nil {
		logger.Errorf("Cannot build delivery point: %v", err)
		return
	}
	service, err := getServiceFromMap(kv, true)
	if err != nil {
		logger.Errorf("From=%v Cannot get service name: %v; %v", remoteAddr, service, err)
		return
	}
	subs, err := getSubscribersFromMap(kv, true)
	if err != nil {
		logger.Errorf("From=%v Service=%v Cannot get subscriber: %v", remoteAddr, service, err)
		return
	}

	var psp *PushServiceProvider
	if issub {
		psp, err = self.backend.Subscribe(service, subs[0], dp)
	} else {
		err = self.backend.Unsubscribe(service, subs[0], dp)
	}
	if err != nil {
		logger.Errorf("From=%v Failed: %v", remoteAddr, err)
		return
	}
	if psp == nil {
		logger.Infof("From=%v Service=%v Subscriber=%v DeliveryPoint=%v Success!", remoteAddr, service, subs[0], dp.Name())
	} else {
		logger.Infof("From=%v Service=%v Subscriber=%v PushServiceProvider=%v DeliveryPoint=%v Success!", remoteAddr, service, subs[0], psp.Name(), dp.Name())
	}
}

func (self *RestAPI) pushNotification(reqId string, kv map[string]string, perdp map[string][]string, logger log.Logger, remoteAddr string) {
	service, err := getServiceFromMap(kv, true)
	if err != nil {
		logger.Errorf("RequestId=%v From=%v Cannot get service name: %v; %v", reqId, remoteAddr, service, err)
		return
	}
	subs, err := getSubscribersFromMap(kv, false)
	if err != nil {
		logger.Errorf("RequestId=%v From=%v Service=%v Cannot get subscriber: %v", reqId, remoteAddr, service, err)
		return
	}
	if len(subs) == 0 {
		logger.Errorf("RequestId=%v From=%v Service=%v NoSubscriber", reqId, remoteAddr, service)
		return
	}

	notif := NewEmptyNotification()

	for k, v := range kv {
		if len(v) <= 0 {
			continue
		}
		switch k {
		case "subscriber":
		case "subscribers":
		case "service":
			// three keys need to be ignored
		case "badge":
			if v != "" {
				var e error
				_, e = strconv.Atoi(v)
				if e == nil {
					notif.Data["badge"] = v
				} else {
					notif.Data["badge"] = "0"
				}
			}
		default:
			notif.Data[k] = v
		}
	}

	if notif.IsEmpty() {
		logger.Errorf("RequestId=%v From=%v Service=%v EmptyNotification", reqId, remoteAddr, service)
		return
	}

	logger.Infof("RequestId=%v From=%v Service=%v Subscribers=\"%v\"", reqId, remoteAddr, service, subs)

	self.backend.Push(reqId, service, subs, notif, perdp, logger)
	return
}

func (self *RestAPI) stop(w io.Writer, remoteAddr string) {
	self.waitGroup.Wait()
	self.backend.Finalize()
	self.loggers[LOGGER_WEB].Infof("stopped by %v", remoteAddr)
	if w != nil {
		fmt.Fprintf(w, "Stopped\r\n")
	}
	self.stopChan <- true
	return
}

func (self *RestAPI) numberOfDeliveryPoints(kv map[string][]string, logger log.Logger, remoteAddr string) int {
	ret := 0
	ss, ok := kv["service"]
	if !ok {
		return ret
	}
	if len(ss) == 0 {
		return ret
	}
	service := ss[0]
	subs, ok := kv["subscriber"]
	if !ok {
		return ret
	}
	if len(subs) == 0 {
		return ret
	}
	sub := subs[0]
	ret = self.backend.NumberOfDeliveryPoints(service, sub, logger)
	return ret
}

func (self *RestAPI) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	defer r.Body.Close()
	remoteAddr := r.RemoteAddr
	switch r.URL.Path {
	case QUERY_NUMBER_OF_DELIVERY_POINTS_URL:
		r.ParseForm()
		n := self.numberOfDeliveryPoints(r.Form, self.loggers[LOGGER_WEB], remoteAddr)
		fmt.Fprintf(w, "%v\r\n", n)
		return
	case VERSION_INFO_URL:
		fmt.Fprintf(w, "%v\r\n", self.version)
		self.loggers[LOGGER_WEB].Infof("Checked version from %v", remoteAddr)
		return
	case STOP_PROGRAM_URL:
		self.stop(w, remoteAddr)
		return
	}
	r.ParseForm()
	kv := make(map[string]string, len(r.Form))
	perdp := make(map[string][]string, 3)
	perdpPrefix := "uniqush.perdp."
	for k, v := range r.Form {
		if len(k) > len(perdpPrefix) {
			if k[:len(perdpPrefix)] == perdpPrefix {
				key := k[len(perdpPrefix):]
				perdp[key] = v
				continue
			}
		}
		if len(v) > 0 {
			kv[k] = v[0]
		}
	}
	writer := w
	logLevel := log.LOGLEVEL_INFO

	self.waitGroup.Add(1)
	defer self.waitGroup.Done()
	switch r.URL.Path {
	case ADD_PUSH_SERVICE_PROVIDER_TO_SERVICE_URL:
		weblogger := log.NewLogger(writer, "[AddPushServiceProvider]", logLevel)
		logger := log.MultiLogger(weblogger, self.loggers[LOGGER_ADDPSP])
		self.changePushServiceProvider(kv, logger, remoteAddr, true)
	case REMOVE_PUSH_SERVICE_PROVIDER_TO_SERVICE_URL:
		weblogger := log.NewLogger(writer, "[RemovePushServiceProvider]", logLevel)
		logger := log.MultiLogger(weblogger, self.loggers[LOGGER_RMPSP])
		self.changePushServiceProvider(kv, logger, remoteAddr, false)
	case ADD_DELIVERY_POINT_TO_SERVICE_URL:
		weblogger := log.NewLogger(writer, "[Subscribe]", logLevel)
		logger := log.MultiLogger(weblogger, self.loggers[LOGGER_SUB])
		self.changeSubscription(kv, logger, remoteAddr, true)
	case REMOVE_DELIVERY_POINT_FROM_SERVICE_URL:
		weblogger := log.NewLogger(writer, "[Unsubscribe]", logLevel)
		logger := log.MultiLogger(weblogger, self.loggers[LOGGER_UNSUB])
		self.changeSubscription(kv, logger, remoteAddr, false)
	case PUSH_NOTIFICATION_URL:
		weblogger := log.NewLogger(writer, "[Push]", logLevel)
		logger := log.MultiLogger(weblogger, self.loggers[LOGGER_PUSH])
		rid, _ := uuid.NewV4()
		self.pushNotification(rid.String(), kv, perdp, logger, remoteAddr)
	}
}

func (self *RestAPI) Run(addr string, stopChan chan<- bool) {
	self.loggers[LOGGER_WEB].Configf("[Start] %s", addr)
	self.loggers[LOGGER_WEB].Debugf("[Version] %s", self.version)
	http.Handle(STOP_PROGRAM_URL, self)
	http.Handle(VERSION_INFO_URL, self)
	http.Handle(ADD_PUSH_SERVICE_PROVIDER_TO_SERVICE_URL, self)
	http.Handle(ADD_DELIVERY_POINT_TO_SERVICE_URL, self)
	http.Handle(REMOVE_DELIVERY_POINT_FROM_SERVICE_URL, self)
	http.Handle(REMOVE_PUSH_SERVICE_PROVIDER_TO_SERVICE_URL, self)
	http.Handle(PUSH_NOTIFICATION_URL, self)
	http.Handle(QUERY_NUMBER_OF_DELIVERY_POINTS_URL, self)
	self.stopChan = stopChan
	err := http.ListenAndServe(addr, nil)
	if err != nil {
		self.loggers[LOGGER_WEB].Fatalf("HTTPServerError \"%v\"", err)
	}
	return
}
<|endoftext|>


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

package nodes

import (
	"sort"
)

// NodeType is type for parsed codelab nodes tree.
type NodeType uint32

// Codelab node kinds.
const (
	NodeInvalid     NodeType = 1 << iota
	NodeList                 // A node which contains a list of other nodes
	NodeGrid                 // Table
	NodeText                 // Simple node with a string as the value
	NodeCode                 // Source code or console (terminal) output
	NodeInfobox              // An aside box for notes or warnings
	NodeSurvey               // Sets of grouped questions
	NodeURL                  // Represents elements such as
	NodeImage                // Image
	NodeButton               // Button
	NodeItemsList            // Set of NodeList items
	NodeItemsCheck           // Special kind of NodeItemsList, checklist
	NodeItemsFAQ             // Special kind of NodeItemsList, FAQ
	NodeHeader               // A header text node
	NodeHeaderCheck          // Special kind of header, checklist
	NodeHeaderFAQ            // Special kind of header, FAQ
	NodeYouTube              // YouTube video
	NodeIframe               // Embedded iframe
	NodeImport               // A node which holds content imported from another resource
)

// Node is an interface common to all node types.
type Node interface {
	// Type returns node type.
	Type() NodeType
	// MutateType changes node type where possible.
	// Only changes within this same category are allowed.
	// For instance, items list or header nodes can change their types
	// to another kind of items list or header.
	MutateType(NodeType)
	// Block returns a source reference of the node.
	Block() interface{}
	// MutateBlock updates source reference of the node.
	MutateBlock(interface{})
	// Empty returns true if the node has no content.
	Empty() bool
	// Env returns node environment
	Env() []string
	// MutateEnv replaces current node environment tags with env.
	MutateEnv(env []string)
}

// IsItemsList returns true if t is one of ItemsListNode types.
func IsItemsList(t NodeType) bool {
	return t&(NodeItemsList|NodeItemsCheck|NodeItemsFAQ) != 0
}

// IsInline returns true if t is an inline node type.
func IsInline(t NodeType) bool {
	return t&(NodeText|NodeURL|NodeImage|NodeButton) != 0
}

// EmptyNodes returns true if all of nodes are empty.
func EmptyNodes(nodes []Node) bool {
	for _, n := range nodes {
		if !n.Empty() {
			return false
		}
	}
	return true
}

type node struct {
	typ   NodeType
	block interface{}
	env   []string
}

func (b *node) Type() NodeType {
	return b.typ
}

func (b *node) MutateType(t NodeType) {
	if IsItemsList(b.typ) && IsItemsList(t) || IsHeader(b.typ) && IsHeader(t) {
		b.typ = t
	}
}

func (b *node) Block() interface{} {
	return b.block
}

func (b *node) MutateBlock(v interface{}) {
	b.block = v
}

func (b *node) Env() []string {
	return b.env
}

func (b *node) MutateEnv(e []string) {
	b.env = make([]string, len(e))
	copy(b.env, e)
	sort.Strings(b.env)
}

// NewItemsListNode creates a new ItemsListNode of type NodeItemsList,
// which defaults to an unordered list.
// Provide a positive start to make this a numbered list.
// NodeItemsCheck and NodeItemsFAQ are always unnumbered.
func NewItemsListNode(typ string, start int) *ItemsListNode {
	iln := ItemsListNode{
		node: node{typ: NodeItemsList},
		// TODO document this
		ListType: typ,
		Start:    start,
	}
	iln.MutateBlock(true)
	return &iln
}

// ItemsListNode containts sets of ListNode.
// Non-zero ListType indicates an ordered list.
type ItemsListNode struct {
	node
	ListType string
	Start    int
	Items    []*ListNode
}

// Empty returns true if every item has empty content.
func (il *ItemsListNode) Empty() bool {
	for _, i := range il.Items {
		if !i.Empty() {
			return false
		}
	}
	return true
}

// NewItem creates a new ListNode and adds it to il.Items.
func (il *ItemsListNode) NewItem(nodes ...Node) *ListNode {
	n := NewListNode(nodes...)
	il.Items = append(il.Items, n)
	return n
}
<commit_msg>Add some TODOs.<commit_after>// Copyright 2016 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package nodes

import (
	"sort"
)

// NodeType is type for parsed codelab nodes tree.
type NodeType uint32

// Codelab node kinds.
const (
	NodeInvalid     NodeType = 1 << iota
	NodeList                 // A node which contains a list of other nodes
	NodeGrid                 // Table
	NodeText                 // Simple node with a string as the value
	NodeCode                 // Source code or console (terminal) output
	NodeInfobox              // An aside box for notes or warnings
	NodeSurvey               // Sets of grouped questions
	NodeURL                  // Represents elements such as
	NodeImage                // Image
	NodeButton               // Button
	NodeItemsList            // Set of NodeList items
	NodeItemsCheck           // Special kind of NodeItemsList, checklist
	NodeItemsFAQ             // Special kind of NodeItemsList, FAQ
	NodeHeader               // A header text node
	NodeHeaderCheck          // Special kind of header, checklist
	NodeHeaderFAQ            // Special kind of header, FAQ
	NodeYouTube              // YouTube video
	NodeIframe               // Embedded iframe
	NodeImport               // A node which holds content imported from another resource
)

// Node is an interface common to all node types.
type Node interface {
	// Type returns node type.
	Type() NodeType
	// MutateType changes node type where possible.
	// Only changes within this same category are allowed.
	// For instance, items list or header nodes can change their types
	// to another kind of items list or header.
	MutateType(NodeType)
	// Block returns a source reference of the node.
	Block() interface{}
	// MutateBlock updates source reference of the node.
	MutateBlock(interface{})
	// Empty returns true if the node has no content.
	Empty() bool
	// Env returns node environment
	Env() []string
	// MutateEnv replaces current node environment tags with env.
	MutateEnv(env []string)
}

// IsItemsList returns true if t is one of ItemsListNode types.
func IsItemsList(t NodeType) bool {
	return t&(NodeItemsList|NodeItemsCheck|NodeItemsFAQ) != 0
}

// IsInline returns true if t is an inline node type.
func IsInline(t NodeType) bool {
	return t&(NodeText|NodeURL|NodeImage|NodeButton) != 0
}

// EmptyNodes returns true if all of nodes are empty.
func EmptyNodes(nodes []Node) bool {
	for _, n := range nodes {
		if !n.Empty() {
			return false
		}
	}
	return true
}

type node struct {
	typ   NodeType
	block interface{}
	env   []string
}

func (b *node) Type() NodeType {
	return b.typ
}

// TODO test
func (b *node) MutateType(t NodeType) {
	if IsItemsList(b.typ) && IsItemsList(t) || IsHeader(b.typ) && IsHeader(t) {
		b.typ = t
	}
}

func (b *node) Block() interface{} {
	return b.block
}

func (b *node) MutateBlock(v interface{}) {
	b.block = v
}

func (b *node) Env() []string {
	return b.env
}

// TODO test
func (b *node) MutateEnv(e []string) {
	b.env = make([]string, len(e))
	copy(b.env, e)
	sort.Strings(b.env)
}

// TODO test ItemsList related code
// NewItemsListNode creates a new ItemsListNode of type NodeItemsList,
// which defaults to an unordered list.
// Provide a positive start to make this a numbered list.
// NodeItemsCheck and NodeItemsFAQ are always unnumbered.
func NewItemsListNode(typ string, start int) *ItemsListNode {
	iln := ItemsListNode{
		node: node{typ: NodeItemsList},
		// TODO document this
		ListType: typ,
		Start:    start,
	}
	iln.MutateBlock(true)
	return &iln
}

// ItemsListNode containts sets of ListNode.
// Non-zero ListType indicates an ordered list.
type ItemsListNode struct {
	node
	ListType string
	Start    int
	Items    []*ListNode
}

// Empty returns true if every item has empty content.
func (il *ItemsListNode) Empty() bool {
	for _, i := range il.Items {
		if !i.Empty() {
			return false
		}
	}
	return true
}

// NewItem creates a new ListNode and adds it to il.Items.
func (il *ItemsListNode) NewItem(nodes ...Node) *ListNode {
	n := NewListNode(nodes...)
	il.Items = append(il.Items, n)
	return n
}
<|endoftext|>


<commit_before>package openweathermap

const (
	baseUrl  string = "http://api.openweathermap.org/data/2.5/weather?%s"
	cityUrl  string = "http://api.openweathermap.org/data/2.5/weather?q=%s"
	coordUrl string = "http://api.openweathermap.org/data/2.5/weather?lat=%s&long=%s"
)
<commit_msg>fix str format when using float64<commit_after>package openweathermap

const (
	baseUrl  string = "http://api.openweathermap.org/data/2.5/weather?%s"
	cityUrl  string = "http://api.openweathermap.org/data/2.5/weather?q=%s"
	coordUrl string = "http://api.openweathermap.org/data/2.5/weather?lat=%f&lon=%f"
)
<|endoftext|>


<commit_before>package core

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"strings"
	"sync"

	"golang.org/x/net/websocket"
)

// Router is set of command handlers
type Router struct {
	handlers        map[string]*CommandHandler
	conn            *websocket.Conn
	client          *Client
	allowedCommands string
	exit            chan bool

	handlersLock *sync.Mutex
}

// NewRouter creates new router instance
func NewRouter(handlers []*CommandHandler, c *Client) *Router {
	r := &Router{}

	r.exit = make(chan bool)
	r.handlersLock = &sync.Mutex{}
	r.client = c

	r.SetHandlers(handlers)

	return r
}

// Send data to client
func (r *Router) Send(cmd string, data interface{}) {
	com := Command{
		Cmd:  cmd,
		Data: data,
	}

	resp, err := json.Marshal(com)

	if err != nil {
		panic("can't generate packet")
	}

	r.conn.Write(resp)
}

// SetHandlers set new handlers for the router
func (r *Router) SetHandlers(handlers []*CommandHandler) {
	r.handlersLock.Lock()
	defer r.handlersLock.Unlock()

	r.handlers = make(map[string]*CommandHandler)

	var buf bytes.Buffer

	for _, h := range handlers {
		r.handlers[h.Name] = h

		buf.WriteString(h.Name)
		buf.WriteString(", ")
	}
	r.allowedCommands = buf.String()
}

// Listen handles web socket read
func (r *Router) Listen(conn *websocket.Conn) {
	log.Println("client connected")
	r.conn = conn

	for true {
		var res *Result
		var cmd CommandJSON
		// read command from socket
		err := websocket.JSON.Receive(r.conn, &cmd)
		// check for disconnect
		if err == io.EOF {
			log.Println("client disconnected")
			return
		} else if err != nil {
			if strings.HasPrefix(err.Error(), "invalid character") {
				res = &Result{
					Status: 400,
					Data:   err.Error(),
				}

				r.reply(cmd.Cmd, res)
				continue
			}
			log.Panicf("Unhandled error %v", err.Error())
		}

		// log command
		log.Printf("received %v err %v\n", cmd.Cmd, err)

		// handlers can change over the time, so use lock
		r.handlersLock.Lock()
		handler, ok := r.handlers[cmd.Cmd]
		var allowedCommands = r.allowedCommands
		r.handlersLock.Unlock()

		// if we found handler - execute
		if ok {
			log.Printf("handling %v", cmd.Cmd)
			res = handler.Handle(cmd.Data, r.client)
			// else provide some data about problem
		} else {
			res = &Result{
				Status: 404,
				Data:   fmt.Sprintf(`Unknown command. Allowed command are: %v. Read examples at github.com/arukim/galaxy`, allowedCommands),
			}
		}

		r.reply(cmd.Cmd, res)
	}
}

// reply sends response into socket
func (r *Router) reply(cmd string, result *Result) {
	// create response struct
	response := ResponseJSON{
		Cmd:    cmd,
		Result: result,
	}

	// try to marshall
	resp, err := json.Marshal(response)
	if err != nil {
		// wtf something gone wrong, let's return 500
		response.Result.Status = 500
		response.Result.Data = "Fatal server error"
		if resp, err = json.Marshal(response); err != nil {
			// we can't even generate 500? time to panic
			panic("can't generate 500 error")
		}
	}

	// write the response
	r.conn.Write(resp)
}
<commit_msg>added 500 on unknown error<commit_after>package core

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"strings"
	"sync"

	"golang.org/x/net/websocket"
)

// Router is set of command handlers
type Router struct {
	handlers        map[string]*CommandHandler
	conn            *websocket.Conn
	client          *Client
	allowedCommands string
	exit            chan bool

	handlersLock *sync.Mutex
}

// NewRouter creates new router instance
func NewRouter(handlers []*CommandHandler, c *Client) *Router {
	r := &Router{}

	r.exit = make(chan bool)
	r.handlersLock = &sync.Mutex{}
	r.client = c

	r.SetHandlers(handlers)

	return r
}

// Send data to client
func (r *Router) Send(cmd string, data interface{}) {
	com := Command{
		Cmd:  cmd,
		Data: data,
	}

	resp, err := json.Marshal(com)

	if err != nil {
		panic("can't generate packet")
	}

	r.conn.Write(resp)
}

// SetHandlers set new handlers for the router
func (r *Router) SetHandlers(handlers []*CommandHandler) {
	r.handlersLock.Lock()
	defer r.handlersLock.Unlock()

	r.handlers = make(map[string]*CommandHandler)

	var buf bytes.Buffer

	for _, h := range handlers {
		r.handlers[h.Name] = h

		buf.WriteString(h.Name)
		buf.WriteString(", ")
	}
	r.allowedCommands = buf.String()
}

// Listen handles web socket read
func (r *Router) Listen(conn *websocket.Conn) {
	log.Println("client connected")
	r.conn = conn

	for true {
		var res *Result
		var cmd CommandJSON
		// read command from socket
		err := websocket.JSON.Receive(r.conn, &cmd)
		// check for disconnect
		if err == io.EOF {
			log.Println("client disconnected")
			return
		} else if err != nil {
			// handle know errors, e.g. bad json in request
			if strings.HasPrefix(err.Error(), "invalid character") {
				res = &Result{
					Status: 400,
					Data:   err.Error(),
				}

				r.reply(cmd.Cmd, res)
				continue
			}
			// Unknown error gonna crush the socket
			res = &Result{
				Status: 500,
				Data:   err.Error(),
			}

			r.reply(cmd.Cmd, res)
			log.Panicf("Unhandled error %v", err.Error())
		}

		// log command
		log.Printf("received %v err %v\n", cmd.Cmd, err)

		// handlers can change over the time, so use lock
		r.handlersLock.Lock()
		handler, ok := r.handlers[cmd.Cmd]
		var allowedCommands = r.allowedCommands
		r.handlersLock.Unlock()

		// if we found handler - execute
		if ok {
			log.Printf("handling %v", cmd.Cmd)
			res = handler.Handle(cmd.Data, r.client)
			// else provide some data about problem
		} else {
			res = &Result{
				Status: 404,
				Data:   fmt.Sprintf(`Unknown command. Allowed command are: %v. Read examples at github.com/arukim/galaxy`, allowedCommands),
			}
		}

		r.reply(cmd.Cmd, res)
	}
}

// reply sends response into socket
func (r *Router) reply(cmd string, result *Result) {
	// create response struct
	response := ResponseJSON{
		Cmd:    cmd,
		Result: result,
	}

	// try to marshall
	resp, err := json.Marshal(response)
	if err != nil {
		// wtf something gone wrong, let's return 500
		response.Result.Status = 500
		response.Result.Data = "Fatal server error"
		if resp, err = json.Marshal(response); err != nil {
			// we can't even generate 500? time to panic
			panic("can't generate 500 error")
		}
	}

	// write the response
	r.conn.Write(resp)
}
<|endoftext|>


<commit_before>package store

import (
	"github.com/stretchr/testify/assert"
	"io/ioutil"
	"testing"
	"time"
)

func Test_Fetch(t *testing.T) {
	a := assert.New(t)
	dir, _ := ioutil.TempDir("", "message_store_test")
	//defer os.RemoveAll(dir)

	// when i store a message
	store := NewFileMessageStore(dir)
	a.NoError(store.Store("p1", uint64(1), []byte("aaaaaaaaaa")))
	a.NoError(store.Store("p1", uint64(2), []byte("bbbbbbbbbb")))
	a.NoError(store.Store("p2", uint64(1), []byte("1111111111")))
	a.NoError(store.Store("p2", uint64(2), []byte("2222222222")))

	testCases := []struct {
		description     string
		req             FetchRequest
		expectedResults []string
	}{
		{`match in partition 1`,
			FetchRequest{Partition: "p1", StartId: 2, Count: 1},
			[]string{"bbbbbbbbbb"},
		},
		{`match in partition 2`,
			FetchRequest{Partition: "p2", StartId: 2, Count: 1},
			[]string{"2222222222"},
		},
	}

	for _, testcase := range testCases {
		testcase.req.MessageC = make(chan MessageAndId)
		testcase.req.ErrorCallback = make(chan error)
		testcase.req.StartCallback = make(chan int)

		messages := []string{}

		store.Fetch(testcase.req)

		select {
		case numberOfResults := <-testcase.req.StartCallback:
			a.Equal(len(testcase.expectedResults), numberOfResults)
		case <-time.After(time.Second):
			a.Fail("timeout")
			return
		}

	loop:
		for {
			select {
			case msg, open := <-testcase.req.MessageC:
				if !open {
					break loop
				}
				messages = append(messages, string(msg.Message))
			case err := <-testcase.req.ErrorCallback:
				a.Fail(err.Error())
				break loop
			case <-time.After(time.Second):
				a.Fail("timeout")
				return
			}
		}
		a.Equal(testcase.expectedResults, messages, "Tescase: "+testcase.description)
	}
}

func Test_MessageStore_Close(t *testing.T) {
	a := assert.New(t)
	dir, _ := ioutil.TempDir("", "message_store_test")
	//defer os.RemoveAll(dir)

	// when i store a message
	store := NewFileMessageStore(dir)
	a.NoError(store.Store("p1", uint64(1), []byte("aaaaaaaaaa")))
	a.NoError(store.Store("p2", uint64(1), []byte("1111111111")))

	a.Equal(2, len(store.partitions))

	a.NoError(store.Stop())

	a.Equal(0, len(store.partitions))
}
<commit_msg>Added some more test.Package coverage at 80%<commit_after>package store

import (
	"fmt"
	"github.com/stretchr/testify/assert"
	"io/ioutil"
	"testing"
	"time"
)

func Test_Fetch(t *testing.T) {
	a := assert.New(t)
	dir, _ := ioutil.TempDir("", "message_store_test")
	//defer os.RemoveAll(dir)

	// when i store a message
	store := NewFileMessageStore(dir)
	a.NoError(store.Store("p1", uint64(1), []byte("aaaaaaaaaa")))
	a.NoError(store.Store("p1", uint64(2), []byte("bbbbbbbbbb")))
	a.NoError(store.Store("p2", uint64(1), []byte("1111111111")))
	a.NoError(store.Store("p2", uint64(2), []byte("2222222222")))

	testCases := []struct {
		description     string
		req             FetchRequest
		expectedResults []string
	}{
		{`match in partition 1`,
			FetchRequest{Partition: "p1", StartId: 2, Count: 1},
			[]string{"bbbbbbbbbb"},
		},
		{`match in partition 2`,
			FetchRequest{Partition: "p2", StartId: 2, Count: 1},
			[]string{"2222222222"},
		},
	}

	for _, testcase := range testCases {
		testcase.req.MessageC = make(chan MessageAndId)
		testcase.req.ErrorCallback = make(chan error)
		testcase.req.StartCallback = make(chan int)

		messages := []string{}

		store.Fetch(testcase.req)

		select {
		case numberOfResults := <-testcase.req.StartCallback:
			a.Equal(len(testcase.expectedResults), numberOfResults)
		case <-time.After(time.Second):
			a.Fail("timeout")
			return
		}

	loop:
		for {
			select {
			case msg, open := <-testcase.req.MessageC:
				if !open {
					break loop
				}
				messages = append(messages, string(msg.Message))
			case err := <-testcase.req.ErrorCallback:
				a.Fail(err.Error())
				break loop
			case <-time.After(time.Second):
				a.Fail("timeout")
				return
			}
		}
		a.Equal(testcase.expectedResults, messages, "Tescase: "+testcase.description)
	}
}

func Test_MessageStore_Close(t *testing.T) {
	a := assert.New(t)
	dir, _ := ioutil.TempDir("", "message_store_test")
	//defer os.RemoveAll(dir)

	// when i store a message
	store := NewFileMessageStore(dir)
	a.NoError(store.Store("p1", uint64(1), []byte("aaaaaaaaaa")))
	a.NoError(store.Store("p2", uint64(1), []byte("1111111111")))

	a.Equal(2, len(store.partitions))

	a.NoError(store.Stop())

	a.Equal(0, len(store.partitions))
}

func Test_MaxMessageId(t *testing.T) {
	a := assert.New(t)
	dir, _ := ioutil.TempDir("", "message_store_test")
	//defer os.RemoveAll(dir)
	expectedMaxId := 2

	// when i store a message
	store := NewFileMessageStore(dir)
	a.NoError(store.Store("p1", uint64(1), []byte("aaaaaaaaaa")))
	a.NoError(store.Store("p1", uint64(expectedMaxId), []byte("bbbbbbbbbb")))

	maxID, err := store.MaxMessageId("p1")
	a.Nil(err, "No error should be received for partition p1")
	a.Equal(maxID, uint64(expectedMaxId), fmt.Sprintf("MaxId should be [%d]", expectedMaxId))
}

func Test_MessagePartitionReturningError(t *testing.T) {
	a := assert.New(t)

	store := NewFileMessageStore("/TestDir")
	_, err := store.partitionStore("p1")
	a.NotNil(err)
	fmt.Println(err)

	store2 := NewFileMessageStore("/")
	_, err2 := store2.partitionStore("p1")
	fmt.Println(err2)
}

func Test_FetchWithError(t *testing.T) {
	a := assert.New(t)
	store := NewFileMessageStore("/TestDir")

	chanCallBack := make(chan error, 1)
	aFetchRequest := FetchRequest{Partition: "p1", StartId: 2, Count: 1, ErrorCallback: chanCallBack}
	store.Fetch(aFetchRequest)
	err := <-aFetchRequest.ErrorCallback
	a.NotNil(err)
}

func Test_StoreWithError(t *testing.T) {
	a := assert.New(t)
	store := NewFileMessageStore("/TestDir")

	err := store.Store("p1", uint64(1), []byte("124151qfas"))
	a.NotNil(err)
}
<|endoftext|>


<commit_before>package postman

import (
    "fmt"
    "strings"
    "net/http"
    "io/ioutil"
)

type PostRequest struct {
    Request         string
    Ret             chan string
}

type Postman struct {
    RequestChan         chan *PostRequest
}

func NewPostman()  *Postman {
    pm := &Postman{make(chan *PostRequest, 128)}
    go pm.loop()
    return pm
}

func (this *Postman) loop() {
    for {
        select {
            case req := <-this.RequestChan:
               this.post(req)
        }
    }
}

func (this *Postman) post(req *PostRequest) {
    url := "http://127.0.0.1:8080/rpc"
    b := strings.NewReader(req.Request)
    fmt.Println("post-", req.Request, len(req.Request))
    http_req, err := http.Post(url, "application/json", b)
    if err == nil {
        if body, e := ioutil.ReadAll(http_req.Body); e == nil {
            req.Ret <- string(body)
        } else {
            fmt.Println("http post ret err:", e)
        }
        http_req.Body.Close()
    } else {
        close(req.Ret)
    }
}

func (this *Postman) Post(s string) string {
    req := &PostRequest{s, make(chan string, 1)}
    this.post(req)
    return <-req.Ret
}

<commit_msg>+ url arg<commit_after>package postman

import (
    "fmt"
    "strings"
    "net/http"
    "io/ioutil"
)

type PostRequest struct {
    Url             string
    Request         string
    Ret             chan string
}

type Postman struct {
    RequestChan         chan *PostRequest
}

func NewPostman()  *Postman {
    pm := &Postman{make(chan *PostRequest, 128)}
    go pm.loop()
    return pm
}

func (this *Postman) loop() {
    for {
        select {
            case req := <-this.RequestChan:
               this.post(req)
        }
    }
}

func (this *Postman) post(req *PostRequest) {
    b := strings.NewReader(req.Request)
    fmt.Println("post-", req.Request, len(req.Request))
    http_req, err := http.Post(req.Url, "application/json", b)
    if err == nil {
        if body, e := ioutil.ReadAll(http_req.Body); e == nil {
            req.Ret <- string(body)
        } else {
            fmt.Println("http post ret err:", e)
        }
        http_req.Body.Close()
    } else {
        close(req.Ret)
    }
}

func (this *Postman) Post(url, s string) string {
    req := &PostRequest{url, s, make(chan string, 1)}
    this.post(req)
    return <-req.Ret
}

<|endoftext|>


<commit_before>package reactor

import (
	"encoding/json"
	"fmt"
	"io/ioutil"
	"path/filepath"
	"strings"

	"github.com/kirillrdy/nadeshiko/html"
	"github.com/sparkymat/reactor/property"
	"github.com/sparkymat/webdsl/css"
)

type fileMapping struct {
	filePath string
	webPath  string
}

type reactor struct {
	name                  string
	skipReactorStartup    bool
	javascriptFolders     []fileMapping
	cssFolders            []fileMapping
	customJavascriptLinks []string
	customCssLinks        []string
	props                 map[string]Property
}

func New(name string) reactor {
	r := reactor{name: name}
	r.props = make(map[string]Property)
	return r
}

func (r *reactor) EnableReactStartup() {
	r.skipReactorStartup = false
}

func (r *reactor) DisableReactStartup() {
	r.skipReactorStartup = true
}

func (r *reactor) SetStringProperty(name string, value string) {
	property := Property{propertyType: property.String, value: value}
	r.props[name] = property
}

func (r *reactor) SetIntegerProperty(name string, value int64) {
	property := Property{propertyType: property.Integer, value: fmt.Sprintf("%v", value)}
	r.props[name] = property
}

func (r *reactor) SetFloatProperty(name string, value float64) {
	property := Property{propertyType: property.Float, value: fmt.Sprintf("%v", value)}
	r.props[name] = property
}

func (r *reactor) SetObjectProperty(name string, value interface{}) {
	jsonValue, _ := json.Marshal(value)

	// FIXME: Ignoring error for now
	property := Property{propertyType: property.Object, value: strings.Replace(string(jsonValue), "\"", "\\\"\\", -1)}
	r.props[name] = property
}

func (r *reactor) MapJavascriptFolder(file string, web string) {
	r.javascriptFolders = append(r.javascriptFolders, fileMapping{filePath: file, webPath: web})
}

func (r *reactor) MapCssFolder(file string, web string) {
	r.cssFolders = append(r.cssFolders, fileMapping{filePath: file, webPath: web})
}

func (r *reactor) AddCustomJavascriptLink(link string) {
	r.customJavascriptLinks = append(r.customJavascriptLinks, link)
}

func (r *reactor) AddCustomCssLink(link string) {
	r.customCssLinks = append(r.customCssLinks, link)
}

func (r reactor) Html() html.Node {
	headNodes := []html.Node{}

	headNodes = append(headNodes, html.Title().Text(r.name))

	// List javascript files
	for _, jsMap := range r.javascriptFolders {
		files, err := ioutil.ReadDir(jsMap.filePath)
		if err == nil {
			for _, jsFile := range files {
				if filepath.Ext(jsFile.Name()) == ".js" {
					finalPath := fmt.Sprintf("/%v/%v", jsMap.webPath, jsFile.Name())
					headNodes = append(headNodes, html.Script().Attribute("async", "async").Type("text/javascript").Src(finalPath))
				}
			}
		}
	}

	for _, jsLink := range r.customJavascriptLinks {
		headNodes = append(headNodes, html.Script().Type("text/javascript").Src(jsLink))
	}

	// List css files
	for _, cssMap := range r.cssFolders {
		files, err := ioutil.ReadDir(cssMap.filePath)

		if err == nil {
			for _, cssFile := range files {
				if filepath.Ext(cssFile.Name()) == ".css" {
					finalPath := fmt.Sprintf("/%v/%v", cssMap.webPath, cssFile.Name())
					headNodes = append(headNodes, html.Link().Rel("stylesheet").Href(finalPath))
				}
			}
		}
	}

	for _, cssLink := range r.customCssLinks {
		headNodes = append(headNodes, html.Link().Rel("stylesheet").Href(cssLink))
	}

	propsList := []string{}

	if r.props != nil {
		for key, value := range r.props {
			propsList = append(propsList, fmt.Sprintf("%v: %v", key, value.String()))
		}
	}

	propsString := fmt.Sprintf("{%v}", strings.Join(propsList, ","))

	childNodes := []html.Node{html.Div().Id(css.Id("app-container"))}

	if r.skipReactorStartup == false {
		childNodes = append(childNodes, html.Script().TextUnsafe(fmt.Sprintf("ReactDOM.render(React.createElement(%v, %v), document.getElementById('app-container'));", r.name, propsString)))
	}

	return html.Html().Children(
		html.Head().Children(headNodes...),
		html.Body().Children(childNodes...),
	)
}
<commit_msg>Updated for webdsl/html<commit_after>package reactor

import (
	"fmt"
	"io/ioutil"
	"path/filepath"

	"github.com/sparkymat/webdsl/html"
)

type fileMapping struct {
	filePath string
	webPath  string
}

type reactor struct {
	name                  string
	javascriptFolders     []fileMapping
	cssFolders            []fileMapping
	customJavascriptLinks []string
	customCssLinks        []string
}

func New(name string) reactor {
	r := reactor{name: name}
	return r
}

func (r *reactor) MapJavascriptFolder(file string, web string) {
	r.javascriptFolders = append(r.javascriptFolders, fileMapping{filePath: file, webPath: web})
}

func (r *reactor) MapCssFolder(file string, web string) {
	r.cssFolders = append(r.cssFolders, fileMapping{filePath: file, webPath: web})
}

func (r *reactor) AddCustomJavascriptLink(link string) {
	r.customJavascriptLinks = append(r.customJavascriptLinks, link)
}

func (r *reactor) AddCustomCssLink(link string) {
	r.customCssLinks = append(r.customCssLinks, link)
}

func (r reactor) Html() html.HtmlDocument {
	javascriptNodes := []*html.Node{}
	headNodes := []*html.Node{}

	headNodes = append(headNodes, html.Title(r.name))

	// List javascript files
	for _, jsMap := range r.javascriptFolders {
		files, err := ioutil.ReadDir(jsMap.filePath)
		if err == nil {
			for _, jsFile := range files {
				if filepath.Ext(jsFile.Name()) == ".js" {
					finalPath := fmt.Sprintf("/%v/%v", jsMap.webPath, jsFile.Name())
					javascriptNodes = append(javascriptNodes, html.Script().Attr("type", "text/javascript").Attr("src", finalPath))
				}
			}
		}
	}

	for _, jsLink := range r.customJavascriptLinks {
		headNodes = append(headNodes, html.Script().Attr("type", "text/javascript").Attr("src", jsLink))
	}

	// List css files
	for _, cssMap := range r.cssFolders {
		files, err := ioutil.ReadDir(cssMap.filePath)

		if err == nil {
			for _, cssFile := range files {
				if filepath.Ext(cssFile.Name()) == ".css" {
					finalPath := fmt.Sprintf("/%v/%v", cssMap.webPath, cssFile.Name())
					headNodes = append(headNodes, html.Link().Attr("rel", "stylesheet").Attr("href", finalPath))
				}
			}
		}
	}

	for _, cssLink := range r.customCssLinks {
		headNodes = append(headNodes, html.Link().Attr("rel", "stylesheet").Attr("href", cssLink))
	}

	childNodes := []*html.Node{html.Div().Attr("id", "js-reactor-app")}

	childNodes = append(childNodes, javascriptNodes...)

	return html.Html(
		html.Head(headNodes...),
		html.Body(childNodes...),
	)
}
<|endoftext|>


<commit_before>package server

import (
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/pressly/qmd/config"
)

func TestPing(t *testing.T) {
	conf, _ := config.New("../../etc/qmd.conf.sample")

	apiHandler := New(conf)
	ts := httptest.NewServer(apiHandler)
	defer ts.Close()

	res, err := http.Get(ts.URL + "/ping")
	if err != nil {
		t.Error(err)
	}

	dot, err := ioutil.ReadAll(res.Body)
	if err != nil {
		t.Error(err)
	}
	defer res.Body.Close()

	if res.StatusCode != 200 {
		t.Error("unexpected response status code")
	}

	if string(dot) != "." {
		t.Error("unexpected response body")
	}
}
<commit_msg>Fix tests<commit_after>package server

import (
	"io/ioutil"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/pressly/qmd/config"
)

func TestPing(t *testing.T) {
	conf, _ := config.New("../etc/qmd.conf.sample")

	apiHandler := New(conf)
	ts := httptest.NewServer(apiHandler)
	defer ts.Close()

	res, err := http.Get(ts.URL + "/ping")
	if err != nil {
		t.Error(err)
	}

	dot, err := ioutil.ReadAll(res.Body)
	if err != nil {
		t.Error(err)
	}
	defer res.Body.Close()

	if res.StatusCode != 200 {
		t.Error("unexpected response status code")
	}

	if string(dot) != "." {
		t.Error("unexpected response body")
	}
}
<|endoftext|>


<commit_before>package server

import (
	"bytes"
	"compress/gzip"
	"crypto/sha256"
	"encoding/base64"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/dsnet/compress/brotli"
	"github.com/khlieng/dispatch/assets"
	"github.com/spf13/viper"
)

const longCacheControl = "public, max-age=31536000, immutable"
const disabledCacheControl = "no-cache, no-store, must-revalidate"

type File struct {
	Path         string
	Asset        string
	GzipAsset    []byte
	Hash         string
	ContentType  string
	CacheControl string
	Compressed   bool
}

type h2PushAsset struct {
	path string
	hash string
}

func newH2PushAsset(name string) h2PushAsset {
	return h2PushAsset{
		path: name,
		hash: strings.Split(name, ".")[1],
	}
}

var (
	files []*File

	indexStylesheet      string
	indexScripts         []string
	inlineScript         string
	inlineScriptSha256   string
	inlineScriptSW       string
	inlineScriptSWSha256 string
	serviceWorker        []byte

	h2PushAssets      []h2PushAsset
	h2PushCookieValue string

	contentTypes = map[string]string{
		".js":    "text/javascript",
		".css":   "text/css",
		".woff2": "font/woff2",
		".woff":  "application/font-woff",
		".ttf":   "application/x-font-ttf",
		".png":   "image/png",
		".ico":   "image/x-icon",
		".json":  "application/json",
	}

	hstsHeader string
	cspEnabled bool
)

func (d *Dispatch) initFileServer() {
	if viper.GetBool("dev") {
		indexScripts = []string{"bundle.js"}
	} else {
		bootloader := decompressedAsset(findAssetName("boot*.js"))
		runtime := decompressedAsset(findAssetName("runtime*.js"))

		inlineScript = string(runtime)
		inlineScriptSW = string(bootloader) + string(runtime)

		hash := sha256.New()
		hash.Write(runtime)
		inlineScriptSha256 = base64.StdEncoding.EncodeToString(hash.Sum(nil))

		hash.Reset()
		hash.Write(bootloader)
		hash.Write(runtime)
		inlineScriptSWSha256 = base64.StdEncoding.EncodeToString(hash.Sum(nil))

		indexStylesheet = findAssetName("main*.css")
		indexScripts = []string{
			findAssetName("vendors*.js"),
			findAssetName("main*.js"),
		}

		h2PushAssets = []h2PushAsset{
			newH2PushAsset(indexStylesheet),
			newH2PushAsset(indexScripts[0]),
			newH2PushAsset(indexScripts[1]),
		}

		for _, asset := range h2PushAssets {
			h2PushCookieValue += asset.hash
		}

		ignoreAssets := []string{
			findAssetName("runtime*.js"),
			findAssetName("boot*.js"),
			"sw.js",
		}

	outer:
		for _, asset := range assets.AssetNames() {
			assetName := strings.TrimSuffix(asset, ".br")

			for _, ignored := range ignoreAssets {
				if ignored == assetName {
					continue outer
				}
			}

			file := &File{
				Path:         assetName,
				Asset:        asset,
				ContentType:  contentTypes[filepath.Ext(assetName)],
				CacheControl: longCacheControl,
				Compressed:   strings.HasSuffix(asset, ".br"),
			}

			if file.Compressed {
				data, err := assets.Asset(file.Asset)
				if err != nil {
					log.Fatal(err)
				}

				file.GzipAsset = gzipAsset(data)
			}

			files = append(files, file)
		}

		serviceWorker = decompressedAsset("sw.js")
		hash.Reset()
		IndexTemplate(hash, nil, indexStylesheet, inlineScriptSW, indexScripts, true)
		indexHash := base64.StdEncoding.EncodeToString(hash.Sum(nil))

		serviceWorker = append(serviceWorker, []byte(`
workbox.precaching.precacheAndRoute([{
	revision: '`+indexHash+`',
	url: '/?sw'
}]);
workbox.routing.registerNavigationRoute('/?sw');`)...)

		if viper.GetBool("https.hsts.enabled") && viper.GetBool("https.enabled") {
			hstsHeader = "max-age=" + viper.GetString("https.hsts.max_age")

			if viper.GetBool("https.hsts.include_subdomains") {
				hstsHeader += "; includeSubDomains"
			}
			if viper.GetBool("https.hsts.preload") {
				hstsHeader += "; preload"
			}
		}

		cspEnabled = true
	}
}

func findAssetName(glob string) string {
	for _, assetName := range assets.AssetNames() {
		assetName = strings.TrimSuffix(assetName, ".br")

		if m, _ := filepath.Match(glob, assetName); m {
			return assetName
		}
	}
	return ""
}

func decompressAsset(data []byte) []byte {
	br, err := brotli.NewReader(bytes.NewReader(data), nil)
	if err != nil {
		log.Fatal(err)
	}

	buf := &bytes.Buffer{}
	io.Copy(buf, br)
	return buf.Bytes()
}

func decompressedAsset(name string) []byte {
	asset, err := assets.Asset(name + ".br")
	if err != nil {
		log.Fatal(err)
	}
	return decompressAsset(asset)
}

func gzipAsset(data []byte) []byte {
	br, err := brotli.NewReader(bytes.NewReader(data), nil)
	if err != nil {
		log.Fatal(err)
	}

	buf := &bytes.Buffer{}
	gzw, err := gzip.NewWriterLevel(buf, gzip.BestCompression)
	if err != nil {
		log.Fatal(err)
	}

	io.Copy(gzw, br)
	gzw.Close()
	return buf.Bytes()
}

func (d *Dispatch) serveFiles(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/" {
		d.serveIndex(w, r)
		return
	}

	if r.URL.Path == "/sw.js" {
		w.Header().Set("Cache-Control", disabledCacheControl)
		w.Header().Set("Content-Type", "text/javascript")
		w.Header().Set("Content-Length", strconv.Itoa(len(serviceWorker)))
		w.Write(serviceWorker)
		return
	}

	for _, file := range files {
		if strings.HasSuffix(r.URL.Path, file.Path) {
			d.serveFile(w, r, file)
			return
		}
	}

	d.serveIndex(w, r)
}

func (d *Dispatch) serveIndex(w http.ResponseWriter, r *http.Request) {
	if pusher, ok := w.(http.Pusher); ok {
		options := &http.PushOptions{
			Header: http.Header{
				"Accept-Encoding": r.Header["Accept-Encoding"],
			},
		}

		cookie, err := r.Cookie("push")
		if err != nil {
			for _, asset := range h2PushAssets {
				pusher.Push(asset.path, options)
			}

			setPushCookie(w, r)
		} else {
			pushed := false

			for i, asset := range h2PushAssets {
				if len(cookie.Value) >= (i+1)*8 &&
					asset.hash != cookie.Value[i*8:(i+1)*8] {
					pusher.Push(asset.path, options)
					pushed = true
				}
			}

			if pushed {
				setPushCookie(w, r)
			}
		}
	}

	_, sw := r.URL.Query()["sw"]

	if cspEnabled {
		var wsSrc string
		if r.TLS != nil {
			wsSrc = "wss://" + r.Host
		} else {
			wsSrc = "ws://" + r.Host
		}

		inlineSha := inlineScriptSha256
		if sw {
			inlineSha = inlineScriptSWSha256
		}

		w.Header().Set("Content-Security-Policy", "default-src 'none'; script-src 'self' 'sha256-"+inlineSha+"'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self'; manifest-src 'self'; connect-src 'self' "+wsSrc)
	}

	w.Header().Set("Content-Type", "text/html")
	w.Header().Set("Cache-Control", disabledCacheControl)
	w.Header().Set("X-Content-Type-Options", "nosniff")
	w.Header().Set("X-Frame-Options", "deny")
	w.Header().Set("X-XSS-Protection", "1; mode=block")
	w.Header().Set("Referrer-Policy", "same-origin")

	if hstsHeader != "" {
		w.Header().Set("Strict-Transport-Security", hstsHeader)
	}

	var data *indexData
	inline := inlineScriptSW
	if !sw {
		data = getIndexData(r, r.URL.EscapedPath(), d.handleAuth(w, r, false, true))
		inline = inlineScript
	}

	if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
		w.Header().Set("Content-Encoding", "gzip")

		gzw := getGzipWriter(w)
		IndexTemplate(gzw, data, indexStylesheet, inline, indexScripts, sw)
		putGzipWriter(gzw)
	} else {
		IndexTemplate(w, data, indexStylesheet, inline, indexScripts, sw)
	}
}

func setPushCookie(w http.ResponseWriter, r *http.Request) {
	http.SetCookie(w, &http.Cookie{
		Name:     "push",
		Value:    h2PushCookieValue,
		Path:     "/",
		Expires:  time.Now().AddDate(1, 0, 0),
		HttpOnly: true,
		Secure:   r.TLS != nil,
	})
}

func (d *Dispatch) serveFile(w http.ResponseWriter, r *http.Request, file *File) {
	data, err := assets.Asset(file.Asset)
	if err != nil {
		http.Error(w, "", http.StatusInternalServerError)
		return
	}

	if file.CacheControl != "" {
		w.Header().Set("Cache-Control", file.CacheControl)
	}

	w.Header().Set("Content-Type", file.ContentType)

	if file.Compressed && strings.Contains(r.Header.Get("Accept-Encoding"), "br") {
		w.Header().Set("Content-Encoding", "br")
		w.Header().Set("Content-Length", strconv.Itoa(len(data)))
		w.Write(data)
	} else if file.Compressed && strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
		w.Header().Set("Content-Encoding", "gzip")
		w.Header().Set("Content-Length", strconv.Itoa(len(file.GzipAsset)))
		w.Write(file.GzipAsset)
	} else if !file.Compressed {
		w.Header().Set("Content-Length", strconv.Itoa(len(data)))
		w.Write(data)
	} else {
		gzr, err := gzip.NewReader(bytes.NewReader(file.GzipAsset))
		buf, err := ioutil.ReadAll(gzr)
		if err != nil {
			http.Error(w, "", http.StatusInternalServerError)
			return
		}

		w.Header().Set("Content-Length", strconv.Itoa(len(buf)))
		w.Write(buf)
	}
}
<commit_msg>Add worker-src csp directive<commit_after>package server

import (
	"bytes"
	"compress/gzip"
	"crypto/sha256"
	"encoding/base64"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"path/filepath"
	"strconv"
	"strings"
	"time"

	"github.com/dsnet/compress/brotli"
	"github.com/khlieng/dispatch/assets"
	"github.com/spf13/viper"
)

const longCacheControl = "public, max-age=31536000, immutable"
const disabledCacheControl = "no-cache, no-store, must-revalidate"

type File struct {
	Path         string
	Asset        string
	GzipAsset    []byte
	Hash         string
	ContentType  string
	CacheControl string
	Compressed   bool
}

type h2PushAsset struct {
	path string
	hash string
}

func newH2PushAsset(name string) h2PushAsset {
	return h2PushAsset{
		path: name,
		hash: strings.Split(name, ".")[1],
	}
}

var (
	files []*File

	indexStylesheet      string
	indexScripts         []string
	inlineScript         string
	inlineScriptSha256   string
	inlineScriptSW       string
	inlineScriptSWSha256 string
	serviceWorker        []byte

	h2PushAssets      []h2PushAsset
	h2PushCookieValue string

	contentTypes = map[string]string{
		".js":    "text/javascript",
		".css":   "text/css",
		".woff2": "font/woff2",
		".woff":  "application/font-woff",
		".ttf":   "application/x-font-ttf",
		".png":   "image/png",
		".ico":   "image/x-icon",
		".json":  "application/json",
	}

	hstsHeader string
	cspEnabled bool
)

func (d *Dispatch) initFileServer() {
	if viper.GetBool("dev") {
		indexScripts = []string{"bundle.js"}
	} else {
		bootloader := decompressedAsset(findAssetName("boot*.js"))
		runtime := decompressedAsset(findAssetName("runtime*.js"))

		inlineScript = string(runtime)
		inlineScriptSW = string(bootloader) + string(runtime)

		hash := sha256.New()
		hash.Write(runtime)
		inlineScriptSha256 = base64.StdEncoding.EncodeToString(hash.Sum(nil))

		hash.Reset()
		hash.Write(bootloader)
		hash.Write(runtime)
		inlineScriptSWSha256 = base64.StdEncoding.EncodeToString(hash.Sum(nil))

		indexStylesheet = findAssetName("main*.css")
		indexScripts = []string{
			findAssetName("vendors*.js"),
			findAssetName("main*.js"),
		}

		h2PushAssets = []h2PushAsset{
			newH2PushAsset(indexStylesheet),
			newH2PushAsset(indexScripts[0]),
			newH2PushAsset(indexScripts[1]),
		}

		for _, asset := range h2PushAssets {
			h2PushCookieValue += asset.hash
		}

		ignoreAssets := []string{
			findAssetName("runtime*.js"),
			findAssetName("boot*.js"),
			"sw.js",
		}

	outer:
		for _, asset := range assets.AssetNames() {
			assetName := strings.TrimSuffix(asset, ".br")

			for _, ignored := range ignoreAssets {
				if ignored == assetName {
					continue outer
				}
			}

			file := &File{
				Path:         assetName,
				Asset:        asset,
				ContentType:  contentTypes[filepath.Ext(assetName)],
				CacheControl: longCacheControl,
				Compressed:   strings.HasSuffix(asset, ".br"),
			}

			if file.Compressed {
				data, err := assets.Asset(file.Asset)
				if err != nil {
					log.Fatal(err)
				}

				file.GzipAsset = gzipAsset(data)
			}

			files = append(files, file)
		}

		serviceWorker = decompressedAsset("sw.js")
		hash.Reset()
		IndexTemplate(hash, nil, indexStylesheet, inlineScriptSW, indexScripts, true)
		indexHash := base64.StdEncoding.EncodeToString(hash.Sum(nil))

		serviceWorker = append(serviceWorker, []byte(`
workbox.precaching.precacheAndRoute([{
	revision: '`+indexHash+`',
	url: '/?sw'
}]);
workbox.routing.registerNavigationRoute('/?sw');`)...)

		if viper.GetBool("https.hsts.enabled") && viper.GetBool("https.enabled") {
			hstsHeader = "max-age=" + viper.GetString("https.hsts.max_age")

			if viper.GetBool("https.hsts.include_subdomains") {
				hstsHeader += "; includeSubDomains"
			}
			if viper.GetBool("https.hsts.preload") {
				hstsHeader += "; preload"
			}
		}

		cspEnabled = true
	}
}

func findAssetName(glob string) string {
	for _, assetName := range assets.AssetNames() {
		assetName = strings.TrimSuffix(assetName, ".br")

		if m, _ := filepath.Match(glob, assetName); m {
			return assetName
		}
	}
	return ""
}

func decompressAsset(data []byte) []byte {
	br, err := brotli.NewReader(bytes.NewReader(data), nil)
	if err != nil {
		log.Fatal(err)
	}

	buf := &bytes.Buffer{}
	io.Copy(buf, br)
	return buf.Bytes()
}

func decompressedAsset(name string) []byte {
	asset, err := assets.Asset(name + ".br")
	if err != nil {
		log.Fatal(err)
	}
	return decompressAsset(asset)
}

func gzipAsset(data []byte) []byte {
	br, err := brotli.NewReader(bytes.NewReader(data), nil)
	if err != nil {
		log.Fatal(err)
	}

	buf := &bytes.Buffer{}
	gzw, err := gzip.NewWriterLevel(buf, gzip.BestCompression)
	if err != nil {
		log.Fatal(err)
	}

	io.Copy(gzw, br)
	gzw.Close()
	return buf.Bytes()
}

func (d *Dispatch) serveFiles(w http.ResponseWriter, r *http.Request) {
	if r.URL.Path == "/" {
		d.serveIndex(w, r)
		return
	}

	if r.URL.Path == "/sw.js" {
		w.Header().Set("Cache-Control", disabledCacheControl)
		w.Header().Set("Content-Type", "text/javascript")
		w.Header().Set("Content-Length", strconv.Itoa(len(serviceWorker)))
		w.Write(serviceWorker)
		return
	}

	for _, file := range files {
		if strings.HasSuffix(r.URL.Path, file.Path) {
			d.serveFile(w, r, file)
			return
		}
	}

	d.serveIndex(w, r)
}

func (d *Dispatch) serveIndex(w http.ResponseWriter, r *http.Request) {
	if pusher, ok := w.(http.Pusher); ok {
		options := &http.PushOptions{
			Header: http.Header{
				"Accept-Encoding": r.Header["Accept-Encoding"],
			},
		}

		cookie, err := r.Cookie("push")
		if err != nil {
			for _, asset := range h2PushAssets {
				pusher.Push(asset.path, options)
			}

			setPushCookie(w, r)
		} else {
			pushed := false

			for i, asset := range h2PushAssets {
				if len(cookie.Value) >= (i+1)*8 &&
					asset.hash != cookie.Value[i*8:(i+1)*8] {
					pusher.Push(asset.path, options)
					pushed = true
				}
			}

			if pushed {
				setPushCookie(w, r)
			}
		}
	}

	_, sw := r.URL.Query()["sw"]

	if cspEnabled {
		var wsSrc string
		if r.TLS != nil {
			wsSrc = "wss://" + r.Host
		} else {
			wsSrc = "ws://" + r.Host
		}

		inlineSha := inlineScriptSha256
		if sw {
			inlineSha = inlineScriptSWSha256
		}

		csp := []string{
			"default-src 'none'",
			"script-src 'self' 'sha256-" + inlineSha + "'",
			"style-src 'self' 'unsafe-inline'",
			"font-src 'self'",
			"img-src 'self'",
			"manifest-src 'self'",
			"connect-src 'self' " + wsSrc,
			"worker-src 'self'",
		}

		w.Header().Set("Content-Security-Policy", strings.Join(csp, "; "))
	}

	w.Header().Set("Content-Type", "text/html")
	w.Header().Set("Cache-Control", disabledCacheControl)
	w.Header().Set("X-Content-Type-Options", "nosniff")
	w.Header().Set("X-Frame-Options", "deny")
	w.Header().Set("X-XSS-Protection", "1; mode=block")
	w.Header().Set("Referrer-Policy", "same-origin")

	if hstsHeader != "" {
		w.Header().Set("Strict-Transport-Security", hstsHeader)
	}

	var data *indexData
	inline := inlineScriptSW
	if !sw {
		data = getIndexData(r, r.URL.EscapedPath(), d.handleAuth(w, r, false, true))
		inline = inlineScript
	}

	if strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
		w.Header().Set("Content-Encoding", "gzip")

		gzw := getGzipWriter(w)
		IndexTemplate(gzw, data, indexStylesheet, inline, indexScripts, sw)
		putGzipWriter(gzw)
	} else {
		IndexTemplate(w, data, indexStylesheet, inline, indexScripts, sw)
	}
}

func setPushCookie(w http.ResponseWriter, r *http.Request) {
	http.SetCookie(w, &http.Cookie{
		Name:     "push",
		Value:    h2PushCookieValue,
		Path:     "/",
		Expires:  time.Now().AddDate(1, 0, 0),
		HttpOnly: true,
		Secure:   r.TLS != nil,
	})
}

func (d *Dispatch) serveFile(w http.ResponseWriter, r *http.Request, file *File) {
	data, err := assets.Asset(file.Asset)
	if err != nil {
		http.Error(w, "", http.StatusInternalServerError)
		return
	}

	if file.CacheControl != "" {
		w.Header().Set("Cache-Control", file.CacheControl)
	}

	w.Header().Set("Content-Type", file.ContentType)

	if file.Compressed && strings.Contains(r.Header.Get("Accept-Encoding"), "br") {
		w.Header().Set("Content-Encoding", "br")
		w.Header().Set("Content-Length", strconv.Itoa(len(data)))
		w.Write(data)
	} else if file.Compressed && strings.Contains(r.Header.Get("Accept-Encoding"), "gzip") {
		w.Header().Set("Content-Encoding", "gzip")
		w.Header().Set("Content-Length", strconv.Itoa(len(file.GzipAsset)))
		w.Write(file.GzipAsset)
	} else if !file.Compressed {
		w.Header().Set("Content-Length", strconv.Itoa(len(data)))
		w.Write(data)
	} else {
		gzr, err := gzip.NewReader(bytes.NewReader(file.GzipAsset))
		buf, err := ioutil.ReadAll(gzr)
		if err != nil {
			http.Error(w, "", http.StatusInternalServerError)
			return
		}

		w.Header().Set("Content-Length", strconv.Itoa(len(buf)))
		w.Write(buf)
	}
}
<|endoftext|>