<commit_before>package util

import (
	"strings"
	"strconv"
	"time"
	"unicode/utf8"
)

type Date struct{
	Month string
	Day string
	Time string
}

var months = [12]string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}

// Determines time layout for the following:
// YYYY-MM-DD, MM-DD-YYYY, MM-DD-YY (the punctuation is variable)
// Month and Day can be single digits
// The punctuation (-,/,_,etc) is determined by the second parameter
func parseTimeLayout(s []string, p string) string {
	if len(s) == 3 {
		if utf8.RuneCountInString(s[0]) == 4 {
			return "2006" + p + "1" + p + "_2"
		} else {
			if utf8.RuneCountInString(s[2]) == 4 {
				return "_2" + p + "1" + p + "2006"
			} else if utf8.RuneCountInString(s[2])== 2 {
				return "_2" + p + "1" + p + "06"
			}
		}
	}
	return ""
}

// Returns time layout for time.Parse
func returnTimeLayout(t string) string {
	splits := strings.Split(t, "/")
	layout := parseTimeLayout(splits, "/")
	if layout == "" {
		splits = strings.Split(t, "-")
		layout = parseTimeLayout(splits, "-")
	}
	if layout == "" {
		return "1-_2-2006"
	} else {
		return layout
	}
}

// Takes year/month/day (in a variety of formats) and HH:MM parameters
// Checks if the time is valid and returns error if it is not
// Parses in the location of Santiago
func ReturnTime(d string, t string) (time.Time, error) {
	loc, err := time.LoadLocation("America/Santiago")
	if err != nil {
		return time.Time{}, NewError(err, "Internal server error", 500)
	}

	layout := returnTimeLayout(d)
	splits := strings.Split(t, ":")
	if len(splits) == 1 {
		layout += " 15"
	} else {
		layout += " 15:04"
	}

	timeVar, err := time.ParseInLocation(layout, d + " " + t, loc)
	if err != nil {
		return time.Time{}, NewError(nil, "Invalid time format", 400)
	}

	return timeVar, nil
}

// Normalizes time format to one of two layouts (machine or human readable)
// Checks if the time is valid and returns error if it is not
// Parses in the location of Santiago
func ReturnTimeString(humanReadable bool, d string, t string) (string, string, error) {
	const (
		layoutHuman = "2/1/2006"
		layoutMachine = "2006-01-02"
	)
	loc, err := time.LoadLocation("America/Santiago")
	if err != nil {
		return "", "", NewError(err, "Internal server error", 500)
	}

	layout := returnTimeLayout(d)
	splits := strings.Split(t, ":")
	if len(splits) == 1 {
		layout += " 15"
	} else {
		layout += " 15:04"
	}
	timeVar, err := time.ParseInLocation(layout, d + " " + t, loc)
	if err != nil {
		return "", "", NewError(nil, "Invalid time format", 400)
	}
	if humanReadable {
		return timeVar.Format(layoutHuman), timeVar.Format("15:04"), nil
	} else {
		return timeVar.Format(layoutMachine), timeVar.Format("15:04"), nil
	}
}

func ReturnCurrentTimeString(rounded bool) (string, string) {
	if rounded {
		return time.Now().Local().Format("2006-01-02"), time.Now().Local().Format("15") + ":00"
	} else {
		return time.Now().Local().Format("2006-01-02"), time.Now().Local().Format("15:04")
	}
}

//FORM yyyy-mm-dd hh:mm:ss Drop the seconds. Parse the rest.
func PrettyDate(timestamp string, suffix bool) (Date, error) {
	var splits []string = strings.Split(timestamp, "")
	var date Date
	month := splits[5] + splits[6]
	m, err := strconv.ParseInt(month, 10, 8)
	if err != nil {
		return date, NewError(err, "Internal server error", 500)
	}
	date.Month = months[m-1]
	var day string
	if splits[8]=="0" {
		day = splits[9]
	} else {
		day = splits[8] + splits[9]
	}
	if suffix {
		if splits[9]=="1" {
			day+="st"
		} else if splits[9]=="2" {
			day+="nd"
		} else if splits[9]=="3" {
			day+="rd"
		} else{
			day+="th"
		}
	}
	date.Day = day
	date.Time = splits[11] + splits[12] + ":" + splits[14] + splits[15]
	return date, nil
}<commit_msg>-Create listing time leaving accepts HH00 now<commit_after>package util

import (
	"strings"
	"strconv"
	"time"
	"unicode/utf8"
)

type Date struct{
	Month string
	Day string
	Time string
}

var months = [12]string{"Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"}

// Determines time layout for the following:
// YYYY-MM-DD, MM-DD-YYYY, MM-DD-YY (the punctuation is variable)
// Month and Day can be single digits
// The punctuation (-,/,_,etc) is determined by the second parameter
func parseTimeLayout(s []string, p string) string {
	if len(s) == 3 {
		if utf8.RuneCountInString(s[0]) == 4 {
			return "2006" + p + "1" + p + "_2"
		} else {
			if utf8.RuneCountInString(s[2]) == 4 {
				return "_2" + p + "1" + p + "2006"
			} else if utf8.RuneCountInString(s[2])== 2 {
				return "_2" + p + "1" + p + "06"
			}
		}
	}
	return ""
}

// Returns time layout for time.Parse
func returnTimeLayout(t string) string {
	splits := strings.Split(t, "/")
	layout := parseTimeLayout(splits, "/")
	if layout == "" {
		splits = strings.Split(t, "-")
		layout = parseTimeLayout(splits, "-")
	}
	if layout == "" {
		return "1-_2-2006"
	} else {
		return layout
	}
}

// Takes year/month/day (in a variety of formats) and HH:MM parameters
// Checks if the time is valid and returns error if it is not
// Parses in the location of Santiago
func ReturnTime(d string, t string) (time.Time, error) {
	loc, err := time.LoadLocation("America/Santiago")
	if err != nil {
		return time.Time{}, NewError(err, "Internal server error", 500)
	}

	layout := returnTimeLayout(d)
	splits := strings.Split(t, ":")
	if len(splits) == 1 {
		if utf8.RuneCountInString(t) == 2 {
			layout += " 15"
		} else {
			layout += " 1500"
		}
	} else {
		layout += " 15:04"
	}

	timeVar, err := time.ParseInLocation(layout, d + " " + t, loc)
	if err != nil {
		return time.Time{}, NewError(nil, "Invalid time format", 400)
	}

	return timeVar, nil
}

// Normalizes time format to one of two layouts (machine or human readable)
// Checks if the time is valid and returns error if it is not
// Parses in the location of Santiago
func ReturnTimeString(humanReadable bool, d string, t string) (string, string, error) {
	const (
		layoutHuman = "2/1/2006"
		layoutMachine = "2006-01-02"
	)
	loc, err := time.LoadLocation("America/Santiago")
	if err != nil {
		return "", "", NewError(err, "Internal server error", 500)
	}

	layout := returnTimeLayout(d)
	splits := strings.Split(t, ":")
	if len(splits) == 1 {
		layout += " 15"
	} else {
		layout += " 15:04"
	}
	timeVar, err := time.ParseInLocation(layout, d + " " + t, loc)
	if err != nil {
		return "", "", NewError(nil, "Invalid time format", 400)
	}
	if humanReadable {
		return timeVar.Format(layoutHuman), timeVar.Format("15:04"), nil
	} else {
		return timeVar.Format(layoutMachine), timeVar.Format("15:04"), nil
	}
}

func ReturnCurrentTimeString(rounded bool) (string, string) {
	if rounded {
		return time.Now().Local().Format("2006-01-02"), time.Now().Local().Format("15") + ":00"
	} else {
		return time.Now().Local().Format("2006-01-02"), time.Now().Local().Format("15:04")
	}
}

//FORM yyyy-mm-dd hh:mm:ss Drop the seconds. Parse the rest.
func PrettyDate(timestamp string, suffix bool) (Date, error) {
	var splits []string = strings.Split(timestamp, "")
	var date Date
	month := splits[5] + splits[6]
	m, err := strconv.ParseInt(month, 10, 8)
	if err != nil {
		return date, NewError(err, "Internal server error", 500)
	}
	date.Month = months[m-1]
	var day string
	if splits[8]=="0" {
		day = splits[9]
	} else {
		day = splits[8] + splits[9]
	}
	if suffix {
		if splits[9]=="1" {
			day+="st"
		} else if splits[9]=="2" {
			day+="nd"
		} else if splits[9]=="3" {
			day+="rd"
		} else{
			day+="th"
		}
	}
	date.Day = day
	date.Time = splits[11] + splits[12] + ":" + splits[14] + splits[15]
	return date, nil
}<|endoftext|>


<commit_before>package model

import "github.com/af83/edwig/logger"

type StopAreaUpdateManager struct {
	ClockConsumer
	UUIDConsumer

	transactionProvider TransactionProvider
}

type StopVisitUpdater struct {
	ClockConsumer

	tx    *Transaction
	event *StopVisitUpdateEvent
}

func NewStopAreaUpdateManager(transactionProvider TransactionProvider) func(*StopAreaUpdateEvent) {
	manager := newStopAreaUpdateManager(transactionProvider)
	return manager.UpdateStopArea
}

func newStopAreaUpdateManager(transactionProvider TransactionProvider) *StopAreaUpdateManager {
	return &StopAreaUpdateManager{transactionProvider: transactionProvider}
}

func (manager *StopAreaUpdateManager) UpdateStopArea(event *StopAreaUpdateEvent) {
	if event.StopAreaMonitoredEvent != nil {
		manager.UpdateMonitoredStopArea(event)
		return
	}

	tx := manager.transactionProvider.NewTransaction()

	stopArea, found := tx.Model().StopAreas().Find(event.StopAreaId)
	if !found {
		logger.Log.Debugf("StopAreaUpdateEvent for unknown StopArea %v", event.StopAreaId)

		stopArea = tx.Model().StopAreas().New()
		parentSA, _ := tx.Model().StopAreas().FindByObjectId(event.StopAreaAttributes.ParentObjectId)

		stopArea.SetObjectID(event.StopAreaAttributes.ObjectId)
		stopArea.ParentId = parentSA.Id()
		stopArea.Name = event.StopAreaAttributes.Name
		stopArea.CollectedAlways = event.StopAreaAttributes.CollectedAlways
		stopArea.CollectGeneralMessages = true
		stopArea.Monitored = true
		stopArea.id = StopAreaId(manager.NewUUID())

		event.StopAreaId = stopArea.id
	}

	stopArea.Updated(manager.Clock().Now())
	tx.Model().StopAreas().Save(&stopArea)
	tx.Commit()
	tx.Close()

	if event.Origin != "" {
		status, ok := stopArea.Origins.Origin(event.Origin)
		if !status || !ok {
			manager.UpdateMonitoredStopArea(NewStopAreaMonitoredEvent(manager.NewUUID(), event.StopAreaId, event.Origin, true))
		}
	}

	for _, stopVisitUpdateEvent := range event.StopVisitUpdateEvents {
		manager.UpdateStopVisit(stopVisitUpdateEvent)
	}
	for _, stopVisitNotCollectedEvent := range event.StopVisitNotCollectedEvents {
		manager.UpdateNotCollectedStopVisit(stopVisitNotCollectedEvent)
	}
}

func (manager *StopAreaUpdateManager) UpdateMonitoredStopArea(event *StopAreaUpdateEvent) {
	// Should never happen, but don't want to ever have a go nil pointer exception
	if event.StopAreaMonitoredEvent == nil {
		return
	}

	tx := manager.transactionProvider.NewTransaction()

	for _, stopArea := range tx.Model().StopAreas().FindAscendants(event.StopAreaId) {
		stopArea.Origins.SetPartnerStatus(event.StopAreaMonitoredEvent.Partner, event.StopAreaMonitoredEvent.Status)
		stopArea.Monitored = stopArea.Origins.Monitored()
		tx.Model().StopAreas().Save(&stopArea)
	}

	tx.Commit()
	tx.Close()
}

func (manager *StopAreaUpdateManager) UpdateStopVisit(event *StopVisitUpdateEvent) {
	tx := manager.transactionProvider.NewTransaction()
	defer tx.Close()

	NewStopVisitUpdater(tx, event).Update()

	tx.Commit()
}

func (manager *StopAreaUpdateManager) UpdateNotCollectedStopVisit(event *StopVisitNotCollectedEvent) {
	tx := manager.transactionProvider.NewTransaction()
	defer tx.Close()

	stopVisit, found := tx.Model().StopVisits().FindByObjectId(event.StopVisitObjectId)
	if !found {
		logger.Log.Debugf("StopVisitNotCollectedEvent on unknown StopVisit: %#v", event)
		return
	}

	stopVisit.NotCollected()
	stopVisit.ArrivalStatus = STOP_VISIT_ARRIVAL_CANCELLED
	stopVisit.DepartureStatus = STOP_VISIT_DEPARTURE_DEPARTED

	stopVisit.Save()

	logger.Log.Debugf("StopVisit not Collected: %s (%v)", stopVisit.Id(), event.StopVisitObjectId)

	tx.Commit()
}

func NewStopVisitUpdater(tx *Transaction, event *StopVisitUpdateEvent) *StopVisitUpdater {
	return &StopVisitUpdater{tx: tx, event: event}
}

func (updater *StopVisitUpdater) Update() {
	stopArea, ok := updater.tx.Model().StopAreas().FindByObjectId(updater.event.StopAreaObjectId)
	if !ok { // Should never happen
		logger.Log.Debugf("can't find SA: %v", updater.event.StopAreaObjectId)
		return
	}

	// Find the Line
	line := updater.findOrCreateLine()

	// Update StopArea
	stopArea.LineIds.Add(line.Id())
	referent, ok := updater.tx.Model().StopAreas().Find(stopArea.ReferentId)
	if ok {
		referent.LineIds.Add(line.Id())
		referent.Save()
	}
	stopArea.Save()

	// Create or update VJ
	vehicleJourney, ok := updater.tx.Model().VehicleJourneys().FindByObjectId(NewObjectID(updater.event.StopVisitObjectid.Kind(), updater.event.DatedVehicleJourneyRef))
	if !ok {
		vehicleJourneyAttributes := updater.event.Attributes.VehicleJourneyAttributes()

		vehicleJourney = updater.tx.Model().VehicleJourneys().New()
		vehicleJourney.SetObjectID(vehicleJourneyAttributes.ObjectId)
		vehicleJourney.SetObjectID(NewObjectID("_default", vehicleJourneyAttributes.ObjectId.HashValue()))

		vehicleJourney.LineId = line.Id()
		vehicleJourney.Name = vehicleJourneyAttributes.Attributes["VehicleJourneyName"]

		vehicleJourney.Attributes = vehicleJourneyAttributes.Attributes
		vehicleJourney.References = vehicleJourneyAttributes.References
	} else {
		vehicleJourney.References.SetObjectId("OriginRef", NewObjectID(updater.event.StopVisitObjectid.Kind(), updater.event.OriginRef))
		vehicleJourney.References.SetObjectId("DestinationRef", NewObjectID(updater.event.StopVisitObjectid.Kind(), updater.event.DestinationRef))
	}

	vehicleJourney.Monitored = updater.event.Monitored
	vehicleJourney.OriginName = updater.event.OriginName
	vehicleJourney.DestinationName = updater.event.DestinationName
	vehicleJourney.Save()

	// Create or update SV
	stopVisit, ok := updater.tx.Model().StopVisits().FindByObjectId(updater.event.StopVisitObjectid)
	if !ok {
		stopVisitAttributes := updater.event.Attributes.StopVisitAttributes()

		stopVisit = updater.tx.Model().StopVisits().New()
		stopVisit.Origin = updater.event.Origin
		stopVisit.SetObjectID(stopVisitAttributes.ObjectId)
		stopVisit.SetObjectID(NewObjectID("_default", stopVisitAttributes.ObjectId.HashValue()))

		stopVisit.StopAreaId = stopArea.Id()
		stopVisit.VehicleJourneyId = vehicleJourney.Id()
		stopVisit.PassageOrder = stopVisitAttributes.PassageOrder

		stopVisit.Attributes = stopVisitAttributes.Attributes
		stopVisit.References = stopVisitAttributes.References
	}

	stopVisit.Schedules.Merge(&updater.event.Schedules)
	stopVisit.DepartureStatus = updater.event.DepartureStatus
	stopVisit.ArrivalStatus = updater.event.ArrivalStatus
	stopVisit.RecordedAt = updater.event.RecordedAt
	stopVisit.VehicleAtStop = updater.event.VehicleAtStop
	stopVisit.Collected(updater.Clock().Now())

	stopVisit.Save()
}

func (updater *StopVisitUpdater) findOrCreateLine() *Line {
	lineAttributes := updater.event.Attributes.LineAttributes()

	line, ok := updater.tx.Model().Lines().FindByObjectId(lineAttributes.ObjectId)
	if ok {
		return &line
	}

	// logger.Log.Debugf("Create new Line, objectid: %v", lineAttributes.ObjectId)
	line = updater.tx.Model().Lines().New()
	line.SetObjectID(lineAttributes.ObjectId)
	line.SetObjectID(NewObjectID("_default", lineAttributes.ObjectId.HashValue()))
	line.Name = lineAttributes.Name
	line.CollectGeneralMessages = true

	line.Save()

	return &line
}
<commit_msg>Now update StopArea ParentId in StopVisitUpdateManager even if StopArea already exists<commit_after>package model

import "github.com/af83/edwig/logger"

type StopAreaUpdateManager struct {
	ClockConsumer
	UUIDConsumer

	transactionProvider TransactionProvider
}

type StopVisitUpdater struct {
	ClockConsumer

	tx    *Transaction
	event *StopVisitUpdateEvent
}

func NewStopAreaUpdateManager(transactionProvider TransactionProvider) func(*StopAreaUpdateEvent) {
	manager := newStopAreaUpdateManager(transactionProvider)
	return manager.UpdateStopArea
}

func newStopAreaUpdateManager(transactionProvider TransactionProvider) *StopAreaUpdateManager {
	return &StopAreaUpdateManager{transactionProvider: transactionProvider}
}

func (manager *StopAreaUpdateManager) UpdateStopArea(event *StopAreaUpdateEvent) {
	if event.StopAreaMonitoredEvent != nil {
		manager.UpdateMonitoredStopArea(event)
		return
	}

	tx := manager.transactionProvider.NewTransaction()

	stopArea, found := tx.Model().StopAreas().Find(event.StopAreaId)
	if !found {
		logger.Log.Debugf("StopAreaUpdateEvent for unknown StopArea %v", event.StopAreaId)

		stopArea = tx.Model().StopAreas().New()

		stopArea.SetObjectID(event.StopAreaAttributes.ObjectId)
		stopArea.Name = event.StopAreaAttributes.Name
		stopArea.CollectedAlways = event.StopAreaAttributes.CollectedAlways
		stopArea.CollectGeneralMessages = true
		stopArea.Monitored = true
		stopArea.id = StopAreaId(manager.NewUUID())

		event.StopAreaId = stopArea.id
	}

	if stopArea.ParentId == "" && event.StopAreaAttributes.ParentObjectId.Value() != "" {
		parentSA, _ := tx.Model().StopAreas().FindByObjectId(event.StopAreaAttributes.ParentObjectId)
		stopArea.ParentId = parentSA.Id()
	}

	stopArea.Updated(manager.Clock().Now())
	tx.Model().StopAreas().Save(&stopArea)
	tx.Commit()
	tx.Close()

	if event.Origin != "" {
		status, ok := stopArea.Origins.Origin(event.Origin)
		if !status || !ok {
			manager.UpdateMonitoredStopArea(NewStopAreaMonitoredEvent(manager.NewUUID(), event.StopAreaId, event.Origin, true))
		}
	}

	for _, stopVisitUpdateEvent := range event.StopVisitUpdateEvents {
		manager.UpdateStopVisit(stopVisitUpdateEvent)
	}
	for _, stopVisitNotCollectedEvent := range event.StopVisitNotCollectedEvents {
		manager.UpdateNotCollectedStopVisit(stopVisitNotCollectedEvent)
	}
}

func (manager *StopAreaUpdateManager) UpdateMonitoredStopArea(event *StopAreaUpdateEvent) {
	// Should never happen, but don't want to ever have a go nil pointer exception
	if event.StopAreaMonitoredEvent == nil {
		return
	}

	tx := manager.transactionProvider.NewTransaction()

	for _, stopArea := range tx.Model().StopAreas().FindAscendants(event.StopAreaId) {
		stopArea.Origins.SetPartnerStatus(event.StopAreaMonitoredEvent.Partner, event.StopAreaMonitoredEvent.Status)
		stopArea.Monitored = stopArea.Origins.Monitored()
		tx.Model().StopAreas().Save(&stopArea)
	}

	tx.Commit()
	tx.Close()
}

func (manager *StopAreaUpdateManager) UpdateStopVisit(event *StopVisitUpdateEvent) {
	tx := manager.transactionProvider.NewTransaction()
	defer tx.Close()

	NewStopVisitUpdater(tx, event).Update()

	tx.Commit()
}

func (manager *StopAreaUpdateManager) UpdateNotCollectedStopVisit(event *StopVisitNotCollectedEvent) {
	tx := manager.transactionProvider.NewTransaction()
	defer tx.Close()

	stopVisit, found := tx.Model().StopVisits().FindByObjectId(event.StopVisitObjectId)
	if !found {
		logger.Log.Debugf("StopVisitNotCollectedEvent on unknown StopVisit: %#v", event)
		return
	}

	stopVisit.NotCollected()
	stopVisit.ArrivalStatus = STOP_VISIT_ARRIVAL_CANCELLED
	stopVisit.DepartureStatus = STOP_VISIT_DEPARTURE_DEPARTED

	stopVisit.Save()

	logger.Log.Debugf("StopVisit not Collected: %s (%v)", stopVisit.Id(), event.StopVisitObjectId)

	tx.Commit()
}

func NewStopVisitUpdater(tx *Transaction, event *StopVisitUpdateEvent) *StopVisitUpdater {
	return &StopVisitUpdater{tx: tx, event: event}
}

func (updater *StopVisitUpdater) Update() {
	stopArea, ok := updater.tx.Model().StopAreas().FindByObjectId(updater.event.StopAreaObjectId)
	if !ok { // Should never happen
		logger.Log.Debugf("can't find SA: %v", updater.event.StopAreaObjectId)
		return
	}

	// Find the Line
	line := updater.findOrCreateLine()

	// Update StopArea
	stopArea.LineIds.Add(line.Id())
	referent, ok := updater.tx.Model().StopAreas().Find(stopArea.ReferentId)
	if ok {
		referent.LineIds.Add(line.Id())
		referent.Save()
	}
	stopArea.Save()

	// Create or update VJ
	vehicleJourney, ok := updater.tx.Model().VehicleJourneys().FindByObjectId(NewObjectID(updater.event.StopVisitObjectid.Kind(), updater.event.DatedVehicleJourneyRef))
	if !ok {
		vehicleJourneyAttributes := updater.event.Attributes.VehicleJourneyAttributes()

		vehicleJourney = updater.tx.Model().VehicleJourneys().New()
		vehicleJourney.SetObjectID(vehicleJourneyAttributes.ObjectId)
		vehicleJourney.SetObjectID(NewObjectID("_default", vehicleJourneyAttributes.ObjectId.HashValue()))

		vehicleJourney.LineId = line.Id()
		vehicleJourney.Name = vehicleJourneyAttributes.Attributes["VehicleJourneyName"]

		vehicleJourney.Attributes = vehicleJourneyAttributes.Attributes
		vehicleJourney.References = vehicleJourneyAttributes.References
	} else {
		vehicleJourney.References.SetObjectId("OriginRef", NewObjectID(updater.event.StopVisitObjectid.Kind(), updater.event.OriginRef))
		vehicleJourney.References.SetObjectId("DestinationRef", NewObjectID(updater.event.StopVisitObjectid.Kind(), updater.event.DestinationRef))
	}

	vehicleJourney.Monitored = updater.event.Monitored
	vehicleJourney.OriginName = updater.event.OriginName
	vehicleJourney.DestinationName = updater.event.DestinationName
	vehicleJourney.Save()

	// Create or update SV
	stopVisit, ok := updater.tx.Model().StopVisits().FindByObjectId(updater.event.StopVisitObjectid)
	if !ok {
		stopVisitAttributes := updater.event.Attributes.StopVisitAttributes()

		stopVisit = updater.tx.Model().StopVisits().New()
		stopVisit.Origin = updater.event.Origin
		stopVisit.SetObjectID(stopVisitAttributes.ObjectId)
		stopVisit.SetObjectID(NewObjectID("_default", stopVisitAttributes.ObjectId.HashValue()))

		stopVisit.StopAreaId = stopArea.Id()
		stopVisit.VehicleJourneyId = vehicleJourney.Id()
		stopVisit.PassageOrder = stopVisitAttributes.PassageOrder

		stopVisit.Attributes = stopVisitAttributes.Attributes
		stopVisit.References = stopVisitAttributes.References
	}

	stopVisit.Schedules.Merge(&updater.event.Schedules)
	stopVisit.DepartureStatus = updater.event.DepartureStatus
	stopVisit.ArrivalStatus = updater.event.ArrivalStatus
	stopVisit.RecordedAt = updater.event.RecordedAt
	stopVisit.VehicleAtStop = updater.event.VehicleAtStop
	stopVisit.Collected(updater.Clock().Now())

	stopVisit.Save()
}

func (updater *StopVisitUpdater) findOrCreateLine() *Line {
	lineAttributes := updater.event.Attributes.LineAttributes()

	line, ok := updater.tx.Model().Lines().FindByObjectId(lineAttributes.ObjectId)
	if ok {
		return &line
	}

	// logger.Log.Debugf("Create new Line, objectid: %v", lineAttributes.ObjectId)
	line = updater.tx.Model().Lines().New()
	line.SetObjectID(lineAttributes.ObjectId)
	line.SetObjectID(NewObjectID("_default", lineAttributes.ObjectId.HashValue()))
	line.Name = lineAttributes.Name
	line.CollectGeneralMessages = true

	line.Save()

	return &line
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/csv"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"reflect"
	"strconv"
	"time"

	"github.com/julienschmidt/httprouter"
)

const timeFormat = "2006-01-02"

type rateSlice []rate

func (rs rateSlice) Values() string {
	str := "["
	for i, r := range rs {
		str += "["
		str += strconv.FormatInt(r.Date.Unix()*1000, 10) // in milli seconds
		str += ","
		str += strconv.FormatFloat(r.Value, 'g', 4, 64)
		str += "]"
		if i != len(rs)-1 {
			str += ","
		}
	}
	str += "]"

	return str
}

type rate struct {
	Date  time.Time `json:"date"`
	Value float64   `json:"value"`
}

// MarshalJSON overrides marshal, prints shorter date string
func (r *rate) MarshalJSON() ([]byte, error) {
	type alias rate
	return json.Marshal(&struct {
		Date string `json:"date"`
		*alias
	}{
		Date:  r.Date.Format("2006-01-02"),
		alias: (*alias)(r),
	})
}

var lastRefresh time.Time
var maturities = []string{"1w", "2w", "1m", "2m", "3m", "6m", "9m", "12m"}
var retentions = []string{"week", "month", "three_months", "six_months", "year", "two_years", "six_years"}
var historyPath string
var historyCache map[string][]rate
var influxCache map[string]map[string][]rate

// runs in go routine and takes care of refreshing the cache
func refreshCache() {
	historyCache = make(map[string][]rate)
	influxCache = make(map[string]map[string][]rate)

	for {
		if time.Since(lastRefresh) < time.Hour*24 {
			time.Sleep(time.Minute)
			continue
		}

		// refresh
		log.Println("refreshing history cache")
		for _, maturity := range maturities {
			// TODO: make path to files configureable
			file := fmt.Sprintf("%s/euribor-rates-%s.csv", historyPath, maturity)
			historyCache[maturity] = parseFile(file)
		}
		log.Println("refreshing influx cache")
		for _, retention := range retentions {
			results := queryInflux(retention)
			cache := make(map[string][]rate)
			// fmt.Println(results.Values)
			// fmt.Println(reflect.TypeOf(results.Values).Kind())
			for _, value := range results.Values {
				// fmt.Println(value, reflect.TypeOf(value).Kind(), reflect.TypeOf(value))
				m, r, err := transformInfluxValueToRate(reflect.ValueOf(value).Interface().([]interface{}))
				if err != nil {
					log.Println("error converting influx value to rate")
					continue
				}
				if rates, ok := cache[m]; ok {
					rates = append(rates, r)
					cache[m] = rates
				} else {
					cache[m] = []rate{r}
				}
			}
			influxCache[retention] = cache
		}
		log.Println("cache refresh completed")

		lastRefresh = time.Now()
	}
}

func parseFile(file string) []rate {
	f, err := os.Open(file)
	if err != nil {
		log.Println(err)
		return []rate{}
	}

	r := csv.NewReader(f)
	r.Comment = '#'
	r.FieldsPerRecord = 2

	records, err := r.ReadAll()
	if err != nil {
		log.Println(err)
		return []rate{}
	}

	rates := []rate{}
	for _, record := range records {
		date, err := time.ParseInLocation(timeFormat, record[0], time.UTC)
		if err != nil {
			log.Println(err)
			continue
		}
		value, err := strconv.ParseFloat(record[1], 64)
		if err != nil {
			log.Println(err)
			continue
		}
		rates = append(rates, rate{date, value})
	}

	return rates
}

func isValidRetention(r string) bool {
	// TODO: fix some kind of mapping
	// last-week
	// last-month
	// last-quater
	// last-six-months
	// last-year
	// last-two-years
	// last-six-years

	return false
}

func isValidMaturity(m string) bool {
	switch m {
	case "1w":
		return true
	case "2w":
		return true
	case "1m":
		return true
	case "2m":
		return true
	case "3m":
		return true
	case "6m":
		return true
	case "9m":
		return true
	case "12m":
		return true
	}
	return false
}

// index handler
func index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	fmt.Fprint(w, "Welcome to the Euribor rates service!\n")
}

// influx handler
func influx(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	retention := params.ByName("retention")
	if isValidRetention(retention) == false {
		//TODO: return http error code
		fmt.Fprintf(w, errorMsg("uknown retention"))
		return
	}

	maturity := params.ByName("maturity")
	if isValidMaturity(maturity) == false {
		// TODO: return http error code
		fmt.Fprintf(w, errorMsg("uknown maturity"))
		return
	}

	rates := []rate{}
	for _, r := range influxCache[retention][maturity] {
		rates = append(rates, r)
	}

	jsonData, err := json.Marshal(rates)
	if err != nil {
		fmt.Fprintf(w, errorMsg(err.Error()))
		return
	}

	fmt.Fprintf(w, string(jsonData))
}

// highstock handler
func highstock(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	maturity := params.ByName("maturity")
	if isValidMaturity(maturity) == false {
		// TODO: return http error code
		fmt.Fprintf(w, errorMsg("uknown maturity"))
		return
	}

	rates := rateSlice{}
	for _, r := range historyCache[maturity] {
		rates = append(rates, r)
	}

	fmt.Fprintf(w, rates.Values())
}

// history handler
func history(w http.ResponseWriter, r *http.Request, params httprouter.Params) {

	year, err := strconv.ParseInt(params.ByName("year"), 10, 32)
	if err != nil {
		// TODO: return http error code
		fmt.Fprint(w, errorMsg(err.Error()))
		return
	}
	if year < 2010 || year > int64(time.Now().Year()) {
		fmt.Fprintf(w, errorMsg("no data"))
		return
	}

	maturity := params.ByName("maturity")
	if isValidMaturity(maturity) == false {
		// TODO: return http error code
		fmt.Fprintf(w, errorMsg("uknown maturity"))
		return
	}

	rates := []rate{}
	for _, r := range historyCache[maturity] {
		if int64(r.Date.Year()) != year {
			continue
		}
		rates = append(rates, r)
	}

	jsonData, err := json.Marshal(rates)
	if err != nil {
		fmt.Fprintf(w, errorMsg(err.Error()))
		return
	}

	fmt.Fprintf(w, string(jsonData))
}

// webapp handler
func webapp(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	url := baseURL(r)

	fmt.Fprintf(w, renderWebapp(url))
}

func errorMsg(msg string) string {
	return fmt.Sprintf("{\"error\":\"%s\"}", msg)
}

func main() {
	var host string
	var port string
	flag.StringVar(&host, "host", "localhost", "host to bind to")
	flag.StringVar(&port, "port", "8080", "port to bind to")
	flag.StringVar(&historyPath, "history-path", ".", "path to history rate CSV files")
	flag.Parse()

	go refreshCache()

	router := httprouter.New()

	// routes for general info
	// TODO: add routes for list of supported retentions/maturities
	router.GET("/", index)

	// routes to serve the app
	router.GET("/rates/app/if/:retention/:maturity", influx)
	router.GET("/rates/app/hs/:maturity", highstock)

	// routes to serve historical queries
	router.GET("/rates/history/:year/:maturity", history)

	// routes to serve the webapp
	router.GET("/webapp", webapp)

	log.Printf("listening on %s:%s", host, port)
	log.Fatal(http.ListenAndServe(host+":"+port, router))
}
<commit_msg>Log requests<commit_after>package main

import (
	"encoding/csv"
	"encoding/json"
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"reflect"
	"strconv"
	"time"

	"github.com/julienschmidt/httprouter"
)

const timeFormat = "2006-01-02"

type rateSlice []rate

func (rs rateSlice) Values() string {
	str := "["
	for i, r := range rs {
		str += "["
		str += strconv.FormatInt(r.Date.Unix()*1000, 10) // in milli seconds
		str += ","
		str += strconv.FormatFloat(r.Value, 'g', 4, 64)
		str += "]"
		if i != len(rs)-1 {
			str += ","
		}
	}
	str += "]"

	return str
}

type rate struct {
	Date  time.Time `json:"date"`
	Value float64   `json:"value"`
}

// MarshalJSON overrides marshal, prints shorter date string
func (r *rate) MarshalJSON() ([]byte, error) {
	type alias rate
	return json.Marshal(&struct {
		Date string `json:"date"`
		*alias
	}{
		Date:  r.Date.Format("2006-01-02"),
		alias: (*alias)(r),
	})
}

var lastRefresh time.Time
var maturities = []string{"1w", "2w", "1m", "2m", "3m", "6m", "9m", "12m"}
var retentions = []string{"week", "month", "three_months", "six_months", "year", "two_years", "six_years"}
var historyPath string
var historyCache map[string][]rate
var influxCache map[string]map[string][]rate

// runs in go routine and takes care of refreshing the cache
func refreshCache() {
	historyCache = make(map[string][]rate)
	influxCache = make(map[string]map[string][]rate)

	for {
		if time.Since(lastRefresh) < time.Hour*24 {
			time.Sleep(time.Minute)
			continue
		}

		// refresh
		log.Println("refreshing history cache")
		for _, maturity := range maturities {
			// TODO: make path to files configureable
			file := fmt.Sprintf("%s/euribor-rates-%s.csv", historyPath, maturity)
			historyCache[maturity] = parseFile(file)
		}
		log.Println("refreshing influx cache")
		for _, retention := range retentions {
			results := queryInflux(retention)
			cache := make(map[string][]rate)
			// fmt.Println(results.Values)
			// fmt.Println(reflect.TypeOf(results.Values).Kind())
			for _, value := range results.Values {
				// fmt.Println(value, reflect.TypeOf(value).Kind(), reflect.TypeOf(value))
				m, r, err := transformInfluxValueToRate(reflect.ValueOf(value).Interface().([]interface{}))
				if err != nil {
					log.Println("error converting influx value to rate")
					continue
				}
				if rates, ok := cache[m]; ok {
					rates = append(rates, r)
					cache[m] = rates
				} else {
					cache[m] = []rate{r}
				}
			}
			influxCache[retention] = cache
		}
		log.Println("cache refresh completed")

		lastRefresh = time.Now()
	}
}

func parseFile(file string) []rate {
	f, err := os.Open(file)
	if err != nil {
		log.Println(err)
		return []rate{}
	}

	r := csv.NewReader(f)
	r.Comment = '#'
	r.FieldsPerRecord = 2

	records, err := r.ReadAll()
	if err != nil {
		log.Println(err)
		return []rate{}
	}

	rates := []rate{}
	for _, record := range records {
		date, err := time.ParseInLocation(timeFormat, record[0], time.UTC)
		if err != nil {
			log.Println(err)
			continue
		}
		value, err := strconv.ParseFloat(record[1], 64)
		if err != nil {
			log.Println(err)
			continue
		}
		rates = append(rates, rate{date, value})
	}

	return rates
}

func isValidRetention(r string) bool {
	// TODO: fix some kind of mapping
	// last-week
	// last-month
	// last-quater
	// last-six-months
	// last-year
	// last-two-years
	// last-six-years

	return false
}

func isValidMaturity(m string) bool {
	switch m {
	case "1w":
		return true
	case "2w":
		return true
	case "1m":
		return true
	case "2m":
		return true
	case "3m":
		return true
	case "6m":
		return true
	case "9m":
		return true
	case "12m":
		return true
	}
	return false
}

// index handler
func index(w http.ResponseWriter, r *http.Request, _ httprouter.Params) {
	log.Printf("%v /", r.RemoteAddr)
	fmt.Fprint(w, "Welcome to the Euribor rates service!\n")
}

// influx handler
func influx(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	log.Printf("%v /rates/app/%s/%s", r.RemoteAddr, params.ByName("retention"), params.ByName("maturity"))
	retention := params.ByName("retention")
	if isValidRetention(retention) == false {
		//TODO: return http error code
		fmt.Fprintf(w, errorMsg("uknown retention"))
		return
	}

	maturity := params.ByName("maturity")
	if isValidMaturity(maturity) == false {
		// TODO: return http error code
		fmt.Fprintf(w, errorMsg("uknown maturity"))
		return
	}

	rates := []rate{}
	for _, r := range influxCache[retention][maturity] {
		rates = append(rates, r)
	}

	jsonData, err := json.Marshal(rates)
	if err != nil {
		fmt.Fprintf(w, errorMsg(err.Error()))
		return
	}

	fmt.Fprintf(w, string(jsonData))
}

// highstock handler
func highstock(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	log.Printf("%v /rates/app/hs/%s", r.RemoteAddr, params.ByName("maturity"))
	maturity := params.ByName("maturity")
	if isValidMaturity(maturity) == false {
		// TODO: return http error code
		fmt.Fprintf(w, errorMsg("uknown maturity"))
		return
	}

	rates := rateSlice{}
	for _, r := range historyCache[maturity] {
		rates = append(rates, r)
	}

	fmt.Fprintf(w, rates.Values())
}

// history handler
func history(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	log.Printf("%v /rates/history/%s/%s", r.RemoteAddr, params.ByName("year"), params.ByName("maturity"))
	year, err := strconv.ParseInt(params.ByName("year"), 10, 32)
	if err != nil {
		// TODO: return http error code
		fmt.Fprint(w, errorMsg(err.Error()))
		return
	}
	if year < 2010 || year > int64(time.Now().Year()) {
		fmt.Fprintf(w, errorMsg("no data"))
		return
	}

	maturity := params.ByName("maturity")
	if isValidMaturity(maturity) == false {
		// TODO: return http error code
		fmt.Fprintf(w, errorMsg("uknown maturity"))
		return
	}

	rates := []rate{}
	for _, r := range historyCache[maturity] {
		if int64(r.Date.Year()) != year {
			continue
		}
		rates = append(rates, r)
	}

	jsonData, err := json.Marshal(rates)
	if err != nil {
		fmt.Fprintf(w, errorMsg(err.Error()))
		return
	}

	fmt.Fprintf(w, string(jsonData))
}

// webapp handler
func webapp(w http.ResponseWriter, r *http.Request, params httprouter.Params) {
	log.Printf("%v /webapp", r.RemoteAddr)
	url := baseURL(r)

	fmt.Fprintf(w, renderWebapp(url))
}

func errorMsg(msg string) string {
	return fmt.Sprintf("{\"error\":\"%s\"}", msg)
}

func main() {
	var host string
	var port string
	flag.StringVar(&host, "host", "localhost", "host to bind to")
	flag.StringVar(&port, "port", "8080", "port to bind to")
	flag.StringVar(&historyPath, "history-path", ".", "path to history rate CSV files")
	flag.Parse()

	go refreshCache()

	router := httprouter.New()

	// routes for general info
	// TODO: add routes for list of supported retentions/maturities
	router.GET("/", index)

	// routes to serve the app
	router.GET("/rates/app/if/:retention/:maturity", influx)
	router.GET("/rates/app/hs/:maturity", highstock)

	// routes to serve historical queries
	router.GET("/rates/history/:year/:maturity", history)

	// routes to serve the webapp
	router.GET("/webapp", webapp)

	log.Printf("listening on %s:%s", host, port)
	log.Fatal(http.ListenAndServe(host+":"+port, router))
}
<|endoftext|>


<commit_before>// Copyright 2011 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build !appengine

package main

import (
	"flag"
	"fmt"
	"go/build"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"code.google.com/p/go.talks/pkg/socket"

	// Imports so that go build/install automatically installs them.
	_ "code.google.com/p/go-tour/pic"
	_ "code.google.com/p/go-tour/tree"
	_ "code.google.com/p/go-tour/wc"
)

const (
	basePkg    = "code.google.com/p/go-tour/"
	socketPath = "/socket"
)

var (
	httpListen  = flag.String("http", "127.0.0.1:3999", "host:port to listen on")
	htmlOutput  = flag.Bool("html", false, "render program output as HTML")
	openBrowser = flag.Bool("openbrowser", true, "open browser automatically")
)

var (
	// GOPATH containing the tour packages
	gopath = os.Getenv("GOPATH")

	httpAddr string
)

func isRoot(path string) bool {
	_, err := os.Stat(filepath.Join(path, "tour.article"))
	return err == nil
}

func findRoot() (string, error) {
	ctx := build.Default
	p, err := ctx.Import(basePkg, "", build.FindOnly)
	if err == nil && isRoot(p.Dir) {
		return p.Dir, nil
	}
	tourRoot := filepath.Join(runtime.GOROOT(), "misc", "tour")
	ctx.GOPATH = tourRoot
	p, err = ctx.Import(basePkg, "", build.FindOnly)
	if err == nil && isRoot(tourRoot) {
		gopath = tourRoot
		return tourRoot, nil
	}
	return "", fmt.Errorf("could not find go-tour content; check $GOROOT and $GOPATH")
}

func main() {
	flag.Parse()

	// find and serve the go tour files
	root, err := findRoot()
	if err != nil {
		log.Fatalf("Couldn't find tour files: %v", err)
	}

	log.Println("Serving content from", root)

	host, port, err := net.SplitHostPort(*httpListen)
	if err != nil {
		log.Fatal(err)
	}
	if host == "" {
		host = "localhost"
	}
	if host != "127.0.0.1" && host != "localhost" {
		log.Print(localhostWarning)
	}
	httpAddr = host + ":" + port

	if err := initTour(root); err != nil {
		log.Fatal(err)
	}

	fs := http.FileServer(http.Dir(root))
	http.Handle("/favicon.ico", fs)
	http.Handle("/static/", fs)
	http.Handle("/talks/", fs)

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/" {
			if err := renderTour(w); err != nil {
				log.Println(err)
			}
			return
		}
		http.Error(w, "not found", 404)
	})

	http.Handle(socketPath, socket.Handler)

	err = serveScripts(filepath.Join(root, "js"), "SocketTransport")
	if err != nil {
		log.Fatal(err)
	}

	go func() {
		url := "http://" + httpAddr
		if waitServer(url) && *openBrowser && startBrowser(url) {
			log.Printf("A browser window should open. If not, please visit %s", url)
		} else {
			log.Printf("Please open your web browser and visit %s", url)
		}
	}()
	log.Fatal(http.ListenAndServe(httpAddr, nil))
}

const localhostWarning = `
WARNING!  WARNING!  WARNING!

I appear to be listening on an address that is not localhost.
Anyone with access to this address and port will have access
to this machine as the user running gotour.

If you don't understand this message, hit Control-C to terminate this process.

WARNING!  WARNING!  WARNING!
`

type response struct {
	Output string `json:"output"`
	Errors string `json:"compile_errors"`
}

func init() {
	socket.Environ = environ
}

// environ returns the original execution environment with GOPATH
// replaced (or added) with the value of the global var gopath.
func environ() (env []string) {
	for _, v := range os.Environ() {
		if !strings.HasPrefix(v, "GOPATH=") {
			env = append(env, v)
		}
	}
	env = append(env, "GOPATH="+gopath)
	return
}

// waitServer waits some time for the http Server to start
// serving url and returns whether it starts
func waitServer(url string) bool {
	tries := 20
	for tries > 0 {
		resp, err := http.Get(url)
		if err == nil {
			resp.Body.Close()
			return true
		}
		time.Sleep(100 * time.Millisecond)
		tries--
	}
	return false
}

// startBrowser tries to open the URL in a browser, and returns
// whether it succeed.
func startBrowser(url string) bool {
	// try to start the browser
	var args []string
	switch runtime.GOOS {
	case "darwin":
		args = []string{"open"}
	case "windows":
		args = []string{"cmd", "/c", "start"}
	default:
		args = []string{"xdg-open"}
	}
	cmd := exec.Command(args[0], append(args[1:], url)...)
	return cmd.Start() == nil
}

// prepContent for the local tour simply returns the content as-is.
func prepContent(r io.Reader) io.Reader { return r }

// socketAddr returns the WebSocket handler address.
func socketAddr() string { return "ws://" + httpAddr + socketPath }
<commit_msg>go-tour: remove unused flag<commit_after>// Copyright 2011 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// +build !appengine

package main

import (
	"flag"
	"fmt"
	"go/build"
	"io"
	"log"
	"net"
	"net/http"
	"os"
	"os/exec"
	"path/filepath"
	"runtime"
	"strings"
	"time"

	"code.google.com/p/go.talks/pkg/socket"

	// Imports so that go build/install automatically installs them.
	_ "code.google.com/p/go-tour/pic"
	_ "code.google.com/p/go-tour/tree"
	_ "code.google.com/p/go-tour/wc"
)

const (
	basePkg    = "code.google.com/p/go-tour/"
	socketPath = "/socket"
)

var (
	httpListen  = flag.String("http", "127.0.0.1:3999", "host:port to listen on")
	openBrowser = flag.Bool("openbrowser", true, "open browser automatically")
)

var (
	// GOPATH containing the tour packages
	gopath = os.Getenv("GOPATH")

	httpAddr string
)

func isRoot(path string) bool {
	_, err := os.Stat(filepath.Join(path, "tour.article"))
	return err == nil
}

func findRoot() (string, error) {
	ctx := build.Default
	p, err := ctx.Import(basePkg, "", build.FindOnly)
	if err == nil && isRoot(p.Dir) {
		return p.Dir, nil
	}
	tourRoot := filepath.Join(runtime.GOROOT(), "misc", "tour")
	ctx.GOPATH = tourRoot
	p, err = ctx.Import(basePkg, "", build.FindOnly)
	if err == nil && isRoot(tourRoot) {
		gopath = tourRoot
		return tourRoot, nil
	}
	return "", fmt.Errorf("could not find go-tour content; check $GOROOT and $GOPATH")
}

func main() {
	flag.Parse()

	// find and serve the go tour files
	root, err := findRoot()
	if err != nil {
		log.Fatalf("Couldn't find tour files: %v", err)
	}

	log.Println("Serving content from", root)

	host, port, err := net.SplitHostPort(*httpListen)
	if err != nil {
		log.Fatal(err)
	}
	if host == "" {
		host = "localhost"
	}
	if host != "127.0.0.1" && host != "localhost" {
		log.Print(localhostWarning)
	}
	httpAddr = host + ":" + port

	if err := initTour(root); err != nil {
		log.Fatal(err)
	}

	fs := http.FileServer(http.Dir(root))
	http.Handle("/favicon.ico", fs)
	http.Handle("/static/", fs)
	http.Handle("/talks/", fs)

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		if r.URL.Path == "/" {
			if err := renderTour(w); err != nil {
				log.Println(err)
			}
			return
		}
		http.Error(w, "not found", 404)
	})

	http.Handle(socketPath, socket.Handler)

	err = serveScripts(filepath.Join(root, "js"), "SocketTransport")
	if err != nil {
		log.Fatal(err)
	}

	go func() {
		url := "http://" + httpAddr
		if waitServer(url) && *openBrowser && startBrowser(url) {
			log.Printf("A browser window should open. If not, please visit %s", url)
		} else {
			log.Printf("Please open your web browser and visit %s", url)
		}
	}()
	log.Fatal(http.ListenAndServe(httpAddr, nil))
}

const localhostWarning = `
WARNING!  WARNING!  WARNING!

I appear to be listening on an address that is not localhost.
Anyone with access to this address and port will have access
to this machine as the user running gotour.

If you don't understand this message, hit Control-C to terminate this process.

WARNING!  WARNING!  WARNING!
`

type response struct {
	Output string `json:"output"`
	Errors string `json:"compile_errors"`
}

func init() {
	socket.Environ = environ
}

// environ returns the original execution environment with GOPATH
// replaced (or added) with the value of the global var gopath.
func environ() (env []string) {
	for _, v := range os.Environ() {
		if !strings.HasPrefix(v, "GOPATH=") {
			env = append(env, v)
		}
	}
	env = append(env, "GOPATH="+gopath)
	return
}

// waitServer waits some time for the http Server to start
// serving url and returns whether it starts
func waitServer(url string) bool {
	tries := 20
	for tries > 0 {
		resp, err := http.Get(url)
		if err == nil {
			resp.Body.Close()
			return true
		}
		time.Sleep(100 * time.Millisecond)
		tries--
	}
	return false
}

// startBrowser tries to open the URL in a browser, and returns
// whether it succeed.
func startBrowser(url string) bool {
	// try to start the browser
	var args []string
	switch runtime.GOOS {
	case "darwin":
		args = []string{"open"}
	case "windows":
		args = []string{"cmd", "/c", "start"}
	default:
		args = []string{"xdg-open"}
	}
	cmd := exec.Command(args[0], append(args[1:], url)...)
	return cmd.Start() == nil
}

// prepContent for the local tour simply returns the content as-is.
func prepContent(r io.Reader) io.Reader { return r }

// socketAddr returns the WebSocket handler address.
func socketAddr() string { return "ws://" + httpAddr + socketPath }
<|endoftext|>


<commit_before>package gottyclient

import (
	"crypto/tls"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strings"
	"sync"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/creack/goselect"
	"github.com/gorilla/websocket"
	"golang.org/x/crypto/ssh/terminal"
)

// GetAuthTokenURL transforms a GoTTY http URL to its AuthToken file URL
func GetAuthTokenURL(httpURL string) (*url.URL, *http.Header, error) {
	header := http.Header{}
	target, err := url.Parse(httpURL)
	if err != nil {
		return nil, nil, err
	}

	target.Path = strings.TrimLeft(target.Path+"auth_token.js", "/")

	if target.User != nil {
		header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(target.User.String())))
		target.User = nil
	}

	return target, &header, nil
}

// GetURLQuery returns url.query
func GetURLQuery(rawurl string) (url.Values, error) {
	target, err := url.Parse(rawurl)
	if err != nil {
		return nil, err
	}
	return target.Query(), nil
}

// GetWebsocketURL transforms a GoTTY http URL to its WebSocket URL
func GetWebsocketURL(httpURL string) (*url.URL, *http.Header, error) {
	header := http.Header{}
	target, err := url.Parse(httpURL)
	if err != nil {
		return nil, nil, err
	}

	if target.Scheme == "https" {
		target.Scheme = "wss"
	} else {
		target.Scheme = "ws"
	}

	target.Path = strings.TrimLeft(target.Path+"ws", "/")

	if target.User != nil {
		header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(target.User.String())))
		target.User = nil
	}

	return target, &header, nil
}

type Client struct {
	Dialer          *websocket.Dialer
	Conn            *websocket.Conn
	URL             string
	WriteMutex      *sync.Mutex
	Output          io.Writer
	QuitChan        chan struct{}
	QuitChanClosed  bool
	SkipTLSVerify   bool
	UseProxyFromEnv bool
	Connected       bool
}

type querySingleType struct {
	AuthToken string `json:"AuthToken"`
	Arguments string `json:"Arguments"`
}

func (c *Client) write(data []byte) error {
	c.WriteMutex.Lock()
	defer c.WriteMutex.Unlock()
	return c.Conn.WriteMessage(websocket.TextMessage, data)
}

// GetAuthToken retrieves an Auth Token from dynamic auth_token.js file
func (c *Client) GetAuthToken() (string, error) {
	target, header, err := GetAuthTokenURL(c.URL)
	if err != nil {
		return "", err
	}

	logrus.Debugf("Fetching auth token auth-token: %q", target.String())
	req, err := http.NewRequest("GET", target.String(), nil)
	req.Header = *header
	tr := &http.Transport{}
	if c.SkipTLSVerify {
		conf := &tls.Config{InsecureSkipVerify: true}
		tr.TLSClientConfig = conf
	}
	if c.UseProxyFromEnv {
		tr.Proxy = http.ProxyFromEnvironment
	}
	client := &http.Client{Transport: tr}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}

	switch resp.StatusCode {
	case 200:
		// Everything is OK
	default:
		return "", fmt.Errorf("unknown status code: %d (%s)", resp.StatusCode, http.StatusText(resp.StatusCode))
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	re := regexp.MustCompile("var gotty_auth_token = '(.*)'")
	output := re.FindStringSubmatch(string(body))
	if len(output) == 0 {
		return "", fmt.Errorf("Cannot fetch GoTTY auth-token, please upgrade your GoTTY server.")
	}

	return output[1], nil
}

// Connect tries to dial a websocket server
func (c *Client) Connect() error {
	// Retrieve AuthToken
	authToken, err := c.GetAuthToken()
	if err != nil {
		return err
	}
	logrus.Debugf("Auth-token: %q", authToken)

	// Open WebSocket connection
	target, header, err := GetWebsocketURL(c.URL)
	if err != nil {
		return err
	}
	logrus.Debugf("Connecting to websocket: %q", target.String())
	if c.SkipTLSVerify {
		c.Dialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
	}
	if c.UseProxyFromEnv {
		c.Dialer.Proxy = http.ProxyFromEnvironment
	}
	conn, _, err := c.Dialer.Dial(target.String(), *header)
	if err != nil {
		return err
	}
	c.Conn = conn
	c.Connected = true

	// Pass arguments and auth-token
	query, err := GetURLQuery(c.URL)
	if err != nil {
		return err
	}
	querySingle := querySingleType{
		Arguments: "?" + query.Encode(),
		AuthToken: authToken,
	}
	json, err := json.Marshal(querySingle)
	if err != nil {
		logrus.Errorf("Failed to parse init message %v", err)
		return err
	}
	// Send Json
	logrus.Debugf("Sending arguments and auth-token")
	err = c.write(json)
	if err != nil {
		return err
	}

	go c.pingLoop()

	return nil
}

func (c *Client) pingLoop() {
	for {
		logrus.Debugf("Sending ping")
		c.write([]byte("1"))
		time.Sleep(30 * time.Second)
	}
}

// Close will nicely close the dialer
func (c *Client) Close() {
	c.Conn.Close()
}

// ExitLoop will kill all goroutine
// ExitLoop() -> wait Loop() -> Close()
func (c *Client) ExitLoop() {
	if !c.QuitChanClosed {
		close(c.QuitChan)
		c.QuitChanClosed = true
	}
}

// Loop will look indefinitely for new messages
func (c *Client) Loop() error {
	if !c.Connected {
		err := c.Connect()
		if err != nil {
			return err
		}
	}

	var wg sync.WaitGroup
	done := make(chan bool)

	wg.Add(1)
	go c.termsizeLoop(&wg)
	wg.Add(1)
	go c.readLoop(done, &wg)
	wg.Add(1)
	go c.writeLoop(done, &wg)
	select {
	case <-done:
		close(c.QuitChan)
		c.QuitChanClosed = true
	case <-c.QuitChan:
	}
	wg.Wait()
	return nil
}

type winsize struct {
	Rows    uint16 `json:"rows"`
	Columns uint16 `json:"columns"`
	// unused
	x uint16
	y uint16
}

func (c *Client) termsizeLoop(wg *sync.WaitGroup) {
	defer wg.Done()
	ch := make(chan os.Signal, 1)
	notifySignalSIGWINCH(ch)
	defer resetSignalSIGWINCH()

	for {
		if b, err := syscallTIOCGWINSZ(); err != nil {
			logrus.Warn(err)
		} else {
			if err = c.write(append([]byte("2"), b...)); err != nil {
				logrus.Warnf("ws.WriteMessage failed: %v", err)
			}
		}
		select {
		case <-c.QuitChan:
			return
		case <-ch:
		}
	}
}

type exposeFd interface {
	Fd() uintptr
}

func (c *Client) writeLoop(done chan bool, wg *sync.WaitGroup) {
	defer wg.Done()

	buff := make([]byte, 128)
	oldState, err := terminal.MakeRaw(0)
	if err == nil {
		defer terminal.Restore(0, oldState)
	}

	rdfs := &goselect.FDSet{}
	reader := io.Reader(os.Stdin)
	for {
		rdfs.Zero()
		rdfs.Set(reader.(exposeFd).Fd())
		err := goselect.Select(1, rdfs, nil, nil, 50*time.Millisecond)
		if err != nil {
			done <- true
			return
		}
		if rdfs.IsSet(reader.(exposeFd).Fd()) {
			size, err := reader.Read(buff)
			if size <= 0 || err != nil {
				done <- true
				return
			}
			data := buff[:size]
			err = c.write(append([]byte("0"), data...))
			if err != nil {
				done <- true
				return
			}
		}
		select {
		case <-c.QuitChan:
			return
		default:
			break
		}
	}
}

func (c *Client) readLoop(done chan bool, wg *sync.WaitGroup) {
	defer wg.Done()

	type MessageNonBlocking struct {
		Data []byte
		Err  error
	}
	msgChan := make(chan MessageNonBlocking)

	for {
		go func() {
			_, data, err := c.Conn.ReadMessage()
			msgChan <- MessageNonBlocking{Data: data, Err: err}
		}()

		select {
		case <-c.QuitChan:
			return
		case msg := <-msgChan:
			if msg.Err != nil {
				done <- true
				if _, ok := msg.Err.(*websocket.CloseError); !ok {
					logrus.Warnf("c.Conn.ReadMessage: %v", msg.Err)
				}
				return
			}
			if len(msg.Data) == 0 {
				done <- true
				logrus.Warnf("An error has occured")
				return
			}
			switch msg.Data[0] {
			case '0': // data
				buf, err := base64.StdEncoding.DecodeString(string(msg.Data[1:]))
				if err != nil {
					logrus.Warnf("Invalid base64 content: %q", msg.Data[1:])
					break
				}
				c.Output.Write(buf)
			case '1': // pong
			case '2': // new title
				newTitle := string(msg.Data[1:])
				fmt.Fprintf(c.Output, "\033]0;%s\007", newTitle)
			case '3': // json prefs
				logrus.Debugf("Unhandled protocol message: json pref: %s", string(msg.Data[1:]))
			case '4': // autoreconnect
				logrus.Debugf("Unhandled protocol message: autoreconnect: %s", string(msg.Data))
			default:
				logrus.Warnf("Unhandled protocol message: %s", string(msg.Data))
			}
		}
	}
}

// SetOutput changes the output stream
func (c *Client) SetOutput(w io.Writer) {
	c.Output = w
}

// ParseURL parses an URL which may be incomplete and tries to standardize it
func ParseURL(input string) (string, error) {
	parsed, err := url.Parse(input)
	if err != nil {
		return "", err
	}
	switch parsed.Scheme {
	case "http", "https":
		// everything is ok
	default:
		return ParseURL(fmt.Sprintf("http://%s", input))
	}
	return parsed.String(), nil
}

// NewClient returns a GoTTY client object
func NewClient(inputURL string) (*Client, error) {
	url, err := ParseURL(inputURL)
	if err != nil {
		return nil, err
	}
	return &Client{
		Dialer:     &websocket.Dialer{},
		URL:        url,
		WriteMutex: &sync.Mutex{},
		Output:     os.Stdout,
		QuitChan:   make(chan struct{}),
	}, nil
}
<commit_msg>Fix race condition<commit_after>package gottyclient

import (
	"crypto/tls"
	"encoding/base64"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"regexp"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/creack/goselect"
	"github.com/gorilla/websocket"
	"golang.org/x/crypto/ssh/terminal"
)

// GetAuthTokenURL transforms a GoTTY http URL to its AuthToken file URL
func GetAuthTokenURL(httpURL string) (*url.URL, *http.Header, error) {
	header := http.Header{}
	target, err := url.Parse(httpURL)
	if err != nil {
		return nil, nil, err
	}

	target.Path = strings.TrimLeft(target.Path+"auth_token.js", "/")

	if target.User != nil {
		header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(target.User.String())))
		target.User = nil
	}

	return target, &header, nil
}

// GetURLQuery returns url.query
func GetURLQuery(rawurl string) (url.Values, error) {
	target, err := url.Parse(rawurl)
	if err != nil {
		return nil, err
	}
	return target.Query(), nil
}

// GetWebsocketURL transforms a GoTTY http URL to its WebSocket URL
func GetWebsocketURL(httpURL string) (*url.URL, *http.Header, error) {
	header := http.Header{}
	target, err := url.Parse(httpURL)
	if err != nil {
		return nil, nil, err
	}

	if target.Scheme == "https" {
		target.Scheme = "wss"
	} else {
		target.Scheme = "ws"
	}

	target.Path = strings.TrimLeft(target.Path+"ws", "/")

	if target.User != nil {
		header.Add("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(target.User.String())))
		target.User = nil
	}

	return target, &header, nil
}

type Client struct {
	Dialer          *websocket.Dialer
	Conn            *websocket.Conn
	URL             string
	WriteMutex      *sync.Mutex
	Output          io.Writer
	QuitChan        chan struct{}
	QuitChanClosed  int32 // atomic value
	SkipTLSVerify   bool
	UseProxyFromEnv bool
	Connected       bool
}

type querySingleType struct {
	AuthToken string `json:"AuthToken"`
	Arguments string `json:"Arguments"`
}

func (c *Client) write(data []byte) error {
	c.WriteMutex.Lock()
	defer c.WriteMutex.Unlock()
	return c.Conn.WriteMessage(websocket.TextMessage, data)
}

// GetAuthToken retrieves an Auth Token from dynamic auth_token.js file
func (c *Client) GetAuthToken() (string, error) {
	target, header, err := GetAuthTokenURL(c.URL)
	if err != nil {
		return "", err
	}

	logrus.Debugf("Fetching auth token auth-token: %q", target.String())
	req, err := http.NewRequest("GET", target.String(), nil)
	req.Header = *header
	tr := &http.Transport{}
	if c.SkipTLSVerify {
		conf := &tls.Config{InsecureSkipVerify: true}
		tr.TLSClientConfig = conf
	}
	if c.UseProxyFromEnv {
		tr.Proxy = http.ProxyFromEnvironment
	}
	client := &http.Client{Transport: tr}
	resp, err := client.Do(req)
	if err != nil {
		return "", err
	}

	switch resp.StatusCode {
	case 200:
		// Everything is OK
	default:
		return "", fmt.Errorf("unknown status code: %d (%s)", resp.StatusCode, http.StatusText(resp.StatusCode))
	}

	defer resp.Body.Close()
	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return "", err
	}

	re := regexp.MustCompile("var gotty_auth_token = '(.*)'")
	output := re.FindStringSubmatch(string(body))
	if len(output) == 0 {
		return "", fmt.Errorf("Cannot fetch GoTTY auth-token, please upgrade your GoTTY server.")
	}

	return output[1], nil
}

// Connect tries to dial a websocket server
func (c *Client) Connect() error {
	// Retrieve AuthToken
	authToken, err := c.GetAuthToken()
	if err != nil {
		return err
	}
	logrus.Debugf("Auth-token: %q", authToken)

	// Open WebSocket connection
	target, header, err := GetWebsocketURL(c.URL)
	if err != nil {
		return err
	}
	logrus.Debugf("Connecting to websocket: %q", target.String())
	if c.SkipTLSVerify {
		c.Dialer.TLSClientConfig = &tls.Config{InsecureSkipVerify: true}
	}
	if c.UseProxyFromEnv {
		c.Dialer.Proxy = http.ProxyFromEnvironment
	}
	conn, _, err := c.Dialer.Dial(target.String(), *header)
	if err != nil {
		return err
	}
	c.Conn = conn
	c.Connected = true

	// Pass arguments and auth-token
	query, err := GetURLQuery(c.URL)
	if err != nil {
		return err
	}
	querySingle := querySingleType{
		Arguments: "?" + query.Encode(),
		AuthToken: authToken,
	}
	json, err := json.Marshal(querySingle)
	if err != nil {
		logrus.Errorf("Failed to parse init message %v", err)
		return err
	}
	// Send Json
	logrus.Debugf("Sending arguments and auth-token")
	err = c.write(json)
	if err != nil {
		return err
	}

	go c.pingLoop()

	return nil
}

func (c *Client) pingLoop() {
	for {
		logrus.Debugf("Sending ping")
		c.write([]byte("1"))
		time.Sleep(30 * time.Second)
	}
}

// Close will nicely close the dialer
func (c *Client) Close() {
	c.Conn.Close()
}

// ExitLoop will kill all goroutine
// ExitLoop() -> wait Loop() -> Close()
func (c *Client) ExitLoop() {
	if atomic.CompareAndSwapInt32(&c.QuitChanClosed, 0, 1) {
		close(c.QuitChan)
	}
}

// Loop will look indefinitely for new messages
func (c *Client) Loop() error {
	if !c.Connected {
		err := c.Connect()
		if err != nil {
			return err
		}
	}

	var wg sync.WaitGroup
	done := make(chan bool)

	wg.Add(1)
	go c.termsizeLoop(&wg)
	wg.Add(1)
	go c.readLoop(done, &wg)
	wg.Add(1)
	go c.writeLoop(done, &wg)
	select {
	case <-done:
		if atomic.CompareAndSwapInt32(&c.QuitChanClosed, 0, 1) {
			close(c.QuitChan)
		}
	case <-c.QuitChan:
	}
	wg.Wait()
	return nil
}

type winsize struct {
	Rows    uint16 `json:"rows"`
	Columns uint16 `json:"columns"`
	// unused
	x uint16
	y uint16
}

func (c *Client) termsizeLoop(wg *sync.WaitGroup) {
	defer wg.Done()
	ch := make(chan os.Signal, 1)
	notifySignalSIGWINCH(ch)
	defer resetSignalSIGWINCH()

	for {
		if b, err := syscallTIOCGWINSZ(); err != nil {
			logrus.Warn(err)
		} else {
			if err = c.write(append([]byte("2"), b...)); err != nil {
				logrus.Warnf("ws.WriteMessage failed: %v", err)
			}
		}
		select {
		case <-c.QuitChan:
			return
		case <-ch:
		}
	}
}

type exposeFd interface {
	Fd() uintptr
}

func (c *Client) writeLoop(done chan bool, wg *sync.WaitGroup) {
	defer wg.Done()

	buff := make([]byte, 128)
	oldState, err := terminal.MakeRaw(0)
	if err == nil {
		defer terminal.Restore(0, oldState)
	}

	rdfs := &goselect.FDSet{}
	reader := io.Reader(os.Stdin)
	for {
		rdfs.Zero()
		rdfs.Set(reader.(exposeFd).Fd())
		err := goselect.Select(1, rdfs, nil, nil, 50*time.Millisecond)
		if err != nil {
			done <- true
			return
		}
		if rdfs.IsSet(reader.(exposeFd).Fd()) {
			size, err := reader.Read(buff)
			if size <= 0 || err != nil {
				done <- true
				return
			}
			data := buff[:size]
			err = c.write(append([]byte("0"), data...))
			if err != nil {
				done <- true
				return
			}
		}
		select {
		case <-c.QuitChan:
			return
		default:
			break
		}
	}
}

func (c *Client) readLoop(done chan bool, wg *sync.WaitGroup) {
	defer wg.Done()

	type MessageNonBlocking struct {
		Data []byte
		Err  error
	}
	msgChan := make(chan MessageNonBlocking)

	for {
		go func() {
			_, data, err := c.Conn.ReadMessage()
			msgChan <- MessageNonBlocking{Data: data, Err: err}
		}()

		select {
		case <-c.QuitChan:
			return
		case msg := <-msgChan:
			if msg.Err != nil {
				done <- true
				if _, ok := msg.Err.(*websocket.CloseError); !ok {
					logrus.Warnf("c.Conn.ReadMessage: %v", msg.Err)
				}
				return
			}
			if len(msg.Data) == 0 {
				done <- true
				logrus.Warnf("An error has occured")
				return
			}
			switch msg.Data[0] {
			case '0': // data
				buf, err := base64.StdEncoding.DecodeString(string(msg.Data[1:]))
				if err != nil {
					logrus.Warnf("Invalid base64 content: %q", msg.Data[1:])
					break
				}
				c.Output.Write(buf)
			case '1': // pong
			case '2': // new title
				newTitle := string(msg.Data[1:])
				fmt.Fprintf(c.Output, "\033]0;%s\007", newTitle)
			case '3': // json prefs
				logrus.Debugf("Unhandled protocol message: json pref: %s", string(msg.Data[1:]))
			case '4': // autoreconnect
				logrus.Debugf("Unhandled protocol message: autoreconnect: %s", string(msg.Data))
			default:
				logrus.Warnf("Unhandled protocol message: %s", string(msg.Data))
			}
		}
	}
}

// SetOutput changes the output stream
func (c *Client) SetOutput(w io.Writer) {
	c.Output = w
}

// ParseURL parses an URL which may be incomplete and tries to standardize it
func ParseURL(input string) (string, error) {
	parsed, err := url.Parse(input)
	if err != nil {
		return "", err
	}
	switch parsed.Scheme {
	case "http", "https":
		// everything is ok
	default:
		return ParseURL(fmt.Sprintf("http://%s", input))
	}
	return parsed.String(), nil
}

// NewClient returns a GoTTY client object
func NewClient(inputURL string) (*Client, error) {
	url, err := ParseURL(inputURL)
	if err != nil {
		return nil, err
	}
	return &Client{
		Dialer:     &websocket.Dialer{},
		URL:        url,
		WriteMutex: &sync.Mutex{},
		Output:     os.Stdout,
		QuitChan:   make(chan struct{}),
	}, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"

	"github.com/spf13/cobra"
)

/*
#define _GNU_SOURCE
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <lxc/lxccontainer.h>
#include <lxc/version.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define VERSION_AT_LEAST(major, minor, micro)							\
	((LXC_DEVEL == 1) || (!(major > LXC_VERSION_MAJOR ||					\
	major == LXC_VERSION_MAJOR && minor > LXC_VERSION_MINOR ||				\
	major == LXC_VERSION_MAJOR && minor == LXC_VERSION_MINOR && micro > LXC_VERSION_MICRO)))

extern char* advance_arg(bool required);
extern void error(char *msg);
extern void attach_userns(int pid);
extern int dosetns(int pid, char *nstype);

int mkdir_p(const char *dir, mode_t mode)
{
	const char *tmp = dir;
	const char *orig = dir;
	char *makeme;

	do {
		dir = tmp + strspn(tmp, "/");
		tmp = dir + strcspn(dir, "/");
		makeme = strndup(orig, dir - orig);
		if (*makeme) {
			if (mkdir(makeme, mode) && errno != EEXIST) {
				fprintf(stderr, "failed to create directory '%s': %s\n", makeme, strerror(errno));
				free(makeme);
				return -1;
			}
		}
		free(makeme);
	} while(tmp != dir);

	return 0;
}

void ensure_dir(char *dest) {
	struct stat sb;
	if (stat(dest, &sb) == 0) {
		if ((sb.st_mode & S_IFMT) == S_IFDIR)
			return;
		if (unlink(dest) < 0) {
			fprintf(stderr, "Failed to remove old %s: %s\n", dest, strerror(errno));
			_exit(1);
		}
	}
	if (mkdir(dest, 0755) < 0) {
		fprintf(stderr, "Failed to mkdir %s: %s\n", dest, strerror(errno));
		_exit(1);
	}
}

void ensure_file(char *dest) {
	struct stat sb;
	int fd;

	if (stat(dest, &sb) == 0) {
		if ((sb.st_mode & S_IFMT) != S_IFDIR)
			return;
		if (rmdir(dest) < 0) {
			fprintf(stderr, "Failed to remove old %s: %s\n", dest, strerror(errno));
			_exit(1);
		}
	}

	fd = creat(dest, 0755);
	if (fd < 0) {
		fprintf(stderr, "Failed to mkdir %s: %s\n", dest, strerror(errno));
		_exit(1);
	}
	close(fd);
}

void create(char *src, char *dest) {
	char *dirdup;
	char *destdirname;

	struct stat sb;
	if (stat(src, &sb) < 0) {
		fprintf(stderr, "source %s does not exist\n", src);
		_exit(1);
	}

	dirdup = strdup(dest);
	if (!dirdup)
		_exit(1);

	destdirname = dirname(dirdup);

	if (mkdir_p(destdirname, 0755) < 0) {
		fprintf(stderr, "failed to create path: %s\n", destdirname);
		free(dirdup);
		_exit(1);
	}
	free(dirdup);

	switch (sb.st_mode & S_IFMT) {
	case S_IFDIR:
		ensure_dir(dest);
		return;
	default:
		ensure_file(dest);
		return;
	}
}

void do_lxd_forkmount(pid_t pid) {
	char *src, *dest, *opts;

	attach_userns(pid);

	if (dosetns(pid, "mnt") < 0) {
		fprintf(stderr, "Failed setns to container mount namespace: %s\n", strerror(errno));
		_exit(1);
	}

	src = advance_arg(true);
	dest = advance_arg(true);

	create(src, dest);

	if (access(src, F_OK) < 0) {
		fprintf(stderr, "Mount source doesn't exist: %s\n", strerror(errno));
		_exit(1);
	}

	if (access(dest, F_OK) < 0) {
		fprintf(stderr, "Mount destination doesn't exist: %s\n", strerror(errno));
		_exit(1);
	}

	// Here, we always move recursively, because we sometimes allow
	// recursive mounts. If the mount has no kids then it doesn't matter,
	// but if it does, we want to move those too.
	if (mount(src, dest, "none", MS_MOVE | MS_REC, NULL) < 0) {
		fprintf(stderr, "Failed mounting %s onto %s: %s\n", src, dest, strerror(errno));
		_exit(1);
	}

	_exit(0);
}

void do_lxd_forkumount(pid_t pid) {
	int ret;
	char *path = NULL;

	ret = dosetns(pid, "mnt");
	if (ret < 0) {
		fprintf(stderr, "Failed to setns to container mount namespace: %s\n", strerror(errno));
		_exit(1);
	}

	path = advance_arg(true);

	ret = umount2(path, MNT_DETACH);
	if (ret < 0) {
		// - ENOENT: The user must have unmounted and removed the path.
		// - EINVAL: The user must have unmounted. Other explanations
		//           for EINVAL do not apply.
		if (errno == ENOENT || errno == EINVAL)
			_exit(0);

		fprintf(stderr, "Error unmounting %s: %s\n", path, strerror(errno));
		_exit(1);
	}

	_exit(0);
}

#if VERSION_AT_LEAST(3, 1, 0)
static int lxc_safe_ulong(const char *numstr, unsigned long *converted)
{
	char *err = NULL;
	unsigned long int uli;

	while (isspace(*numstr))
		numstr++;

	if (*numstr == '-')
		return -EINVAL;

	errno = 0;
	uli = strtoul(numstr, &err, 0);
	if (errno == ERANGE && uli == ULONG_MAX)
		return -ERANGE;

	if (err == numstr || *err != '\0')
		return -EINVAL;

	*converted = uli;
	return 0;
}
#endif

void do_lxc_forkmount()
{
#if VERSION_AT_LEAST(3, 1, 0)
	int ret;
	char *config, *flags, *fstype, *lxcpath, *name, *source, *target;
	struct lxc_container *c;
	struct lxc_mount mnt = {0};
	unsigned long mntflags = 0;

	name = advance_arg(true);
	lxcpath = advance_arg(true);
	config = advance_arg(true);
	source = advance_arg(true);
	target = advance_arg(true);
	fstype = advance_arg(true);
	flags = advance_arg(true);

	c = lxc_container_new(name, lxcpath);
	if (!c)
		_exit(1);

	c->clear_config(c);

	if (!c->load_config(c, config)) {
		lxc_container_put(c);
		_exit(1);
	}

	ret = lxc_safe_ulong(flags, &mntflags);
	if (ret < 0) {
		lxc_container_put(c);
		_exit(1);
	}

	ret = c->mount(c, source, target, fstype, mntflags, NULL, &mnt);
	lxc_container_put(c);
	if (ret < 0)
		_exit(1);

	_exit(0);
#endif
}

void do_lxc_forkumount()
{
#if VERSION_AT_LEAST(3, 1, 0)
	int ret;
	char *config, *lxcpath, *name, *target;
	struct lxc_container *c;
	struct lxc_mount mnt = {0};

	name = advance_arg(true);
	lxcpath = advance_arg(true);
	config = advance_arg(true);
	target = advance_arg(true);

	c = lxc_container_new(name, lxcpath);
	if (!c)
		_exit(1);

	c->clear_config(c);

	if (!c->load_config(c, config)) {
		lxc_container_put(c);
		_exit(1);
	}

	ret = c->umount(c, target, MNT_DETACH, &mnt);
	lxc_container_put(c);
	if (ret < 0)
		_exit(1);
#endif
}

void forkmount() {
	char *cur = NULL;

	char *command = NULL;
	char *rootfs = NULL;
	pid_t pid = 0;

	// Get the subcommand
	command = advance_arg(false);
	if (command == NULL || (strcmp(command, "--help") == 0 || strcmp(command, "--version") == 0 || strcmp(command, "-h") == 0)) {
		return;
	}

	// Check that we're root
	if (geteuid() != 0) {
		fprintf(stderr, "Error: forkmount requires root privileges\n");
		_exit(1);
	}

	// Call the subcommands
	if (strcmp(command, "lxd-mount") == 0) {
		// Get the pid
		cur = advance_arg(false);
		if (cur == NULL || (strcmp(cur, "--help") == 0 || strcmp(cur, "--version") == 0 || strcmp(cur, "-h") == 0)) {
			return;
		}
		pid = atoi(cur);

		do_lxd_forkmount(pid);
	} else if (strcmp(command, "lxc-mount") == 0) {
		do_lxc_forkmount();
	} else if (strcmp(command, "lxd-umount") == 0) {
		// Get the pid
		cur = advance_arg(false);
		if (cur == NULL || (strcmp(cur, "--help") == 0 || strcmp(cur, "--version") == 0 || strcmp(cur, "-h") == 0)) {
			return;
		}
		pid = atoi(cur);

		do_lxd_forkumount(pid);
	} else if (strcmp(command, "lxc-umount") == 0) {
		do_lxc_forkumount();
	}
}
*/
// #cgo CFLAGS: -std=gnu11 -Wvla
// #cgo LDFLAGS: -llxc
// #cgo pkg-config: lxc
import "C"

type cmdForkmount struct {
	global *cmdGlobal
}

func (c *cmdForkmount) Command() *cobra.Command {
	// Main subcommand
	cmd := &cobra.Command{}
	cmd.Use = "forkmount"
	cmd.Short = "Perform mount operations"
	cmd.Long = `Description:
  Perform mount operations

  This set of internal commands are used for all container mount
  operations.
`
	cmd.Hidden = true

	// mount
	cmdMount := &cobra.Command{}
	cmdMount.Use = "mount <PID> <source> <destination>"
	cmdMount.Args = cobra.ExactArgs(3)
	cmdMount.RunE = c.Run
	cmd.AddCommand(cmdMount)

	// umount
	cmdUmount := &cobra.Command{}
	cmdUmount.Use = "umount <PID> <path>"
	cmdUmount.Args = cobra.ExactArgs(2)
	cmdUmount.RunE = c.Run
	cmd.AddCommand(cmdUmount)

	return cmd
}

func (c *cmdForkmount) Run(cmd *cobra.Command, args []string) error {
	return fmt.Errorf("This command should have been intercepted in cgo")
}
<commit_msg>forkmount: cleanup macros<commit_after>package main

import (
	"fmt"

	"github.com/spf13/cobra"
)

/*
#define _GNU_SOURCE
#include <ctype.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <limits.h>
#include <lxc/lxccontainer.h>
#include <lxc/version.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mount.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#include "include/memory_utils.h"

#define VERSION_AT_LEAST(major, minor, micro)							\
	((LXC_DEVEL == 1) || (!(major > LXC_VERSION_MAJOR ||					\
	major == LXC_VERSION_MAJOR && minor > LXC_VERSION_MINOR ||				\
	major == LXC_VERSION_MAJOR && minor == LXC_VERSION_MINOR && micro > LXC_VERSION_MICRO)))

extern char* advance_arg(bool required);
extern void error(char *msg);
extern void attach_userns(int pid);
extern int dosetns(int pid, char *nstype);

int mkdir_p(const char *dir, mode_t mode)
{
	const char *tmp = dir;
	const char *orig = dir;

	do {
		__do_free char *makeme = NULL;

		dir = tmp + strspn(tmp, "/");
		tmp = dir + strcspn(dir, "/");
		makeme = strndup(orig, dir - orig);
		if (*makeme) {
			if (mkdir(makeme, mode) && errno != EEXIST) {
				fprintf(stderr, "failed to create directory '%s': %s\n", makeme, strerror(errno));
				return -1;
			}
		}
	} while(tmp != dir);

	return 0;
}

void ensure_dir(char *dest) {
	struct stat sb;
	if (stat(dest, &sb) == 0) {
		if ((sb.st_mode & S_IFMT) == S_IFDIR)
			return;
		if (unlink(dest) < 0) {
			fprintf(stderr, "Failed to remove old %s: %s\n", dest, strerror(errno));
			_exit(1);
		}
	}
	if (mkdir(dest, 0755) < 0) {
		fprintf(stderr, "Failed to mkdir %s: %s\n", dest, strerror(errno));
		_exit(1);
	}
}

void ensure_file(char *dest)
{
	__do_close_prot_errno int fd = -EBADF;
	struct stat sb;

	if (stat(dest, &sb) == 0) {
		if ((sb.st_mode & S_IFMT) != S_IFDIR)
			return;
		if (rmdir(dest) < 0) {
			fprintf(stderr, "Failed to remove old %s: %s\n", dest, strerror(errno));
			_exit(1);
		}
	}

	fd = creat(dest, 0755);
	if (fd < 0) {
		fprintf(stderr, "Failed to mkdir %s: %s\n", dest, strerror(errno));
		_exit(1);
	}
}

void create(char *src, char *dest)
{
	__do_free char *dirdup = NULL;
	char *destdirname;

	struct stat sb;
	if (stat(src, &sb) < 0) {
		fprintf(stderr, "source %s does not exist\n", src);
		_exit(1);
	}

	dirdup = strdup(dest);
	if (!dirdup)
		_exit(1);

	destdirname = dirname(dirdup);

	if (mkdir_p(destdirname, 0755) < 0) {
		fprintf(stderr, "failed to create path: %s\n", destdirname);
		_exit(1);
	}

	switch (sb.st_mode & S_IFMT) {
	case S_IFDIR:
		ensure_dir(dest);
		return;
	default:
		ensure_file(dest);
		return;
	}
}

void do_lxd_forkmount(pid_t pid) {
	char *src, *dest, *opts;

	attach_userns(pid);

	if (dosetns(pid, "mnt") < 0) {
		fprintf(stderr, "Failed setns to container mount namespace: %s\n", strerror(errno));
		_exit(1);
	}

	src = advance_arg(true);
	dest = advance_arg(true);

	create(src, dest);

	if (access(src, F_OK) < 0) {
		fprintf(stderr, "Mount source doesn't exist: %s\n", strerror(errno));
		_exit(1);
	}

	if (access(dest, F_OK) < 0) {
		fprintf(stderr, "Mount destination doesn't exist: %s\n", strerror(errno));
		_exit(1);
	}

	// Here, we always move recursively, because we sometimes allow
	// recursive mounts. If the mount has no kids then it doesn't matter,
	// but if it does, we want to move those too.
	if (mount(src, dest, "none", MS_MOVE | MS_REC, NULL) < 0) {
		fprintf(stderr, "Failed mounting %s onto %s: %s\n", src, dest, strerror(errno));
		_exit(1);
	}

	_exit(0);
}

void do_lxd_forkumount(pid_t pid) {
	int ret;
	char *path = NULL;

	ret = dosetns(pid, "mnt");
	if (ret < 0) {
		fprintf(stderr, "Failed to setns to container mount namespace: %s\n", strerror(errno));
		_exit(1);
	}

	path = advance_arg(true);

	ret = umount2(path, MNT_DETACH);
	if (ret < 0) {
		// - ENOENT: The user must have unmounted and removed the path.
		// - EINVAL: The user must have unmounted. Other explanations
		//           for EINVAL do not apply.
		if (errno == ENOENT || errno == EINVAL)
			_exit(0);

		fprintf(stderr, "Error unmounting %s: %s\n", path, strerror(errno));
		_exit(1);
	}

	_exit(0);
}

#if VERSION_AT_LEAST(3, 1, 0)
static int lxc_safe_ulong(const char *numstr, unsigned long *converted)
{
	char *err = NULL;
	unsigned long int uli;

	while (isspace(*numstr))
		numstr++;

	if (*numstr == '-')
		return -EINVAL;

	errno = 0;
	uli = strtoul(numstr, &err, 0);
	if (errno == ERANGE && uli == ULONG_MAX)
		return -ERANGE;

	if (err == numstr || *err != '\0')
		return -EINVAL;

	*converted = uli;
	return 0;
}
#endif

void do_lxc_forkmount()
{
#if VERSION_AT_LEAST(3, 1, 0)
	int ret;
	char *config, *flags, *fstype, *lxcpath, *name, *source, *target;
	struct lxc_container *c;
	struct lxc_mount mnt = {0};
	unsigned long mntflags = 0;

	name = advance_arg(true);
	lxcpath = advance_arg(true);
	config = advance_arg(true);
	source = advance_arg(true);
	target = advance_arg(true);
	fstype = advance_arg(true);
	flags = advance_arg(true);

	c = lxc_container_new(name, lxcpath);
	if (!c)
		_exit(1);

	c->clear_config(c);

	if (!c->load_config(c, config)) {
		lxc_container_put(c);
		_exit(1);
	}

	ret = lxc_safe_ulong(flags, &mntflags);
	if (ret < 0) {
		lxc_container_put(c);
		_exit(1);
	}

	ret = c->mount(c, source, target, fstype, mntflags, NULL, &mnt);
	lxc_container_put(c);
	if (ret < 0)
		_exit(1);

	_exit(0);
#endif
}

void do_lxc_forkumount()
{
#if VERSION_AT_LEAST(3, 1, 0)
	int ret;
	char *config, *lxcpath, *name, *target;
	struct lxc_container *c;
	struct lxc_mount mnt = {0};

	name = advance_arg(true);
	lxcpath = advance_arg(true);
	config = advance_arg(true);
	target = advance_arg(true);

	c = lxc_container_new(name, lxcpath);
	if (!c)
		_exit(1);

	c->clear_config(c);

	if (!c->load_config(c, config)) {
		lxc_container_put(c);
		_exit(1);
	}

	ret = c->umount(c, target, MNT_DETACH, &mnt);
	lxc_container_put(c);
	if (ret < 0)
		_exit(1);
#endif
}

void forkmount() {
	char *cur = NULL;

	char *command = NULL;
	char *rootfs = NULL;
	pid_t pid = 0;

	// Get the subcommand
	command = advance_arg(false);
	if (command == NULL || (strcmp(command, "--help") == 0 || strcmp(command, "--version") == 0 || strcmp(command, "-h") == 0)) {
		return;
	}

	// Check that we're root
	if (geteuid() != 0) {
		fprintf(stderr, "Error: forkmount requires root privileges\n");
		_exit(1);
	}

	// Call the subcommands
	if (strcmp(command, "lxd-mount") == 0) {
		// Get the pid
		cur = advance_arg(false);
		if (cur == NULL || (strcmp(cur, "--help") == 0 || strcmp(cur, "--version") == 0 || strcmp(cur, "-h") == 0)) {
			return;
		}
		pid = atoi(cur);

		do_lxd_forkmount(pid);
	} else if (strcmp(command, "lxc-mount") == 0) {
		do_lxc_forkmount();
	} else if (strcmp(command, "lxd-umount") == 0) {
		// Get the pid
		cur = advance_arg(false);
		if (cur == NULL || (strcmp(cur, "--help") == 0 || strcmp(cur, "--version") == 0 || strcmp(cur, "-h") == 0)) {
			return;
		}
		pid = atoi(cur);

		do_lxd_forkumount(pid);
	} else if (strcmp(command, "lxc-umount") == 0) {
		do_lxc_forkumount();
	}
}
*/
// #cgo CFLAGS: -std=gnu11 -Wvla
// #cgo LDFLAGS: -llxc
// #cgo pkg-config: lxc
import "C"

type cmdForkmount struct {
	global *cmdGlobal
}

func (c *cmdForkmount) Command() *cobra.Command {
	// Main subcommand
	cmd := &cobra.Command{}
	cmd.Use = "forkmount"
	cmd.Short = "Perform mount operations"
	cmd.Long = `Description:
  Perform mount operations

  This set of internal commands are used for all container mount
  operations.
`
	cmd.Hidden = true

	// mount
	cmdMount := &cobra.Command{}
	cmdMount.Use = "mount <PID> <source> <destination>"
	cmdMount.Args = cobra.ExactArgs(3)
	cmdMount.RunE = c.Run
	cmd.AddCommand(cmdMount)

	// umount
	cmdUmount := &cobra.Command{}
	cmdUmount.Use = "umount <PID> <path>"
	cmdUmount.Args = cobra.ExactArgs(2)
	cmdUmount.RunE = c.Run
	cmd.AddCommand(cmdUmount)

	return cmd
}

func (c *cmdForkmount) Run(cmd *cobra.Command, args []string) error {
	return fmt.Errorf("This command should have been intercepted in cgo")
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"io/ioutil"
	"mime"
	"net/http"
	"path/filepath"
	"strings"

	"github.com/gorilla/mux"
	"go.skia.org/infra/go/common"
	"go.skia.org/infra/go/httputils"
	"go.skia.org/infra/go/sklog"
)

var (
	// The loaded index page of the lit-html version.
	indexPage []byte
)

func main() {
	// flags
	var (
		port         = flag.String("port", ":8000", "HTTP service address (e.g., ':8000')")
		promPort     = flag.String("prom_port", ":20000", "Metrics service address (e.g., ':10110')")
		resourcesDir = flag.String("resources_dir", "/usr/local/share/debugger-app/", "The directory to find lit-html stuff.")
	)

	common.InitWithMust(
		"debugger-app",
		common.PrometheusOpt(promPort),
		common.MetricsLoggingOpt(),
	)
	loadPage(*resourcesDir)

	// Need to set the mime-type for wasm files so streaming compile works.
	if err := mime.AddExtensionType(".wasm", "application/wasm"); err != nil {
		sklog.Fatal(err)
	}

	router := mux.NewRouter()
	router.PathPrefix("/dist/").HandlerFunc(makeResourceHandler(*resourcesDir))
	router.HandleFunc("/", mainHandler)

	http.Handle("/", httputils.HealthzAndHTTPS(httputils.LoggingRequestResponse(router)))
	sklog.Info("Ready to serve.")
	sklog.Fatal(http.ListenAndServe(*port, nil))
}

func loadPage(resourceDir string) {
	p, err := ioutil.ReadFile(filepath.Join(resourceDir, "main.html"))
	if err != nil {
		sklog.Fatalf("Could not find index html: %s", err)
	}
	indexPage = p
}

func mainHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html")
	// Set the HTML to expire at the same time as the JS and WASM, otherwise the HTML
	// (and by extension, the JS with its cachbuster hash) might outlive the WASM
	// and then the two will skew
	w.Header().Set("Cache-Control", "max-age=60")
	w.WriteHeader(http.StatusOK)
	if _, err := w.Write(indexPage); err != nil {
		httputils.ReportError(w, err, "Server could not load page", http.StatusInternalServerError)
	}
}

func makeResourceHandler(dir string) func(http.ResponseWriter, *http.Request) {
	fileServer := http.FileServer(http.Dir(dir))
	return func(w http.ResponseWriter, r *http.Request) {
		// This is structured this way so we can control Cache-Control settings to avoid
		// WASM and JS from skewing out of date.
		w.Header().Add("Cache-Control", "max-age=300")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		p := r.URL.Path
		r.URL.Path = strings.TrimPrefix(p, "/dist")
		fileServer.ServeHTTP(w, r)
	}
}
<commit_msg>Add helpful message to debugger log<commit_after>package main

import (
	"flag"
	"io/ioutil"
	"mime"
	"net/http"
	"path/filepath"
	"strings"

	"github.com/gorilla/mux"
	"go.skia.org/infra/go/common"
	"go.skia.org/infra/go/httputils"
	"go.skia.org/infra/go/sklog"
)

var (
	// The loaded index page of the lit-html version.
	indexPage []byte
)

func main() {
	// flags
	var (
		port         = flag.String("port", ":8000", "HTTP service address (e.g., ':8000')")
		promPort     = flag.String("prom_port", ":20000", "Metrics service address (e.g., ':10110')")
		resourcesDir = flag.String("resources_dir", "/usr/local/share/debugger-app/", "The directory to find lit-html stuff.")
	)

	common.InitWithMust(
		"debugger-app",
		common.PrometheusOpt(promPort),
		common.MetricsLoggingOpt(),
	)
	loadPage(*resourcesDir)

	// Need to set the mime-type for wasm files so streaming compile works.
	if err := mime.AddExtensionType(".wasm", "application/wasm"); err != nil {
		sklog.Fatal(err)
	}

	router := mux.NewRouter()
	router.PathPrefix("/dist/").HandlerFunc(makeResourceHandler(*resourcesDir))
	router.HandleFunc("/", mainHandler)

	http.Handle("/", httputils.HealthzAndHTTPS(httputils.LoggingRequestResponse(router)))
	sklog.Info("Application served at http://localhost:8000/dist/main.html")
	sklog.Fatal(http.ListenAndServe(*port, nil))
}

func loadPage(resourceDir string) {
	p, err := ioutil.ReadFile(filepath.Join(resourceDir, "main.html"))
	if err != nil {
		sklog.Fatalf("Could not find index html: %s", err)
	}
	indexPage = p
}

func mainHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-Type", "text/html")
	// Set the HTML to expire at the same time as the JS and WASM, otherwise the HTML
	// (and by extension, the JS with its cachbuster hash) might outlive the WASM
	// and then the two will skew
	w.Header().Set("Cache-Control", "max-age=60")
	w.WriteHeader(http.StatusOK)
	if _, err := w.Write(indexPage); err != nil {
		httputils.ReportError(w, err, "Server could not load page", http.StatusInternalServerError)
	}
}

func makeResourceHandler(dir string) func(http.ResponseWriter, *http.Request) {
	fileServer := http.FileServer(http.Dir(dir))
	return func(w http.ResponseWriter, r *http.Request) {
		// This is structured this way so we can control Cache-Control settings to avoid
		// WASM and JS from skewing out of date.
		w.Header().Add("Cache-Control", "max-age=300")
		w.Header().Set("Access-Control-Allow-Origin", "*")
		p := r.URL.Path
		r.URL.Path = strings.TrimPrefix(p, "/dist")
		fileServer.ServeHTTP(w, r)
	}
}
<|endoftext|>


<commit_before>package db

import (
	"database/sql"
	"fmt"

	_ "github.com/mattn/go-sqlite3" // required blank import
)

// Config represents the configuration of the SQLite database.
type Config struct {
	DSN string
}

// NewConfig returns an instance of Config for the database at path.
func NewConfig() *Config {
	return &Config{}
}

// DSN returns the fully-qualified datasource name.
func (c *Config) FQDSN(path string) string {
	if c.DSN != "" {
		return fmt.Sprintf("file:%s?%s", path, c.DSN)
	}
	return path
}

// DB is the SQL database.
type DB struct {
	conn *sql.DB
}

type Result struct {
	LastInsertID int64  `json:"last_insert_id,omitempty"`
	RowsAffected int64  `json:"rows_affected,omitempty"`
	Error        string `json:"error,omitempty"`
}

type Rows struct {
	Columns []string        `json:"columns,omitempty"`
	Values  [][]interface{} `json:"values,omitempty"`
	Error   string          `json:"error,omitempty"`
}

// Open an existing database, creating it if it does not exist.
func Open(dbPath string) (*DB, error) {
	dbc, err := sql.Open("sqlite3", dbPath)
	if err != nil {
		return nil, err
	}
	return &DB{
		conn: dbc,
	}, nil
}

// OpenWithConfiguration an existing database, creating it if it does not exist.
func OpenWithConfiguration(dbPath string, conf *Config) (*DB, error) {
	dbc, err := sql.Open("sqlite3", conf.FQDSN(dbPath))
	if err != nil {
		return nil, err
	}
	return &DB{
		conn: dbc,
	}, nil
}

// Close closes the underlying database connection.
func (db *DB) Close() error {
	return db.conn.Close()
}

// Execute executes queries that modify the database.
func (db *DB) Execute(queries []string, tx bool) ([]*Result, error) {
	type Execer interface {
		Exec(query string, args ...interface{}) (sql.Result, error)
	}

	var allResults []*Result
	err := func() error {
		var execer Execer
		var rollback bool

		// Check for the err, if set rollback.
		defer func() {
			if t, ok := execer.(*sql.Tx); ok {
				if rollback {
					t.Rollback()
					return
				}
				t.Commit()
			}
		}()

		// handleError sets the error field on the given result. It returns
		// whether the caller should continue processing or break.
		handleError := func(result *Result, err error) bool {
			result.Error = err.Error()
			allResults = append(allResults, result)
			if tx {
				rollback = true // Will trigger the rollback.
				return false
			}
			return true
		}

		// Create the correct execution object, depending on whether a
		// transaction was requested.
		if tx {
			execer, _ = db.conn.Begin()
		} else {
			execer = db.conn
		}

		// Execute each query.
		for _, q := range queries {
			if q == "" {
				continue
			}

			result := &Result{}

			r, err := execer.Exec(q)
			if err != nil {
				if handleError(result, err) {
					continue
				}
				break
			}

			lid, err := r.LastInsertId()
			if err != nil {
				if handleError(result, err) {
					continue
				}
				break
			}
			result.LastInsertID = lid

			ra, err := r.RowsAffected()
			if err != nil {
				if handleError(result, err) {
					continue
				}
				break
			}
			result.RowsAffected = ra
			allResults = append(allResults, result)
		}

		return nil
	}()

	return allResults, err
}

// Query executes queries that return rows, but don't modify the database.
func (db *DB) Query(queries []string, tx bool) ([]*Rows, error) {
	type Queryer interface {
		Query(query string, args ...interface{}) (*sql.Rows, error)
	}

	var allRows []*Rows
	err := func() (err error) {
		var queryer Queryer
		defer func() {
			// XXX THIS DOESN'T ACTUALLY WORK! Might as WELL JUST COMMIT?
			if t, ok := queryer.(*sql.Tx); ok {
				if err != nil {
					t.Rollback()
					return
				}
				t.Commit()
			}
		}()

		// Create the correct query object, depending on whether a
		// transaction was requested.
		if tx {
			queryer, _ = db.conn.Begin()
		} else {
			queryer = db.conn
		}

	QueryLoop:
		for _, q := range queries {
			if q == "" {
				continue
			}

			rows := &Rows{}

			rs, err := queryer.Query(q)
			if err != nil {
				rows.Error = err.Error()
				allRows = append(allRows, rows)
				continue
			}
			defer rs.Close() // This adds to all defers, right? Nothing leaks? XXX Could consume memory. Perhaps anon would be best.
			columns, err := rs.Columns()
			if err != nil {
				rows.Error = err.Error()
				allRows = append(allRows, rows)
				continue
			}

			rows.Columns = columns
			for rs.Next() {
				// Make a slice of interface{}, and the pointers to each item in the slice.
				values := make([]interface{}, len(rows.Columns))
				ptrs := make([]interface{}, len(rows.Columns))
				for i := range values {
					ptrs[i] = &values[i]
				}

				// Read all the values out.
				err = rs.Scan(ptrs...)
				if err != nil {
					rows.Error = err.Error()
					allRows = append(allRows, rows)
					continue QueryLoop
				}

				// Special case -- convert []uint8 to string. Perhaps this should be a config option.
				for i, v := range values {
					if w, ok := v.([]uint8); ok {
						values[i] = string(w)
					}
				}
				rows.Values = append(rows.Values, values)
			}
			allRows = append(allRows, rows)
		}

		return nil
	}()

	panic("####3")
	return allRows, err
}
<commit_msg>Remove debug panic()<commit_after>package db

import (
	"database/sql"
	"fmt"

	_ "github.com/mattn/go-sqlite3" // required blank import
)

// Config represents the configuration of the SQLite database.
type Config struct {
	DSN string
}

// NewConfig returns an instance of Config for the database at path.
func NewConfig() *Config {
	return &Config{}
}

// DSN returns the fully-qualified datasource name.
func (c *Config) FQDSN(path string) string {
	if c.DSN != "" {
		return fmt.Sprintf("file:%s?%s", path, c.DSN)
	}
	return path
}

// DB is the SQL database.
type DB struct {
	conn *sql.DB
}

type Result struct {
	LastInsertID int64  `json:"last_insert_id,omitempty"`
	RowsAffected int64  `json:"rows_affected,omitempty"`
	Error        string `json:"error,omitempty"`
}

type Rows struct {
	Columns []string        `json:"columns,omitempty"`
	Values  [][]interface{} `json:"values,omitempty"`
	Error   string          `json:"error,omitempty"`
}

// Open an existing database, creating it if it does not exist.
func Open(dbPath string) (*DB, error) {
	dbc, err := sql.Open("sqlite3", dbPath)
	if err != nil {
		return nil, err
	}
	return &DB{
		conn: dbc,
	}, nil
}

// OpenWithConfiguration an existing database, creating it if it does not exist.
func OpenWithConfiguration(dbPath string, conf *Config) (*DB, error) {
	dbc, err := sql.Open("sqlite3", conf.FQDSN(dbPath))
	if err != nil {
		return nil, err
	}
	return &DB{
		conn: dbc,
	}, nil
}

// Close closes the underlying database connection.
func (db *DB) Close() error {
	return db.conn.Close()
}

// Execute executes queries that modify the database.
func (db *DB) Execute(queries []string, tx bool) ([]*Result, error) {
	type Execer interface {
		Exec(query string, args ...interface{}) (sql.Result, error)
	}

	var allResults []*Result
	err := func() error {
		var execer Execer
		var rollback bool

		// Check for the err, if set rollback.
		defer func() {
			if t, ok := execer.(*sql.Tx); ok {
				if rollback {
					t.Rollback()
					return
				}
				t.Commit()
			}
		}()

		// handleError sets the error field on the given result. It returns
		// whether the caller should continue processing or break.
		handleError := func(result *Result, err error) bool {
			result.Error = err.Error()
			allResults = append(allResults, result)
			if tx {
				rollback = true // Will trigger the rollback.
				return false
			}
			return true
		}

		// Create the correct execution object, depending on whether a
		// transaction was requested.
		if tx {
			execer, _ = db.conn.Begin()
		} else {
			execer = db.conn
		}

		// Execute each query.
		for _, q := range queries {
			if q == "" {
				continue
			}

			result := &Result{}

			r, err := execer.Exec(q)
			if err != nil {
				if handleError(result, err) {
					continue
				}
				break
			}

			lid, err := r.LastInsertId()
			if err != nil {
				if handleError(result, err) {
					continue
				}
				break
			}
			result.LastInsertID = lid

			ra, err := r.RowsAffected()
			if err != nil {
				if handleError(result, err) {
					continue
				}
				break
			}
			result.RowsAffected = ra
			allResults = append(allResults, result)
		}

		return nil
	}()

	return allResults, err
}

// Query executes queries that return rows, but don't modify the database.
func (db *DB) Query(queries []string, tx bool) ([]*Rows, error) {
	type Queryer interface {
		Query(query string, args ...interface{}) (*sql.Rows, error)
	}

	var allRows []*Rows
	err := func() (err error) {
		var queryer Queryer
		defer func() {
			// XXX THIS DOESN'T ACTUALLY WORK! Might as WELL JUST COMMIT?
			if t, ok := queryer.(*sql.Tx); ok {
				if err != nil {
					t.Rollback()
					return
				}
				t.Commit()
			}
		}()

		// Create the correct query object, depending on whether a
		// transaction was requested.
		if tx {
			queryer, _ = db.conn.Begin()
		} else {
			queryer = db.conn
		}

	QueryLoop:
		for _, q := range queries {
			if q == "" {
				continue
			}

			rows := &Rows{}

			rs, err := queryer.Query(q)
			if err != nil {
				rows.Error = err.Error()
				allRows = append(allRows, rows)
				continue
			}
			defer rs.Close() // This adds to all defers, right? Nothing leaks? XXX Could consume memory. Perhaps anon would be best.
			columns, err := rs.Columns()
			if err != nil {
				rows.Error = err.Error()
				allRows = append(allRows, rows)
				continue
			}

			rows.Columns = columns
			for rs.Next() {
				// Make a slice of interface{}, and the pointers to each item in the slice.
				values := make([]interface{}, len(rows.Columns))
				ptrs := make([]interface{}, len(rows.Columns))
				for i := range values {
					ptrs[i] = &values[i]
				}

				// Read all the values out.
				err = rs.Scan(ptrs...)
				if err != nil {
					rows.Error = err.Error()
					allRows = append(allRows, rows)
					continue QueryLoop
				}

				// Special case -- convert []uint8 to string. Perhaps this should be a config option.
				for i, v := range values {
					if w, ok := v.([]uint8); ok {
						values[i] = string(w)
					}
				}
				rows.Values = append(rows.Values, values)
			}
			allRows = append(allRows, rows)
		}

		return nil
	}()

	return allRows, err
}
<|endoftext|>


<commit_before>package db

import (
	"os"

	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
)

type Query bson.M

func Connect() (*DB, error) {
	dbHost := os.Getenv("MONGOHQ_URL")
	return New(dbHost)
}

func New(host string) (*DB, error) {
	session, err := mgo.Dial(host)
	if err != nil {
		return nil, err
	}

	session.SetMode(mgo.Monotonic, true)
	db := &DB{session: session}
	err = db.ensureDefaultIndex()

	return db, err
}

type DB struct {
	name    string
	session *mgo.Session
}

func (db *DB) Close() {
	db.session.Close()
}

func (db *DB) DB() *mgo.Database {
	return db.session.DB(db.name)
}

func (db *DB) C(name string) *mgo.Collection {
	return db.DB().C(name)
}

func (db *DB) Upsert(name string, q Query, v interface{}) (updated bool, err error) {
	info, err := db.C(name).Upsert(q, v)
	updated = info != nil && info.UpsertedId != nil
	return
}

func (db *DB) EnsureIndexKey(colName string, keys ...string) error {
	for _, key := range keys {
		index := mgo.Index{
			Key:        []string{key},
			Background: true,
		}

		err := db.C(colName).EnsureIndex(index)
		if err != nil {
			return err
		}
	}

	return nil
}

func (db *DB) EnsureUniqueIndexKey(colName string, keys ...string) error {
	for _, key := range keys {
		index := mgo.Index{
			Key:        []string{key},
			Unique:     true,
			DropDups:   true,
			Background: true,
		}

		err := db.C(colName).EnsureIndex(index)
		if err != nil {
			return err
		}
	}

	return nil
}

func (db *DB) ensureDefaultIndex() error {
	err := db.EnsureUniqueIndexKey("new_builds", "lastbuildid")
	if err != nil {
		return err
	}

	return db.EnsureIndexKey("new_builds", "lastbuildstartedat")
}
<commit_msg>Use MONGO_URL<commit_after>package db

import (
	"os"

	"labix.org/v2/mgo"
	"labix.org/v2/mgo/bson"
)

type Query bson.M

func Connect() (*DB, error) {
	dbHost := os.Getenv("MONGO_URL")
	return New(dbHost)
}

func New(host string) (*DB, error) {
	session, err := mgo.Dial(host)
	if err != nil {
		return nil, err
	}

	session.SetMode(mgo.Monotonic, true)
	db := &DB{session: session}
	err = db.ensureDefaultIndex()

	return db, err
}

type DB struct {
	name    string
	session *mgo.Session
}

func (db *DB) Close() {
	db.session.Close()
}

func (db *DB) DB() *mgo.Database {
	return db.session.DB(db.name)
}

func (db *DB) C(name string) *mgo.Collection {
	return db.DB().C(name)
}

func (db *DB) Upsert(name string, q Query, v interface{}) (updated bool, err error) {
	info, err := db.C(name).Upsert(q, v)
	updated = info != nil && info.UpsertedId != nil
	return
}

func (db *DB) EnsureIndexKey(colName string, keys ...string) error {
	for _, key := range keys {
		index := mgo.Index{
			Key:        []string{key},
			Background: true,
		}

		err := db.C(colName).EnsureIndex(index)
		if err != nil {
			return err
		}
	}

	return nil
}

func (db *DB) EnsureUniqueIndexKey(colName string, keys ...string) error {
	for _, key := range keys {
		index := mgo.Index{
			Key:        []string{key},
			Unique:     true,
			DropDups:   true,
			Background: true,
		}

		err := db.C(colName).EnsureIndex(index)
		if err != nil {
			return err
		}
	}

	return nil
}

func (db *DB) ensureDefaultIndex() error {
	err := db.EnsureUniqueIndexKey("new_builds", "lastbuildid")
	if err != nil {
		return err
	}

	return db.EnsureIndexKey("new_builds", "lastbuildstartedat")
}
<|endoftext|>