<commit_before>package sms // import "github.com/mndrix/sms-over-xmpp"
import (
	"crypto/rand"
	"encoding/base32"
	"encoding/xml"
	"log"
	"strings"

	xco "github.com/mndrix/go-xco"
	"github.com/pkg/errors"
)

// gatewayProcess is the piece which sits between the XMPP and HTTP
// processes, translating between their different protocols.
type gatewayProcess struct {
	// fields shared with Component. see docs there
	config     Config
	receiptFor map[string]*xco.Message
	smsRx      <-chan RxSms
	xmppRx     <-chan *xco.Message
	xmppTx     chan<- *xco.Message
}

func (g *gatewayProcess) run() <-chan struct{} {
	healthCh := make(chan struct{})
	go g.loop(healthCh)
	return healthCh
}

func (g *gatewayProcess) loop(healthCh chan<- struct{}) {
	defer func() { close(healthCh) }()

	for {
		select {
		case rxSms := <-g.smsRx:
			var err error
			errCh := rxSms.ErrCh()

			switch x := rxSms.(type) {
			case *rxSmsMessage:
				err = g.sms2xmpp(x.sms)
			case *rxSmsStatus:
				switch x.status {
				case smsDelivered:
					err = g.smsDelivered(x.id)
				default:
					log.Panicf("unexpected SMS status: %d", x.status)
				}
			default:
				log.Panicf("unexpected RxSms type: %#v", rxSms)
			}

			go func() { errCh <- err }()
		case msg := <-g.xmppRx:
			err := g.xmpp2sms(msg)
			if err != nil {
				log.Printf("ERROR: converting XMPP to SMS: %s", err)
				return
			}
		}
	}
}

func (g *gatewayProcess) sms2xmpp(sms *Sms) error {
	var err error
	msg := &xco.Message{
		XMLName: xml.Name{
			Local: "message",
			Space: "jabber:component:accept",
		},
		Header: xco.Header{
			ID: NewId(),
		},
		Type: "chat",
		Body: sms.Body,
	}

	// convert author's phone number into XMPP address
	msg.Header.From, err = g.config.PhoneToAddress(sms.From)
	if err == nil {
		msg.Header.To, err = g.config.PhoneToAddress(sms.To)
	}
	switch err {
	case nil:
		go func() {
			if x, ok := g.config.(CanCnam); ok {
				msg.Nick = x.Cnam(sms.From, sms.To)
			}
			g.xmppTx <- msg
		}()
	case ErrIgnoreMessage:
		log.Println("ignoring message based on phone number")
	default:
		return errors.Wrap(err, "sms2xmpp")
	}

	return nil
}

func (g *gatewayProcess) smsDelivered(smsId string) error {
	if receipt, ok := g.receiptFor[smsId]; ok {
		go func() { g.xmppTx <- receipt }()
		log.Printf("Sent SMS delivery receipt")
		delete(g.receiptFor, smsId)
	}
	return nil
}

func (g *gatewayProcess) xmpp2sms(m *xco.Message) error {
	var err error
	sms := &Sms{Body: m.Body}

	sms.To, err = g.config.AddressToPhone(m.To)
	if err == nil {
		sms.From, err = g.config.AddressToPhone(m.From)
	}
	switch err {
	case nil:
		// all is well. we'll continue below
	case ErrIgnoreMessage:
		log.Println("ignoring message based on jid")
		return nil
	default:
		return errors.Wrap(err, "xmpp2sms")
	}

	// choose an SMS provider
	provider, err := g.config.SmsProvider()
	switch err {
	case nil:
		// all is well. we'll continue below
	case ErrIgnoreMessage:
		return nil
	default:
		return errors.Wrap(err, "choosing an SMS provider")
	}

	// send the message
	id, err := provider.SendSms(sms)
	if err != nil {
		return errors.Wrap(err, "sending SMS")
	}
	log.Printf("Sent SMS with ID %s", id)

	// prepare to handle delivery receipts
	if m.ReceiptRequest != nil && id != "" {
		receipt := xco.Message{
			Header: xco.Header{
				From: m.Header.To,
				To:   m.Header.From,
				ID:   NewId(),
			},
			ReceiptAck: &xco.ReceiptAck{
				Id: m.Header.ID,
			},
			XMLName: m.XMLName,
		}
		if len(g.receiptFor) > 10 { // don't get too big
			log.Printf("clearing pending receipts queue")
			g.receiptFor = make(map[string]*xco.Message)
		}
		g.receiptFor[id] = &receipt
		log.Printf("Waiting to send receipt: %#v", receipt)
	}

	return nil
}

// NewId generates a random string which is suitable as an XMPP stanza
// ID.  The string contains enough entropy to be universally unique.
func NewId() string {
	// generate 128 random bits (6 more than standard UUID)
	bytes := make([]byte, 16)
	_, err := rand.Read(bytes)
	if err != nil {
		panic(err)
	}

	// convert them to base 32 encoding
	s := base32.StdEncoding.EncodeToString(bytes)
	return strings.ToLower(strings.TrimRight(s, "="))
}
<commit_msg>Remove unnecessary phone number <-> JID conversions<commit_after>package sms // import "github.com/mndrix/sms-over-xmpp"
import (
	"crypto/rand"
	"encoding/base32"
	"encoding/xml"
	"log"
	"strings"

	xco "github.com/mndrix/go-xco"
	"github.com/pkg/errors"
)

// gatewayProcess is the piece which sits between the XMPP and HTTP
// processes, translating between their different protocols.
type gatewayProcess struct {
	// fields shared with Component. see docs there
	config     Config
	receiptFor map[string]*xco.Message
	smsRx      <-chan RxSms
	xmppRx     <-chan *xco.Message
	xmppTx     chan<- *xco.Message
}

func (g *gatewayProcess) run() <-chan struct{} {
	healthCh := make(chan struct{})
	go g.loop(healthCh)
	return healthCh
}

func (g *gatewayProcess) loop(healthCh chan<- struct{}) {
	defer func() { close(healthCh) }()

	for {
		select {
		case rxSms := <-g.smsRx:
			var err error
			errCh := rxSms.ErrCh()

			switch x := rxSms.(type) {
			case *rxSmsMessage:
				err = g.sms2xmpp(x.sms)
			case *rxSmsStatus:
				switch x.status {
				case smsDelivered:
					err = g.smsDelivered(x.id)
				default:
					log.Panicf("unexpected SMS status: %d", x.status)
				}
			default:
				log.Panicf("unexpected RxSms type: %#v", rxSms)
			}

			go func() { errCh <- err }()
		case msg := <-g.xmppRx:
			err := g.xmpp2sms(msg)
			if err != nil {
				log.Printf("ERROR: converting XMPP to SMS: %s", err)
				return
			}
		}
	}
}

func (g *gatewayProcess) sms2xmpp(sms *Sms) error {
	var err error
	msg := &xco.Message{
		XMLName: xml.Name{
			Local: "message",
			Space: "jabber:component:accept",
		},
		Header: xco.Header{
			ID: NewId(),
			From: xco.Address{
				LocalPart: sms.From,
				DomainPart: g.config.ComponentName(),
			},
		},
		Type: "chat",
		Body: sms.Body,
	}

	msg.Header.To, err = g.config.PhoneToAddress(sms.To)
	switch err {
	case nil:
		go func() {
			if x, ok := g.config.(CanCnam); ok {
				msg.Nick = x.Cnam(sms.From, sms.To)
			}
			g.xmppTx <- msg
		}()
	case ErrIgnoreMessage:
		log.Println("ignoring message based on phone number")
	default:
		return errors.Wrap(err, "sms2xmpp")
	}

	return nil
}

func (g *gatewayProcess) smsDelivered(smsId string) error {
	if receipt, ok := g.receiptFor[smsId]; ok {
		go func() { g.xmppTx <- receipt }()
		log.Printf("Sent SMS delivery receipt")
		delete(g.receiptFor, smsId)
	}
	return nil
}

func (g *gatewayProcess) xmpp2sms(m *xco.Message) error {
	var err error
	sms := &Sms{
		Body: m.Body,
		To:   m.To.LocalPart,
	}

	sms.From, err = g.config.AddressToPhone(m.From)
	switch err {
	case nil:
		// all is well. we'll continue below
	case ErrIgnoreMessage:
		log.Println("ignoring message based on jid")
		return nil
	default:
		return errors.Wrap(err, "xmpp2sms")
	}

	// choose an SMS provider
	provider, err := g.config.SmsProvider()
	switch err {
	case nil:
		// all is well. we'll continue below
	case ErrIgnoreMessage:
		return nil
	default:
		return errors.Wrap(err, "choosing an SMS provider")
	}

	// send the message
	id, err := provider.SendSms(sms)
	if err != nil {
		return errors.Wrap(err, "sending SMS")
	}
	log.Printf("Sent SMS with ID %s", id)

	// prepare to handle delivery receipts
	if m.ReceiptRequest != nil && id != "" {
		receipt := xco.Message{
			Header: xco.Header{
				From: m.Header.To,
				To:   m.Header.From,
				ID:   NewId(),
			},
			ReceiptAck: &xco.ReceiptAck{
				Id: m.Header.ID,
			},
			XMLName: m.XMLName,
		}
		if len(g.receiptFor) > 10 { // don't get too big
			log.Printf("clearing pending receipts queue")
			g.receiptFor = make(map[string]*xco.Message)
		}
		g.receiptFor[id] = &receipt
		log.Printf("Waiting to send receipt: %#v", receipt)
	}

	return nil
}

// NewId generates a random string which is suitable as an XMPP stanza
// ID.  The string contains enough entropy to be universally unique.
func NewId() string {
	// generate 128 random bits (6 more than standard UUID)
	bytes := make([]byte, 16)
	_, err := rand.Read(bytes)
	if err != nil {
		panic(err)
	}

	// convert them to base 32 encoding
	s := base32.StdEncoding.EncodeToString(bytes)
	return strings.ToLower(strings.TrimRight(s, "="))
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"lambda.sx/marcus/lambdago/migrate"
	"lambda.sx/marcus/lambdago/sql"
	"lambda.sx/marcus/lambdago/views"
	"net/http"
	"strings"
)

// Function that is called for a view
type ViewFunc func(*http.Request, http.ResponseWriter) (error, string)

var urlMap = map[string]ViewFunc{
	"/":              views.HandleIndex,
	"/register":      views.HandleRegister,
	"/login":         views.HandleLogin,
	"/logout":        views.HandleLogout,
	"/usercp":        views.HandleUserCP,
	"/toggleencrypt": views.HandleToggleEncryption,
	"/settheme":      views.HandleSetTheme,
	"/keycheck":      views.HandleVerifyKey,
	"/getkey":        views.HandleGetKey,
	"/upload":        views.HandleUpload,
	"/paste":         views.HandlePaste,
	"/p":             views.HandlePaste,
	"/about":         views.HandleAbout,
	"/manageuploads": views.HandleManageUploads,
	"/delete":        views.HandleDelete,
}

func main() {
	// Start up SQL connection
	sql.Init()
	migrate.MigrateDB()

	// Create a static server for serving things in the static/ directory
	staticServer := http.FileServer(http.Dir("static"))
	// Use a static file server for /static/
	http.Handle("/static/", http.StripPrefix("/static/", staticServer))
	// Use handler for everything else
	http.HandleFunc("/", handler)

	// Start listening on port 9000 on every interface
	http.ListenAndServe(":9000", nil)
}

// handler dispatches requests to their view or serves an error
func handler(w http.ResponseWriter, r *http.Request) {
	vfunc := urlMap[strings.Split(strings.ToLower(r.URL.String()), "?")[0]]
	if vfunc == nil { // 404, no view to handle request
		vfunc = views.HandleDefault
	}
	err, responseHtml := vfunc(r, w)
	if err != nil { // 500, server dun goofed
		//TODO 500 page
		fmt.Println("500 at " + r.URL.String())
		panic(err)
	}
	fmt.Fprint(w, responseHtml) // Print the html to the response writer
}
<commit_msg>Forgot to call init<commit_after>package main

import (
	"fmt"
	"lambda.sx/marcus/lambdago/migrate"
	"lambda.sx/marcus/lambdago/settings"
	"lambda.sx/marcus/lambdago/sql"
	"lambda.sx/marcus/lambdago/views"
	"net/http"
	"strings"
)

// Function that is called for a view
type ViewFunc func(*http.Request, http.ResponseWriter) (error, string)

var urlMap = map[string]ViewFunc{
	"/":              views.HandleIndex,
	"/register":      views.HandleRegister,
	"/login":         views.HandleLogin,
	"/logout":        views.HandleLogout,
	"/usercp":        views.HandleUserCP,
	"/toggleencrypt": views.HandleToggleEncryption,
	"/settheme":      views.HandleSetTheme,
	"/keycheck":      views.HandleVerifyKey,
	"/getkey":        views.HandleGetKey,
	"/upload":        views.HandleUpload,
	"/paste":         views.HandlePaste,
	"/p":             views.HandlePaste,
	"/about":         views.HandleAbout,
	"/manageuploads": views.HandleManageUploads,
	"/delete":        views.HandleDelete,
}

func main() {
	settings.Init()
	// Start up SQL connection
	sql.Init()
	migrate.MigrateDB()

	// Create a static server for serving things in the static/ directory
	staticServer := http.FileServer(http.Dir("static"))
	// Use a static file server for /static/
	http.Handle("/static/", http.StripPrefix("/static/", staticServer))
	// Use handler for everything else
	http.HandleFunc("/", handler)

	// Start listening on port 9000 on every interface
	http.ListenAndServe(":9000", nil)
}

// handler dispatches requests to their view or serves an error
func handler(w http.ResponseWriter, r *http.Request) {
	vfunc := urlMap[strings.Split(strings.ToLower(r.URL.String()), "?")[0]]
	if vfunc == nil { // 404, no view to handle request
		vfunc = views.HandleDefault
	}
	err, responseHtml := vfunc(r, w)
	if err != nil { // 500, server dun goofed
		//TODO 500 page
		fmt.Println("500 at " + r.URL.String())
		panic(err)
	}
	fmt.Fprint(w, responseHtml) // Print the html to the response writer
}
<|endoftext|>


<commit_before>package drivers

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os/exec"
	"strings"

	"github.com/lxc/lxd/lxd/migration"
	"github.com/lxc/lxd/lxd/operations"
	"github.com/lxc/lxd/lxd/revert"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	log "github.com/lxc/lxd/shared/log15"
	"github.com/lxc/lxd/shared/units"
	"github.com/lxc/lxd/shared/validate"
)

var cephAllowedFilesystems = []string{"btrfs", "ext4", "xfs"}
var cephVersion string
var cephLoaded bool

type ceph struct {
	common
}

// load is used to run one-time action per-driver rather than per-pool.
func (d *ceph) load() error {
	// Register the patches.
	d.patches = map[string]func() error{
		"storage_create_vm":                        nil,
		"storage_zfs_mount":                        nil,
		"storage_create_vm_again":                  nil,
		"storage_zfs_volmode":                      nil,
		"storage_rename_custom_volume_add_project": nil,
		"storage_lvm_skipactivation":               nil,
	}

	// Done if previously loaded.
	if cephLoaded {
		return nil
	}

	// Validate the required binaries.
	for _, tool := range []string{"ceph", "rbd"} {
		_, err := exec.LookPath(tool)
		if err != nil {
			return fmt.Errorf("Required tool '%s' is missing", tool)
		}
	}

	// Detect and record the version.
	if cephVersion == "" {
		out, err := shared.RunCommand("rbd", "--version")
		if err != nil {
			return err
		}

		cephVersion = strings.TrimSpace(out)
	}

	cephLoaded = true
	return nil
}

// isRemote returns true indicating this driver uses remote storage.
func (d *ceph) isRemote() bool {
	return true
}

// Info returns info about the driver and its environment.
func (d *ceph) Info() Info {
	return Info{
		Name:              "ceph",
		Version:           cephVersion,
		OptimizedImages:   true,
		PreservesInodes:   false,
		Remote:            d.isRemote(),
		VolumeTypes:       []VolumeType{VolumeTypeCustom, VolumeTypeImage, VolumeTypeContainer, VolumeTypeVM},
		BlockBacking:      true,
		RunningCopyFreeze: true,
		DirectIO:          true,
		MountedRoot:       false,
	}
}

// Create is called during pool creation and is effectively using an empty driver struct.
// WARNING: The Create() function cannot rely on any of the struct attributes being set.
func (d *ceph) Create() error {
	revert := revert.New()
	defer revert.Fail()

	d.config["volatile.initial_source"] = d.config["source"]

	// Set default properties if missing.
	if d.config["ceph.cluster_name"] == "" {
		d.config["ceph.cluster_name"] = "ceph"
	}

	if d.config["ceph.user.name"] == "" {
		d.config["ceph.user.name"] = "admin"
	}

	if d.config["ceph.osd.pg_num"] == "" {
		d.config["ceph.osd.pg_num"] = "32"
	} else {
		// Validate.
		_, err := units.ParseByteSizeString(d.config["ceph.osd.pg_num"])
		if err != nil {
			return err
		}
	}

	// Sanity check.
	if d.config["source"] != "" && d.config["ceph.osd.pool_name"] != "" && d.config["source"] != d.config["ceph.osd.pool_name"] {
		return fmt.Errorf(`The "source" and "ceph.osd.pool_name" property must not differ for Ceph OSD storage pools`)
	}

	// Use an existing OSD pool.
	if d.config["source"] != "" {
		d.config["ceph.osd.pool_name"] = d.config["source"]
	}

	if d.config["ceph.osd.pool_name"] == "" {
		d.config["ceph.osd.pool_name"] = d.name
		d.config["source"] = d.name
	}

	dummyVol := NewVolume(d, d.name, VolumeType("lxd"), ContentTypeFS, d.config["ceph.osd.pool_name"], nil, nil)

	if !d.osdPoolExists() {
		// Create new osd pool.
		_, err := shared.TryRunCommand("ceph",
			"--name", fmt.Sprintf("client.%s", d.config["ceph.user.name"]),
			"--cluster", d.config["ceph.cluster_name"],
			"osd",
			"pool",
			"create",
			d.config["ceph.osd.pool_name"],
			d.config["ceph.osd.pg_num"])
		if err != nil {
			return err
		}

		revert.Add(func() { d.osdDeletePool() })

		// Initialize the pool. This is not necessary but allows the pool to be monitored.
		_, err = shared.TryRunCommand("rbd",
			"--id", d.config["ceph.user.name"],
			"--cluster", d.config["ceph.cluster_name"],
			"pool",
			"init",
			d.config["ceph.osd.pool_name"])
		if err != nil {
			d.logger.Warn("Failed to initialize pool", log.Ctx{"pool": d.config["ceph.osd.pool_name"], "cluster": d.config["ceph.cluster_name"]})
		}

		// Create dummy storage volume. Other LXD instances will use this to detect whether this osd pool is already in use by another LXD instance.
		err = d.rbdCreateVolume(dummyVol, "0")
		if err != nil {
			return err
		}
		d.config["volatile.pool.pristine"] = "true"
	} else {
		ok := d.HasVolume(dummyVol)
		d.config["volatile.pool.pristine"] = "false"
		if ok {
			if d.config["ceph.osd.force_reuse"] == "" || !shared.IsTrue(d.config["ceph.osd.force_reuse"]) {
				return fmt.Errorf("Pool '%s' in cluster '%s' seems to be in use by another LXD instance. Use 'ceph.osd.force_reuse=true' to force", d.config["ceph.osd.pool_name"], d.config["ceph.cluster_name"])
			}
		}

		// Use existing OSD pool.
		msg, err := shared.RunCommand("ceph",
			"--name", fmt.Sprintf("client.%s", d.config["ceph.user.name"]),
			"--cluster", d.config["ceph.cluster_name"],
			"osd",
			"pool",
			"get",
			d.config["ceph.osd.pool_name"],
			"pg_num")
		if err != nil {
			return err
		}

		idx := strings.Index(msg, "pg_num:")
		if idx == -1 {
			return fmt.Errorf("Failed to parse number of placement groups for pool: %s", msg)
		}

		msg = msg[(idx + len("pg_num:")):]
		msg = strings.TrimSpace(msg)

		// It is ok to update the pool configuration since storage pool
		// creation via API is implemented such that the storage pool is
		// checked for a changed config after this function returns and
		// if so the db for it is updated.
		d.config["ceph.osd.pg_num"] = msg
	}

	revert.Success()

	return nil
}

// Delete removes the storage pool from the storage device.
func (d *ceph) Delete(op *operations.Operation) error {
	// Test if the pool exists.
	poolExists := d.osdPoolExists()
	if !poolExists {
		d.logger.Warn("Pool does not exist", log.Ctx{"pool": d.config["ceph.osd.pool_name"], "cluster": d.config["ceph.cluster_name"]})
	}

	// Check whether we own the pool and only remove in this case.
	if d.config["volatile.pool.pristine"] != "" &&
		shared.IsTrue(d.config["volatile.pool.pristine"]) {

		// Delete the osd pool.
		if poolExists {
			err := d.osdDeletePool()
			if err != nil {
				return err
			}
		}
	}

	// If the user completely destroyed it, call it done.
	if !shared.PathExists(GetPoolMountPath(d.name)) {
		return nil
	}

	// On delete, wipe everything in the directory.
	err := wipeDirectory(GetPoolMountPath(d.name))
	if err != nil {
		return err
	}

	return nil
}

// Validate checks that all provide keys are supported and that no conflicting or missing configuration is present.
func (d *ceph) Validate(config map[string]string) error {
	rules := map[string]func(value string) error{
		"ceph.cluster_name":       validate.IsAny,
		"ceph.osd.force_reuse":    validate.Optional(validate.IsBool),
		"ceph.osd.pg_num":         validate.IsAny,
		"ceph.osd.pool_name":      validate.IsAny,
		"ceph.osd.data_pool_name": validate.IsAny,
		"ceph.rbd.clone_copy":     validate.Optional(validate.IsBool),
		"ceph.user.name":          validate.IsAny,
		"volatile.pool.pristine":  validate.IsAny,
		"volume.block.filesystem": func(value string) error {
			if value == "" {
				return nil
			}
			return validate.IsOneOf(value, cephAllowedFilesystems)
		},
		"volume.block.mount_options": validate.IsAny,
	}

	return d.validatePool(config, rules)
}

// Update applies any driver changes required from a configuration change.
func (d *ceph) Update(changedConfig map[string]string) error {
	return nil
}

// Mount mounts the storage pool.
func (d *ceph) Mount() (bool, error) {
	// Nothing to do here.
	return true, nil
}

// Unmount unmounts the storage pool.
func (d *ceph) Unmount() (bool, error) {
	// Nothing to do here.
	return true, nil
}

// GetResources returns the pool resource usage information.
func (d *ceph) GetResources() (*api.ResourcesStoragePool, error) {
	var stdout bytes.Buffer

	err := shared.RunCommandWithFds(nil, &stdout,
		"ceph",
		"--name", fmt.Sprintf("client.%s", d.config["ceph.user.name"]),
		"--cluster", d.config["ceph.cluster_name"],
		"df",
		"-f", "json")
	if err != nil {
		return nil, err
	}

	// Temporary structs for parsing.
	type cephDfPoolStats struct {
		BytesUsed      int64 `json:"bytes_used"`
		BytesAvailable int64 `json:"max_avail"`
	}

	type cephDfPool struct {
		Name  string          `json:"name"`
		Stats cephDfPoolStats `json:"stats"`
	}

	type cephDf struct {
		Pools []cephDfPool `json:"pools"`
	}

	// Parse the JSON output.
	df := cephDf{}
	err = json.NewDecoder(&stdout).Decode(&df)
	if err != nil {
		return nil, err
	}

	var pool *cephDfPool
	for _, entry := range df.Pools {
		if entry.Name == d.config["ceph.osd.pool_name"] {
			pool = &entry
			break
		}
	}

	if pool == nil {
		return nil, fmt.Errorf("OSD pool missing in df output")
	}

	spaceUsed := uint64(pool.Stats.BytesUsed)
	spaceAvailable := uint64(pool.Stats.BytesAvailable)

	res := api.ResourcesStoragePool{}
	res.Space.Total = spaceAvailable + spaceUsed
	res.Space.Used = spaceUsed

	return &res, nil
}

// MigrationType returns the type of transfer methods to be used when doing migrations between pools in preference order.
func (d *ceph) MigrationTypes(contentType ContentType, refresh bool) []migration.Type {
	var rsyncFeatures []string

	// Do not pass compression argument to rsync if the associated
	// config key, that is rsync.compression, is set to false.
	if d.Config()["rsync.compression"] != "" && !shared.IsTrue(d.Config()["rsync.compression"]) {
		rsyncFeatures = []string{"delete", "bidirectional"}
	} else {
		rsyncFeatures = []string{"delete", "compress", "bidirectional"}
	}

	if refresh {
		var transportType migration.MigrationFSType

		if contentType == ContentTypeBlock {
			transportType = migration.MigrationFSType_BLOCK_AND_RSYNC
		} else {
			transportType = migration.MigrationFSType_RSYNC
		}

		return []migration.Type{
			{
				FSType:   transportType,
				Features: rsyncFeatures,
			},
		}
	}

	if contentType == ContentTypeBlock {
		return []migration.Type{
			{
				FSType: migration.MigrationFSType_RBD,
			},
			{
				FSType:   migration.MigrationFSType_BLOCK_AND_RSYNC,
				Features: rsyncFeatures,
			},
		}
	}

	return []migration.Type{
		{
			FSType: migration.MigrationFSType_RBD,
		},
		{
			FSType:   migration.MigrationFSType_RSYNC,
			Features: rsyncFeatures,
		},
	}
}
<commit_msg>lxd/storage/drivers/driver/ceph: Simplify Delete logic<commit_after>package drivers

import (
	"bytes"
	"encoding/json"
	"fmt"
	"os/exec"
	"strings"

	"github.com/lxc/lxd/lxd/migration"
	"github.com/lxc/lxd/lxd/operations"
	"github.com/lxc/lxd/lxd/revert"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/api"
	log "github.com/lxc/lxd/shared/log15"
	"github.com/lxc/lxd/shared/units"
	"github.com/lxc/lxd/shared/validate"
)

var cephAllowedFilesystems = []string{"btrfs", "ext4", "xfs"}
var cephVersion string
var cephLoaded bool

type ceph struct {
	common
}

// load is used to run one-time action per-driver rather than per-pool.
func (d *ceph) load() error {
	// Register the patches.
	d.patches = map[string]func() error{
		"storage_create_vm":                        nil,
		"storage_zfs_mount":                        nil,
		"storage_create_vm_again":                  nil,
		"storage_zfs_volmode":                      nil,
		"storage_rename_custom_volume_add_project": nil,
		"storage_lvm_skipactivation":               nil,
	}

	// Done if previously loaded.
	if cephLoaded {
		return nil
	}

	// Validate the required binaries.
	for _, tool := range []string{"ceph", "rbd"} {
		_, err := exec.LookPath(tool)
		if err != nil {
			return fmt.Errorf("Required tool '%s' is missing", tool)
		}
	}

	// Detect and record the version.
	if cephVersion == "" {
		out, err := shared.RunCommand("rbd", "--version")
		if err != nil {
			return err
		}

		cephVersion = strings.TrimSpace(out)
	}

	cephLoaded = true
	return nil
}

// isRemote returns true indicating this driver uses remote storage.
func (d *ceph) isRemote() bool {
	return true
}

// Info returns info about the driver and its environment.
func (d *ceph) Info() Info {
	return Info{
		Name:              "ceph",
		Version:           cephVersion,
		OptimizedImages:   true,
		PreservesInodes:   false,
		Remote:            d.isRemote(),
		VolumeTypes:       []VolumeType{VolumeTypeCustom, VolumeTypeImage, VolumeTypeContainer, VolumeTypeVM},
		BlockBacking:      true,
		RunningCopyFreeze: true,
		DirectIO:          true,
		MountedRoot:       false,
	}
}

// Create is called during pool creation and is effectively using an empty driver struct.
// WARNING: The Create() function cannot rely on any of the struct attributes being set.
func (d *ceph) Create() error {
	revert := revert.New()
	defer revert.Fail()

	d.config["volatile.initial_source"] = d.config["source"]

	// Set default properties if missing.
	if d.config["ceph.cluster_name"] == "" {
		d.config["ceph.cluster_name"] = "ceph"
	}

	if d.config["ceph.user.name"] == "" {
		d.config["ceph.user.name"] = "admin"
	}

	if d.config["ceph.osd.pg_num"] == "" {
		d.config["ceph.osd.pg_num"] = "32"
	} else {
		// Validate.
		_, err := units.ParseByteSizeString(d.config["ceph.osd.pg_num"])
		if err != nil {
			return err
		}
	}

	// Sanity check.
	if d.config["source"] != "" && d.config["ceph.osd.pool_name"] != "" && d.config["source"] != d.config["ceph.osd.pool_name"] {
		return fmt.Errorf(`The "source" and "ceph.osd.pool_name" property must not differ for Ceph OSD storage pools`)
	}

	// Use an existing OSD pool.
	if d.config["source"] != "" {
		d.config["ceph.osd.pool_name"] = d.config["source"]
	}

	if d.config["ceph.osd.pool_name"] == "" {
		d.config["ceph.osd.pool_name"] = d.name
		d.config["source"] = d.name
	}

	dummyVol := NewVolume(d, d.name, VolumeType("lxd"), ContentTypeFS, d.config["ceph.osd.pool_name"], nil, nil)

	if !d.osdPoolExists() {
		// Create new osd pool.
		_, err := shared.TryRunCommand("ceph",
			"--name", fmt.Sprintf("client.%s", d.config["ceph.user.name"]),
			"--cluster", d.config["ceph.cluster_name"],
			"osd",
			"pool",
			"create",
			d.config["ceph.osd.pool_name"],
			d.config["ceph.osd.pg_num"])
		if err != nil {
			return err
		}

		revert.Add(func() { d.osdDeletePool() })

		// Initialize the pool. This is not necessary but allows the pool to be monitored.
		_, err = shared.TryRunCommand("rbd",
			"--id", d.config["ceph.user.name"],
			"--cluster", d.config["ceph.cluster_name"],
			"pool",
			"init",
			d.config["ceph.osd.pool_name"])
		if err != nil {
			d.logger.Warn("Failed to initialize pool", log.Ctx{"pool": d.config["ceph.osd.pool_name"], "cluster": d.config["ceph.cluster_name"]})
		}

		// Create dummy storage volume. Other LXD instances will use this to detect whether this osd pool is already in use by another LXD instance.
		err = d.rbdCreateVolume(dummyVol, "0")
		if err != nil {
			return err
		}
		d.config["volatile.pool.pristine"] = "true"
	} else {
		ok := d.HasVolume(dummyVol)
		d.config["volatile.pool.pristine"] = "false"
		if ok {
			if d.config["ceph.osd.force_reuse"] == "" || !shared.IsTrue(d.config["ceph.osd.force_reuse"]) {
				return fmt.Errorf("Pool '%s' in cluster '%s' seems to be in use by another LXD instance. Use 'ceph.osd.force_reuse=true' to force", d.config["ceph.osd.pool_name"], d.config["ceph.cluster_name"])
			}
		}

		// Use existing OSD pool.
		msg, err := shared.RunCommand("ceph",
			"--name", fmt.Sprintf("client.%s", d.config["ceph.user.name"]),
			"--cluster", d.config["ceph.cluster_name"],
			"osd",
			"pool",
			"get",
			d.config["ceph.osd.pool_name"],
			"pg_num")
		if err != nil {
			return err
		}

		idx := strings.Index(msg, "pg_num:")
		if idx == -1 {
			return fmt.Errorf("Failed to parse number of placement groups for pool: %s", msg)
		}

		msg = msg[(idx + len("pg_num:")):]
		msg = strings.TrimSpace(msg)

		// It is ok to update the pool configuration since storage pool
		// creation via API is implemented such that the storage pool is
		// checked for a changed config after this function returns and
		// if so the db for it is updated.
		d.config["ceph.osd.pg_num"] = msg
	}

	revert.Success()

	return nil
}

// Delete removes the storage pool from the storage device.
func (d *ceph) Delete(op *operations.Operation) error {
	// Test if the pool exists.
	poolExists := d.osdPoolExists()
	if !poolExists {
		d.logger.Warn("Pool does not exist", log.Ctx{"pool": d.config["ceph.osd.pool_name"], "cluster": d.config["ceph.cluster_name"]})
	}

	// Check whether we own the pool and only remove in this case.
	if shared.IsTrue(d.config["volatile.pool.pristine"]) {
		// Delete the osd pool.
		if poolExists {
			err := d.osdDeletePool()
			if err != nil {
				return err
			}
		}
	}

	// If the user completely destroyed it, call it done.
	if !shared.PathExists(GetPoolMountPath(d.name)) {
		return nil
	}

	// On delete, wipe everything in the directory.
	err := wipeDirectory(GetPoolMountPath(d.name))
	if err != nil {
		return err
	}

	return nil
}

// Validate checks that all provide keys are supported and that no conflicting or missing configuration is present.
func (d *ceph) Validate(config map[string]string) error {
	rules := map[string]func(value string) error{
		"ceph.cluster_name":       validate.IsAny,
		"ceph.osd.force_reuse":    validate.Optional(validate.IsBool),
		"ceph.osd.pg_num":         validate.IsAny,
		"ceph.osd.pool_name":      validate.IsAny,
		"ceph.osd.data_pool_name": validate.IsAny,
		"ceph.rbd.clone_copy":     validate.Optional(validate.IsBool),
		"ceph.user.name":          validate.IsAny,
		"volatile.pool.pristine":  validate.IsAny,
		"volume.block.filesystem": func(value string) error {
			if value == "" {
				return nil
			}
			return validate.IsOneOf(value, cephAllowedFilesystems)
		},
		"volume.block.mount_options": validate.IsAny,
	}

	return d.validatePool(config, rules)
}

// Update applies any driver changes required from a configuration change.
func (d *ceph) Update(changedConfig map[string]string) error {
	return nil
}

// Mount mounts the storage pool.
func (d *ceph) Mount() (bool, error) {
	// Nothing to do here.
	return true, nil
}

// Unmount unmounts the storage pool.
func (d *ceph) Unmount() (bool, error) {
	// Nothing to do here.
	return true, nil
}

// GetResources returns the pool resource usage information.
func (d *ceph) GetResources() (*api.ResourcesStoragePool, error) {
	var stdout bytes.Buffer

	err := shared.RunCommandWithFds(nil, &stdout,
		"ceph",
		"--name", fmt.Sprintf("client.%s", d.config["ceph.user.name"]),
		"--cluster", d.config["ceph.cluster_name"],
		"df",
		"-f", "json")
	if err != nil {
		return nil, err
	}

	// Temporary structs for parsing.
	type cephDfPoolStats struct {
		BytesUsed      int64 `json:"bytes_used"`
		BytesAvailable int64 `json:"max_avail"`
	}

	type cephDfPool struct {
		Name  string          `json:"name"`
		Stats cephDfPoolStats `json:"stats"`
	}

	type cephDf struct {
		Pools []cephDfPool `json:"pools"`
	}

	// Parse the JSON output.
	df := cephDf{}
	err = json.NewDecoder(&stdout).Decode(&df)
	if err != nil {
		return nil, err
	}

	var pool *cephDfPool
	for _, entry := range df.Pools {
		if entry.Name == d.config["ceph.osd.pool_name"] {
			pool = &entry
			break
		}
	}

	if pool == nil {
		return nil, fmt.Errorf("OSD pool missing in df output")
	}

	spaceUsed := uint64(pool.Stats.BytesUsed)
	spaceAvailable := uint64(pool.Stats.BytesAvailable)

	res := api.ResourcesStoragePool{}
	res.Space.Total = spaceAvailable + spaceUsed
	res.Space.Used = spaceUsed

	return &res, nil
}

// MigrationType returns the type of transfer methods to be used when doing migrations between pools in preference order.
func (d *ceph) MigrationTypes(contentType ContentType, refresh bool) []migration.Type {
	var rsyncFeatures []string

	// Do not pass compression argument to rsync if the associated
	// config key, that is rsync.compression, is set to false.
	if d.Config()["rsync.compression"] != "" && !shared.IsTrue(d.Config()["rsync.compression"]) {
		rsyncFeatures = []string{"delete", "bidirectional"}
	} else {
		rsyncFeatures = []string{"delete", "compress", "bidirectional"}
	}

	if refresh {
		var transportType migration.MigrationFSType

		if contentType == ContentTypeBlock {
			transportType = migration.MigrationFSType_BLOCK_AND_RSYNC
		} else {
			transportType = migration.MigrationFSType_RSYNC
		}

		return []migration.Type{
			{
				FSType:   transportType,
				Features: rsyncFeatures,
			},
		}
	}

	if contentType == ContentTypeBlock {
		return []migration.Type{
			{
				FSType: migration.MigrationFSType_RBD,
			},
			{
				FSType:   migration.MigrationFSType_BLOCK_AND_RSYNC,
				Features: rsyncFeatures,
			},
		}
	}

	return []migration.Type{
		{
			FSType: migration.MigrationFSType_RBD,
		},
		{
			FSType:   migration.MigrationFSType_RSYNC,
			Features: rsyncFeatures,
		},
	}
}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/elsonwu/goio"
	"github.com/go-martini/martini"
)

var flagHost = flag.String("host", "127.0.0.1:9999", "the default server host")
var flagSSLHost = flag.String("sslhost", "", "the server host for https, it will override the host setting")
var flagAllowOrigin = flag.String("alloworigin", "", "the host allow to cross site ajax")
var flagDebug = flag.Bool("debug", false, "enable debug mode or not")
var flagClientLifeCycle = flag.Int64("lifecycle", 30, "how many seconds of the client life cycle")
var flagClientMessageTimeout = flag.Int64("messagetimeout", 15, "how many seconds of the client keep waiting for new messages")
var flagEnableHttps = flag.Bool("enablehttps", false, "enable https or not")
var flagDisableHttp = flag.Bool("disablehttp", false, "disable http and use https only")
var flagCertFile = flag.String("certfile", "", "certificate file path")
var flagKeyFile = flag.String("keyfile", "", "private file path")

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())

	flag.Parse()
	goio.Debug = *flagDebug
	goio.LifeCycle = *flagClientLifeCycle

	clients := goio.GlobalClients()
	rooms := goio.GlobalRooms()
	users := goio.GlobalUsers()

	if *flagDebug {
		go func() {
			for {
				time.Sleep(3 * time.Second)
				log.Printf("rooms: %d, users: %d, clients: %d \n", rooms.Count(), users.Count(), clients.Count())
			}
		}()
	}

	martini.Env = martini.Dev
	router := martini.NewRouter()
	mart := martini.New()
	mart.Action(router.Handle)
	m := &martini.ClassicMartini{mart, router}
	m.Use(martini.Recovery())
	m.Use(func(res http.ResponseWriter) {
		res.Header().Set("Content-Type", "text/plain; charset=utf-8")
		res.Header().Set("Access-Control-Allow-Credentials", "true")
		res.Header().Set("Access-Control-Allow-Methods", "GET,POST")
		if "" != *flagAllowOrigin {
			allowOrigins := strings.Split(*flagAllowOrigin, ",")
			for _, allowOrigin := range allowOrigins {
				res.Header().Add("Access-Control-Allow-Origin", allowOrigin)
			}
		}
	})

	if *flagDebug {
		m.Get("/test", func() string {

			st := time.Now().Unix()
			for i := 0; i < 10000; i++ {
				userId := strconv.Itoa(i)
				user := users.Get(userId)
				if user == nil {
					user = goio.NewUser(userId)
				}

				clt, done := goio.NewClient()
				user.Add(clt)
				done <- true

				room := rooms.Get(strconv.Itoa(i%1000), true)
				room.Add(user)
			}

			return strconv.Itoa(int(time.Now().Unix()-st)) + " seconds"
		})
	}

	m.Get("/count", func(req *http.Request) string {
		res := ""
		res += fmt.Sprintf("rooms: %d, users: %d, clients: %d \n", rooms.Count(), users.Count(), clients.Count())

		if "1" == req.URL.Query().Get("detail") {
			res += fmt.Sprintf("-------------------------------\n")

			rooms.Each(func(room *goio.Room) {
				res += fmt.Sprintf("# room id: %s \n", room.Id)
				for userId, _ := range room.UserIds.Map {
					res += fmt.Sprintf(" - user id: %s \n", userId)
				}
				res += fmt.Sprintf("\n")
			})

			res += fmt.Sprintf("-------------------------------\n")

			users.Each(func(user *goio.User) {
				res += fmt.Sprintf("# user id: %s \n", user.Id)
				for clientId, _ := range user.ClientIds.Map {
					res += fmt.Sprintf(" - client id: %s \n", clientId)
				}
				res += fmt.Sprintf("\n")
			})
		}

		return res
	})

	m.Get("/room/users/:room_id", func(params martini.Params, req *http.Request) (int, string) {
		roomId := params["room_id"]
		if roomId == "" {
			return 403, "room_id is missing"
		}

		room := rooms.Get(roomId, false)
		if room == nil {
			return 200, ""
		}

		return 200, strings.Join(room.UserIds.Array(), ",")
	})

	m.Get("/user/data/:user_id/:key", func(params martini.Params, req *http.Request) (int, string) {
		userId := params["user_id"]
		if userId == "" {
			return 403, "user_id is missing"
		}

		key := params["key"]
		if key == "" {
			return 403, "key is missing"
		}

		user := users.Get(userId)
		if user == nil {
			return 200, ""
		}

		return 200, user.Data().Get(key)
	})

	m.Post("/user/data/:client_id/:key", func(params martini.Params, req *http.Request) (int, string) {
		val, err := ioutil.ReadAll(req.Body)
		if err != nil {
			return 500, err.Error()
		}

		clientId := params["client_id"]
		if clientId == "" {
			return 403, "client_id is missing"
		}

		key := params["key"]
		if key == "" {
			return 403, "key is missing"
		}

		clt := clients.Get(clientId)
		if clt != nil && clt.UserId != "" {
			user := users.Get(clt.UserId)
			if user != nil {
				user.Data().Set(key, string(val))
			}
		}

		return 200, ""
	})

	m.Post("/online_status", func(params martini.Params, req *http.Request) (int, string) {
		val, err := ioutil.ReadAll(req.Body)
		if err != nil {
			return 500, err.Error()
		}

		userIds := strings.Split(string(val), ",")
		status := ""
		for _, userId := range userIds {
			if nil == users.Get(userId) {
				status += "0,"
			} else {
				status += "1,"
			}
		}

		return 200, status
	})

	m.Post("/client/:user_id", func(params martini.Params, req *http.Request) (int, string) {
		userId := params["user_id"]
		user := users.Get(userId)
		if user == nil {
			user = goio.NewUser(userId)
		}

		clt, done := goio.NewClient()
		user.Add(clt)
		done <- true

		return 200, clt.Id
	})

	m.Get("/kill_client/:client_id", func(params martini.Params, req *http.Request) (int, string) {
		id := params["client_id"]
		clt := clients.Get(id)
		if clt != nil {
			clt.Destroy()
		}

		return 204, ""
	})

	m.Get("/message/:client_id", func(params martini.Params, req *http.Request) (int, string) {
		id := params["client_id"]
		clt := clients.Get(id)
		if clt == nil {
			return 404, fmt.Sprintf("Client %s does not exist\n", id)
		}

		clt.Handshake()
		// we handshake again after it finished no matter timeout or ok
		defer clt.Handshake()

		timeNow := time.Now().Unix()
		for {
			if 0 < len(clt.Messages) {
				msgs, err := json.Marshal(clt.Messages)
				if err != nil {
					return 500, err.Error()
				}

				clt.CleanMessages()
				return 200, string(msgs)
			}

			if time.Now().Unix() > timeNow+*flagClientMessageTimeout {
				break
			}

			time.Sleep(100000 * time.Microsecond)
		}

		return 204, ""
	})

	m.Post("/message/:client_id", func(params martini.Params, req *http.Request) (int, string) {
		id := params["client_id"]
		clt := clients.Get(id)
		if clt == nil {
			return 403, fmt.Sprintf("Client %s does not exist\n", id)
		}

		user := users.Get(clt.UserId)
		if user == nil {
			return 403, fmt.Sprintf("Client %s does not connect with any user\n", id)
		}

		clt.Handshake()
		// we handshake again after it finished no matter timeout or ok
		defer clt.Handshake()
		defer req.Body.Close()

		message := &goio.Message{}
		json.NewDecoder(req.Body).Decode(message)
		message.CallerId = clt.UserId
		go func(message *goio.Message) {
			if *flagDebug {
				log.Printf("post message: %#v", *message)
			}

			if message.RoomId == "" && (message.EventName == "join" || message.EventName == "leave") {
				clt.Receive(&goio.Message{
					EventName: "error",
					Data:      "room id is missing",
				})

				return
			}

			// We change CallerId as current user
			message.CallerId = user.Id
			message.ClientId = clt.Id
			user.Emit(message.EventName, message)
		}(message)

		return 204, ""
	})

	m.Options("/.*", func(req *http.Request) {})

	host := *flagHost
	if !*flagEnableHttps && *flagDisableHttp {
		log.Fatalln("You cannot disable http but not enable https in the same time")
	}

	//Prevent exiting
	ch := make(chan bool)

	if !*flagDisableHttp {
		go func() {
			log.Println("Serve at " + host + " - http")
			if err := http.ListenAndServe(host, m); err != nil {
				log.Println(err)
			}
		}()
	}

	if *flagEnableHttps {
		go func() {
			if *flagSSLHost != "" {
				host = *flagSSLHost
			}

			log.Println("Serve at " + host + " - https")
			if err := http.ListenAndServeTLS(host, *flagCertFile, *flagKeyFile, m); err != nil {
				log.Println(err)
			}
		}()
	}

	<-ch
}
<commit_msg>use goji to replace the web framework<commit_after>package main

import (
	"encoding/json"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"runtime"
	"strconv"
	"strings"
	"time"

	"github.com/elsonwu/goio"
	"github.com/zenazn/goji"
	"github.com/zenazn/goji/web"
	"github.com/zenazn/goji/web/middleware"
)

var flagAllowOrigin = flag.String("alloworigin", "", "the host allow to cross site ajax")
var flagDebug = flag.Bool("debug", false, "enable debug mode or not")
var flagClientLifeCycle = flag.Int64("lifecycle", 30, "how many seconds of the client life cycle")
var flagClientMessageTimeout = flag.Int64("messagetimeout", 15, "how many seconds of the client keep waiting for new messages")

func main() {
	runtime.GOMAXPROCS(runtime.NumCPU())

	flag.Parse()
	goio.Debug = *flagDebug
	goio.LifeCycle = *flagClientLifeCycle

	clients := goio.GlobalClients()
	rooms := goio.GlobalRooms()
	users := goio.GlobalUsers()

	if *flagDebug {
		go func() {
			for {
				time.Sleep(3 * time.Second)
				log.Printf("rooms: %d, users: %d, clients: %d \n", rooms.Count(), users.Count(), clients.Count())
			}
		}()
	}

	goji.Abandon(middleware.RequestID)
	goji.Abandon(middleware.Logger)
	goji.Use(func(h http.Handler) http.Handler {
		fn := func(res http.ResponseWriter, r *http.Request) {
			res.Header().Set("Content-Type", "text/plain; charset=utf-8")
			res.Header().Set("Access-Control-Allow-Credentials", "true")
			res.Header().Set("Access-Control-Allow-Methods", "GET,POST")
			if "" != *flagAllowOrigin {
				allowOrigins := strings.Split(*flagAllowOrigin, ",")
				for _, allowOrigin := range allowOrigins {
					res.Header().Add("Access-Control-Allow-Origin", allowOrigin)
				}
			}
			res.Header().Set("Content-Type", "text/plain")
			h.ServeHTTP(res, r)
		}

		return http.HandlerFunc(fn)
	})

	if *flagDebug {
		goji.Get("/test", func(w http.ResponseWriter, req *http.Request) {
			st := time.Now().Unix()
			for i := 0; i < 10000; i++ {
				userId := strconv.Itoa(i)
				user := users.Get(userId)
				if user == nil {
					user = goio.NewUser(userId)
				}

				clt, done := goio.NewClient()
				user.Add(clt)
				done <- true

				room := rooms.Get(strconv.Itoa(i%1000), true)
				room.Add(user)
			}

			io.WriteString(w, strconv.Itoa(int(time.Now().Unix()-st))+" seconds")
		})
	}

	goji.Get("/count", func(w http.ResponseWriter, req *http.Request) {
		res := ""
		res += fmt.Sprintf("rooms: %d, users: %d, clients: %d \n", rooms.Count(), users.Count(), clients.Count())

		if "1" == req.URL.Query().Get("detail") {
			res += fmt.Sprintf("-------------------------------\n")

			rooms.Each(func(room *goio.Room) {
				res += fmt.Sprintf("# room id: %s \n", room.Id)
				for userId, _ := range room.UserIds.Map {
					res += fmt.Sprintf(" - user id: %s \n", userId)
				}
				res += fmt.Sprintf("\n")
			})

			res += fmt.Sprintf("-------------------------------\n")

			users.Each(func(user *goio.User) {
				res += fmt.Sprintf("# user id: %s \n", user.Id)
				for clientId, _ := range user.ClientIds.Map {
					res += fmt.Sprintf(" - client id: %s \n", clientId)
				}
				res += fmt.Sprintf("\n")
			})
		}

		io.WriteString(w, res)
	})

	goji.Get("/room/users/:room_id", func(ctx web.C, w http.ResponseWriter, req *http.Request) {
		roomId := ctx.URLParams["room_id"]
		if roomId == "" {
			http.Error(w, "room_id is missing", http.StatusForbidden)
			return
		}

		room := rooms.Get(roomId, false)
		if room == nil {
			io.WriteString(w, "room does not exist")
			return
		}

		io.WriteString(w, strings.Join(room.UserIds.Array(), ","))
	})

	goji.Get("/room/users/:room_id", func(ctx web.C, w http.ResponseWriter, req *http.Request) {
		roomId := ctx.URLParams["room_id"]
		if roomId == "" {
			http.Error(w, "room_id is missing", http.StatusForbidden)
			return
		}

		room := rooms.Get(roomId, false)
		if room == nil {
			io.WriteString(w, "")
			return
		}

		io.WriteString(w, strings.Join(room.UserIds.Array(), ","))
	})

	goji.Get("/user/data/:user_id/:key", func(ctx web.C, w http.ResponseWriter, req *http.Request) {
		userId := ctx.URLParams["user_id"]
		if userId == "" {
			http.Error(w, "user_id is missing", http.StatusForbidden)
			return
		}

		key := ctx.URLParams["key"]
		if key == "" {
			http.Error(w, "key is missing", http.StatusForbidden)
			return
		}

		user := users.Get(userId)
		if user == nil {
			io.WriteString(w, "")
			return
		}

		io.WriteString(w, user.Data().Get(key))
	})

	goji.Post("/user/data/:client_id/:key", func(ctx web.C, w http.ResponseWriter, req *http.Request) {
		val, err := ioutil.ReadAll(req.Body)
		if err != nil {
			http.Error(w, err.Error(), http.StatusForbidden)
			return
		}

		clientId := ctx.URLParams["client_id"]
		if clientId == "" {
			http.Error(w, "client_id is missing", http.StatusForbidden)
			return
		}

		key := ctx.URLParams["key"]
		if key == "" {
			http.Error(w, "key is missing", http.StatusForbidden)
			return
		}

		clt := clients.Get(clientId)
		if clt != nil && clt.UserId != "" {
			user := users.Get(clt.UserId)
			if user != nil {
				user.Data().Set(key, string(val))
			}
		}

		io.WriteString(w, "")
	})

	goji.Post("/online_status", func(ctx web.C, w http.ResponseWriter, req *http.Request) {
		val, err := ioutil.ReadAll(req.Body)
		if err != nil {
			http.Error(w, err.Error(), http.StatusForbidden)
			return
		}

		userIds := strings.Split(string(val), ",")
		status := ""
		for _, userId := range userIds {
			if nil == users.Get(userId) {
				status += "0,"
			} else {
				status += "1,"
			}
		}

		io.WriteString(w, status)
	})

	goji.Post("/client/:user_id", func(ctx web.C, w http.ResponseWriter, req *http.Request) {
		userId := ctx.URLParams["user_id"]
		user := users.Get(userId)
		if user == nil {
			user = goio.NewUser(userId)
		}

		clt, done := goio.NewClient()
		user.Add(clt)
		done <- true

		io.WriteString(w, clt.Id)
	})

	goji.Get("/kill_client/:client_id", func(ctx web.C, w http.ResponseWriter, req *http.Request) {
		id := ctx.URLParams["client_id"]
		clt := clients.Get(id)
		if clt != nil {
			clt.Destroy()
		}

		io.WriteString(w, "")
	})

	goji.Get("/message/:client_id", func(ctx web.C, w http.ResponseWriter, req *http.Request) {
		id := ctx.URLParams["client_id"]
		clt := clients.Get(id)
		if clt == nil {
			http.Error(w, fmt.Sprintf("Client %s does not exist\n", id), http.StatusNotFound)
			return
		}

		clt.Handshake()
		// we handshake again after it finished no matter timeout or ok
		defer clt.Handshake()

		timeNow := time.Now().Unix()
		for {
			if 0 < len(clt.Messages) {
				msgs, err := json.Marshal(clt.Messages)
				if err != nil {
					http.Error(w, err.Error(), http.StatusInternalServerError)
					return
				}

				clt.CleanMessages()
				io.WriteString(w, string(msgs))
				return
			}

			if time.Now().Unix() > timeNow+*flagClientMessageTimeout {
				break
			}

			time.Sleep(100000 * time.Microsecond)
		}

		io.WriteString(w, "")
	})

	goji.Post("/message/:client_id", func(ctx web.C, w http.ResponseWriter, req *http.Request) {
		id := ctx.URLParams["client_id"]
		clt := clients.Get(id)
		if clt == nil {
			http.Error(w, fmt.Sprintf("Client %s does not exist\n", id), http.StatusForbidden)
			return
		}

		user := users.Get(clt.UserId)
		if user == nil {
			http.Error(w, fmt.Sprintf("Client %s does not connect with any user\n", id), http.StatusForbidden)
			return
		}

		clt.Handshake()
		// we handshake again after it finished no matter timeout or ok
		defer clt.Handshake()
		defer req.Body.Close()

		message := &goio.Message{}
		json.NewDecoder(req.Body).Decode(message)
		message.CallerId = clt.UserId
		go func(message *goio.Message) {
			if *flagDebug {
				log.Printf("post message: %#v", *message)
			}

			if message.RoomId == "" && (message.EventName == "join" || message.EventName == "leave") {
				clt.Receive(&goio.Message{
					EventName: "error",
					Data:      "room id is missing",
				})

				return
			}

			// We change CallerId as current user
			message.CallerId = user.Id
			message.ClientId = clt.Id
			user.Emit(message.EventName, message)
		}(message)

		io.WriteString(w, "")
	})

	goji.Serve()
}
<|endoftext|>


<commit_before>package main

import (
	"io"
	"regexp"

	"github.com/IssueSquare/blog-daddy/adapters/git"
	"github.com/IssueSquare/blog-daddy/providers/s3"

	"log"
	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/spf13/viper"
	"gopkg.in/russross/blackfriday.v2"
)

type (
	Article struct {
		Name    string
		ModTime string
	}
)

type MarkdownParser struct {
	reader io.Reader
}

func NewMarkdownParser(r io.Reader) *MarkdownParser {
	return &MarkdownParser{r}
}

func (m *MarkdownParser) Read(p []byte) (n int, err error) {
	n, err = m.reader.Read(p)
	copy(p, blackfriday.Run(p))

	return n, err
}

func main() {
	viper.SetConfigName("config")
	viper.AddConfigPath(".")
	//viper.AddConfigPath("/var/run/secret")
	err := viper.ReadInConfig() // Find and read the config file
	if err != nil {             // Handle errors reading the config file
		panic(err)
	}

	router := gin.Default()
	router.LoadHTMLGlob("templates/*")

	router.POST("/setup", func(c *gin.Context) {
		var u git.User
		if c.BindJSON(&u) == nil {
			//minio cannot create bucket name with uppercase
			//and github is not case sensitive
			//trans to lowercase is not dangerous
			u.User = strings.ToLower(u.User)
			GithubHandler := git.NewGitHandler("https://api.github.com")
			mds := make([]git.GitRepoContent, 0)
			mds, err := GithubHandler.FetchRepoContents(u)
			if err != nil {
				panic(err)
			}
			log.Printf("You have %s\n", mds)

			//create user's bucket
			S3Provider := s3.NewS3Provider(viper.GetString("S3Endpoint"), viper.GetString("S3AccessKey"), viper.GetString("S3SecretKey"))

			err = S3Provider.CreateBucket(u.User)
			if err != nil {
				panic(err)
			}

			for _, md := range mds {
				if md.Type != "file" {
					continue
				}

				resp, err := http.Get(md.Download_Url)

				if err != nil {
					panic(err)
				}

				defer resp.Body.Close()

				m := NewMarkdownParser(resp.Body)

				// upload html to s3 bucket
				err = S3Provider.Upload(u.User, regexp.MustCompile("\\.[^.]+$").ReplaceAllString(md.Name, ".html"), m)
			}

			c.JSON(http.StatusOK, gin.H{"url": "https://s3.arthurma.com.tw/" + u.User + "/index.html"})
		}
	})

	/*router.POST("/webhook", func(c *gin.Context) {

	})*/

	router.Run(":8080")
}
<commit_msg>TODO<commit_after>package main

import (
	"io"
	"regexp"

	"github.com/IssueSquare/blog-daddy/adapters/git"
	"github.com/IssueSquare/blog-daddy/providers/s3"

	"net/http"
	"strings"

	"github.com/gin-gonic/gin"
	"github.com/spf13/viper"
	"gopkg.in/russross/blackfriday.v2"
)

type (
	Article struct {
		Name    string
		ModTime string
	}
)

type MarkdownParser struct {
	reader io.Reader
}

func NewMarkdownParser(r io.Reader) *MarkdownParser {
	return &MarkdownParser{r}
}

func (m *MarkdownParser) Read(p []byte) (n int, err error) {
	n, err = m.reader.Read(p)
	copy(p, blackfriday.Run(p))

	return n, err
}

func main() {
	viper.SetConfigName("config")
	viper.AddConfigPath(".")
	//viper.AddConfigPath("/var/run/secret")
	err := viper.ReadInConfig() // Find and read the config file
	if err != nil {             // Handle errors reading the config file
		panic(err)
	}

	router := gin.Default()
	router.LoadHTMLGlob("templates/*")

	router.POST("/setup", func(c *gin.Context) {
		var u git.User
		if c.BindJSON(&u) == nil {
			//minio cannot create bucket name with uppercase
			//and github is not case sensitive
			//trans to lowercase is not dangerous
			u.User = strings.ToLower(u.User)
			GithubHandler := git.NewGitHandler("https://api.github.com")
			mds := make([]git.GitRepoContent, 0)
			mds, err := GithubHandler.FetchRepoContents(u)
			if err != nil {
				panic(err)
			}

			//create user's bucket
			S3Provider := s3.NewS3Provider(viper.GetString("S3Endpoint"), viper.GetString("S3AccessKey"), viper.GetString("S3SecretKey"))

			err = S3Provider.CreateBucket(u.User)
			if err != nil {
				panic(err)
			}

			for _, md := range mds {

				resp, err := http.Get(md.Download_Url)

				if err != nil {
					panic(err)
				}

				defer resp.Body.Close()

				m := NewMarkdownParser(resp.Body)

				//TODO upload really index.html
				err = S3Provider.Upload(u.User, regexp.MustCompile("\\.[^.]+$").ReplaceAllString(md.Path, ".html"), m)
			}

			c.JSON(http.StatusOK, gin.H{"url": "https://s3.arthurma.com.tw/" + u.User + "/index.html"})
		}
	})

	/*router.POST("/webhook", func(c *gin.Context) {

	})*/

	router.Run(":8080")
}
<|endoftext|>


<commit_before>// Copyright (c) 2013-2019 by Michael Dvorkin and contributors. All Rights Reserved.
// Use of this source code is governed by a MIT-style license that can
// be found in the LICENSE file.

package mop

import (
	"bytes"
	"fmt"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"text/template"
	"time"
	"unicode"
)

var currencies = map[string]string{
	"RUB": "₽",
	"GBP": "£",
	"GBp": "p",
	"SEK": "kr",
	"EUR": "€",
	"JPY": "¥",
}

// Column describes formatting rules for individual column within the list
// of stock quotes.
type Column struct {
	width     int                    // Column width.
	name      string                 // The name of the field in the Stock struct.
	title     string                 // Column title to display in the header.
	formatter func(...string) string // Optional function to format the contents of the column.
}

// Layout is used to format and display all the collected data, i.e. market
// updates and the list of stock quotes.
type Layout struct {
	columns        []Column           // List of stock quotes columns.
	sorter         *Sorter            // Pointer to sorting receiver.
	filter         *Filter            // Pointer to filtering receiver.
	regex          *regexp.Regexp     // Pointer to regular expression to align decimal points.
	marketTemplate *template.Template // Pointer to template to format market data.
	quotesTemplate *template.Template // Pointer to template to format the list of stock quotes.
}

// Creates the layout and assigns the default values that stay unchanged.
func NewLayout() *Layout {
	layout := &Layout{}
	layout.columns = []Column{
		{-10, `Ticker`, `Ticker`, nil},
		{10, `LastTrade`, `Last`, currency},
		{10, `Change`, `Change`, currency},
		{10, `ChangePct`, `Change%`, last},
		{10, `Open`, `Open`, currency},
		{10, `Low`, `Low`, currency},
		{10, `High`, `High`, currency},
		{10, `Low52`, `52w Low`, currency},
		{10, `High52`, `52w High`, currency},
		{11, `Volume`, `Volume`, integer},
		{11, `AvgVolume`, `AvgVolume`, integer},
		{9, `PeRatio`, `P/E`, blank},
		{9, `Dividend`, `Dividend`, zero},
		{9, `Yield`, `Yield`, percent},
		{11, `MarketCap`, `MktCap`, currency},
		{13, `PreOpen`, `PreMktChg%`, percent},
		{13, `AfterHours`, `AfterMktChg%`, percent},
	}
	layout.regex = regexp.MustCompile(`(\.\d+)[TBMK]?$`)
	layout.marketTemplate = buildMarketTemplate()
	layout.quotesTemplate = buildQuotesTemplate()

	return layout
}

// Market merges given market data structure with the market template and
// returns formatted string that includes highlighting markup.
func (layout *Layout) Market(market *Market) string {
	if ok, err := market.Ok(); !ok { // If there was an error fetching market data...
		return err // then simply return the error string.
	}

	highlight(market.Dow, market.Sp500, market.Nasdaq,
		market.Tokyo, market.HongKong, market.London, market.Frankfurt,
		market.Yield, market.Oil, market.Euro, market.Gold)
	buffer := new(bytes.Buffer)
	layout.marketTemplate.Execute(buffer, market)

	return buffer.String()
}

// Quotes uses quotes template to format timestamp, stock quotes header,
// and the list of given stock quotes. It returns formatted string with
// all the necessary markup.
func (layout *Layout) Quotes(quotes *Quotes) string {
	zonename, _ := time.Now().In(time.Local).Zone()
	if ok, err := quotes.Ok(); !ok { // If there was an error fetching stock quotes...
		return err // then simply return the error string.
	}

	vars := struct {
		Now    string  // Current timestamp.
		Header string  // Formatted header line.
		Stocks []Stock // List of formatted stock quotes.
	}{
		time.Now().Format(`3:04:05pm ` + zonename),
		layout.Header(quotes.profile),
		layout.prettify(quotes),
	}

	buffer := new(bytes.Buffer)
	layout.quotesTemplate.Execute(buffer, vars)

	return buffer.String()
}

// Header iterates over column titles and formats the header line. The
// formatting includes placing an arrow next to the sorted column title.
// When the column editor is active it knows how to highlight currently
// selected column title.
func (layout *Layout) Header(profile *Profile) string {
	str, selectedColumn := ``, profile.selectedColumn

	for i, col := range layout.columns {
		arrow := arrowFor(i, profile)
		if i != selectedColumn {
			str += fmt.Sprintf(`%*s`, col.width, arrow+col.title)
		} else {
			str += fmt.Sprintf(`<r>%*s</r>`, col.width, arrow+col.title)
		}
	}

	return `<u>` + str + `</u>`
}

// TotalColumns is the utility method for the column editor that returns
// total number of columns.
func (layout *Layout) TotalColumns() int {
	return len(layout.columns)
}

//-----------------------------------------------------------------------------
func (layout *Layout) prettify(quotes *Quotes) []Stock {
	pretty := make([]Stock, len(quotes.stocks))
	//
	// Iterate over the list of stocks and properly format all its columns.
	//
	for i, stock := range quotes.stocks {
		pretty[i].Direction = stock.Direction
		//
		// Iterate over the list of stock columns. For each column name:
		// - Get current column value.
		// - If the column has the formatter method then call it.
		// - Set the column value padding it to the given width.
		//
		for _, column := range layout.columns {
			// ex. value = stock.Change
			value := reflect.ValueOf(&stock).Elem().FieldByName(column.name).String()
			if column.formatter != nil {
				// ex. value = currency(value)
				value = column.formatter(value, stock.Currency)
			}
			// ex. pretty[i].Change = layout.pad(value, 10)
			reflect.ValueOf(&pretty[i]).Elem().FieldByName(column.name).SetString(layout.pad(value, column.width))
		}
	}

	profile := quotes.profile

	if profile.Filter != ""{ // Fix for blank display if invalid filter expression was cleared.
		if profile.filterExpression != nil {
			if layout.filter == nil { // Initialize filter on first invocation.
				layout.filter = NewFilter(profile)
			}
			pretty = layout.filter.Apply(pretty)
		}
	}

	if layout.sorter == nil { // Initialize sorter on first invocation.
		layout.sorter = NewSorter(profile)
	}
	layout.sorter.SortByCurrentColumn(pretty)
	//
	// Group stocks by advancing/declining unless sorted by Chanage or Change%
	// in which case the grouping has been done already.
	//
	if profile.Grouped && (profile.SortColumn < 2 || profile.SortColumn > 3) {
		pretty = group(pretty)
	}

	return pretty
}

//-----------------------------------------------------------------------------
func (layout *Layout) pad(str string, width int) string {
	match := layout.regex.FindStringSubmatch(str)
	if len(match) > 0 {
		switch len(match[1]) {
		case 2:
			str = strings.Replace(str, match[1], match[1]+`0`, 1)
		case 4, 5:
			str = strings.Replace(str, match[1], match[1][0:3], 1)
		}
	}

	return fmt.Sprintf(`%*s`, width, str)
}

//-----------------------------------------------------------------------------
func buildMarketTemplate() *template.Template {
	markup := `<yellow>Dow</> {{.Dow.change}} ({{.Dow.percent}}) at {{.Dow.latest}} <yellow>S&P 500</> {{.Sp500.change}} ({{.Sp500.percent}}) at {{.Sp500.latest}} <yellow>NASDAQ</> {{.Nasdaq.change}} ({{.Nasdaq.percent}}) at {{.Nasdaq.latest}}
<yellow>Tokyo</> {{.Tokyo.change}} ({{.Tokyo.percent}}) at {{.Tokyo.latest}} <yellow>HK</> {{.HongKong.change}} ({{.HongKong.percent}}) at {{.HongKong.latest}} <yellow>London</> {{.London.change}} ({{.London.percent}}) at {{.London.latest}} <yellow>Frankfurt</> {{.Frankfurt.change}} ({{.Frankfurt.percent}}) at {{.Frankfurt.latest}} {{if .IsClosed}}<right>U.S. markets closed</right>{{end}}
<yellow>10-Year Yield</> {{.Yield.latest}} ({{.Yield.change}}) <yellow>Euro</> ${{.Euro.latest}} ({{.Euro.change}}) <yellow>Yen</> ¥{{.Yen.latest}} ({{.Yen.change}}) <yellow>Oil</> ${{.Oil.latest}} ({{.Oil.change}}) <yellow>Gold</> ${{.Gold.latest}} ({{.Gold.change}})`

	return template.Must(template.New(`market`).Parse(markup))
}

//-----------------------------------------------------------------------------
func buildQuotesTemplate() *template.Template {
	markup := `<right><white>{{.Now}}</></right>

{{.Header}}
{{range.Stocks}}{{if eq .Direction 1}}<green>{{else if eq .Direction -1}}<red>{{end}}{{.Ticker}}{{.LastTrade}}{{.Change}}{{.ChangePct}}{{.Open}}{{.Low}}{{.High}}{{.Low52}}{{.High52}}{{.Volume}}{{.AvgVolume}}{{.PeRatio}}{{.Dividend}}{{.Yield}}{{.MarketCap}}{{.PreOpen}}{{.AfterHours}}</>
{{end}}`

	return template.Must(template.New(`quotes`).Parse(markup))
}

//-----------------------------------------------------------------------------
func highlight(collections ...map[string]string) {
	for _, collection := range collections {
		change := collection[`change`]
		if change[len(change)-1:] == `%` {
			change = change[0:len(change)-1]
		}
		adv, err := strconv.ParseFloat(change, 64)
		if err == nil {
			if adv < 0.0 {
				collection[`change`] = `<red>` + collection[`change`] + `</>`
			} else if adv > 0.0 {
				collection[`change`] = `<green>` + collection[`change`] + `</>`
			}
		}
	}
}

//-----------------------------------------------------------------------------
func group(stocks []Stock) []Stock {
	grouped := make([]Stock, len(stocks))
	current := 0

	for _, stock := range stocks {
		if stock.Direction >= 0 {
			grouped[current] = stock
			current++
		}
	}
	for _, stock := range stocks {
		if stock.Direction < 0 {
			grouped[current] = stock
			current++
		}
	}

	return grouped
}

//-----------------------------------------------------------------------------
func arrowFor(column int, profile *Profile) string {
	if column == profile.SortColumn {
		if profile.Ascending {
			return string('\U00002191')
		}
		return string('\U00002193')
	}
	return ``
}

//-----------------------------------------------------------------------------
func blank(str ...string) string {
	if len(str) < 1 {
		return "ERR"
	}
	if (len(str[0]) == 3 && str[0][0:3] == `N/A`) || len(str[0]) == 0 {
		return `-`
	}

	return str[0]
}

//-----------------------------------------------------------------------------
func zero(str ...string) string {
	if len(str) < 2 {
		return "ERR"
	}
	if str[0] == `0.00` {
		return `-`
	}

	return currency(str[0], str[1])
}

//-----------------------------------------------------------------------------
func last(str ...string) string {
	if len(str) < 1 {
		return "ERR"
	}
	if len(str[0]) >= 6 && str[0][0:6] == `N/A - ` {
		return str[0][6:]
	}

	return percent(str[0])
}

//-----------------------------------------------------------------------------
func currency(str ...string) string {
	if len(str) < 2 {
		return "ERR"
	}
	//default to $
	symbol := "$"
	c, ok := currencies[str[1]]
	if ok {
		symbol = c
	}
	if str[0] == `N/A` || len(str[0]) == 0 {
		return `-`
	}
	if sign := str[0][0:1]; sign == `+` || sign == `-` {
		return sign + symbol + str[0][1:]
	}

	return symbol + str[0]
}

// Returns percent value truncated at 2 decimal points.
//-----------------------------------------------------------------------------
func percent(str ...string) string {
	if len(str) < 1 {
		return "ERR"
	}
	if str[0] == `N/A` || len(str[0]) == 0 {
		return `-`
	}

	split := strings.Split(str[0], ".")
	if len(split) == 2 {
		digits := len(split[1])
		if digits > 2 {
			digits = 2
		}
		str[0] = split[0] + "." + split[1][0:digits]
	}
	if str[0][len(str)-1] != '%' {
		str[0] += `%`
	}
	return str[0]
}

// Returns value as integer (no trailing digits after a '.').
//-----------------------------------------------------------------------------
func integer(str ...string) string {
	if len(str) < 1 {
		return "ERR"
	}
	if str[0] == `N/A` || len(str[0]) == 0 {
		return `-`
	}

	// Don't strip after the '.' if we have a value such as 123.45M
	if unicode.IsDigit(rune(str[0][len(str[0])-1])) {
		split := strings.Split(str[0], ".")
		if len(split) == 2 {
			return split[0]
		}
	}
	return str[0]
}
<commit_msg>Yen value was not properly highlighted<commit_after>// Copyright (c) 2013-2019 by Michael Dvorkin and contributors. All Rights Reserved.
// Use of this source code is governed by a MIT-style license that can
// be found in the LICENSE file.

package mop

import (
	"bytes"
	"fmt"
	"reflect"
	"regexp"
	"strconv"
	"strings"
	"text/template"
	"time"
	"unicode"
)

var currencies = map[string]string{
	"RUB": "₽",
	"GBP": "£",
	"GBp": "p",
	"SEK": "kr",
	"EUR": "€",
	"JPY": "¥",
}

// Column describes formatting rules for individual column within the list
// of stock quotes.
type Column struct {
	width     int                    // Column width.
	name      string                 // The name of the field in the Stock struct.
	title     string                 // Column title to display in the header.
	formatter func(...string) string // Optional function to format the contents of the column.
}

// Layout is used to format and display all the collected data, i.e. market
// updates and the list of stock quotes.
type Layout struct {
	columns        []Column           // List of stock quotes columns.
	sorter         *Sorter            // Pointer to sorting receiver.
	filter         *Filter            // Pointer to filtering receiver.
	regex          *regexp.Regexp     // Pointer to regular expression to align decimal points.
	marketTemplate *template.Template // Pointer to template to format market data.
	quotesTemplate *template.Template // Pointer to template to format the list of stock quotes.
}

// Creates the layout and assigns the default values that stay unchanged.
func NewLayout() *Layout {
	layout := &Layout{}
	layout.columns = []Column{
		{-10, `Ticker`, `Ticker`, nil},
		{10, `LastTrade`, `Last`, currency},
		{10, `Change`, `Change`, currency},
		{10, `ChangePct`, `Change%`, last},
		{10, `Open`, `Open`, currency},
		{10, `Low`, `Low`, currency},
		{10, `High`, `High`, currency},
		{10, `Low52`, `52w Low`, currency},
		{10, `High52`, `52w High`, currency},
		{11, `Volume`, `Volume`, integer},
		{11, `AvgVolume`, `AvgVolume`, integer},
		{9, `PeRatio`, `P/E`, blank},
		{9, `Dividend`, `Dividend`, zero},
		{9, `Yield`, `Yield`, percent},
		{11, `MarketCap`, `MktCap`, currency},
		{13, `PreOpen`, `PreMktChg%`, percent},
		{13, `AfterHours`, `AfterMktChg%`, percent},
	}
	layout.regex = regexp.MustCompile(`(\.\d+)[TBMK]?$`)
	layout.marketTemplate = buildMarketTemplate()
	layout.quotesTemplate = buildQuotesTemplate()

	return layout
}

// Market merges given market data structure with the market template and
// returns formatted string that includes highlighting markup.
func (layout *Layout) Market(market *Market) string {
	if ok, err := market.Ok(); !ok { // If there was an error fetching market data...
		return err // then simply return the error string.
	}

	highlight(market.Dow, market.Sp500, market.Nasdaq,
		market.Tokyo, market.HongKong, market.London, market.Frankfurt,
		market.Yield, market.Oil, market.Euro, market.Yen, market.Gold)
	buffer := new(bytes.Buffer)
	layout.marketTemplate.Execute(buffer, market)

	return buffer.String()
}

// Quotes uses quotes template to format timestamp, stock quotes header,
// and the list of given stock quotes. It returns formatted string with
// all the necessary markup.
func (layout *Layout) Quotes(quotes *Quotes) string {
	zonename, _ := time.Now().In(time.Local).Zone()
	if ok, err := quotes.Ok(); !ok { // If there was an error fetching stock quotes...
		return err // then simply return the error string.
	}

	vars := struct {
		Now    string  // Current timestamp.
		Header string  // Formatted header line.
		Stocks []Stock // List of formatted stock quotes.
	}{
		time.Now().Format(`3:04:05pm ` + zonename),
		layout.Header(quotes.profile),
		layout.prettify(quotes),
	}

	buffer := new(bytes.Buffer)
	layout.quotesTemplate.Execute(buffer, vars)

	return buffer.String()
}

// Header iterates over column titles and formats the header line. The
// formatting includes placing an arrow next to the sorted column title.
// When the column editor is active it knows how to highlight currently
// selected column title.
func (layout *Layout) Header(profile *Profile) string {
	str, selectedColumn := ``, profile.selectedColumn

	for i, col := range layout.columns {
		arrow := arrowFor(i, profile)
		if i != selectedColumn {
			str += fmt.Sprintf(`%*s`, col.width, arrow+col.title)
		} else {
			str += fmt.Sprintf(`<r>%*s</r>`, col.width, arrow+col.title)
		}
	}

	return `<u>` + str + `</u>`
}

// TotalColumns is the utility method for the column editor that returns
// total number of columns.
func (layout *Layout) TotalColumns() int {
	return len(layout.columns)
}

//-----------------------------------------------------------------------------
func (layout *Layout) prettify(quotes *Quotes) []Stock {
	pretty := make([]Stock, len(quotes.stocks))
	//
	// Iterate over the list of stocks and properly format all its columns.
	//
	for i, stock := range quotes.stocks {
		pretty[i].Direction = stock.Direction
		//
		// Iterate over the list of stock columns. For each column name:
		// - Get current column value.
		// - If the column has the formatter method then call it.
		// - Set the column value padding it to the given width.
		//
		for _, column := range layout.columns {
			// ex. value = stock.Change
			value := reflect.ValueOf(&stock).Elem().FieldByName(column.name).String()
			if column.formatter != nil {
				// ex. value = currency(value)
				value = column.formatter(value, stock.Currency)
			}
			// ex. pretty[i].Change = layout.pad(value, 10)
			reflect.ValueOf(&pretty[i]).Elem().FieldByName(column.name).SetString(layout.pad(value, column.width))
		}
	}

	profile := quotes.profile

	if profile.Filter != ""{ // Fix for blank display if invalid filter expression was cleared.
		if profile.filterExpression != nil {
			if layout.filter == nil { // Initialize filter on first invocation.
				layout.filter = NewFilter(profile)
			}
			pretty = layout.filter.Apply(pretty)
		}
	}

	if layout.sorter == nil { // Initialize sorter on first invocation.
		layout.sorter = NewSorter(profile)
	}
	layout.sorter.SortByCurrentColumn(pretty)
	//
	// Group stocks by advancing/declining unless sorted by Chanage or Change%
	// in which case the grouping has been done already.
	//
	if profile.Grouped && (profile.SortColumn < 2 || profile.SortColumn > 3) {
		pretty = group(pretty)
	}

	return pretty
}

//-----------------------------------------------------------------------------
func (layout *Layout) pad(str string, width int) string {
	match := layout.regex.FindStringSubmatch(str)
	if len(match) > 0 {
		switch len(match[1]) {
		case 2:
			str = strings.Replace(str, match[1], match[1]+`0`, 1)
		case 4, 5:
			str = strings.Replace(str, match[1], match[1][0:3], 1)
		}
	}

	return fmt.Sprintf(`%*s`, width, str)
}

//-----------------------------------------------------------------------------
func buildMarketTemplate() *template.Template {
	markup := `<yellow>Dow</> {{.Dow.change}} ({{.Dow.percent}}) at {{.Dow.latest}} <yellow>S&P 500</> {{.Sp500.change}} ({{.Sp500.percent}}) at {{.Sp500.latest}} <yellow>NASDAQ</> {{.Nasdaq.change}} ({{.Nasdaq.percent}}) at {{.Nasdaq.latest}}
<yellow>Tokyo</> {{.Tokyo.change}} ({{.Tokyo.percent}}) at {{.Tokyo.latest}} <yellow>HK</> {{.HongKong.change}} ({{.HongKong.percent}}) at {{.HongKong.latest}} <yellow>London</> {{.London.change}} ({{.London.percent}}) at {{.London.latest}} <yellow>Frankfurt</> {{.Frankfurt.change}} ({{.Frankfurt.percent}}) at {{.Frankfurt.latest}} {{if .IsClosed}}<right>U.S. markets closed</right>{{end}}
<yellow>10-Year Yield</> {{.Yield.latest}} ({{.Yield.change}}) <yellow>Euro</> ${{.Euro.latest}} ({{.Euro.change}}) <yellow>Yen</> ¥{{.Yen.latest}} ({{.Yen.change}}) <yellow>Oil</> ${{.Oil.latest}} ({{.Oil.change}}) <yellow>Gold</> ${{.Gold.latest}} ({{.Gold.change}})`

	return template.Must(template.New(`market`).Parse(markup))
}

//-----------------------------------------------------------------------------
func buildQuotesTemplate() *template.Template {
	markup := `<right><white>{{.Now}}</></right>

{{.Header}}
{{range.Stocks}}{{if eq .Direction 1}}<green>{{else if eq .Direction -1}}<red>{{end}}{{.Ticker}}{{.LastTrade}}{{.Change}}{{.ChangePct}}{{.Open}}{{.Low}}{{.High}}{{.Low52}}{{.High52}}{{.Volume}}{{.AvgVolume}}{{.PeRatio}}{{.Dividend}}{{.Yield}}{{.MarketCap}}{{.PreOpen}}{{.AfterHours}}</>
{{end}}`

	return template.Must(template.New(`quotes`).Parse(markup))
}

//-----------------------------------------------------------------------------
func highlight(collections ...map[string]string) {
	for _, collection := range collections {
		change := collection[`change`]
		if change[len(change)-1:] == `%` {
			change = change[0:len(change)-1]
		}
		adv, err := strconv.ParseFloat(change, 64)
		if err == nil {
			if adv < 0.0 {
				collection[`change`] = `<red>` + collection[`change`] + `</>`
			} else if adv > 0.0 {
				collection[`change`] = `<green>` + collection[`change`] + `</>`
			}
		}
	}
}

//-----------------------------------------------------------------------------
func group(stocks []Stock) []Stock {
	grouped := make([]Stock, len(stocks))
	current := 0

	for _, stock := range stocks {
		if stock.Direction >= 0 {
			grouped[current] = stock
			current++
		}
	}
	for _, stock := range stocks {
		if stock.Direction < 0 {
			grouped[current] = stock
			current++
		}
	}

	return grouped
}

//-----------------------------------------------------------------------------
func arrowFor(column int, profile *Profile) string {
	if column == profile.SortColumn {
		if profile.Ascending {
			return string('\U00002191')
		}
		return string('\U00002193')
	}
	return ``
}

//-----------------------------------------------------------------------------
func blank(str ...string) string {
	if len(str) < 1 {
		return "ERR"
	}
	if (len(str[0]) == 3 && str[0][0:3] == `N/A`) || len(str[0]) == 0 {
		return `-`
	}

	return str[0]
}

//-----------------------------------------------------------------------------
func zero(str ...string) string {
	if len(str) < 2 {
		return "ERR"
	}
	if str[0] == `0.00` {
		return `-`
	}

	return currency(str[0], str[1])
}

//-----------------------------------------------------------------------------
func last(str ...string) string {
	if len(str) < 1 {
		return "ERR"
	}
	if len(str[0]) >= 6 && str[0][0:6] == `N/A - ` {
		return str[0][6:]
	}

	return percent(str[0])
}

//-----------------------------------------------------------------------------
func currency(str ...string) string {
	if len(str) < 2 {
		return "ERR"
	}
	//default to $
	symbol := "$"
	c, ok := currencies[str[1]]
	if ok {
		symbol = c
	}
	if str[0] == `N/A` || len(str[0]) == 0 {
		return `-`
	}
	if sign := str[0][0:1]; sign == `+` || sign == `-` {
		return sign + symbol + str[0][1:]
	}

	return symbol + str[0]
}

// Returns percent value truncated at 2 decimal points.
//-----------------------------------------------------------------------------
func percent(str ...string) string {
	if len(str) < 1 {
		return "ERR"
	}
	if str[0] == `N/A` || len(str[0]) == 0 {
		return `-`
	}

	split := strings.Split(str[0], ".")
	if len(split) == 2 {
		digits := len(split[1])
		if digits > 2 {
			digits = 2
		}
		str[0] = split[0] + "." + split[1][0:digits]
	}
	if str[0][len(str)-1] != '%' {
		str[0] += `%`
	}
	return str[0]
}

// Returns value as integer (no trailing digits after a '.').
//-----------------------------------------------------------------------------
func integer(str ...string) string {
	if len(str) < 1 {
		return "ERR"
	}
	if str[0] == `N/A` || len(str[0]) == 0 {
		return `-`
	}

	// Don't strip after the '.' if we have a value such as 123.45M
	if unicode.IsDigit(rune(str[0][len(str[0])-1])) {
		split := strings.Split(str[0], ".")
		if len(split) == 2 {
			return split[0]
		}
	}
	return str[0]
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"regexp"
	"sync"

	"github.com/nsf/termbox-go"

	"github.com/prattmic/lesser/lineio"
)

type size struct {
	x int
	y int
}

type Event int

const (
	// EventQuit requests an application exit.
	EventQuit Event = iota

	// EventRefresh requests a display refresh.
	EventRefresh
)

type Mode int

const (
	// ModeNormal is the standard mode, allowing file navigation.
	ModeNormal Mode = iota

	// ModeSearchEntry is search entry mode. Key presses are added
	// to the search string.
	ModeSearchEntry
)

type Lesser struct {
	// src is the source file being displayed.
	src lineio.LineReader

	// tabStop is the number of spaces per tab.
	tabStop int

	// events is used to notify the main goroutine of events.
	events chan Event

	// mu locks the fields below.
	mu sync.Mutex

	// size is the size of the file display.
	// There is a statusbar beneath the display.
	size size

	// line is the line number of the first line of the display.
	line int64

	// mode is the viewer mode.
	mode Mode

	// regexp is the search regexp specified by the user.
	// Must only be modified by the event goroutine.
	regexp string

	// searchResults are the results for the current search.
	// They should be highlighted.
	searchResults searchResults
}

// lastLine returns the last line on the display.  It may be beyond the end
// of the file, if the file is short enough.
// mu must be held on call.
func (l *Lesser) lastLine() int64 {
	return l.line + int64(l.size.y) - 1
}

// scrollUp moves the display up (i.e., decrements the first line number).
// You cannot scroll beyond the beginning of the file.
// refreshScreen must be called for the display to actually update.
func (l *Lesser) scrollUp() {
	l.mu.Lock()
	defer l.mu.Unlock()

	if l.line > 1 {
		l.line--
	}
}

// scrollDown moves the display down (i.e., increments the first line number).
// refreshScreen must be called for the display to actually update.
func (l *Lesser) scrollDown() {
	l.mu.Lock()
	defer l.mu.Unlock()

	// You can only scroll down if the next line exists.
	if l.src.LineExists(l.lastLine() + 1) {
		l.line++
	}
}

// scrollTop moves to the first line.
// refreshScreen must be called for the display to actually update.
func (l *Lesser) scrollTop() {
	l.mu.Lock()
	defer l.mu.Unlock()

	l.line = 1
}

// scrollBottom moves to the last line.
// refreshScreen must be called for the display to actually update.
func (l *Lesser) scrollBottom() {
	l.mu.Lock()
	defer l.mu.Unlock()

	l.line = 1

	// TODO(prattmic): binary search
	for l.src.LineExists(l.lastLine() + 1) {
		l.line++
	}
}

func (l *Lesser) handleEvent(e termbox.Event) {
	l.mu.Lock()
	mode := l.mode
	l.mu.Unlock()

	if e.Type != termbox.EventKey {
		return
	}

	c := e.Ch

	switch mode {
	case ModeNormal:
		switch c {
		case 'q':
			l.events <- EventQuit
		case 'j':
			l.scrollDown()
			l.events <- EventRefresh
		case 'k':
			l.scrollUp()
			l.events <- EventRefresh
		case 'g':
			l.scrollTop()
			l.events <- EventRefresh
		case 'G':
			l.scrollBottom()
			l.events <- EventRefresh
		case '/':
			l.mu.Lock()
			l.mode = ModeSearchEntry
			l.mu.Unlock()
			l.events <- EventRefresh
		}
	case ModeSearchEntry:
		switch c {
		case 0:
			switch e.Key {
			case termbox.KeyEnter:
				r := l.search(l.regexp)
				l.mu.Lock()
				l.mode = ModeNormal
				l.regexp = ""
				l.searchResults = r
				l.mu.Unlock()
				l.events <- EventRefresh
			}
		default:
			l.mu.Lock()
			l.regexp += string(c)
			l.mu.Unlock()
			l.events <- EventRefresh
		}
	}
}

func (l *Lesser) listenEvents() {
	for {
		e := termbox.PollEvent()
		l.handleEvent(e)
	}
}

// searchResult describes search matches on a single line.
type searchResult struct {
	line    int64
	matches [][]int
	err     error
}

// matchesChar returns true if the search result contains a match for
// character index c.
func (s searchResult) matchesChar(c int) bool {
	for _, match := range s.matches {
		if len(match) < 2 {
			continue
		}

		if c >= match[0] && c < match[1] {
			return true
		}
	}
	return false
}

type searchResults []searchResult

// findLine finds the result for a specific line, if any.
// TODO(prattmic): Make a much more efficient data structure.
func (s searchResults) findLine(line int64) searchResult {
	for _, r := range s {
		if r.line == line {
			return r
		}
	}

	return searchResult{}
}

func (l *Lesser) search(s string) []searchResult {
	reg, err := regexp.Compile(s)
	if err != nil {
		// TODO(prattmic): display a better error
		log.Printf("regexp failed to compile: %v", err)
		return nil
	}

	results := make(chan searchResult, 100)

	searchLine := func(line int64) {
		r, err := l.src.SearchLine(reg, line)
		if err != nil {
			r = nil
		}

		results <- searchResult{
			line:    line,
			matches: r,
			err:     err,
		}
	}

	nextLine := int64(1)
	// Spawn initial search goroutines
	for ; nextLine <= 5; nextLine++ {
		go searchLine(nextLine)
	}

	all := make([]searchResult, 0)

	// Collect results, start searching next lines until we start
	// hitting EOF.
	for {
		ret := <-results
		all = append(all, ret)

		// We started hitting errors on a previous line,
		// there is no reason to search later lines.
		if ret.err != nil {
			break
		}

		go searchLine(nextLine)
		nextLine++
	}

	// Collect the remaing results.
	for int64(len(all)) < nextLine-1 {
		all = append(all, <-results)
	}

	return all
}

// statusBar renders the status bar.
// mu must be held on call.
func (l *Lesser) statusBar() {
	// The statusbar is just below the display.

	// Clear the statusbar
	for i := 0; i < l.size.x; i++ {
		termbox.SetCell(i, l.size.y, ' ', 0, 0)
	}

	switch l.mode {
	case ModeNormal:
		// Just a colon and a cursor
		termbox.SetCell(0, l.size.y, ':', 0, 0)
		termbox.SetCursor(1, l.size.y)
	case ModeSearchEntry:
		// / and search string
		termbox.SetCell(0, l.size.y, '/', 0, 0)
		for i, c := range l.regexp {
			termbox.SetCell(1+i, l.size.y, c, 0, 0)
		}
		termbox.SetCursor(1+len(l.regexp), l.size.y)
	}
}

func (l *Lesser) refreshScreen() error {
	l.mu.Lock()
	defer l.mu.Unlock()

	for y := 0; y < l.size.y; y++ {
		buf := make([]byte, l.size.x)
		line := l.line + int64(y)

		_, err := l.src.ReadLine(buf, line)
		// EOF just means the line was shorter than the display.
		if err != nil && err != io.EOF {
			return err
		}

		highlight := l.searchResults.findLine(line)

		var tabOffset int
		for i, c := range buf {
			// If there are tabs, we may get to the end of the
			// display before we run out of characters.
			if i >= l.size.x {
				break
			}

			fg := termbox.ColorDefault
			bg := termbox.ColorDefault

			// Highlight matches
			if highlight.matchesChar(i) {
				fg = termbox.ColorBlack
				bg = termbox.ColorWhite
			}

			if c == '\t' {
				// Clear the tab spaces
				for j := 0; j < l.tabStop; j++ {
					termbox.SetCell(tabOffset+j, y, ' ', 0, 0)
				}
				tabOffset += l.tabStop - 1
			} else {
				termbox.SetCell(tabOffset+i, y, rune(c), fg, bg)
			}
		}
	}

	l.statusBar()

	termbox.Flush()

	return nil
}

func (l *Lesser) Run() {
	go l.listenEvents()

	err := l.refreshScreen()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to refresh screen: %v\n", err)
		return
	}

	for {
		e := <-l.events

		switch e {
		case EventQuit:
			return
		case EventRefresh:
			err = l.refreshScreen()
			if err != nil {
				fmt.Fprintf(os.Stderr, "Failed to refresh screen: %v\n", err)
				return
			}
		}
	}
}

func NewLesser(f *os.File, ts int) Lesser {
	x, y := termbox.Size()

	return Lesser{
		src:     lineio.NewLineReader(f),
		tabStop: ts,
		// Save one line for statusbar.
		size:   size{x: x, y: y - 1},
		line:   1,
		events: make(chan Event, 1),
		mode:   ModeNormal,
	}
}
<commit_msg>Use generic switch<commit_after>package main

import (
	"fmt"
	"io"
	"log"
	"os"
	"regexp"
	"sync"

	"github.com/nsf/termbox-go"

	"github.com/prattmic/lesser/lineio"
)

type size struct {
	x int
	y int
}

type Event int

const (
	// EventQuit requests an application exit.
	EventQuit Event = iota

	// EventRefresh requests a display refresh.
	EventRefresh
)

type Mode int

const (
	// ModeNormal is the standard mode, allowing file navigation.
	ModeNormal Mode = iota

	// ModeSearchEntry is search entry mode. Key presses are added
	// to the search string.
	ModeSearchEntry
)

type Lesser struct {
	// src is the source file being displayed.
	src lineio.LineReader

	// tabStop is the number of spaces per tab.
	tabStop int

	// events is used to notify the main goroutine of events.
	events chan Event

	// mu locks the fields below.
	mu sync.Mutex

	// size is the size of the file display.
	// There is a statusbar beneath the display.
	size size

	// line is the line number of the first line of the display.
	line int64

	// mode is the viewer mode.
	mode Mode

	// regexp is the search regexp specified by the user.
	// Must only be modified by the event goroutine.
	regexp string

	// searchResults are the results for the current search.
	// They should be highlighted.
	searchResults searchResults
}

// lastLine returns the last line on the display.  It may be beyond the end
// of the file, if the file is short enough.
// mu must be held on call.
func (l *Lesser) lastLine() int64 {
	return l.line + int64(l.size.y) - 1
}

// scrollUp moves the display up (i.e., decrements the first line number).
// You cannot scroll beyond the beginning of the file.
// refreshScreen must be called for the display to actually update.
func (l *Lesser) scrollUp() {
	l.mu.Lock()
	defer l.mu.Unlock()

	if l.line > 1 {
		l.line--
	}
}

// scrollDown moves the display down (i.e., increments the first line number).
// refreshScreen must be called for the display to actually update.
func (l *Lesser) scrollDown() {
	l.mu.Lock()
	defer l.mu.Unlock()

	// You can only scroll down if the next line exists.
	if l.src.LineExists(l.lastLine() + 1) {
		l.line++
	}
}

// scrollTop moves to the first line.
// refreshScreen must be called for the display to actually update.
func (l *Lesser) scrollTop() {
	l.mu.Lock()
	defer l.mu.Unlock()

	l.line = 1
}

// scrollBottom moves to the last line.
// refreshScreen must be called for the display to actually update.
func (l *Lesser) scrollBottom() {
	l.mu.Lock()
	defer l.mu.Unlock()

	l.line = 1

	// TODO(prattmic): binary search
	for l.src.LineExists(l.lastLine() + 1) {
		l.line++
	}
}

func (l *Lesser) handleEvent(e termbox.Event) {
	l.mu.Lock()
	mode := l.mode
	l.mu.Unlock()

	if e.Type != termbox.EventKey {
		return
	}

	c := e.Ch
	k := e.Key
	// Key is only valid is Ch is 0
	if c != 0 {
		k = 0
	}

	switch mode {
	case ModeNormal:
		switch {
		case c == 'q':
			l.events <- EventQuit
		case c == 'j':
			l.scrollDown()
			l.events <- EventRefresh
		case c == 'k':
			l.scrollUp()
			l.events <- EventRefresh
		case c == 'g':
			l.scrollTop()
			l.events <- EventRefresh
		case c == 'G':
			l.scrollBottom()
			l.events <- EventRefresh
		case c == '/':
			l.mu.Lock()
			l.mode = ModeSearchEntry
			l.mu.Unlock()
			l.events <- EventRefresh
		}
	case ModeSearchEntry:
		switch {
		case k == termbox.KeyEnter:
			r := l.search(l.regexp)
			l.mu.Lock()
			l.mode = ModeNormal
			l.regexp = ""
			l.searchResults = r
			l.mu.Unlock()
			l.events <- EventRefresh
		default:
			l.mu.Lock()
			l.regexp += string(c)
			l.mu.Unlock()
			l.events <- EventRefresh
		}
	}
}

func (l *Lesser) listenEvents() {
	for {
		e := termbox.PollEvent()
		l.handleEvent(e)
	}
}

// searchResult describes search matches on a single line.
type searchResult struct {
	line    int64
	matches [][]int
	err     error
}

// matchesChar returns true if the search result contains a match for
// character index c.
func (s searchResult) matchesChar(c int) bool {
	for _, match := range s.matches {
		if len(match) < 2 {
			continue
		}

		if c >= match[0] && c < match[1] {
			return true
		}
	}
	return false
}

type searchResults []searchResult

// findLine finds the result for a specific line, if any.
// TODO(prattmic): Make a much more efficient data structure.
func (s searchResults) findLine(line int64) searchResult {
	for _, r := range s {
		if r.line == line {
			return r
		}
	}

	return searchResult{}
}

func (l *Lesser) search(s string) []searchResult {
	reg, err := regexp.Compile(s)
	if err != nil {
		// TODO(prattmic): display a better error
		log.Printf("regexp failed to compile: %v", err)
		return nil
	}

	results := make(chan searchResult, 100)

	searchLine := func(line int64) {
		r, err := l.src.SearchLine(reg, line)
		if err != nil {
			r = nil
		}

		results <- searchResult{
			line:    line,
			matches: r,
			err:     err,
		}
	}

	nextLine := int64(1)
	// Spawn initial search goroutines
	for ; nextLine <= 5; nextLine++ {
		go searchLine(nextLine)
	}

	all := make([]searchResult, 0)

	// Collect results, start searching next lines until we start
	// hitting EOF.
	for {
		ret := <-results
		all = append(all, ret)

		// We started hitting errors on a previous line,
		// there is no reason to search later lines.
		if ret.err != nil {
			break
		}

		go searchLine(nextLine)
		nextLine++
	}

	// Collect the remaing results.
	for int64(len(all)) < nextLine-1 {
		all = append(all, <-results)
	}

	return all
}

// statusBar renders the status bar.
// mu must be held on call.
func (l *Lesser) statusBar() {
	// The statusbar is just below the display.

	// Clear the statusbar
	for i := 0; i < l.size.x; i++ {
		termbox.SetCell(i, l.size.y, ' ', 0, 0)
	}

	switch l.mode {
	case ModeNormal:
		// Just a colon and a cursor
		termbox.SetCell(0, l.size.y, ':', 0, 0)
		termbox.SetCursor(1, l.size.y)
	case ModeSearchEntry:
		// / and search string
		termbox.SetCell(0, l.size.y, '/', 0, 0)
		for i, c := range l.regexp {
			termbox.SetCell(1+i, l.size.y, c, 0, 0)
		}
		termbox.SetCursor(1+len(l.regexp), l.size.y)
	}
}

func (l *Lesser) refreshScreen() error {
	l.mu.Lock()
	defer l.mu.Unlock()

	for y := 0; y < l.size.y; y++ {
		buf := make([]byte, l.size.x)
		line := l.line + int64(y)

		_, err := l.src.ReadLine(buf, line)
		// EOF just means the line was shorter than the display.
		if err != nil && err != io.EOF {
			return err
		}

		highlight := l.searchResults.findLine(line)

		var tabOffset int
		for i, c := range buf {
			// If there are tabs, we may get to the end of the
			// display before we run out of characters.
			if i >= l.size.x {
				break
			}

			fg := termbox.ColorDefault
			bg := termbox.ColorDefault

			// Highlight matches
			if highlight.matchesChar(i) {
				fg = termbox.ColorBlack
				bg = termbox.ColorWhite
			}

			if c == '\t' {
				// Clear the tab spaces
				for j := 0; j < l.tabStop; j++ {
					termbox.SetCell(tabOffset+j, y, ' ', 0, 0)
				}
				tabOffset += l.tabStop - 1
			} else {
				termbox.SetCell(tabOffset+i, y, rune(c), fg, bg)
			}
		}
	}

	l.statusBar()

	termbox.Flush()

	return nil
}

func (l *Lesser) Run() {
	go l.listenEvents()

	err := l.refreshScreen()
	if err != nil {
		fmt.Fprintf(os.Stderr, "Failed to refresh screen: %v\n", err)
		return
	}

	for {
		e := <-l.events

		switch e {
		case EventQuit:
			return
		case EventRefresh:
			err = l.refreshScreen()
			if err != nil {
				fmt.Fprintf(os.Stderr, "Failed to refresh screen: %v\n", err)
				return
			}
		}
	}
}

func NewLesser(f *os.File, ts int) Lesser {
	x, y := termbox.Size()

	return Lesser{
		src:     lineio.NewLineReader(f),
		tabStop: ts,
		// Save one line for statusbar.
		size:   size{x: x, y: y - 1},
		line:   1,
		events: make(chan Event, 1),
		mode:   ModeNormal,
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"fmt"
	"github.com/davemeehan/Neo4j-GO"
	"log"
	"os"
	"os/exec"
	"strings"
)

func getCreds() (string, string) {
	cmd := exec.Command("git", "config", "--get", "gerrit.user")
	var usrout bytes.Buffer
	cmd.Stdout = &usrout
	err := cmd.Run()
	if err != nil {
		log.Fatal(err)
	}
	user := strings.TrimSpace(usrout.String())

	cmd = exec.Command("git", "config", "--get", "gerrit.pass")
	var pwdout bytes.Buffer
	cmd.Stdout = &pwdout
	err = cmd.Run()
	if err != nil {
		log.Fatal(err)
	}
	pass := strings.TrimSpace(pwdout.String())

	return user, pass
}

func main() {
	// Fail if not enough parameters
	if len(os.Args) < 2 {
		log.Printf("I don't know what you want me to do.")
		return
	}

	user, pass := getCreds()

	neo, err := neo4j.NewNeo4j("http://gerrit.dev.returnpath.net/a/changes", user, pass)
	if err != nil {
		// log.Printf("%v\n", err)
		return
	}

	node := map[string]string{
		"q": "status:open",
	}

	data, _ := neo.CreateNode(node)
	fmt.Printf("\nNode data: %s\n", data)
	switch os.Args[1] {
	case "branch":
		Branch()
	case "push":
		fallthrough
	case "pull":
		// Check for changeset in params yet
		// If not, do push and get changeset back
		//
		fallthrough
	default:
		fmt.Printf("You have chosen to \"%s\".\n", os.Args[1])
		break
	}
}

func _get_current_branch() string {
	cmd := exec.Command("git", "symbolic-ref", "--short", "HEAD")

	var usrout bytes.Buffer
	cmd.Stdout = &usrout
	err := cmd.Run()
	if err != nil {
		log.Fatal(err)
	}
	out := strings.TrimSpace(usrout.String())

	return out
}
<commit_msg>Good request being sent with bad BasicAuth<commit_after>package main

import (
	"bytes"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"os/exec"
	"strings"
)

func getCreds() (string, string) {
	cmd := exec.Command("git", "config", "--get", "gerrit.user")
	var usrout bytes.Buffer
	cmd.Stdout = &usrout
	err := cmd.Run()
	if err != nil {
		log.Fatal(err)
	}
	user := strings.TrimSpace(usrout.String())

	cmd = exec.Command("git", "config", "--get", "gerrit.pass")
	var pwdout bytes.Buffer
	cmd.Stdout = &pwdout
	err = cmd.Run()
	if err != nil {
		log.Fatal(err)
	}
	pass := strings.TrimSpace(pwdout.String())

	return user, pass
}

func main() {
	// Fail if not enough parameters
	if len(os.Args) < 2 {
		log.Fatal("I don't know what you want me to do.")
	}

	user, pass := getCreds()

	var client http.Client

	resp, err := client.Get("http://gerrit.dev.returnpath.net/a/changes/?q=status:open")
	if err != nil {
		log.Fatal(err)
	}

	req, err := http.NewRequest("GET", "http://gerrit.dev.returnpath.net/a/changes/?q=status:open", nil)
	if err != nil {
		log.Fatal(err)
	}

	req.SetBasicAuth(user, pass)

	resp, err = client.Do(req)

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

	fmt.Println(string(body))

	/*
	   switch os.Args[1] {
	   case "push":

	       return 0
	   case "pull":
	       // Check for changeset in params yet
	       // If not, do push and get changeset back
	       //
	       fallthrough
	   default:
	       fmt.Printf("You have chosen to \"%s\".\n", os.Args[1])
	       break
	   }
	*/
}
<|endoftext|>