hlfw.ca

plumb

ref: d562d0dba9329a650901acf406b8af3c7230fe40
dir: /plumb.go/

View raw version
package main

import (
	"flag"
	"io"
	"log"
	"mime"
	"net/http"
	"net/url"
	"os"
	"strings"

	"9fans.net/go/plumb"
        "9fans.net/go/plan9"
)

var (
	plumbfile = flag.String("p", "send", "write the message to plumbfile (default send)")
	attributes = flag.String("a", "", "set the attr field of the message (default is empty), expects key=value")
	source = flag.String("s", "", "set the src field of the message (default is store)")
	destination = flag.String("d", "", "set the dst filed of the message (default is store)")
        typefield = flag.String("t", "", "override the type message sent to the plumber")
	directory = flag.String("w", "", "set the wdir field of the message (default is current directory)")
        input = flag.Bool("i", false, "take the data from standard input rather than the argument strings")
)

type storeMsg struct {
	src string
	dst string
	wdir string
	msgtype string
	attr *plumb.Attribute
	ndata int
	data string
}

func (s storeMsg) send() error {
        // Switch on GOOS here eventually 
	fd, err := plumb.Open(*plumbfile, plan9.OWRITE)
	if err != nil {
		return err
	}
	message := &plumb.Message{
		Src: s.src,
		Dst: s.dst,
		Dir: s.wdir,
		Type: s.msgtype,
		Attr: s.attr,
		Data: []byte(s.data),
	}
	return message.Send(fd)
}

func newStoreMsg(mediaType, wdir, data string, attr *plumb.Attribute) *storeMsg {
	sf := &storeMsg{
		src: os.Args[0],
		dst: "",
		wdir: wdir,
		msgtype: mediaType,
		attr: attr,
		ndata: len(data),
		data: data,
	}
	if *source != "" {
		sf.src = *source
	}
	if *destination != "" {
		sf.dst = *destination
	}
	return sf
}

func paramsToAttr(params map[string]string) *plumb.Attribute {
	// Attribute is a linked list - we only get one from content-type, the encoding
	attr := &plumb.Attribute{Name: "", Value: ""}
	for key, value := range params {
		if (attr.Name == "" || attr.Value == "") {
			continue
		}
		attr.Name = key
		attr.Value = value
	}
	if *attributes != "" {
		attr.Name = strings.TrimLeft(*attributes, "=")
		attr.Value = strings.TrimRight(*attributes, "=")
	}
	return attr
}

func contentTypeUrl(data string) (string, error) {
	// We read in 512 bytes 
	buf := make([]byte, 512)
	u, err := url.ParseRequestURI(data)
	if err != nil {
		return "text", nil
	}
	r, err := http.Get(u.String())
	if err != nil {
		return "", err
	}
	defer r.Body.Close()
	n, err := r.Body.Read(buf)
	if err != nil {
		return "", err
	}
	mediaType := http.DetectContentType(buf[:n])
	if (mediaType == "application/octet-stream" || mediaType == "" ) {
		mediaType := r.Header.Get("Content-type")
		if mediaType == "" {
			return "text", nil
		}
	}

	return mediaType, nil
}

func contentTypeFile(data string) (string, error) {
	buf := make([]byte, 512)
	fd, err := os.Open(data)
	if err != nil {
		return "", err
	}
	defer fd.Close()
	n, err := fd.Read(buf)
	if err != nil {
		return "", err
	}
	mediaType := http.DetectContentType(buf[:n])
	if mediaType == "application/octet-stream" {
		return "text", nil
	}
	return mediaType, nil
}

func content(data string) (string, error) {
        if *typefield != "" {
            return *typefield, nil
        }
 
	if _, err := os.Stat(data); os.IsNotExist(err)  {
		return contentTypeUrl(data)
	}
	return contentTypeFile(data)
}

func getMediaType(ct string) (string, *plumb.Attribute) {
		if ct == "text" {
			return ct, nil
		}
		mediaType, params, err := mime.ParseMediaType(ct)
		if err != nil {
			log.Fatal(err)
		}
		return mediaType, paramsToAttr(params)
}

func readInput() string {
        data := new(strings.Builder)
        if *input {
		// readInput on stdin will come with a fancy newline
		// return a slice without the last character 
		io.Copy(data, os.Stdin)
		return data.String()[:data.Len()-1]
        } else {
		data.WriteString(flag.Arg(0))
        }

	return data.String()
}

func main() {

	flag.Parse()
	if flag.Lookup("h") != nil {
		flag.Usage()
		os.Exit(1)
	}
	wdir, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}

        data := readInput()
	ct, err := content(data)
	if err != nil {
		log.Fatal(err)
	}
	mediaType, params := getMediaType(ct)
	storeMsg := newStoreMsg(mediaType, wdir, data, params)
	err = storeMsg.send()
	if err != nil {
		log.Fatal(err)
	}
}