<commit_before>/*
(BSD 2-clause license)

Copyright (c) 2014, Shawn Webb
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

   * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
   * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package jail

/*
 * The jail.Jail object implements the VirtualMachine interface
 */

import (
    /* "fmt" */
    "os/exec"
    "github.com/nu7hatch/gouuid"
    "github.com/coopernurse/gorp"
    "github.com/virtbsd/network"
    "github.com/virtbsd/VirtualMachine"
    "github.com/virtbsd/zfs"
)

type MountPoint struct {
    JailUUID string
    Source string
    Destination string
    Options string
    Driver string
    MountOrder int
}

type JailOption struct {
    JailUUID string
    OptionKey string
    OptionValue string
}

type Jail struct {
    UUID string
    Name string
    HostName string
    CreateDate int
    ModificationDate int
    ZFSDataset string

    NetworkDevices []*network.NetworkDevice `db:"-"`
    Mounts []*MountPoint `db:"-"`
    Options []*JailOption `db:"-"`
    BootEnvironments map[string]bool `db:"-"`
    Snapshots []string `db:"-"`
    ZFSDatasetObj *zfs.Dataset `db:"-"`
    Routes []*network.Route `db:"-"`

    Path string `db:"-"`
    Dirty bool `db:"-"`
}

func (jail *Jail) PostGet(s gorp.SqlExecutor) error {
    jail.NetworkDevices = network.GetNetworkDevices(map[string]interface{}{"sqlexecutor": s}, jail)

    s.Select(&jail.Mounts, "select * from MountPoint where JailUUID = ? order by MountOrder", jail.UUID)
    s.Select(&jail.Options, "select * from JailOption where JailUUID = ?", jail.UUID)
    s.Select(&jail.Routes, "select * from Route WHERE VmUUID = ?", jail.UUID)
    if len(jail.HostName) == 0 {
        jail.HostName = jail.Name
    }

    jail.ZFSDatasetObj = zfs.GetDataset(jail.ZFSDataset)

    return nil
}

func (jail *Jail) GetUUID() string {
    return jail.UUID
}

func LookupUUID(db *gorp.DbMap, field map[string]interface{}) string {
    fields := []string{ "name", "hostname" }

    if uuid, ok := field["uuid"]; ok == true {
        return uuid.(string)
    }

    for i := 0; i < len(fields); i++ {
        if val, ok := field[fields[i]]; ok == true {
            myuuid, err := db.SelectStr("select UUID from jail where " + fields[i] + " = ?", val)
            if err == nil {
                return myuuid
            }
        }
    }

    return ""
}

func GetJail(db *gorp.DbMap, field map[string]interface{}) *Jail {
    uuid := LookupUUID(db, field)
    if len(uuid) == 0 {
        return nil
    }

    obj, err := db.Get(Jail{}, uuid)
    if err != nil {
        panic(err)
        return nil
    }

    if obj == nil {
        /* Jail not found */
        return nil
    }

    return obj.(*Jail)
}

func (jail *Jail) Start() error {
    path := jail.GetPath()

    if jail.IsOnline() == true {
        return nil
    }

    cmd := exec.Command("/sbin/mount", "-t", "devfs", "devfs", path + "/dev")
    if err := cmd.Run(); err != nil {
        return err
    }

    cmd = exec.Command("/usr/sbin/jail", "-c", "vnet", "name=" + jail.UUID, "host.hostname=" + jail.HostName, "path=" + path, "persist")
    if err := cmd.Run(); err != nil {
        return err
    }

    for i := range jail.Mounts {
        cmd = exec.Command("/usr/sbin/jexec", jail.UUID, "/sbin/mount")
        if len(jail.Mounts[i].Driver) > 0 {
            cmd.Args = append(cmd.Args, "-t")
            cmd.Args = append(cmd.Args, jail.Mounts[i].Driver)
        }

        if len(jail.Mounts[i].Options) > 0 {
            cmd.Args = append(cmd.Args, "-o")
            cmd.Args = append(cmd.Args, jail.Mounts[i].Options)
        }

        cmd.Args = append(cmd.Args, jail.Mounts[i].Source)
        cmd.Args = append(cmd.Args, path + "/" + jail.Mounts[i].Destination)

        if err := cmd.Run(); err != nil {
            return err
        }
    }

    return nil
}

func (jail *Jail) Stop() error {
    return nil
}

func (jail *Jail) Status() string {
    if jail.IsOnline() {
        return "Online"
    } else {
        return "Offline"
    }
}

func (jail *Jail) CreateSnapshot(snapname string) error {
    return nil
}

func (jail *Jail) RestoreSnapshot(snapname string) error {
    return nil
}

func (jail *Jail) DeleteSnapshot(snapname string) error {
    return nil
}

func (jail *Jail) PrepareHostNetworking() error {
    for i := range jail.NetworkDevices {
        if err := jail.NetworkDevices[i].BringHostOnline(); err != nil {
            return err
        }
    }

    return nil
}

func (jail *Jail) PrepareGuestNetworking() error {
    for i := range jail.NetworkDevices {
        if err := jail.NetworkDevices[i].BringGuestOnline(jail); err != nil {
            return err
        }
    }

    cmd := exec.Command("/usr/sbin/jexec", jail.UUID, "/sbin/ifconfig", "lo0", "inet", "127.0.0.1", "up")
    if err := cmd.Run(); err != nil {
        return err
    }

    return nil
}

func (jail *Jail) NetworkingStatus() string {
    return ""
}

func (jail *Jail) GetPath() string {
    if len(jail.Path) > 0 {
        return jail.Path
    }

    path, err := zfs.GetDatasetPath(jail.ZFSDataset)
    if err != nil {
        panic(err)
        return ""
    }

    jail.Path = path

    return path
}

func (jail *Jail) IsOnline() bool {
    cmd := exec.Command("/usr/sbin/jls", "-j", jail.UUID)
    err := cmd.Run()
    if err == nil {
        return true
    }

    return false
}

func (jail *Jail) Validate() error {
    if len(jail.UUID) == 0 {
        /* If we haven't been persisted (this is a new jail), then we don't have a UUID */
        myuuid, _ := uuid.NewV4()
        jail.UUID = myuuid.String()
    }

    if _, err := uuid.ParseHex(jail.UUID); err != nil {
        return VirtualMachine.VirtualMachineError{"Invalid UUID", jail}
    }

    if len(jail.GetPath()) == 0 {
        return VirtualMachine.VirtualMachineError{"Invalid Path, ZFS Dataset: " + jail.ZFSDataset, jail}
    }

    return nil
}

func (jail *Jail) Persist(db *gorp.DbMap) error {
    if err := jail.Validate(); err != nil {
        return err
    }

    return nil
}

func (jail *Jail) Delete(db *gorp.DbMap) error {
    return nil
}

func (jail *Jail) Archive(archivename string) error {
    return nil
}
<commit_msg>Support boot environments<commit_after>/*
(BSD 2-clause license)

Copyright (c) 2014, Shawn Webb
All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

   * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
   * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

package jail

/*
 * The jail.Jail object implements the VirtualMachine interface
 */

import (
    "strings"
    "strconv"
    "fmt"
    "os/exec"
    "github.com/nu7hatch/gouuid"
    "github.com/coopernurse/gorp"
    "github.com/virtbsd/network"
    "github.com/virtbsd/VirtualMachine"
    "github.com/virtbsd/zfs"
    "github.com/virtbsd/util"
)

type MountPoint struct {
    JailUUID string
    Source string
    Destination string
    Options string
    Driver string
    MountOrder int
}

type JailOption struct {
    JailUUID string
    OptionKey string
    OptionValue string
}

type Jail struct {
    UUID string
    Name string
    HostName string
    CreateDate int
    ModificationDate int
    ZFSDataset string

    NetworkDevices []*network.NetworkDevice `db:"-"`
    Mounts []*MountPoint `db:"-"`
    Options []*JailOption `db:"-"`
    BootEnvironments map[string]bool `db:"-"`
    Snapshots []string `db:"-"`
    ZFSDatasetObj *zfs.Dataset `db:"-"`
    Routes []*network.Route `db:"-"`

    Path string `db:"-"`
    Dirty bool `db:"-"`
}

func (jail *Jail) PostGet(s gorp.SqlExecutor) error {
    jail.NetworkDevices = network.GetNetworkDevices(map[string]interface{}{"sqlexecutor": s}, jail)

    s.Select(&jail.Mounts, "select * from MountPoint where JailUUID = ? order by MountOrder", jail.UUID)
    s.Select(&jail.Options, "select * from JailOption where JailUUID = ?", jail.UUID)
    s.Select(&jail.Routes, "select * from Route WHERE VmUUID = ?", jail.UUID)
    if len(jail.HostName) == 0 {
        jail.HostName = jail.Name
    }

    jail.BootEnvironments = make(map[string]bool)

    jail.ZFSDatasetObj = zfs.GetDataset(jail.ZFSDataset)
    for _, rootDataset := range jail.ZFSDatasetObj.Children {
        if strings.HasPrefix(rootDataset.DatasetPath, jail.ZFSDataset + "/ROOT") {
            for _, dataset := range rootDataset.Children {
                if _, ok := dataset.Options["jailadmin:be_active"]; ok == true {
                    jail.BootEnvironments[dataset.DatasetPath], _ = strconv.ParseBool(dataset.Options["jailadmin:be_active"])
                }
            }

            break
        }
    }

    return nil
}

func (jail *Jail) GetUUID() string {
    return jail.UUID
}

func LookupUUID(db *gorp.DbMap, field map[string]interface{}) string {
    fields := []string{ "name", "hostname" }

    if uuid, ok := field["uuid"]; ok == true {
        return uuid.(string)
    }

    for i := 0; i < len(fields); i++ {
        if val, ok := field[fields[i]]; ok == true {
            myuuid, err := db.SelectStr("select UUID from jail where " + fields[i] + " = ?", val)
            if err == nil {
                return myuuid
            }
        }
    }

    return ""
}

func GetJail(db *gorp.DbMap, field map[string]interface{}) *Jail {
    uuid := LookupUUID(db, field)
    if len(uuid) == 0 {
        return nil
    }

    obj, err := db.Get(Jail{}, uuid)
    if err != nil {
        panic(err)
        return nil
    }

    if obj == nil {
        /* Jail not found */
        return nil
    }

    return obj.(*Jail)
}

func (jail *Jail) Start() error {
    path, err := jail.GetPath()
    if err != nil {
        return err
    }

    if jail.IsOnline() == true {
        return nil
    }

    cmd := exec.Command("/sbin/mount", "-t", "devfs", "devfs", path + "/dev")
    if rawoutput, err := cmd.CombinedOutput(); err != nil {
        return fmt.Errorf("Error mount devfs in jail: %s", virtbsdutil.ByteToString(rawoutput))
        return err
    }

    cmd = exec.Command("/usr/sbin/jail", "-c", "vnet", "name=" + jail.UUID, "host.hostname=" + jail.HostName, "path=" + path, "persist")
    for i := range jail.Options {
        opt := jail.Options[i].OptionKey
        if len(jail.Options[i].OptionValue) > 0 {
            opt += jail.Options[i].OptionValue
        }

        cmd.Args = append(cmd.Args, opt)
    }

    if rawoutput, err := cmd.CombinedOutput(); err != nil {
        return fmt.Errorf("Error starting jail: %s", virtbsdutil.ByteToString(rawoutput))
    }

    for i := range jail.Mounts {
        cmd = exec.Command("/usr/sbin/jexec", jail.UUID, "/sbin/mount")
        if len(jail.Mounts[i].Driver) > 0 {
            cmd.Args = append(cmd.Args, "-t")
            cmd.Args = append(cmd.Args, jail.Mounts[i].Driver)
        }

        if len(jail.Mounts[i].Options) > 0 {
            cmd.Args = append(cmd.Args, "-o")
            cmd.Args = append(cmd.Args, jail.Mounts[i].Options)
        }

        cmd.Args = append(cmd.Args, jail.Mounts[i].Source)
        cmd.Args = append(cmd.Args, path + "/" + jail.Mounts[i].Destination)

        if rawoutput, err := cmd.CombinedOutput(); err != nil {
            return fmt.Errorf("Error mounting %s: %s", jail.Mounts[i].Destination, virtbsdutil.ByteToString(rawoutput))
        }
    }

    return nil
}

func (jail *Jail) Stop() error {
    path, err := jail.GetPath()
    if err != nil {
        return err
    }

    if jail.IsOnline() == false {
        return nil
    }

    cmd := exec.Command("/usr/sbin/jail", "-r", jail.UUID)
    if err := cmd.Run(); err != nil {
        return nil
    }

    for i := range jail.Mounts {
        cmd = exec.Command("/sbin/umount", path + "/" + jail.Mounts[i].Destination)
        if rawoutput, err := cmd.CombinedOutput(); err != nil {
            return fmt.Errorf("/sbin/unmount %s/%s: %s", path, jail.Mounts[i].Destination, virtbsdutil.ByteToString(rawoutput))
        }
    }

    for i := range jail.NetworkDevices {
        if err := jail.NetworkDevices[i].BringOffline(); err != nil {
            return err
        }
    }

    cmd = exec.Command("/sbin/umount", path + "/dev")
    if rawoutput, err := cmd.CombinedOutput(); err != nil {
        return fmt.Errorf("/sbin/unmount %s/dev: %s\n", path, virtbsdutil.ByteToString(rawoutput))
    }

    return nil
}

func (jail *Jail) Status() string {
    if jail.IsOnline() {
        return "Online"
    } else {
        return "Offline"
    }
}

func (jail *Jail) CreateSnapshot(snapname string) error {
    return nil
}

func (jail *Jail) RestoreSnapshot(snapname string) error {
    return nil
}

func (jail *Jail) DeleteSnapshot(snapname string) error {
    return nil
}

func (jail *Jail) PrepareHostNetworking() error {
    for i := range jail.NetworkDevices {
        if err := jail.NetworkDevices[i].BringHostOnline(); err != nil {
            return err
        }
    }

    return nil
}

func (jail *Jail) PrepareGuestNetworking() error {
    for i := range jail.NetworkDevices {
        if err := jail.NetworkDevices[i].BringGuestOnline(jail); err != nil {
            return err
        }
    }

    cmd := exec.Command("/usr/sbin/jexec", jail.UUID, "/sbin/ifconfig", "lo0", "inet", "127.0.0.1", "up")
    if err := cmd.Run(); err != nil {
        return err
    }

    return nil
}

func (jail *Jail) NetworkingStatus() string {
    return ""
}

func (jail *Jail) GetPath() (string, error) {
    if len(jail.Path) > 0 {
        return jail.Path, nil
    }

    if len(jail.BootEnvironments) > 0 {
        for k, v := range jail.BootEnvironments {
            if v == true {
                path, err := zfs.GetDatasetPath(k)
                if err == nil && len(path) > 0 {
                    jail.Path = path
                    return path, nil
                }
            }
        }

        return "", fmt.Errorf("Boot environments enabled. No active boot environment found.")
    }

    path, err := zfs.GetDatasetPath(jail.ZFSDataset)
    if err != nil {
        return "", err
    }

    jail.Path = path

    return path, nil
}

func (jail *Jail) IsOnline() bool {
    cmd := exec.Command("/usr/sbin/jls", "-j", jail.UUID)
    err := cmd.Run()
    if err == nil {
        return true
    }

    return false
}

func (jail *Jail) Validate() error {
    if len(jail.UUID) == 0 {
        /* If we haven't been persisted (this is a new jail), then we don't have a UUID */
        myuuid, _ := uuid.NewV4()
        jail.UUID = myuuid.String()
    }

    if _, err := uuid.ParseHex(jail.UUID); err != nil {
        return VirtualMachine.VirtualMachineError{"Invalid UUID", jail}
    }

    if path, err := jail.GetPath(); err != nil || len(path) == 0 {
        return VirtualMachine.VirtualMachineError{"Invalid Path, ZFS Dataset: " + jail.ZFSDataset, jail}
    }

    return nil
}

func (jail *Jail) Persist(db *gorp.DbMap) error {
    if err := jail.Validate(); err != nil {
        return err
    }

    return nil
}

func (jail *Jail) Delete(db *gorp.DbMap) error {
    return nil
}

func (jail *Jail) Archive(archivename string) error {
    return nil
}
<|endoftext|>


<commit_before>package busetabot

import (
	"context"
	"encoding/json"
	"log"
	"strconv"
	"sync"
	"time"

	"github.com/getsentry/raven-go"
	"github.com/pkg/errors"
	"github.com/yi-jiayu/datamall/v3"
	"github.com/yi-jiayu/telegram-bot-api"
	aelog "google.golang.org/appengine/log"

	"github.com/yi-jiayu/bus-eta-bot/v4/telegram"
)

// ResponseBufferSize is the size of the channel used to queue responses to be sent via the Telegram Bot API.
const ResponseBufferSize = 10

const MaxMessageLength = 35

var handlers = Handlers{
	CommandHandlers:           commandHandlers,
	FallbackCommandHandler:    FallbackCommandHandler,
	TextHandler:               TextHandler,
	LocationHandler:           LocationHandler,
	CallbackQueryHandlers:     callbackQueryHandlers,
	InlineQueryHandler:        InlineQueryHandler,
	ChosenInlineResultHandler: ChosenInlineResultHandler,
	MessageErrorHandler:       messageErrorHandler,
	CallbackErrorHandler:      callbackErrorHandler,
}

// BusStopRepository provides bus stop information.
type BusStopRepository interface {
	Get(ID string) *BusStop
	Nearby(ctx context.Context, lat, lon, radius float64, limit int) (nearby []NearbyBusStop)
	Search(ctx context.Context, query string, limit int) []BusStop
}

type UserRepository interface {
	UpdateUserLastSeenTime(ctx context.Context, userID int, t time.Time) error
	GetUserFavourites(ctx context.Context, userID int) (favourites []string, err error)
	SetUserFavourites(ctx context.Context, userID int, favourites []string) error
}

type ETAService interface {
	GetBusArrival(busStopCode string, serviceNo string) (datamall.BusArrival, error)
}

type TelegramService interface {
	Do(request telegram.Request) error
}

// BusEtaBot contains all the bot's dependencies
type BusEtaBot struct {
	Handlers            Handlers
	Telegram            *tgbotapi.BotAPI
	Datamall            ETAService
	StreetView          StreetViewProvider
	MeasurementProtocol *MeasurementProtocolClient
	NowFunc             func() time.Time
	BusStops            BusStopRepository
	Users               UserRepository
	TelegramService     TelegramService
	Sentry              *raven.Client
}

// Handlers contains all the handlers used by the bot.
type Handlers struct {
	CommandHandlers           map[string]CommandHandler
	FallbackCommandHandler    MessageHandler
	TextHandler               MessageHandler
	LocationHandler           MessageHandler
	CallbackQueryHandlers     map[string]CallbackQueryHandler
	InlineQueryHandler        func(ctx context.Context, bot *BusEtaBot, ilq *tgbotapi.InlineQuery) error
	ChosenInlineResultHandler func(ctx context.Context, bot *BusEtaBot, cir *tgbotapi.ChosenInlineResult) error
	MessageErrorHandler       func(ctx context.Context, bot *BusEtaBot, message *tgbotapi.Message, err error)
	CallbackErrorHandler      func(ctx context.Context, bot *BusEtaBot, query *tgbotapi.CallbackQuery, err error)
}

type Response struct {
	Request telegram.Request
	Error   error
}

func ok(r telegram.Request) Response {
	return Response{
		Request: r,
	}
}

func notOk(err error) Response {
	return Response{
		Error: err,
	}
}

// DefaultHandlers returns a default set of handlers.
func DefaultHandlers() Handlers {
	return handlers
}

// NewBot creates a new Bus Eta Bot with the provided tgbotapi.BotAPI and datamall.APIClient.
func NewBot(handlers Handlers, tg *tgbotapi.BotAPI, dm ETAService, sv *StreetViewAPI, mp *MeasurementProtocolClient) BusEtaBot {
	bot := BusEtaBot{
		Handlers:            handlers,
		Telegram:            tg,
		Datamall:            dm,
		StreetView:          sv,
		MeasurementProtocol: mp,
	}
	bot.NowFunc = time.Now
	sentry, err := raven.New("")
	if err != nil {
		log.Printf("error creating sentry client: %v\n", err)
	}
	bot.Sentry = sentry
	return bot
}

// Dispatch makes requests to the Telegram Bot API for each response in responses.
func (bot *BusEtaBot) Dispatch(ctx context.Context, responses <-chan Response) {
	var wg sync.WaitGroup
	for r := range responses {
		err := r.Error
		if err != nil {
			aelog.Errorf(ctx, "%+v", err)
			bot.Sentry.CaptureError(err, nil)
		} else {
			wg.Add(1)
			go func(request telegram.Request) {
				defer wg.Done()
				err := bot.TelegramService.Do(request)
				if err != nil {
					aelog.Errorf(ctx, "%+v", err)
					bot.Sentry.CaptureError(err, nil)
				}
			}(r.Request)
		}
	}
	wg.Wait()
}

// HandleUpdate dispatches an incoming update to the corresponding handler depending on the update type
func (bot *BusEtaBot) HandleUpdate(ctx context.Context, update *tgbotapi.Update) {
	var wg sync.WaitGroup
	defer wg.Wait()

	if message := update.Message; message != nil {
		if bot.Users != nil {
			wg.Add(1)
			go func() {
				defer wg.Done()
				err := bot.Users.UpdateUserLastSeenTime(ctx, message.From.ID, time.Now())
				if err != nil {
					aelog.Warningf(ctx, "%+v", err)
				}
			}()
		}
		bot.handleMessage(ctx, message)
		return
	}

	if cbq := update.CallbackQuery; cbq != nil {
		if bot.Users != nil {
			wg.Add(1)
			go func() {
				defer wg.Done()
				err := bot.Users.UpdateUserLastSeenTime(ctx, cbq.From.ID, time.Now())
				if err != nil {
					aelog.Warningf(ctx, "%+v", err)
				}
			}()
		}

		if bot.Handlers.CallbackQueryHandlers != nil {
			bot.handleCallbackQuery(ctx, cbq)
		}
		return
	}

	if ilq := update.InlineQuery; ilq != nil {
		if bot.Users != nil {
			wg.Add(1)
			go func() {
				defer wg.Done()
				err := bot.Users.UpdateUserLastSeenTime(ctx, ilq.From.ID, time.Now())
				if err != nil {
					aelog.Warningf(ctx, "%+v", err)
				}
			}()
		}

		if bot.Handlers.InlineQueryHandler != nil {
			bot.handleInlineQuery(ctx, ilq)
		}
		return
	}

	if cir := update.ChosenInlineResult; cir != nil {
		bot.handleChosenInlineResult(ctx, cir)
		return
	}
}

func (bot *BusEtaBot) handleMessage(ctx context.Context, message *tgbotapi.Message) {
	if bot.Sentry != nil {
		bot.Sentry.SetUserContext(&raven.User{
			ID: strconv.Itoa(message.From.ID),
		})
	}

	// ignore messages longer than a certain length
	if len(message.Text) > MaxMessageLength {
		return
	}

	if command := message.Command(); command != "" {
		bot.handleCommand(ctx, command, message)
		return
	}

	if text := message.Text; text != "" {
		bot.handleText(ctx, message)
		return
	}

	if location := message.Location; location != nil {
		bot.handleLocation(ctx, message)
		return
	}
}

func (bot *BusEtaBot) handleCommand(ctx context.Context, command string, message *tgbotapi.Message) {
	if handler, exists := bot.Handlers.CommandHandlers[command]; exists {
		responses := make(chan Response, ResponseBufferSize)
		go handler(ctx, bot, message, responses)
		bot.Dispatch(ctx, responses)
	} else {
		err := bot.Handlers.FallbackCommandHandler(ctx, bot, message)
		if err != nil {
			messageErrorHandler(ctx, bot, message, err)
		}
	}
}

func (bot *BusEtaBot) handleText(ctx context.Context, message *tgbotapi.Message) {
	err := bot.Handlers.TextHandler(ctx, bot, message)
	if err != nil {
		messageErrorHandler(ctx, bot, message, err)
	}
}

func (bot *BusEtaBot) handleLocation(ctx context.Context, message *tgbotapi.Message) {
	err := bot.Handlers.LocationHandler(ctx, bot, message)
	if err != nil {
		messageErrorHandler(ctx, bot, message, err)
	}
}

func (bot *BusEtaBot) handleCallbackQuery(ctx context.Context, cbq *tgbotapi.CallbackQuery) {
	if bot.Sentry != nil {
		bot.Sentry.SetUserContext(&raven.User{
			ID: strconv.Itoa(cbq.From.ID),
		})
	}
	var data map[string]interface{}
	err := json.Unmarshal([]byte(cbq.Data), &data)
	if err != nil {
		callbackErrorHandler(ctx, bot, cbq, err)
		return
	}

	if cbqType, ok := data["t"].(string); ok {
		if handler, ok := bot.Handlers.CallbackQueryHandlers[cbqType]; ok {
			responses := make(chan Response, ResponseBufferSize)
			go handler(ctx, bot, cbq, responses)
			bot.Dispatch(ctx, responses)
		}
	} else {
		callbackErrorHandler(ctx, bot, cbq, errors.New("unrecognised callback query"))
	}
}

func (bot *BusEtaBot) handleInlineQuery(ctx context.Context, ilq *tgbotapi.InlineQuery) {
	err := bot.Handlers.InlineQueryHandler(ctx, bot, ilq)
	if err != nil {
		aelog.Errorf(ctx, "%+v", err)
		bot.Sentry.CaptureError(err, nil)
	}
}

func (bot *BusEtaBot) handleChosenInlineResult(ctx context.Context, cir *tgbotapi.ChosenInlineResult) {
	err := bot.Handlers.ChosenInlineResultHandler(ctx, bot, cir)
	if err != nil {
		aelog.Errorf(ctx, "%+v", err)
	}
}

// LogEvent logs an event to the Measurement Protocol if a MeasurementProtocolClient is set on the bot.
func (bot *BusEtaBot) LogEvent(ctx context.Context, user *tgbotapi.User, category, action, label string) {
	if bot.MeasurementProtocol != nil {
		_, err := bot.MeasurementProtocol.LogEvent(user.ID, user.LanguageCode, category, action, label)
		if err != nil {
			aelog.Errorf(ctx, "error while logging event: %v", err)
		}
	}
}
<commit_msg>Log and send an event when messages are ignored<commit_after>package busetabot

import (
	"context"
	"encoding/json"
	"log"
	"strconv"
	"sync"
	"time"

	"github.com/getsentry/raven-go"
	"github.com/pkg/errors"
	"github.com/yi-jiayu/datamall/v3"
	"github.com/yi-jiayu/telegram-bot-api"
	aelog "google.golang.org/appengine/log"

	"github.com/yi-jiayu/bus-eta-bot/v4/telegram"
)

// ResponseBufferSize is the size of the channel used to queue responses to be sent via the Telegram Bot API.
const ResponseBufferSize = 10

const MaxMessageLength = 35

var handlers = Handlers{
	CommandHandlers:           commandHandlers,
	FallbackCommandHandler:    FallbackCommandHandler,
	TextHandler:               TextHandler,
	LocationHandler:           LocationHandler,
	CallbackQueryHandlers:     callbackQueryHandlers,
	InlineQueryHandler:        InlineQueryHandler,
	ChosenInlineResultHandler: ChosenInlineResultHandler,
	MessageErrorHandler:       messageErrorHandler,
	CallbackErrorHandler:      callbackErrorHandler,
}

// BusStopRepository provides bus stop information.
type BusStopRepository interface {
	Get(ID string) *BusStop
	Nearby(ctx context.Context, lat, lon, radius float64, limit int) (nearby []NearbyBusStop)
	Search(ctx context.Context, query string, limit int) []BusStop
}

type UserRepository interface {
	UpdateUserLastSeenTime(ctx context.Context, userID int, t time.Time) error
	GetUserFavourites(ctx context.Context, userID int) (favourites []string, err error)
	SetUserFavourites(ctx context.Context, userID int, favourites []string) error
}

type ETAService interface {
	GetBusArrival(busStopCode string, serviceNo string) (datamall.BusArrival, error)
}

type TelegramService interface {
	Do(request telegram.Request) error
}

// BusEtaBot contains all the bot's dependencies
type BusEtaBot struct {
	Handlers            Handlers
	Telegram            *tgbotapi.BotAPI
	Datamall            ETAService
	StreetView          StreetViewProvider
	MeasurementProtocol *MeasurementProtocolClient
	NowFunc             func() time.Time
	BusStops            BusStopRepository
	Users               UserRepository
	TelegramService     TelegramService
	Sentry              *raven.Client
}

// Handlers contains all the handlers used by the bot.
type Handlers struct {
	CommandHandlers           map[string]CommandHandler
	FallbackCommandHandler    MessageHandler
	TextHandler               MessageHandler
	LocationHandler           MessageHandler
	CallbackQueryHandlers     map[string]CallbackQueryHandler
	InlineQueryHandler        func(ctx context.Context, bot *BusEtaBot, ilq *tgbotapi.InlineQuery) error
	ChosenInlineResultHandler func(ctx context.Context, bot *BusEtaBot, cir *tgbotapi.ChosenInlineResult) error
	MessageErrorHandler       func(ctx context.Context, bot *BusEtaBot, message *tgbotapi.Message, err error)
	CallbackErrorHandler      func(ctx context.Context, bot *BusEtaBot, query *tgbotapi.CallbackQuery, err error)
}

type Response struct {
	Request telegram.Request
	Error   error
}

func ok(r telegram.Request) Response {
	return Response{
		Request: r,
	}
}

func notOk(err error) Response {
	return Response{
		Error: err,
	}
}

// DefaultHandlers returns a default set of handlers.
func DefaultHandlers() Handlers {
	return handlers
}

// NewBot creates a new Bus Eta Bot with the provided tgbotapi.BotAPI and datamall.APIClient.
func NewBot(handlers Handlers, tg *tgbotapi.BotAPI, dm ETAService, sv *StreetViewAPI, mp *MeasurementProtocolClient) BusEtaBot {
	bot := BusEtaBot{
		Handlers:            handlers,
		Telegram:            tg,
		Datamall:            dm,
		StreetView:          sv,
		MeasurementProtocol: mp,
	}
	bot.NowFunc = time.Now
	sentry, err := raven.New("")
	if err != nil {
		log.Printf("error creating sentry client: %v\n", err)
	}
	bot.Sentry = sentry
	return bot
}

// Dispatch makes requests to the Telegram Bot API for each response in responses.
func (bot *BusEtaBot) Dispatch(ctx context.Context, responses <-chan Response) {
	var wg sync.WaitGroup
	for r := range responses {
		err := r.Error
		if err != nil {
			aelog.Errorf(ctx, "%+v", err)
			bot.Sentry.CaptureError(err, nil)
		} else {
			wg.Add(1)
			go func(request telegram.Request) {
				defer wg.Done()
				err := bot.TelegramService.Do(request)
				if err != nil {
					aelog.Errorf(ctx, "%+v", err)
					bot.Sentry.CaptureError(err, nil)
				}
			}(r.Request)
		}
	}
	wg.Wait()
}

// HandleUpdate dispatches an incoming update to the corresponding handler depending on the update type
func (bot *BusEtaBot) HandleUpdate(ctx context.Context, update *tgbotapi.Update) {
	var wg sync.WaitGroup
	defer wg.Wait()

	if message := update.Message; message != nil {
		if bot.Users != nil {
			wg.Add(1)
			go func() {
				defer wg.Done()
				err := bot.Users.UpdateUserLastSeenTime(ctx, message.From.ID, time.Now())
				if err != nil {
					aelog.Warningf(ctx, "%+v", err)
				}
			}()
		}
		bot.handleMessage(ctx, message)
		return
	}

	if cbq := update.CallbackQuery; cbq != nil {
		if bot.Users != nil {
			wg.Add(1)
			go func() {
				defer wg.Done()
				err := bot.Users.UpdateUserLastSeenTime(ctx, cbq.From.ID, time.Now())
				if err != nil {
					aelog.Warningf(ctx, "%+v", err)
				}
			}()
		}

		if bot.Handlers.CallbackQueryHandlers != nil {
			bot.handleCallbackQuery(ctx, cbq)
		}
		return
	}

	if ilq := update.InlineQuery; ilq != nil {
		if bot.Users != nil {
			wg.Add(1)
			go func() {
				defer wg.Done()
				err := bot.Users.UpdateUserLastSeenTime(ctx, ilq.From.ID, time.Now())
				if err != nil {
					aelog.Warningf(ctx, "%+v", err)
				}
			}()
		}

		if bot.Handlers.InlineQueryHandler != nil {
			bot.handleInlineQuery(ctx, ilq)
		}
		return
	}

	if cir := update.ChosenInlineResult; cir != nil {
		bot.handleChosenInlineResult(ctx, cir)
		return
	}
}

func (bot *BusEtaBot) handleMessage(ctx context.Context, message *tgbotapi.Message) {
	if bot.Sentry != nil {
		bot.Sentry.SetUserContext(&raven.User{
			ID: strconv.Itoa(message.From.ID),
		})
	}

	// ignore messages longer than a certain length
	if len(message.Text) > MaxMessageLength {
		go bot.LogEvent(ctx, message.From, CategoryMessage, ActionIgnoredTextMessage, message.Chat.Type)
		aelog.Infof(ctx, "ignoring long message")
		return
	}

	if command := message.Command(); command != "" {
		bot.handleCommand(ctx, command, message)
		return
	}

	if text := message.Text; text != "" {
		bot.handleText(ctx, message)
		return
	}

	if location := message.Location; location != nil {
		bot.handleLocation(ctx, message)
		return
	}
}

func (bot *BusEtaBot) handleCommand(ctx context.Context, command string, message *tgbotapi.Message) {
	if handler, exists := bot.Handlers.CommandHandlers[command]; exists {
		responses := make(chan Response, ResponseBufferSize)
		go handler(ctx, bot, message, responses)
		bot.Dispatch(ctx, responses)
	} else {
		err := bot.Handlers.FallbackCommandHandler(ctx, bot, message)
		if err != nil {
			messageErrorHandler(ctx, bot, message, err)
		}
	}
}

func (bot *BusEtaBot) handleText(ctx context.Context, message *tgbotapi.Message) {
	err := bot.Handlers.TextHandler(ctx, bot, message)
	if err != nil {
		messageErrorHandler(ctx, bot, message, err)
	}
}

func (bot *BusEtaBot) handleLocation(ctx context.Context, message *tgbotapi.Message) {
	err := bot.Handlers.LocationHandler(ctx, bot, message)
	if err != nil {
		messageErrorHandler(ctx, bot, message, err)
	}
}

func (bot *BusEtaBot) handleCallbackQuery(ctx context.Context, cbq *tgbotapi.CallbackQuery) {
	if bot.Sentry != nil {
		bot.Sentry.SetUserContext(&raven.User{
			ID: strconv.Itoa(cbq.From.ID),
		})
	}
	var data map[string]interface{}
	err := json.Unmarshal([]byte(cbq.Data), &data)
	if err != nil {
		callbackErrorHandler(ctx, bot, cbq, err)
		return
	}

	if cbqType, ok := data["t"].(string); ok {
		if handler, ok := bot.Handlers.CallbackQueryHandlers[cbqType]; ok {
			responses := make(chan Response, ResponseBufferSize)
			go handler(ctx, bot, cbq, responses)
			bot.Dispatch(ctx, responses)
		}
	} else {
		callbackErrorHandler(ctx, bot, cbq, errors.New("unrecognised callback query"))
	}
}

func (bot *BusEtaBot) handleInlineQuery(ctx context.Context, ilq *tgbotapi.InlineQuery) {
	err := bot.Handlers.InlineQueryHandler(ctx, bot, ilq)
	if err != nil {
		aelog.Errorf(ctx, "%+v", err)
		bot.Sentry.CaptureError(err, nil)
	}
}

func (bot *BusEtaBot) handleChosenInlineResult(ctx context.Context, cir *tgbotapi.ChosenInlineResult) {
	err := bot.Handlers.ChosenInlineResultHandler(ctx, bot, cir)
	if err != nil {
		aelog.Errorf(ctx, "%+v", err)
	}
}

// LogEvent logs an event to the Measurement Protocol if a MeasurementProtocolClient is set on the bot.
func (bot *BusEtaBot) LogEvent(ctx context.Context, user *tgbotapi.User, category, action, label string) {
	if bot.MeasurementProtocol != nil {
		_, err := bot.MeasurementProtocol.LogEvent(user.ID, user.LanguageCode, category, action, label)
		if err != nil {
			aelog.Errorf(ctx, "error while logging event: %v", err)
		}
	}
}
<|endoftext|>


<commit_before>/*
   Copyright 2014 Outbrain Inc.

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

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

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

package inst

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/github/orchestrator/go/config"
	"github.com/github/orchestrator/go/kv"
)

func GetClusterMasterKVKey(clusterAlias string) string {
	return fmt.Sprintf("%s%s", config.Config.KVClusterMasterPrefix, clusterAlias)
}

func getClusterMasterKVPair(clusterAlias string, masterKey *InstanceKey) *kv.KVPair {
	if clusterAlias == "" {
		return nil
	}
	if masterKey == nil {
		return nil
	}
	return kv.NewKVPair(GetClusterMasterKVKey(clusterAlias), masterKey.StringCode())
}

func GetClusterMasterKVPairs(clusterAlias string, masterKey *InstanceKey) (kvPairs [](*kv.KVPair)) {
	masterKVPair := getClusterMasterKVPair(clusterAlias, masterKey)
	if masterKVPair == nil {
		return kvPairs
	}
	kvPairs = append(kvPairs, masterKVPair)

	addPair := func(keySuffix, value string) {
		key := fmt.Sprintf("%s/%s", masterKVPair.Key, keySuffix)
		kvPairs = append(kvPairs, kv.NewKVPair(key, value))
	}

	addPair("hostname", masterKey.Hostname)
	addPair("port", fmt.Sprintf("%d", masterKey.Port))
	if ipv4, ipv6, err := readHostnameIPs(masterKey.Hostname); err == nil {
		addPair("ipv4", ipv4)
		addPair("ipv6", ipv6)
	}
	return kvPairs
}

// mappedClusterNameToAlias attempts to match a cluster with an alias based on
// configured ClusterNameToAlias map
func mappedClusterNameToAlias(clusterName string) string {
	for pattern, alias := range config.Config.ClusterNameToAlias {
		if pattern == "" {
			// sanity
			continue
		}
		if matched, _ := regexp.MatchString(pattern, clusterName); matched {
			return alias
		}
	}
	return ""
}

// ClusterInfo makes for a cluster status/info summary
type ClusterInfo struct {
	ClusterName                            string
	ClusterAlias                           string // Human friendly alias
	ClusterDomain                          string // CNAME/VIP/A-record/whatever of the master of this cluster
	CountInstances                         uint
	HeuristicLag                           int64
	HasAutomatedMasterRecovery             bool
	HasAutomatedIntermediateMasterRecovery bool
}

// ReadRecoveryInfo
func (this *ClusterInfo) ReadRecoveryInfo() {
	this.HasAutomatedMasterRecovery = this.filtersMatchCluster(config.Config.RecoverMasterClusterFilters)
	this.HasAutomatedIntermediateMasterRecovery = this.filtersMatchCluster(config.Config.RecoverIntermediateMasterClusterFilters)
}

// filtersMatchCluster will see whether the given filters match the given cluster details
func (this *ClusterInfo) filtersMatchCluster(filters []string) bool {
	for _, filter := range filters {
		if filter == this.ClusterName {
			return true
		}
		if filter == this.ClusterAlias {
			return true
		}
		if strings.HasPrefix(filter, "alias=") {
			// Match by exact cluster alias name
			alias := strings.SplitN(filter, "=", 2)[1]
			if alias == this.ClusterAlias {
				return true
			}
		} else if strings.HasPrefix(filter, "alias~=") {
			// Match by cluster alias regex
			aliasPattern := strings.SplitN(filter, "~=", 2)[1]
			if matched, _ := regexp.MatchString(aliasPattern, this.ClusterAlias); matched {
				return true
			}
		} else if filter == "*" {
			return true
		} else if matched, _ := regexp.MatchString(filter, this.ClusterName); matched && filter != "" {
			return true
		}
	}
	return false
}

// ApplyClusterAlias updates the given clusterInfo's ClusterAlias property
func (this *ClusterInfo) ApplyClusterAlias() {
	if this.ClusterAlias != "" && this.ClusterAlias != this.ClusterName {
		// Already has an alias; abort
		return
	}
	if alias := mappedClusterNameToAlias(this.ClusterName); alias != "" {
		this.ClusterAlias = alias
	}
}
<commit_msg>comments<commit_after>/*
   Copyright 2014 Outbrain Inc.

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

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

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

package inst

import (
	"fmt"
	"regexp"
	"strings"

	"github.com/github/orchestrator/go/config"
	"github.com/github/orchestrator/go/kv"
)

func GetClusterMasterKVKey(clusterAlias string) string {
	return fmt.Sprintf("%s%s", config.Config.KVClusterMasterPrefix, clusterAlias)
}

func getClusterMasterKVPair(clusterAlias string, masterKey *InstanceKey) *kv.KVPair {
	if clusterAlias == "" {
		return nil
	}
	if masterKey == nil {
		return nil
	}
	return kv.NewKVPair(GetClusterMasterKVKey(clusterAlias), masterKey.StringCode())
}

// GetClusterMasterKVPairs returns all KV pairs associated with a master. This includes the
// full identity of the master as well as a breakdown by hostname, port, ipv4, ipv6
func GetClusterMasterKVPairs(clusterAlias string, masterKey *InstanceKey) (kvPairs [](*kv.KVPair)) {
	masterKVPair := getClusterMasterKVPair(clusterAlias, masterKey)
	if masterKVPair == nil {
		return kvPairs
	}
	kvPairs = append(kvPairs, masterKVPair)

	addPair := func(keySuffix, value string) {
		key := fmt.Sprintf("%s/%s", masterKVPair.Key, keySuffix)
		kvPairs = append(kvPairs, kv.NewKVPair(key, value))
	}

	addPair("hostname", masterKey.Hostname)
	addPair("port", fmt.Sprintf("%d", masterKey.Port))
	if ipv4, ipv6, err := readHostnameIPs(masterKey.Hostname); err == nil {
		addPair("ipv4", ipv4)
		addPair("ipv6", ipv6)
	}
	return kvPairs
}

// mappedClusterNameToAlias attempts to match a cluster with an alias based on
// configured ClusterNameToAlias map
func mappedClusterNameToAlias(clusterName string) string {
	for pattern, alias := range config.Config.ClusterNameToAlias {
		if pattern == "" {
			// sanity
			continue
		}
		if matched, _ := regexp.MatchString(pattern, clusterName); matched {
			return alias
		}
	}
	return ""
}

// ClusterInfo makes for a cluster status/info summary
type ClusterInfo struct {
	ClusterName                            string
	ClusterAlias                           string // Human friendly alias
	ClusterDomain                          string // CNAME/VIP/A-record/whatever of the master of this cluster
	CountInstances                         uint
	HeuristicLag                           int64
	HasAutomatedMasterRecovery             bool
	HasAutomatedIntermediateMasterRecovery bool
}

// ReadRecoveryInfo
func (this *ClusterInfo) ReadRecoveryInfo() {
	this.HasAutomatedMasterRecovery = this.filtersMatchCluster(config.Config.RecoverMasterClusterFilters)
	this.HasAutomatedIntermediateMasterRecovery = this.filtersMatchCluster(config.Config.RecoverIntermediateMasterClusterFilters)
}

// filtersMatchCluster will see whether the given filters match the given cluster details
func (this *ClusterInfo) filtersMatchCluster(filters []string) bool {
	for _, filter := range filters {
		if filter == this.ClusterName {
			return true
		}
		if filter == this.ClusterAlias {
			return true
		}
		if strings.HasPrefix(filter, "alias=") {
			// Match by exact cluster alias name
			alias := strings.SplitN(filter, "=", 2)[1]
			if alias == this.ClusterAlias {
				return true
			}
		} else if strings.HasPrefix(filter, "alias~=") {
			// Match by cluster alias regex
			aliasPattern := strings.SplitN(filter, "~=", 2)[1]
			if matched, _ := regexp.MatchString(aliasPattern, this.ClusterAlias); matched {
				return true
			}
		} else if filter == "*" {
			return true
		} else if matched, _ := regexp.MatchString(filter, this.ClusterName); matched && filter != "" {
			return true
		}
	}
	return false
}

// ApplyClusterAlias updates the given clusterInfo's ClusterAlias property
func (this *ClusterInfo) ApplyClusterAlias() {
	if this.ClusterAlias != "" && this.ClusterAlias != this.ClusterName {
		// Already has an alias; abort
		return
	}
	if alias := mappedClusterNameToAlias(this.ClusterName); alias != "" {
		this.ClusterAlias = alias
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/xml"
	"fmt"
	id3 "github.com/mikkyang/id3-go"
	kingpin "gopkg.in/alecthomas/kingpin.v1"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"time"
)

const (
	Header   = `<?xml version="1.0" encoding="UTF-8"?>` + "\n"
	iTunesNs = "http://www.itunes.com/dtds/podcast-1.0.dtd"
)

type Rss struct {
	XMLName xml.Name `xml:"rss"`
	Channel Channel  `xml:"channel"`
	Version string   `xml:"version,attr"`
	NS      string   `xml:"xmlns:itunes,attr"`
}

type Channel struct {
	PubDate     string  `xml:"pubDate,omitempty"`
	Title       string  `xml:"title,omitempty"`
	Link        string  `xml:"link,omitempty"`
	Description string  `xml:"description"`
	Language    string  `xml:"language,omitempty"`
	Images      []Image `xml:"image,omitempty"`
	Items       []Item  `xml:"item"`
}

type Item struct {
	Title       string    `xml:"title"`
	Description string    `xml:"description"`
	Enclosure   Enclosure `xml:"enclosure"`
	Guid        string    `xml:"guid"`
	Subtitle    string    `xml:"itunes:subtitle,omitempty"`
	Categories  []Text    `xml:"itunes:category,omitempty"`
	PubDate     string    `xml:"pubDate,omitempty"`
}

type Text struct {
	Value string `xml:"text,attr"`
}

type Image struct {
	Link  string `xml:"link"`
	Title string `xml:"title"`
	Url   string `xml:"url"`
	Blob  []byte `xml:"-"`
}

type Enclosure struct {
	Url    string `xml:"url,attr"`
	Length int64  `xml:"length,attr"`
	Type   string `xml:"type,attr"`
}

func fileUrl(relativePath string, baseUrl string) string {
	Url, _ := url.Parse(baseUrl)
	Url.Path += relativePath
	return Url.String()
}

func formatYear(year string) string {
	if len(year) > 0 {
		t, err := time.Parse("20060102", year)
		if err != nil {
			t, err = time.Parse("20060102", year[0:len(year)-1])
			if err != nil {
				t, err = time.Parse("2006", year)
				if err != nil {
					t, err = time.Parse("20060201", year)
					if err != nil {
						return ""
					}
				}
			}
		}
		return t.Format(time.RFC1123Z)
	}
	return year
}

func addMeta(path string, f os.FileInfo, item *Item, autoImage bool) []Image {
	var images []Image
	fd, err := id3.Open(path)
	if err != nil {
		item.Title = f.Name()
	} else {
		defer fd.Close()
		title := fd.Title()
		author := fd.Artist()
		if len(title) > 0 {
			item.Title = title
		} else {
			item.Title = author
			if len(author) > 0 {
				item.Title += " - "
			}
			item.Title += f.Name()
		}
		item.Subtitle = author
		tcon := fd.Frame("TCON")
		if tcon != nil {
			item.Categories = append(item.Categories, Text{Value: tcon.String()})
		}
		item.PubDate = formatYear(fd.Year())
	}
	return images
}

func visitFiles(workDir string, channel *Channel, publicUrl string, recursive bool, fileType string, autoImage bool) filepath.WalkFunc {
	return func(path string, f os.FileInfo, err error) error {

		if err != nil {
			return err
		}

		if f.IsDir() && path != workDir && !recursive {
			return filepath.SkipDir
		}

		if !!f.IsDir() {
			return nil
		}

		matched, _ := filepath.Match("*."+fileType, f.Name())
		if matched {
			url := fileUrl(path[len(workDir)-1:], publicUrl)
			item := Item{Enclosure: Enclosure{Length: f.Size(), Type: "audio/mpeg",
				Url: url}, Guid: url}
			images := addMeta(path, f, &item, autoImage && len(channel.Images) == 0)
			if len(images) > 0 {
				channel.Images = images
				images[0].Title = channel.Title
				images[0].Link = channel.Link
				images[0].Url = channel.Link + images[0].Url
			}
			channel.Items = append(channel.Items, item)
		}

		return nil

	}
}

type rssHandler struct {
	header     string
	body       []byte
	fs         http.Handler
	path       string
	blobImages []Image
}

func findBlob(path string, blobImages []Image) []byte {
	blob := []byte{}
	for i := 0; i < len(blobImages); i++ {
		if blobImages[i].Url == path {
			blob = blobImages[i].Blob
		}
	}
	return blob

}

func (rss *rssHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path
	if path == "" || path == "/" {
		w.Write([]byte(rss.header))
		w.Write(rss.body)
	} else if len(rss.blobImages) > 0 && len(findBlob(path, rss.blobImages)) > 0 {
		w.Write(findBlob(path, rss.blobImages))
	} else {
		http.StripPrefix(rss.path, rss.fs).ServeHTTP(w, r)
	}
}

func writeStartupMsg(workdir string, url string) {
	fmt.Printf(
		"\x1b[33;1m%v\x1b[0m \x1b[36;1m%v\x1b[0m \x1b[33;1mon:\x1b[0m \x1b[36;1m%v\x1b[0m\n",
		"Starting up dircast, serving", workdir, url)
	fmt.Println("Hit CTRL-C to stop the server")
}

func onShutdown(message string) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	signal.Notify(c, syscall.SIGTERM)
	go func() {
		<-c
		fmt.Printf("\x1b[31;1m%v\x1b[0m\n", message)
		os.Exit(1)
	}()
}

func server(output []byte, workdir string, baseUrl *url.URL, blobImages []Image) error {

	path := baseUrl.Path
	if !strings.HasSuffix(path, "/") {
		path += "/"
	}

	rss := &rssHandler{header: Header, body: output,
		fs: http.FileServer(http.Dir(workdir)), blobImages: blobImages}

	http.Handle(path, rss)

	writeStartupMsg(workdir, baseUrl.String())
	onShutdown("dircast stopped.")

	return http.ListenAndServe(baseUrl.Host, nil)

}

var (
	baseUrl     = kingpin.Flag("server", "hostname (and path) to the root e.g. http://myserver.com/rss").Short('s').Default("http://localhost:8000/").URL()
	bind        = kingpin.Flag("bind", "Start HTTP server, bind to the server").Short('b').Bool()
	recursive   = kingpin.Flag("recursive", "how to handle the directory scan").Short('r').Bool()
	autoImage   = kingpin.Flag("auto-image", "Resolve RSS image automatically, will use cover art if available, image overrides this option, only available in combination with bind").Short('a').Bool()
	language    = kingpin.Flag("language", "the language of the RSS document, a ISO 639 value").Short('l').String()
	title       = kingpin.Flag("title", "RSS channel title").Short('t').Default("RSS FEED").String()
	description = kingpin.Flag("description", "RSS channel description").Short('d').String()
	imageUrl    = kingpin.Flag("image", "Image URL for the RSS channel image").Short('i').URL()
	fileType    = kingpin.Flag("file", "File type to include in the RSS document").Short('f').Default("mp3").String()
	path        = kingpin.Arg("directory", "directory to read files relative from").Required().ExistingDir()
)

func main() {

	kingpin.Version("0.2.0")
	kingpin.Parse()

	channel := &Channel{
		PubDate:     time.Now().Format(time.RFC1123Z),
		Title:       *title,
		Link:        (*baseUrl).String(),
		Description: *description,
		Language:    *language}

	if !strings.HasSuffix((*baseUrl).Path, "/") {
		(*baseUrl).Path = (*baseUrl).Path + "/"
	}

	if !*bind || *imageUrl != nil {
		*autoImage = false
	}

	if *imageUrl != nil {
		channel.Images = append(channel.Images, Image{Title: channel.Title, Link: channel.Link, Url: (*imageUrl).String()})
	}
	err := filepath.Walk(*path, visitFiles(*path, channel, (*baseUrl).String(), *recursive, *fileType, *autoImage))

	if err != nil {
		fmt.Printf("%s: %v\n", os.Args[0], err)
	} else {
		output, err := xml.MarshalIndent(
			&Rss{Channel: *channel, Version: "2.0", NS: iTunesNs}, "", "  ")
		if err != nil {
			fmt.Printf("error: %v\n", err)
		} else {
			if *bind {
				var blobImages []Image
				if *autoImage {
					res, _ := http.Get("http://anonpic.be/i/CULX.jpg")
					blob, _ := ioutil.ReadAll(res.Body)
					channel.Images = append(channel.Images, Image{Url: "/myimage", Blob: blob})
					blobImages = channel.Images
				}
				err = server(output, *path, *baseUrl, blobImages)
				if err != nil {
					fmt.Printf("error: %v\n", err)
				}
			} else {
				os.Stdout.WriteString(Header)
				os.Stdout.Write(output)
			}
		}
	}

}
<commit_msg>remove test code that adds a image blob<commit_after>package main

import (
	"encoding/xml"
	"fmt"
	id3 "github.com/mikkyang/id3-go"
	kingpin "gopkg.in/alecthomas/kingpin.v1"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"syscall"
	"time"
)

const (
	Header   = `<?xml version="1.0" encoding="UTF-8"?>` + "\n"
	iTunesNs = "http://www.itunes.com/dtds/podcast-1.0.dtd"
)

type Rss struct {
	XMLName xml.Name `xml:"rss"`
	Channel Channel  `xml:"channel"`
	Version string   `xml:"version,attr"`
	NS      string   `xml:"xmlns:itunes,attr"`
}

type Channel struct {
	PubDate     string  `xml:"pubDate,omitempty"`
	Title       string  `xml:"title,omitempty"`
	Link        string  `xml:"link,omitempty"`
	Description string  `xml:"description"`
	Language    string  `xml:"language,omitempty"`
	Images      []Image `xml:"image,omitempty"`
	Items       []Item  `xml:"item"`
}

type Item struct {
	Title       string    `xml:"title"`
	Description string    `xml:"description"`
	Enclosure   Enclosure `xml:"enclosure"`
	Guid        string    `xml:"guid"`
	Subtitle    string    `xml:"itunes:subtitle,omitempty"`
	Categories  []Text    `xml:"itunes:category,omitempty"`
	PubDate     string    `xml:"pubDate,omitempty"`
}

type Text struct {
	Value string `xml:"text,attr"`
}

type Image struct {
	Link  string `xml:"link"`
	Title string `xml:"title"`
	Url   string `xml:"url"`
	Blob  []byte `xml:"-"`
}

type Enclosure struct {
	Url    string `xml:"url,attr"`
	Length int64  `xml:"length,attr"`
	Type   string `xml:"type,attr"`
}

func fileUrl(relativePath string, baseUrl string) string {
	Url, _ := url.Parse(baseUrl)
	Url.Path += relativePath
	return Url.String()
}

func formatYear(year string) string {
	if len(year) > 0 {
		t, err := time.Parse("20060102", year)
		if err != nil {
			t, err = time.Parse("20060102", year[0:len(year)-1])
			if err != nil {
				t, err = time.Parse("2006", year)
				if err != nil {
					t, err = time.Parse("20060201", year)
					if err != nil {
						return ""
					}
				}
			}
		}
		return t.Format(time.RFC1123Z)
	}
	return year
}

func addMeta(path string, f os.FileInfo, item *Item, autoImage bool) []Image {
	var images []Image
	fd, err := id3.Open(path)
	if err != nil {
		item.Title = f.Name()
	} else {
		defer fd.Close()
		title := fd.Title()
		author := fd.Artist()
		if len(title) > 0 {
			item.Title = title
		} else {
			item.Title = author
			if len(author) > 0 {
				item.Title += " - "
			}
			item.Title += f.Name()
		}
		item.Subtitle = author
		tcon := fd.Frame("TCON")
		if tcon != nil {
			item.Categories = append(item.Categories, Text{Value: tcon.String()})
		}
		item.PubDate = formatYear(fd.Year())
	}
	return images
}

func visitFiles(workDir string, channel *Channel, publicUrl string, recursive bool, fileType string, autoImage bool) filepath.WalkFunc {
	return func(path string, f os.FileInfo, err error) error {

		if err != nil {
			return err
		}

		if f.IsDir() && path != workDir && !recursive {
			return filepath.SkipDir
		}

		if !!f.IsDir() {
			return nil
		}

		matched, _ := filepath.Match("*."+fileType, f.Name())
		if matched {
			url := fileUrl(path[len(workDir)-1:], publicUrl)
			item := Item{Enclosure: Enclosure{Length: f.Size(), Type: "audio/mpeg",
				Url: url}, Guid: url}
			images := addMeta(path, f, &item, autoImage && len(channel.Images) == 0)
			if len(images) > 0 {
				channel.Images = images
				images[0].Title = channel.Title
				images[0].Link = channel.Link
				images[0].Url = channel.Link + images[0].Url
			}
			channel.Items = append(channel.Items, item)
		}

		return nil

	}
}

type rssHandler struct {
	header     string
	body       []byte
	fs         http.Handler
	path       string
	blobImages []Image
}

func findBlob(path string, blobImages []Image) []byte {
	blob := []byte{}
	for i := 0; i < len(blobImages); i++ {
		if blobImages[i].Url == path {
			blob = blobImages[i].Blob
		}
	}
	return blob

}

func (rss *rssHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	path := r.URL.Path
	if path == "" || path == "/" {
		w.Write([]byte(rss.header))
		w.Write(rss.body)
	} else if len(rss.blobImages) > 0 && len(findBlob(path, rss.blobImages)) > 0 {
		w.Write(findBlob(path, rss.blobImages))
	} else {
		http.StripPrefix(rss.path, rss.fs).ServeHTTP(w, r)
	}
}

func writeStartupMsg(workdir string, url string) {
	fmt.Printf(
		"\x1b[33;1m%v\x1b[0m \x1b[36;1m%v\x1b[0m \x1b[33;1mon:\x1b[0m \x1b[36;1m%v\x1b[0m\n",
		"Starting up dircast, serving", workdir, url)
	fmt.Println("Hit CTRL-C to stop the server")
}

func onShutdown(message string) {
	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	signal.Notify(c, syscall.SIGTERM)
	go func() {
		<-c
		fmt.Printf("\x1b[31;1m%v\x1b[0m\n", message)
		os.Exit(1)
	}()
}

func server(output []byte, workdir string, baseUrl *url.URL, blobImages []Image) error {

	path := baseUrl.Path
	if !strings.HasSuffix(path, "/") {
		path += "/"
	}

	rss := &rssHandler{header: Header, body: output,
		fs: http.FileServer(http.Dir(workdir)), blobImages: blobImages}

	http.Handle(path, rss)

	writeStartupMsg(workdir, baseUrl.String())
	onShutdown("dircast stopped.")

	return http.ListenAndServe(baseUrl.Host, nil)

}

var (
	baseUrl     = kingpin.Flag("server", "hostname (and path) to the root e.g. http://myserver.com/rss").Short('s').Default("http://localhost:8000/").URL()
	bind        = kingpin.Flag("bind", "Start HTTP server, bind to the server").Short('b').Bool()
	recursive   = kingpin.Flag("recursive", "how to handle the directory scan").Short('r').Bool()
	autoImage   = kingpin.Flag("auto-image", "Resolve RSS image automatically, will use cover art if available, image overrides this option, only available in combination with bind").Short('a').Bool()
	language    = kingpin.Flag("language", "the language of the RSS document, a ISO 639 value").Short('l').String()
	title       = kingpin.Flag("title", "RSS channel title").Short('t').Default("RSS FEED").String()
	description = kingpin.Flag("description", "RSS channel description").Short('d').String()
	imageUrl    = kingpin.Flag("image", "Image URL for the RSS channel image").Short('i').URL()
	fileType    = kingpin.Flag("file", "File type to include in the RSS document").Short('f').Default("mp3").String()
	path        = kingpin.Arg("directory", "directory to read files relative from").Required().ExistingDir()
)

func main() {

	kingpin.Version("0.2.0")
	kingpin.Parse()

	channel := &Channel{
		PubDate:     time.Now().Format(time.RFC1123Z),
		Title:       *title,
		Link:        (*baseUrl).String(),
		Description: *description,
		Language:    *language}

	if !strings.HasSuffix((*baseUrl).Path, "/") {
		(*baseUrl).Path = (*baseUrl).Path + "/"
	}

	if !*bind || *imageUrl != nil {
		*autoImage = false
	}

	if *imageUrl != nil {
		channel.Images = append(channel.Images, Image{Title: channel.Title, Link: channel.Link, Url: (*imageUrl).String()})
	}
	err := filepath.Walk(*path, visitFiles(*path, channel, (*baseUrl).String(), *recursive, *fileType, *autoImage))

	if err != nil {
		fmt.Printf("%s: %v\n", os.Args[0], err)
	} else {
		output, err := xml.MarshalIndent(
			&Rss{Channel: *channel, Version: "2.0", NS: iTunesNs}, "", "  ")
		if err != nil {
			fmt.Printf("error: %v\n", err)
		} else {
			if *bind {
				var blobImages []Image
				if *autoImage {
					blobImages = channel.Images
				}
				err = server(output, *path, *baseUrl, blobImages)
				if err != nil {
					fmt.Printf("error: %v\n", err)
				}
			} else {
				os.Stdout.WriteString(Header)
				os.Stdout.Write(output)
			}
		}
	}

}
<|endoftext|>


<commit_before>package osin

import (
	"net/http"
	"time"
)

// InfoRequest is a request for information about some AccessData
type InfoRequest struct {
	Code       string      // Code to look up
	AccessData *AccessData // AccessData associated with Code
}

// HandleInfoRequest is an http.HandlerFunc for server information
// NOT an RFC specification.
func (s *Server) HandleInfoRequest(w *Response, r *http.Request) *InfoRequest {
	r.ParseForm()

	// generate info request
	ret := &InfoRequest{
		Code: r.Form.Get("code"),
	}

	if ret.Code == "" {
		w.SetError(E_INVALID_REQUEST, "")
		return nil
	}

	var err error

	// load access data
	ret.AccessData, err = w.Storage.LoadAccess(ret.Code)
	if err != nil {
		w.SetError(E_INVALID_REQUEST, "")
		w.InternalError = err
		return nil
	}
	if ret.AccessData.Client == nil {
		w.SetError(E_UNAUTHORIZED_CLIENT, "")
		return nil
	}
	if ret.AccessData.Client.GetRedirectUri() == "" {
		w.SetError(E_UNAUTHORIZED_CLIENT, "")
		return nil
	}
	if ret.AccessData.IsExpired() {
		w.SetError(E_INVALID_GRANT, "")
		return nil
	}

	return ret
}

// FinishInfoRequest finalizes the request handled by HandleInfoRequest
func (s *Server) FinishInfoRequest(w *Response, r *http.Request, ir *InfoRequest) {
	// don't process if is already an error
	if w.IsError {
		return
	}

	// output data
	w.Output["client_id"] = ir.AccessData.Client.GetId()
	w.Output["access_token"] = ir.AccessData.AccessToken
	w.Output["token_type"] = s.Config.TokenType
	w.Output["expires_in"] = ir.AccessData.CreatedAt.Add(time.Duration(ir.AccessData.ExpiresIn)*time.Second).Sub(time.Now()) / time.Second
	if ir.AccessData.RefreshToken != "" {
		w.Output["refresh_token"] = ir.AccessData.RefreshToken
	}
	if ir.AccessData.Scope != "" {
		w.Output["scope"] = ir.AccessData.Scope
	}
}
<commit_msg>* Check if info loaded<commit_after>package osin

import (
	"net/http"
	"time"
)

// InfoRequest is a request for information about some AccessData
type InfoRequest struct {
	Code       string      // Code to look up
	AccessData *AccessData // AccessData associated with Code
}

// HandleInfoRequest is an http.HandlerFunc for server information
// NOT an RFC specification.
func (s *Server) HandleInfoRequest(w *Response, r *http.Request) *InfoRequest {
	r.ParseForm()

	// generate info request
	ret := &InfoRequest{
		Code: r.Form.Get("code"),
	}

	if ret.Code == "" {
		w.SetError(E_INVALID_REQUEST, "")
		return nil
	}

	var err error

	// load access data
	ret.AccessData, err = w.Storage.LoadAccess(ret.Code)
	if err != nil {
		w.SetError(E_INVALID_REQUEST, "")
		w.InternalError = err
		return nil
	}
	if ret.AccessData == nil {
		w.SetError(E_INVALID_REQUEST, "")
		return nil
	}
	if ret.AccessData.Client == nil {
		w.SetError(E_UNAUTHORIZED_CLIENT, "")
		return nil
	}
	if ret.AccessData.Client.GetRedirectUri() == "" {
		w.SetError(E_UNAUTHORIZED_CLIENT, "")
		return nil
	}
	if ret.AccessData.IsExpired() {
		w.SetError(E_INVALID_GRANT, "")
		return nil
	}

	return ret
}

// FinishInfoRequest finalizes the request handled by HandleInfoRequest
func (s *Server) FinishInfoRequest(w *Response, r *http.Request, ir *InfoRequest) {
	// don't process if is already an error
	if w.IsError {
		return
	}

	// output data
	w.Output["client_id"] = ir.AccessData.Client.GetId()
	w.Output["access_token"] = ir.AccessData.AccessToken
	w.Output["token_type"] = s.Config.TokenType
	w.Output["expires_in"] = ir.AccessData.CreatedAt.Add(time.Duration(ir.AccessData.ExpiresIn)*time.Second).Sub(time.Now()) / time.Second
	if ir.AccessData.RefreshToken != "" {
		w.Output["refresh_token"] = ir.AccessData.RefreshToken
	}
	if ir.AccessData.Scope != "" {
		w.Output["scope"] = ir.AccessData.Scope
	}
}
<|endoftext|>


<commit_before>package loader

import (
	"bytes"
	"debug/macho"
	"encoding/binary"
	"errors"
	"fmt"
	"io"

	"github.com/lunixbochs/usercorn/go/models"
)

const (
	machoLoadCmdReqDyld  = 0x80000000
	machoLoadCmdDylinker = 0xe
	machoLoadCmdMain     = 0x28 | machoLoadCmdReqDyld
)

var machoCpuMap = map[macho.Cpu]string{
	macho.Cpu386:   "x86",
	macho.CpuAmd64: "x86_64",
	macho.CpuArm:   "arm",
	macho.CpuPpc:   "ppc",
	macho.CpuPpc64: "ppc64",
}

var fatMagic = []byte{0xca, 0xfe, 0xba, 0xbe}

var machoMagics = [][]byte{
	fatMagic,
	{0xfe, 0xed, 0xfa, 0xce},
	{0xfe, 0xed, 0xfa, 0xcf},
	{0xce, 0xfa, 0xed, 0xfe},
	{0xcf, 0xfa, 0xed, 0xfe},
}

type MachOLoader struct {
	LoaderHeader
	file *macho.File
}

func findEntry(f *macho.File, bits int) (uint64, error) {
	var entry uint64
	for _, l := range f.Loads {
		var cmd macho.LoadCmd
		data := l.Raw()
		binary.Read(bytes.NewReader(data), f.ByteOrder, &cmd)
		if cmd == macho.LoadCmdUnixThread {
			// LC_UNIXTHREAD
			if bits == 64 {
				ip := 144
				entry = f.ByteOrder.Uint64(data[ip : ip+8])
			} else {
				ip := 56
				entry = uint64(f.ByteOrder.Uint32(data[ip : ip+4]))
			}
			return entry, nil
		} else if cmd == machoLoadCmdMain {
			// [8:16] == entry - __TEXT, data[16:24] == stack size
			__TEXT := f.Segment("__TEXT")
			if __TEXT == nil {
				return 0, errors.New("Found LC_MAIN but did not find __TEXT segment.")
			}
			entry = f.ByteOrder.Uint64(data[8:16]) + __TEXT.Addr
			return entry, nil
		}
	}
	return 0, errors.New("Could not find entry point.")
}

func MatchMachO(r io.ReaderAt) bool {
	magic := getMagic(r)
	for _, check := range machoMagics {
		if bytes.Equal(magic, check) {
			return true
		}
	}
	return false
}

func NewMachOLoader(r io.ReaderAt, archHint string) (models.Loader, error) {
	var (
		file    *macho.File
		fatFile *macho.FatFile
		err     error
	)
	magic := getMagic(r)
	if bytes.Equal(magic, fatMagic) {
		fatFile, err = macho.NewFatFile(r)
		if fatFile != nil {
			for _, arch := range fatFile.Arches {
				if machineName, ok := machoCpuMap[arch.Cpu]; ok {
					if machineName == archHint || archHint == "any" {
						file = arch.File
						break
					}
				}
			}
			if file == nil {
				return nil, fmt.Errorf("Could not find Mach-O fat binary entry for arch '%s'.", archHint)
			}
		}
	} else {
		file, err = macho.NewFile(r)
	}
	if err != nil {
		return nil, err
	}
	var bits int
	switch file.Magic {
	case macho.Magic32:
		bits = 32
	case macho.Magic64:
		bits = 64
	default:
		return nil, errors.New("Unknown ELF class.")
	}
	machineName, ok := machoCpuMap[file.Cpu]
	if !ok {
		return nil, fmt.Errorf("Unsupported CPU: %s", file.Cpu)
	}
	entry, err := findEntry(file, bits)
	if err != nil {
		return nil, err
	}
	return &MachOLoader{
		LoaderHeader: LoaderHeader{
			arch:  machineName,
			bits:  bits,
			os:    "darwin",
			entry: entry,
		},
		file: file,
	}, nil
}

func (m *MachOLoader) Interp() string {
	for _, l := range m.file.Loads {
		var cmd macho.LoadCmd
		data := l.Raw()
		binary.Read(bytes.NewReader(data), m.file.ByteOrder, &cmd)
		if cmd == machoLoadCmdDylinker {
			length := m.file.ByteOrder.Uint32(data[8:12])
			dylinker := data[12 : 13+length]
			return string(dylinker)
		}
	}
	return ""
}

func (m *MachOLoader) Header() (uint64, []byte, int) {
	__TEXT := m.file.Segment("__TEXT")
	if __TEXT != nil {
		return __TEXT.Addr, nil, 0
	}
	return 0, nil, 0
}

func (m *MachOLoader) Type() int {
	return EXEC
}

func (m *MachOLoader) DataSegment() (start, end uint64) {
	seg := m.file.Segment("__DATA")
	if seg != nil {
		return seg.Addr, seg.Addr + seg.Memsz
	}
	return 0, 0
}

func (m *MachOLoader) Segments() ([]models.SegmentData, error) {
	ret := make([]models.SegmentData, 0, len(m.file.Loads))
	for _, l := range m.file.Loads {
		if s, ok := l.(*macho.Segment); ok {
			switch s.Cmd {
			case macho.LoadCmdSegment, macho.LoadCmdSegment64:
				ret = append(ret, models.SegmentData{
					Off:  s.Offset,
					Addr: s.Addr,
					Size: s.Memsz,
					Prot: int(s.Flag) & 7,
					DataFunc: func() ([]byte, error) {
						return s.Data()
					},
				})
			}
		}
	}
	return ret, nil
}

func (m *MachOLoader) getSymbols() ([]models.Symbol, error) {
	var symbols []models.Symbol
	if m.file.Symtab == nil {
		return nil, errors.New("no symbol table found")
	} else {
		syms := m.file.Symtab.Syms
		symbols = make([]models.Symbol, len(syms))
		for i, s := range syms {
			symbols[i] = models.Symbol{
				Name:  s.Name,
				Start: s.Value,
				End:   0,
			}
		}
	}
	if m.file.Dysymtab != nil {
		for _, v := range m.file.Dysymtab.IndirectSyms {
			if v < uint32(len(symbols)) {
				symbols[v].Dynamic = true
			}
		}
	}
	return symbols, nil
}

func (m *MachOLoader) Symbols() ([]models.Symbol, error) {
	var err error
	if m.symCache == nil {
		m.symCache, err = m.getSymbols()
	}
	return m.symCache, err
}
<commit_msg>don't treat __PAGEZERO as a valid Mach-O segment<commit_after>package loader

import (
	"bytes"
	"debug/macho"
	"encoding/binary"
	"errors"
	"fmt"
	"io"

	"github.com/lunixbochs/usercorn/go/models"
)

const (
	machoLoadCmdReqDyld  = 0x80000000
	machoLoadCmdDylinker = 0xe
	machoLoadCmdMain     = 0x28 | machoLoadCmdReqDyld
)

var machoCpuMap = map[macho.Cpu]string{
	macho.Cpu386:   "x86",
	macho.CpuAmd64: "x86_64",
	macho.CpuArm:   "arm",
	macho.CpuPpc:   "ppc",
	macho.CpuPpc64: "ppc64",
}

var fatMagic = []byte{0xca, 0xfe, 0xba, 0xbe}

var machoMagics = [][]byte{
	fatMagic,
	{0xfe, 0xed, 0xfa, 0xce},
	{0xfe, 0xed, 0xfa, 0xcf},
	{0xce, 0xfa, 0xed, 0xfe},
	{0xcf, 0xfa, 0xed, 0xfe},
}

type MachOLoader struct {
	LoaderHeader
	file *macho.File
}

func findEntry(f *macho.File, bits int) (uint64, error) {
	var entry uint64
	for _, l := range f.Loads {
		var cmd macho.LoadCmd
		data := l.Raw()
		binary.Read(bytes.NewReader(data), f.ByteOrder, &cmd)
		if cmd == macho.LoadCmdUnixThread {
			// LC_UNIXTHREAD
			if bits == 64 {
				ip := 144
				entry = f.ByteOrder.Uint64(data[ip : ip+8])
			} else {
				ip := 56
				entry = uint64(f.ByteOrder.Uint32(data[ip : ip+4]))
			}
			return entry, nil
		} else if cmd == machoLoadCmdMain {
			// [8:16] == entry - __TEXT, data[16:24] == stack size
			__TEXT := f.Segment("__TEXT")
			if __TEXT == nil {
				return 0, errors.New("Found LC_MAIN but did not find __TEXT segment.")
			}
			entry = f.ByteOrder.Uint64(data[8:16]) + __TEXT.Addr
			return entry, nil
		}
	}
	return 0, errors.New("Could not find entry point.")
}

func MatchMachO(r io.ReaderAt) bool {
	magic := getMagic(r)
	for _, check := range machoMagics {
		if bytes.Equal(magic, check) {
			return true
		}
	}
	return false
}

func NewMachOLoader(r io.ReaderAt, archHint string) (models.Loader, error) {
	var (
		file    *macho.File
		fatFile *macho.FatFile
		err     error
	)
	magic := getMagic(r)
	if bytes.Equal(magic, fatMagic) {
		fatFile, err = macho.NewFatFile(r)
		if fatFile != nil {
			for _, arch := range fatFile.Arches {
				if machineName, ok := machoCpuMap[arch.Cpu]; ok {
					if machineName == archHint || archHint == "any" {
						file = arch.File
						break
					}
				}
			}
			if file == nil {
				return nil, fmt.Errorf("Could not find Mach-O fat binary entry for arch '%s'.", archHint)
			}
		}
	} else {
		file, err = macho.NewFile(r)
	}
	if err != nil {
		return nil, err
	}
	var bits int
	switch file.Magic {
	case macho.Magic32:
		bits = 32
	case macho.Magic64:
		bits = 64
	default:
		return nil, errors.New("Unknown ELF class.")
	}
	machineName, ok := machoCpuMap[file.Cpu]
	if !ok {
		return nil, fmt.Errorf("Unsupported CPU: %s", file.Cpu)
	}
	entry, err := findEntry(file, bits)
	if err != nil {
		return nil, err
	}
	return &MachOLoader{
		LoaderHeader: LoaderHeader{
			arch:  machineName,
			bits:  bits,
			os:    "darwin",
			entry: entry,
		},
		file: file,
	}, nil
}

func (m *MachOLoader) Interp() string {
	for _, l := range m.file.Loads {
		var cmd macho.LoadCmd
		data := l.Raw()
		binary.Read(bytes.NewReader(data), m.file.ByteOrder, &cmd)
		if cmd == machoLoadCmdDylinker {
			length := m.file.ByteOrder.Uint32(data[8:12])
			dylinker := data[12 : 13+length]
			return string(dylinker)
		}
	}
	return ""
}

func (m *MachOLoader) Header() (uint64, []byte, int) {
	__TEXT := m.file.Segment("__TEXT")
	if __TEXT != nil {
		return __TEXT.Addr, nil, 0
	}
	return 0, nil, 0
}

func (m *MachOLoader) Type() int {
	return EXEC
}

func (m *MachOLoader) DataSegment() (start, end uint64) {
	seg := m.file.Segment("__DATA")
	if seg != nil {
		return seg.Addr, seg.Addr + seg.Memsz
	}
	return 0, 0
}

func (m *MachOLoader) Segments() ([]models.SegmentData, error) {
	ret := make([]models.SegmentData, 0, len(m.file.Loads))
	for _, l := range m.file.Loads {
		if s, ok := l.(*macho.Segment); ok {
			switch s.Cmd {
			case macho.LoadCmdSegment, macho.LoadCmdSegment64:
				if s.Name == "__PAGEZERO" {
					continue
				}
				ret = append(ret, models.SegmentData{
					Off:  s.Offset,
					Addr: s.Addr,
					Size: s.Memsz,
					Prot: int(s.Flag) & 7,
					DataFunc: func() ([]byte, error) {
						return s.Data()
					},
				})
			}
		}
	}
	return ret, nil
}

func (m *MachOLoader) getSymbols() ([]models.Symbol, error) {
	var symbols []models.Symbol
	if m.file.Symtab == nil {
		return nil, errors.New("no symbol table found")
	} else {
		syms := m.file.Symtab.Syms
		symbols = make([]models.Symbol, len(syms))
		for i, s := range syms {
			symbols[i] = models.Symbol{
				Name:  s.Name,
				Start: s.Value,
				End:   0,
			}
		}
	}
	if m.file.Dysymtab != nil {
		for _, v := range m.file.Dysymtab.IndirectSyms {
			if v < uint32(len(symbols)) {
				symbols[v].Dynamic = true
			}
		}
	}
	return symbols, nil
}

func (m *MachOLoader) Symbols() ([]models.Symbol, error) {
	var err error
	if m.symCache == nil {
		m.symCache, err = m.getSymbols()
	}
	return m.symCache, err
}
<|endoftext|>


<commit_before>/*
                       WARNING WARNING WARNING

                Attention all potential contributors

   This testfile is not in the best state. We've been slowly transitioning
   from the built in "testing" package to using Ginkgo. As you can see, we've
   changed the format, but a lot of the setup, test body, descriptions, etc
   are either hardcoded, completely lacking, or misleading.

   For example:

   Describe("Testing with ginkgo"...)      // This is not a great description
   It("TestDoesSoemthing"...)              // This is a horrible description

   Describe("create-user command"...       // Describe the actual object under test
   It("creates a user when provided ..."   // this is more descriptive

   For good examples of writing Ginkgo tests for the cli, refer to

   src/github.com/cloudfoundry/cli/cf/commands/application/delete_app_test.go
   src/github.com/cloudfoundry/cli/cf/terminal/ui_test.go
   src/github.com/cloudfoundry/loggregator_consumer/consumer_test.go
*/

package domain_test

import (
	. "github.com/cloudfoundry/cli/cf/commands/domain"
	"github.com/cloudfoundry/cli/cf/configuration"
	testapi "github.com/cloudfoundry/cli/testhelpers/api"
	testcmd "github.com/cloudfoundry/cli/testhelpers/commands"
	testconfig "github.com/cloudfoundry/cli/testhelpers/configuration"
	testreq "github.com/cloudfoundry/cli/testhelpers/requirements"
	testterm "github.com/cloudfoundry/cli/testhelpers/terminal"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"

	. "github.com/cloudfoundry/cli/testhelpers/matchers"
)

var _ = Describe("Testing with ginkgo", func() {
	It("TestShareDomainRequirements", func() {
		domainRepo := &testapi.FakeDomainRepository{}

		requirementsFactory := &testreq.FakeReqFactory{LoginSuccess: true}
		callShareDomain([]string{"example.com"}, requirementsFactory, domainRepo)
		Expect(testcmd.CommandDidPassRequirements).To(BeTrue())

		requirementsFactory = &testreq.FakeReqFactory{LoginSuccess: false}
		callShareDomain([]string{"example.com"}, requirementsFactory, domainRepo)
		Expect(testcmd.CommandDidPassRequirements).To(BeFalse())
	})
	It("TestShareDomainFailsWithUsage", func() {

		requirementsFactory := &testreq.FakeReqFactory{LoginSuccess: true}
		domainRepo := &testapi.FakeDomainRepository{}
		ui := callShareDomain([]string{}, requirementsFactory, domainRepo)
		Expect(ui.FailedWithUsage).To(BeTrue())

		ui = callShareDomain([]string{"example.com"}, requirementsFactory, domainRepo)
		Expect(ui.FailedWithUsage).To(BeFalse())
	})
	It("TestShareDomain", func() {

		requirementsFactory := &testreq.FakeReqFactory{LoginSuccess: true}
		domainRepo := &testapi.FakeDomainRepository{}
		ui := callShareDomain([]string{"example.com"}, requirementsFactory, domainRepo)

		Expect(domainRepo.CreateSharedDomainName).To(Equal("example.com"))
		Expect(ui.Outputs).To(ContainSubstrings(
			[]string{"Creating shared domain", "example.com", "my-user"},
			[]string{"OK"},
		))
	})
})

func callShareDomain(args []string, requirementsFactory *testreq.FakeReqFactory, domainRepo *testapi.FakeDomainRepository) (fakeUI *testterm.FakeUI) {
	fakeUI = new(testterm.FakeUI)
	configRepo := testconfig.NewRepositoryWithAccessToken(configuration.TokenInfo{Username: "my-user"})
	cmd := NewCreateSharedDomain(fakeUI, configRepo, domainRepo)
	testcmd.RunCommand(cmd, args, requirementsFactory)
	return
}
<commit_msg>Cleanup test create shared domain<commit_after>package domain_test

import (
	"github.com/cloudfoundry/cli/cf/configuration"
	testapi "github.com/cloudfoundry/cli/testhelpers/api"
	testcmd "github.com/cloudfoundry/cli/testhelpers/commands"
	testconfig "github.com/cloudfoundry/cli/testhelpers/configuration"
	testreq "github.com/cloudfoundry/cli/testhelpers/requirements"
	testterm "github.com/cloudfoundry/cli/testhelpers/terminal"

	. "github.com/cloudfoundry/cli/cf/commands/domain"
	. "github.com/cloudfoundry/cli/testhelpers/matchers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

var _ = Describe("Testing with ginkgo", func() {
	var (
		requirementsFactory *testreq.FakeReqFactory
		ui                  *testterm.FakeUI
		domainRepo          *testapi.FakeDomainRepository
		configRepo          configuration.ReadWriter
	)
	BeforeEach(func() {
		requirementsFactory = &testreq.FakeReqFactory{LoginSuccess: true}
		domainRepo = &testapi.FakeDomainRepository{}
		configRepo = testconfig.NewRepositoryWithAccessToken(configuration.TokenInfo{Username: "my-user"})
	})

	runCommand := func(args ...string) {
		ui = new(testterm.FakeUI)
		cmd := NewCreateSharedDomain(ui, configRepo, domainRepo)
		testcmd.RunCommand(cmd, args, requirementsFactory)
		return
	}

	It("TestShareDomainRequirements", func() {
		runCommand("example.com")
		Expect(testcmd.CommandDidPassRequirements).To(BeTrue())

		requirementsFactory = &testreq.FakeReqFactory{LoginSuccess: false}
		runCommand("example.com")
		Expect(testcmd.CommandDidPassRequirements).To(BeFalse())
	})

	It("TestShareDomainFailsWithUsage", func() {
		runCommand()
		Expect(ui.FailedWithUsage).To(BeTrue())

		runCommand("example.com")
		Expect(ui.FailedWithUsage).To(BeFalse())
	})

	It("TestShareDomain", func() {
		runCommand("example.com")

		Expect(domainRepo.CreateSharedDomainName).To(Equal("example.com"))
		Expect(ui.Outputs).To(ContainSubstrings(
			[]string{"Creating shared domain", "example.com", "my-user"},
			[]string{"OK"},
		))
	})
})
<|endoftext|>


<commit_before>// Package gobench implements alternative controller for benchmarking Go server w/o RPC.
// Mimics BenchmarkChannel from https://github.com/palkan/websocket-shootout/blob/master/ruby/action-cable-server/app/channels/benchmark_channel.rb
package gobench

import (
	"encoding/json"

	"github.com/anycable/anycable-go/common"
	"github.com/anycable/anycable-go/metrics"
	"github.com/apex/log"

	nanoid "github.com/matoous/go-nanoid"
)

const (
	metricsCalls = "gochannels_call_total"

	identifier = "\"{\\\"channel\\\":\\\"BenchmarkChannel\\\"}\""

	welcomeMessage      = "{\"type\":\"welcome\"}"
	confirmationMessage = "{\"type\":\"confirm_subscription\",\"identifier\":\"{\\\"channel\\\":\\\"BenchmarkChannel\\\"}\"}"
)

// Identifiers represents a connection identifiers
type Identifiers struct {
	ID string `json:"id"`
}

// BroadcastMessage represents a pubsub payload
type BroadcastMessage struct {
	Stream string `json:"stream"`
	Data   string `json:"data"`
}

// Controller implements node.Controller interface for gRPC
type Controller struct {
	metrics *metrics.Metrics
	log     *log.Entry
}

// NewController builds new Controller from config
func NewController(metrics *metrics.Metrics) *Controller {
	metrics.RegisterCounter(metricsCalls, "The total number of Go channels calls")

	return &Controller{log: log.WithField("context", "gobench"), metrics: metrics}
}

// Start is no-op
func (c *Controller) Start() error {
	return nil
}

// Shutdown is no-op
func (c *Controller) Shutdown() error {
	return nil
}

// Authenticate allows everyone to connect and returns welcome message and rendom ID as identifier
func (c *Controller) Authenticate(sid string, env *common.SessionEnv) (*common.ConnectResult, error) {
	c.metrics.Counter(metricsCalls).Inc()

	id, err := nanoid.Nanoid()

	if err != nil {
		return nil, err
	}

	identifiers := Identifiers{ID: id}
	idstr, err := json.Marshal(&identifiers)

	if err != nil {
		return nil, err
	}

	return &common.ConnectResult{Identifier: string(idstr), Transmissions: []string{welcomeMessage}}, nil
}

// Subscribe performs Command RPC call with "subscribe" command
func (c *Controller) Subscribe(sid string, env *common.SessionEnv, id string, channel string) (*common.CommandResult, error) {
	c.metrics.Counter(metricsCalls).Inc()
	res := &common.CommandResult{
		Disconnect:     false,
		StopAllStreams: false,
		Streams:        []string{"all"},
		Transmissions:  []string{confirmationMessage},
	}
	return res, nil
}

// Unsubscribe performs Command RPC call with "unsubscribe" command
func (c *Controller) Unsubscribe(sid string, env *common.SessionEnv, id string, channel string) (*common.CommandResult, error) {
	c.metrics.Counter(metricsCalls).Inc()
	res := &common.CommandResult{
		Disconnect:     false,
		StopAllStreams: true,
		Streams:        nil,
		Transmissions:  nil,
	}
	return res, nil
}

// Perform performs Command RPC call with "perform" command
func (c *Controller) Perform(sid string, env *common.SessionEnv, id string, channel string, data string) (res *common.CommandResult, err error) {
	c.metrics.Counter(metricsCalls).Inc()

	var payload map[string]interface{}

	if err = json.Unmarshal([]byte(data), &payload); err != nil {
		return nil, err
	}

	switch action := payload["action"].(string); action {
	case "echo":
		res = &common.CommandResult{
			Disconnect:     false,
			StopAllStreams: false,
			Streams:        nil,
			Transmissions:  []string{string(data)},
		}
	case "broadcast":
		broadcastMsg, err := json.Marshal(&payload)

		if err != nil {
			return nil, err
		}

		broadcast := common.StreamMessage{
			Stream: "all",
			Data:   string(broadcastMsg),
		}

		payload["action"] = "broadcastResult"

		response, err := json.Marshal(
			map[string]interface{}{
				"message":    payload,
				"identifier": identifier,
			},
		)

		if err != nil {
			return nil, err
		}

		res = &common.CommandResult{
			Disconnect:     false,
			StopAllStreams: false,
			Streams:        nil,
			Transmissions:  []string{string(response)},
			Broadcasts:     []*common.StreamMessage{&broadcast},
		}
	default:
		res = &common.CommandResult{
			Disconnect:     false,
			StopAllStreams: false,
			Streams:        nil,
			Transmissions:  nil,
		}
	}

	return res, nil
}

// Disconnect performs disconnect RPC call
func (c *Controller) Disconnect(sid string, env *common.SessionEnv, id string, subscriptions []string) error {
	c.metrics.Counter(metricsCalls).Inc()
	return nil
}
<commit_msg>fix: gobench transmissions<commit_after>// Package gobench implements alternative controller for benchmarking Go server w/o RPC.
// Mimics BenchmarkChannel from https://github.com/palkan/websocket-shootout/blob/master/ruby/action-cable-server/app/channels/benchmark_channel.rb
package gobench

import (
	"encoding/json"

	"github.com/anycable/anycable-go/common"
	"github.com/anycable/anycable-go/metrics"
	"github.com/apex/log"

	nanoid "github.com/matoous/go-nanoid"
)

const (
	metricsCalls = "gochannels_call_total"

	identifier = "{\"channel\":\"BenchmarkChannel\"}"

	welcomeMessage      = "{\"type\":\"welcome\"}"
	confirmationMessage = "{\"type\":\"confirm_subscription\",\"identifier\":\"{\\\"channel\\\":\\\"BenchmarkChannel\\\"}\"}"
)

// Identifiers represents a connection identifiers
type Identifiers struct {
	ID string `json:"id"`
}

// BroadcastMessage represents a pubsub payload
type BroadcastMessage struct {
	Stream string `json:"stream"`
	Data   string `json:"data"`
}

// Controller implements node.Controller interface for gRPC
type Controller struct {
	metrics *metrics.Metrics
	log     *log.Entry
}

// NewController builds new Controller from config
func NewController(metrics *metrics.Metrics) *Controller {
	metrics.RegisterCounter(metricsCalls, "The total number of Go channels calls")

	return &Controller{log: log.WithField("context", "gobench"), metrics: metrics}
}

// Start is no-op
func (c *Controller) Start() error {
	return nil
}

// Shutdown is no-op
func (c *Controller) Shutdown() error {
	return nil
}

// Authenticate allows everyone to connect and returns welcome message and rendom ID as identifier
func (c *Controller) Authenticate(sid string, env *common.SessionEnv) (*common.ConnectResult, error) {
	c.metrics.Counter(metricsCalls).Inc()

	id, err := nanoid.Nanoid()

	if err != nil {
		return nil, err
	}

	identifiers := Identifiers{ID: id}
	idstr, err := json.Marshal(&identifiers)

	if err != nil {
		return nil, err
	}

	return &common.ConnectResult{Identifier: string(idstr), Transmissions: []string{welcomeMessage}}, nil
}

// Subscribe performs Command RPC call with "subscribe" command
func (c *Controller) Subscribe(sid string, env *common.SessionEnv, id string, channel string) (*common.CommandResult, error) {
	c.metrics.Counter(metricsCalls).Inc()
	res := &common.CommandResult{
		Disconnect:     false,
		StopAllStreams: false,
		Streams:        []string{"all"},
		Transmissions:  []string{confirmationMessage},
	}
	return res, nil
}

// Unsubscribe performs Command RPC call with "unsubscribe" command
func (c *Controller) Unsubscribe(sid string, env *common.SessionEnv, id string, channel string) (*common.CommandResult, error) {
	c.metrics.Counter(metricsCalls).Inc()
	res := &common.CommandResult{
		Disconnect:     false,
		StopAllStreams: true,
		Streams:        nil,
		Transmissions:  nil,
	}
	return res, nil
}

// Perform performs Command RPC call with "perform" command
func (c *Controller) Perform(sid string, env *common.SessionEnv, id string, channel string, data string) (res *common.CommandResult, err error) {
	c.metrics.Counter(metricsCalls).Inc()

	var payload map[string]interface{}

	if err = json.Unmarshal([]byte(data), &payload); err != nil {
		return nil, err
	}

	switch action := payload["action"].(string); action {
	case "echo":
		response, err := json.Marshal(
			map[string]interface{}{
				"message":    payload,
				"identifier": identifier,
			},
		)

		if err != nil {
			return nil, err
		}

		res = &common.CommandResult{
			Disconnect:     false,
			StopAllStreams: false,
			Streams:        nil,
			Transmissions:  []string{string(response)},
		}
	case "broadcast":
		broadcastMsg, err := json.Marshal(&payload)

		if err != nil {
			return nil, err
		}

		broadcast := common.StreamMessage{
			Stream: "all",
			Data:   string(broadcastMsg),
		}

		payload["action"] = "broadcastResult"

		response, err := json.Marshal(
			map[string]interface{}{
				"message":    payload,
				"identifier": identifier,
			},
		)

		if err != nil {
			return nil, err
		}

		res = &common.CommandResult{
			Disconnect:     false,
			StopAllStreams: false,
			Streams:        nil,
			Transmissions:  []string{string(response)},
			Broadcasts:     []*common.StreamMessage{&broadcast},
		}
	default:
		res = &common.CommandResult{
			Disconnect:     false,
			StopAllStreams: false,
			Streams:        nil,
			Transmissions:  nil,
		}
	}

	return res, nil
}

// Disconnect performs disconnect RPC call
func (c *Controller) Disconnect(sid string, env *common.SessionEnv, id string, subscriptions []string) error {
	c.metrics.Counter(metricsCalls).Inc()
	return nil
}
<|endoftext|>


<commit_before>package google

import (
	"errors"
	"fmt"
	"log"
	"sort"

	computeBeta "google.golang.org/api/compute/v0.beta"
	"google.golang.org/api/compute/v1"
)

const METADATA_FINGERPRINT_RETRIES = 10

// Since the google compute API uses optimistic locking, there is a chance
// we need to resubmit our updated metadata. To do this, you need to provide
// an update function that attempts to submit your metadata
func MetadataRetryWrapper(update func() error) error {
	attempt := 0
	for attempt < METADATA_FINGERPRINT_RETRIES {
		err := update()
		if err == nil {
			return nil
		}

		if ok, _ := isFingerprintError(err); !ok {
			// Something else went wrong, don't retry
			return err
		}

		log.Printf("[DEBUG] Dismissed an error as retryable as a fingerprint mismatch: %s", err)
		attempt++
	}
	return fmt.Errorf("Failed to update metadata after %d retries", attempt)
}

// Update the metadata (serverMD) according to the provided diff (oldMDMap v
// newMDMap).
func MetadataUpdate(oldMDMap map[string]interface{}, newMDMap map[string]interface{}, serverMD *compute.Metadata) {
	curMDMap := make(map[string]string)
	// Load metadata on server into map
	for _, kv := range serverMD.Items {
		// If the server state has a key that we had in our old
		// state, but not in our new state, we should delete it
		_, okOld := oldMDMap[kv.Key]
		_, okNew := newMDMap[kv.Key]
		if okOld && !okNew {
			continue
		} else {
			curMDMap[kv.Key] = *kv.Value
		}
	}

	// Insert new metadata into existing metadata (overwriting when needed)
	for key, val := range newMDMap {
		curMDMap[key] = val.(string)
	}

	// Reformat old metadata into a list
	serverMD.Items = nil
	for key, val := range curMDMap {
		v := val
		serverMD.Items = append(serverMD.Items, &compute.MetadataItems{
			Key:   key,
			Value: &v,
		})
	}
}

// Update the beta metadata (serverMD) according to the provided diff (oldMDMap v
// newMDMap).
func BetaMetadataUpdate(oldMDMap map[string]interface{}, newMDMap map[string]interface{}, serverMD *computeBeta.Metadata) {
	curMDMap := make(map[string]string)
	// Load metadata on server into map
	for _, kv := range serverMD.Items {
		// If the server state has a key that we had in our old
		// state, but not in our new state, we should delete it
		_, okOld := oldMDMap[kv.Key]
		_, okNew := newMDMap[kv.Key]
		if okOld && !okNew {
			continue
		} else {
			curMDMap[kv.Key] = *kv.Value
		}
	}

	// Insert new metadata into existing metadata (overwriting when needed)
	for key, val := range newMDMap {
		curMDMap[key] = val.(string)
	}

	// Reformat old metadata into a list
	serverMD.Items = nil
	for key, val := range curMDMap {
		v := val
		serverMD.Items = append(serverMD.Items, &computeBeta.MetadataItems{
			Key:   key,
			Value: &v,
		})
	}
}

func expandComputeMetadata(m map[string]interface{}) []*compute.MetadataItems {
	metadata := make([]*compute.MetadataItems, len(m))
	// Append new metadata to existing metadata
	for key, val := range m {
		v := val.(string)
		metadata = append(metadata, &compute.MetadataItems{
			Key:   key,
			Value: &v,
		})
	}

	return metadata
}

func flattenMetadataBeta(metadata *computeBeta.Metadata) map[string]string {
	metadataMap := make(map[string]string)
	for _, item := range metadata.Items {
		metadataMap[item.Key] = *item.Value
	}
	return metadataMap
}

// This function differs from flattenMetadataBeta only in that it takes
// compute.metadata rather than computeBeta.metadata as an argument. It should
// be removed in favour of flattenMetadataBeta if/when all resources using it get
// beta support.
func flattenMetadata(metadata *compute.Metadata) map[string]interface{} {
	metadataMap := make(map[string]interface{})
	for _, item := range metadata.Items {
		metadataMap[item.Key] = *item.Value
	}
	return metadataMap
}

func resourceInstanceMetadata(d TerraformResourceData) (*computeBeta.Metadata, error) {
	m := &computeBeta.Metadata{}
	mdMap := d.Get("metadata").(map[string]interface{})
	if v, ok := d.GetOk("metadata_startup_script"); ok && v.(string) != "" {
		if _, ok := mdMap["startup-script"]; ok {
			return nil, errors.New("Cannot provide both metadata_startup_script and metadata.startup-script.")
		}
		mdMap["startup-script"] = v
	}
	if len(mdMap) > 0 {
		m.Items = make([]*computeBeta.MetadataItems, 0, len(mdMap))
		var keys []string
		for k := range mdMap {
			keys = append(keys, k)
		}
		sort.Strings(keys)
		for _, k := range keys {
			v := mdMap[k].(string)
			m.Items = append(m.Items, &computeBeta.MetadataItems{
				Key:   k,
				Value: &v,
			})
		}

		// Set the fingerprint. If the metadata has never been set before
		// then this will just be blank.
		m.Fingerprint = d.Get("metadata_fingerprint").(string)
	}

	return m, nil
}
<commit_msg>Vcr ignore tests (#3486) (#438)<commit_after>package google

import (
	"errors"
	"fmt"
	"log"
	"sort"

	computeBeta "google.golang.org/api/compute/v0.beta"
	"google.golang.org/api/compute/v1"
)

const METADATA_FINGERPRINT_RETRIES = 10

// Since the google compute API uses optimistic locking, there is a chance
// we need to resubmit our updated metadata. To do this, you need to provide
// an update function that attempts to submit your metadata
func MetadataRetryWrapper(update func() error) error {
	attempt := 0
	for attempt < METADATA_FINGERPRINT_RETRIES {
		err := update()
		if err == nil {
			return nil
		}

		if ok, _ := isFingerprintError(err); !ok {
			// Something else went wrong, don't retry
			return err
		}

		log.Printf("[DEBUG] Dismissed an error as retryable as a fingerprint mismatch: %s", err)
		attempt++
	}
	return fmt.Errorf("Failed to update metadata after %d retries", attempt)
}

// Update the metadata (serverMD) according to the provided diff (oldMDMap v
// newMDMap).
func MetadataUpdate(oldMDMap map[string]interface{}, newMDMap map[string]interface{}, serverMD *compute.Metadata) {
	curMDMap := make(map[string]string)
	// Load metadata on server into map
	for _, kv := range serverMD.Items {
		// If the server state has a key that we had in our old
		// state, but not in our new state, we should delete it
		_, okOld := oldMDMap[kv.Key]
		_, okNew := newMDMap[kv.Key]
		if okOld && !okNew {
			continue
		} else {
			curMDMap[kv.Key] = *kv.Value
		}
	}

	// Insert new metadata into existing metadata (overwriting when needed)
	for key, val := range newMDMap {
		curMDMap[key] = val.(string)
	}

	// Reformat old metadata into a list
	serverMD.Items = nil
	for key, val := range curMDMap {
		v := val
		serverMD.Items = append(serverMD.Items, &compute.MetadataItems{
			Key:   key,
			Value: &v,
		})
	}
}

// Update the beta metadata (serverMD) according to the provided diff (oldMDMap v
// newMDMap).
func BetaMetadataUpdate(oldMDMap map[string]interface{}, newMDMap map[string]interface{}, serverMD *computeBeta.Metadata) {
	curMDMap := make(map[string]string)
	// Load metadata on server into map
	for _, kv := range serverMD.Items {
		// If the server state has a key that we had in our old
		// state, but not in our new state, we should delete it
		_, okOld := oldMDMap[kv.Key]
		_, okNew := newMDMap[kv.Key]
		if okOld && !okNew {
			continue
		} else {
			curMDMap[kv.Key] = *kv.Value
		}
	}

	// Insert new metadata into existing metadata (overwriting when needed)
	for key, val := range newMDMap {
		curMDMap[key] = val.(string)
	}

	// Reformat old metadata into a list
	serverMD.Items = nil
	for key, val := range curMDMap {
		v := val
		serverMD.Items = append(serverMD.Items, &computeBeta.MetadataItems{
			Key:   key,
			Value: &v,
		})
	}
}

func expandComputeMetadata(m map[string]interface{}) []*compute.MetadataItems {
	metadata := make([]*compute.MetadataItems, len(m))
	var keys []string
	for key := range m {
		keys = append(keys, key)
	}
	sort.Strings(keys)
	// Append new metadata to existing metadata
	for _, key := range keys {
		v := m[key].(string)
		metadata = append(metadata, &compute.MetadataItems{
			Key:   key,
			Value: &v,
		})
	}

	return metadata
}

func flattenMetadataBeta(metadata *computeBeta.Metadata) map[string]string {
	metadataMap := make(map[string]string)
	for _, item := range metadata.Items {
		metadataMap[item.Key] = *item.Value
	}
	return metadataMap
}

// This function differs from flattenMetadataBeta only in that it takes
// compute.metadata rather than computeBeta.metadata as an argument. It should
// be removed in favour of flattenMetadataBeta if/when all resources using it get
// beta support.
func flattenMetadata(metadata *compute.Metadata) map[string]interface{} {
	metadataMap := make(map[string]interface{})
	for _, item := range metadata.Items {
		metadataMap[item.Key] = *item.Value
	}
	return metadataMap
}

func resourceInstanceMetadata(d TerraformResourceData) (*computeBeta.Metadata, error) {
	m := &computeBeta.Metadata{}
	mdMap := d.Get("metadata").(map[string]interface{})
	if v, ok := d.GetOk("metadata_startup_script"); ok && v.(string) != "" {
		if _, ok := mdMap["startup-script"]; ok {
			return nil, errors.New("Cannot provide both metadata_startup_script and metadata.startup-script.")
		}
		mdMap["startup-script"] = v
	}
	if len(mdMap) > 0 {
		m.Items = make([]*computeBeta.MetadataItems, 0, len(mdMap))
		var keys []string
		for k := range mdMap {
			keys = append(keys, k)
		}
		sort.Strings(keys)
		for _, k := range keys {
			v := mdMap[k].(string)
			m.Items = append(m.Items, &computeBeta.MetadataItems{
				Key:   k,
				Value: &v,
			})
		}

		// Set the fingerprint. If the metadata has never been set before
		// then this will just be blank.
		m.Fingerprint = d.Get("metadata_fingerprint").(string)
	}

	return m, nil
}
<|endoftext|>