<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"os"
	"strings"
	"sync"
	"testing"

	"github.com/flynn/flynn/Godeps/_workspace/src/github.com/flynn/go-sql"
	_ "github.com/flynn/flynn/Godeps/_workspace/src/github.com/flynn/pq"
	"github.com/flynn/flynn/pkg/random"
)

func TestOSFilesystem(t *testing.T) {
	dir, err := ioutil.TempDir("", "blobstore")
	if err != nil {
		t.Fatal(err)
	}
	testFilesystem(NewOSFilesystem(dir), false, t)
	os.RemoveAll(dir)
}

func TestPostgresFilesystem(t *testing.T) {
	dbname := "blobstoretest"
	if os.Getenv("PGDATABASE") != "" {
		dbname = os.Getenv("PGDATABASE")
	} else {
		os.Setenv("PGDATABASE", dbname)
	}
	if os.Getenv("PGSSLMODE") == "" {
		os.Setenv("PGSSLMODE", "disable")
	}

	db, err := sql.Open("postgres", "dbname=postgres")
	if err != nil {
		t.Fatal(err)
	}
	if _, err := db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", dbname)); err != nil {
		t.Fatal(err)
	}
	if _, err := db.Exec(fmt.Sprintf("CREATE DATABASE %s", dbname)); err != nil {
		t.Fatal(err)
	}
	db.Close()

	db, err = sql.Open("postgres", "")
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	db.SetMaxOpenConns(1)

	fs, err := NewPostgresFilesystem(db)
	if err != nil {
		t.Fatal(err)
	}
	testFilesystem(fs, true, t)
}

const concurrency = 5

func testFilesystem(fs Filesystem, testMeta bool, t *testing.T) {
	srv := httptest.NewServer(handler(fs))
	defer srv.Close()

	var wg sync.WaitGroup
	wg.Add(concurrency)
	for i := 0; i < concurrency; i++ {
		go func() {
			path := srv.URL + "/foo/bar/" + random.Hex(16)
			res, err := http.Get(path)
			if err != nil {
				t.Fatal(err)
			}
			res.Body.Close()
			if res.StatusCode != 404 {
				t.Errorf("Expected 404 for non-existent file, got %d", res.StatusCode)
			}

			res, err = http.Head(path)
			if err != nil {
				t.Fatal(err)
			}
			res.Body.Close()
			if res.StatusCode != 404 {
				t.Errorf("Expected 404 for non-existent file, got %d", res.StatusCode)
			}

			data := random.Hex(16)
			req, err := http.NewRequest("PUT", path, strings.NewReader(data))
			if err != nil {
				t.Fatal(err)
			}
			req.Header.Set("Content-Type", "text/plain")
			res, err = http.DefaultClient.Do(req)
			if err != nil {
				t.Fatal(err)
			}
			res.Body.Close()
			if res.StatusCode != 200 {
				t.Errorf("Expected 200 for successful PUT, got %d", res.StatusCode)
			}

			res, err = http.Get(path)
			if err != nil {
				t.Fatal(err)
			}
			resData, err := ioutil.ReadAll(res.Body)
			res.Body.Close()
			if err != nil {
				t.Fatal(err)
			}
			if res.StatusCode != 200 {
				t.Errorf("Expected 200 for GET, got %d", res.StatusCode)
			}
			if string(resData) != data {
				t.Errorf("Expected data to be %q, got %q", data, string(resData))
			}

			res, err = http.Head(path)
			if err != nil {
				t.Fatal(err)
			}
			res.Body.Close()
			if res.StatusCode != 200 {
				t.Errorf("Expected 200 for HEAD, got %d", res.StatusCode)
			}
			if cl := res.Header.Get("Content-Length"); cl != "32" {
				t.Errorf(`Expected Content-Length to be "32", got %q`, cl)
			}
			if testMeta {
				if ct := res.Header.Get("Content-Type"); ct != "text/plain" {
					t.Errorf(`Expected Content-Type to be "text/plain", got %q`, ct)
				}

				etag := res.Header.Get("Etag")
				if etag == "" {
					t.Error("Expected ETag to be set")
				}
				req, err := http.NewRequest("GET", path, nil)
				if err != nil {
					t.Fatal(err)
				}
				req.Header.Set("If-None-Match", etag)
				res, err = http.DefaultClient.Do(req)
				if err != nil {
					t.Fatal(err)
				}
				res.Body.Close()
				if res.StatusCode != http.StatusNotModified {
					t.Errorf("Expected ETag GET status to be 304, got %d", res.StatusCode)
				}
			}

			newData := random.Hex(32)
			req, err = http.NewRequest("PUT", path, strings.NewReader(newData))
			if err != nil {
				log.Fatal(err)
			}
			req.Header.Set("Content-Type", "application/text")
			res, err = http.DefaultClient.Do(req)
			if err != nil {
				log.Fatal(err)
			}
			res.Body.Close()
			if res.StatusCode != 200 {
				t.Errorf("Expected 200 for update PUT, got %d", res.StatusCode)
			}

			var wg2 sync.WaitGroup
			wg2.Add(concurrency)
			for i := 0; i < concurrency; i++ {
				go func() {
					res, err := http.Get(path)
					if err != nil {
						t.Fatal(err)
					}
					resData, err := ioutil.ReadAll(res.Body)
					res.Body.Close()
					if err != nil {
						t.Fatal(err)
					}
					if res.StatusCode != 200 {
						t.Errorf("Expected 200 for update GET, got %d", res.StatusCode)
					}
					if string(resData) != newData {
						t.Errorf("Expected data to be %q, got %q", newData, string(resData))
					}

					res, err = http.Head(path)
					if err != nil {
						t.Fatal(err)
					}
					res.Body.Close()
					if res.StatusCode != 200 {
						t.Errorf("Expected 200 for update HEAD, got %d", res.StatusCode)
					}
					if cl := res.Header.Get("Content-Length"); cl != "64" {
						t.Errorf(`Expected Content-Length to be "64", got %q`, cl)
					}
					if testMeta {
						if ct := res.Header.Get("Content-Type"); ct != "application/text" {
							t.Errorf(`Expected Content-Type to be "application/text", got %q`, ct)
						}
					}
					wg2.Done()
				}()
			}
			wg2.Wait()

			req, err = http.NewRequest("DELETE", path, nil)
			if err != nil {
				log.Fatal(err)
			}
			res, err = http.DefaultClient.Do(req)
			if err != nil {
				log.Fatal(err)
			}
			res.Body.Close()
			if res.StatusCode != 200 {
				t.Errorf("Expected 200 for DELETE, got %d", res.StatusCode)
			}

			res, err = http.Get(path)
			if err != nil {
				t.Fatal(err)
			}
			res.Body.Close()
			if res.StatusCode != 404 {
				t.Errorf("Expected 200 for deleted GET, got %d", res.StatusCode)
			}

			res, err = http.Head(path)
			if err != nil {
				t.Fatal(err)
			}
			res.Body.Close()
			if res.StatusCode != 404 {
				t.Errorf("Expected 200 for deleted HEAD, got %d", res.StatusCode)
			}

			wg.Done()
		}()
	}

	wg.Wait()
}
<commit_msg>blobstore: Don’t hang when tests fail<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"net/http/httptest"
	"os"
	"strings"
	"sync"
	"testing"

	"github.com/flynn/flynn/Godeps/_workspace/src/github.com/flynn/go-sql"
	_ "github.com/flynn/flynn/Godeps/_workspace/src/github.com/flynn/pq"
	"github.com/flynn/flynn/pkg/random"
)

func TestOSFilesystem(t *testing.T) {
	dir, err := ioutil.TempDir("", "blobstore")
	if err != nil {
		t.Fatal(err)
	}
	testFilesystem(NewOSFilesystem(dir), false, t)
	os.RemoveAll(dir)
}

func TestPostgresFilesystem(t *testing.T) {
	dbname := "blobstoretest"
	if os.Getenv("PGDATABASE") != "" {
		dbname = os.Getenv("PGDATABASE")
	} else {
		os.Setenv("PGDATABASE", dbname)
	}
	if os.Getenv("PGSSLMODE") == "" {
		os.Setenv("PGSSLMODE", "disable")
	}

	db, err := sql.Open("postgres", "dbname=postgres")
	if err != nil {
		t.Fatal(err)
	}
	if _, err := db.Exec(fmt.Sprintf("DROP DATABASE IF EXISTS %s", dbname)); err != nil {
		t.Fatal(err)
	}
	if _, err := db.Exec(fmt.Sprintf("CREATE DATABASE %s", dbname)); err != nil {
		t.Fatal(err)
	}
	db.Close()

	db, err = sql.Open("postgres", "")
	if err != nil {
		t.Fatal(err)
	}
	defer db.Close()
	db.SetMaxOpenConns(1)

	fs, err := NewPostgresFilesystem(db)
	if err != nil {
		t.Fatal(err)
	}
	testFilesystem(fs, true, t)
}

const concurrency = 5

func testFilesystem(fs Filesystem, testMeta bool, t *testing.T) {
	srv := httptest.NewServer(handler(fs))
	defer srv.Close()

	var wg sync.WaitGroup
	wg.Add(concurrency)
	for i := 0; i < concurrency; i++ {
		go func() {
			defer wg.Done()
			path := srv.URL + "/foo/bar/" + random.Hex(16)
			res, err := http.Get(path)
			if err != nil {
				t.Fatal(err)
			}
			res.Body.Close()
			if res.StatusCode != 404 {
				t.Errorf("Expected 404 for non-existent file, got %d", res.StatusCode)
			}

			res, err = http.Head(path)
			if err != nil {
				t.Fatal(err)
			}
			res.Body.Close()
			if res.StatusCode != 404 {
				t.Errorf("Expected 404 for non-existent file, got %d", res.StatusCode)
			}

			data := random.Hex(16)
			req, err := http.NewRequest("PUT", path, strings.NewReader(data))
			if err != nil {
				t.Fatal(err)
			}
			req.Header.Set("Content-Type", "text/plain")
			res, err = http.DefaultClient.Do(req)
			if err != nil {
				t.Fatal(err)
			}
			res.Body.Close()
			if res.StatusCode != 200 {
				t.Errorf("Expected 200 for successful PUT, got %d", res.StatusCode)
			}

			res, err = http.Get(path)
			if err != nil {
				t.Fatal(err)
			}
			resData, err := ioutil.ReadAll(res.Body)
			res.Body.Close()
			if err != nil {
				t.Fatal(err)
			}
			if res.StatusCode != 200 {
				t.Errorf("Expected 200 for GET, got %d", res.StatusCode)
			}
			if string(resData) != data {
				t.Errorf("Expected data to be %q, got %q", data, string(resData))
			}

			res, err = http.Head(path)
			if err != nil {
				t.Fatal(err)
			}
			res.Body.Close()
			if res.StatusCode != 200 {
				t.Errorf("Expected 200 for HEAD, got %d", res.StatusCode)
			}
			if cl := res.Header.Get("Content-Length"); cl != "32" {
				t.Errorf(`Expected Content-Length to be "32", got %q`, cl)
			}
			if testMeta {
				if ct := res.Header.Get("Content-Type"); ct != "text/plain" {
					t.Errorf(`Expected Content-Type to be "text/plain", got %q`, ct)
				}

				etag := res.Header.Get("Etag")
				if etag == "" {
					t.Error("Expected ETag to be set")
				}
				req, err := http.NewRequest("GET", path, nil)
				if err != nil {
					t.Fatal(err)
				}
				req.Header.Set("If-None-Match", etag)
				res, err = http.DefaultClient.Do(req)
				if err != nil {
					t.Fatal(err)
				}
				res.Body.Close()
				if res.StatusCode != http.StatusNotModified {
					t.Errorf("Expected ETag GET status to be 304, got %d", res.StatusCode)
				}
			}

			newData := random.Hex(32)
			req, err = http.NewRequest("PUT", path, strings.NewReader(newData))
			if err != nil {
				log.Fatal(err)
			}
			req.Header.Set("Content-Type", "application/text")
			res, err = http.DefaultClient.Do(req)
			if err != nil {
				log.Fatal(err)
			}
			res.Body.Close()
			if res.StatusCode != 200 {
				t.Errorf("Expected 200 for update PUT, got %d", res.StatusCode)
			}

			var wg2 sync.WaitGroup
			wg2.Add(concurrency)
			for i := 0; i < concurrency; i++ {
				go func() {
					defer wg2.Done()
					res, err := http.Get(path)
					if err != nil {
						t.Fatal(err)
					}
					resData, err := ioutil.ReadAll(res.Body)
					res.Body.Close()
					if err != nil {
						t.Fatal(err)
					}
					if res.StatusCode != 200 {
						t.Errorf("Expected 200 for update GET, got %d", res.StatusCode)
					}
					if string(resData) != newData {
						t.Errorf("Expected data to be %q, got %q", newData, string(resData))
					}

					res, err = http.Head(path)
					if err != nil {
						t.Fatal(err)
					}
					res.Body.Close()
					if res.StatusCode != 200 {
						t.Errorf("Expected 200 for update HEAD, got %d", res.StatusCode)
					}
					if cl := res.Header.Get("Content-Length"); cl != "64" {
						t.Errorf(`Expected Content-Length to be "64", got %q`, cl)
					}
					if testMeta {
						if ct := res.Header.Get("Content-Type"); ct != "application/text" {
							t.Errorf(`Expected Content-Type to be "application/text", got %q`, ct)
						}
					}
				}()
			}
			wg2.Wait()

			req, err = http.NewRequest("DELETE", path, nil)
			if err != nil {
				log.Fatal(err)
			}
			res, err = http.DefaultClient.Do(req)
			if err != nil {
				log.Fatal(err)
			}
			res.Body.Close()
			if res.StatusCode != 200 {
				t.Errorf("Expected 200 for DELETE, got %d", res.StatusCode)
			}

			res, err = http.Get(path)
			if err != nil {
				t.Fatal(err)
			}
			res.Body.Close()
			if res.StatusCode != 404 {
				t.Errorf("Expected 200 for deleted GET, got %d", res.StatusCode)
			}

			res, err = http.Head(path)
			if err != nil {
				t.Fatal(err)
			}
			res.Body.Close()
			if res.StatusCode != 404 {
				t.Errorf("Expected 200 for deleted HEAD, got %d", res.StatusCode)
			}
		}()
	}

	wg.Wait()
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"sourcegraph.com/sourcegraph/appdash"
	"sourcegraph.com/sourcegraph/appdash/httptrace"
	"sourcegraph.com/sourcegraph/appdash/traceapp"

	"github.com/codegangsta/negroni"
	"github.com/gorilla/context"
	"github.com/gorilla/mux"
)

// Used to store the SpanID in a request's context (see gorilla/context docs
// for more information).
const CtxSpanID = 0

var collector appdash.Collector

func main() {
	// Create a recent in-memory store, evicting data after 20s.
	memStore := appdash.NewMemoryStore()
	store := &appdash.RecentStore{
		MinEvictAge: 20 * time.Second,
		DeleteStore: memStore,
	}

	// We can start the web UI on a separate port, as part of our app (another
	// alternative would be to connect to a centralized Appdash collection
	// server).
	tapp := traceapp.New(nil)
	tapp.Store = store
	tapp.Queryer = memStore
	log.Println("Appdash web UI running on HTTP :8700")
	go func() {
		log.Fatal(http.ListenAndServe(":8700", tapp))
	}()

	// We will use a local collector, as we are running the Appdash web UI
	// embedded within our app (see above).
	collector = appdash.NewLocalCollector(store)

	// Create the appdash/httptrace middleware.
	tracemw := httptrace.Middleware(collector, &httptrace.MiddlewareConfig{
		RouteName: func(r *http.Request) string { return r.URL.Path },
		SetContextSpan: func(r *http.Request, spanID appdash.SpanID) {
			context.Set(r, CtxSpanID, spanID)
		},
	})

	// Setup our router:
	router := mux.NewRouter()
	router.HandleFunc("/", Home)
	router.HandleFunc("/endpoint", Endpoint)

	// Setup Negroni for our app:
	n := negroni.Classic()
	n.Use(negroni.HandlerFunc(tracemw)) // Register appdash's HTTP middleware.
	n.UseHandler(router)
	n.Run(":8699")
}

// Home is the homepage handler for our app.
func Home(w http.ResponseWriter, r *http.Request) {
	// Grab the span from the gorilla context.
	span := context.Get(r, CtxSpanID).(appdash.SpanID)

	// We're going to make some API requests, so we create a HTTP client here.
	httpClient := &http.Client{
		Transport: &httptrace.Transport{
			Recorder: appdash.NewRecorder(span, collector),
			SetName:  true,
		},
	}

	// Make three API requests.
	for i := 0; i < 3; i++ {
		resp, err := httpClient.Get("http://localhost:8699/endpoint")
		if err != nil {
			log.Println("/endpoint:", err)
			continue
		}
		resp.Body.Close()
	}

	// Render the page.
	fmt.Fprintf(w, `<p>Three API requests have been made!</p>`)
	fmt.Fprintf(w, `<p><a href="http://localhost:8700/traces/%s" target="_">View the trace (ID:%s)</a></p>`, span.Trace, span.Trace)
}

// Endpoint is an example API endpoint. The backend of your service for
// example needs to contact several external or internal API endpoints.
func Endpoint(w http.ResponseWriter, r *http.Request) {
	time.Sleep(200 * time.Millisecond)
	fmt.Fprintf(w, "Slept for 200ms!")
}
<commit_msg>examples/cmd/webapp: expand and polish the overall documentation.<commit_after>// webapp: a standalone example Negroni / Gorilla based webapp.
//
// This example demonstrates basic usage of Appdash in a Negroni / Gorilla
// based web application. The entire application is ran locally (i.e. on the
// same server) -- even the Appdash web UI.
package main

import (
	"fmt"
	"log"
	"net/http"
	"time"

	"sourcegraph.com/sourcegraph/appdash"
	"sourcegraph.com/sourcegraph/appdash/httptrace"
	"sourcegraph.com/sourcegraph/appdash/traceapp"

	"github.com/codegangsta/negroni"
	"github.com/gorilla/context"
	"github.com/gorilla/mux"
)

// Used to store the SpanID in a request's context (see gorilla/context docs
// for more information).
const CtxSpanID = 0

// We want to create HTTP clients recording to this collector inside our Home
// handler below, so we use a global variable (for simplicity sake) to store
// the collector in use. We could also use gorilla/context to store it.
var collector appdash.Collector

func main() {
	// Create a recent in-memory store, evicting data after 20s.
	//
	// The store defines where information about traces (i.e. spans and
	// annotations) will be stored during the lifetime of the application. This
	// application uses a MemoryStore store wrapped by a RecentStore with an
	// eviction time of 20s (i.e. all data after 20s is deleted from memory).
	memStore := appdash.NewMemoryStore()
	store := &appdash.RecentStore{
		MinEvictAge: 20 * time.Second,
		DeleteStore: memStore,
	}

	// Start the Appdash web UI on port 8700.
	//
	// This is the actual Appdash web UI -- usable as a Go package itself, We
	// embed it directly into our application such that visiting the web server
	// on HTTP port 8700 will bring us to the web UI, displaying information
	// about this specific web-server (another alternative would be to connect
	// to a centralized Appdash collection server).
	tapp := traceapp.New(nil)
	tapp.Store = store
	tapp.Queryer = memStore
	log.Println("Appdash web UI running on HTTP :8700")
	go func() {
		log.Fatal(http.ListenAndServe(":8700", tapp))
	}()

	// We will use a local collector (as we are running the Appdash web UI
	// embedded within our app).
	//
	// A collector is responsible for collecting the information about traces
	// (i.e. spans and annotations) and placing them into a store. In this app
	// we use a local collector (we could also use a remote collector, sending
	// the information to a remote Appdash collection server).
	collector = appdash.NewLocalCollector(store)

	// Create the appdash/httptrace middleware.
	//
	// Here we initialize the appdash/httptrace middleware. It is a Negroni
	// compliant HTTP middleware that will generate HTTP events for Appdash to
	// display. We could also instruct Appdash with events manually, if we
	// wanted to.
	tracemw := httptrace.Middleware(collector, &httptrace.MiddlewareConfig{
		RouteName: func(r *http.Request) string { return r.URL.Path },
		SetContextSpan: func(r *http.Request, spanID appdash.SpanID) {
			context.Set(r, CtxSpanID, spanID)
		},
	})

	// Setup our router (for information, see the gorilla/mux docs):
	router := mux.NewRouter()
	router.HandleFunc("/", Home)
	router.HandleFunc("/endpoint", Endpoint)

	// Setup Negroni for our app (for information, see the negroni docs):
	n := negroni.Classic()
	n.Use(negroni.HandlerFunc(tracemw)) // Register appdash's HTTP middleware.
	n.UseHandler(router)
	n.Run(":8699")
}

// Home is the homepage handler for our app.
func Home(w http.ResponseWriter, r *http.Request) {
	// Grab the span from the gorilla context. We do this so that we can grab
	// the span.Trace ID and link directly to the trace on the web-page itself!
	span := context.Get(r, CtxSpanID).(appdash.SpanID)

	// We're going to make some API requests, so we create a HTTP client using
	// a appdash/httptrace transport here. The transport will inform Appdash of
	// the HTTP events occuring.
	httpClient := &http.Client{
		Transport: &httptrace.Transport{
			Recorder: appdash.NewRecorder(span, collector),
			SetName:  true,
		},
	}

	// Make three API requests using our HTTP client.
	for i := 0; i < 3; i++ {
		resp, err := httpClient.Get("http://localhost:8699/endpoint")
		if err != nil {
			log.Println("/endpoint:", err)
			continue
		}
		resp.Body.Close()
	}

	// Render the page.
	fmt.Fprintf(w, `<p>Three API requests have been made!</p>`)
	fmt.Fprintf(w, `<p><a href="http://localhost:8700/traces/%s" target="_">View the trace (ID:%s)</a></p>`, span.Trace, span.Trace)
}

// Endpoint is an example API endpoint. In a real application, the backend of
// your service would be contacting several external and internal API endpoints
// which may be the bottleneck of your application.
//
// For example purposes we just sleep for 200ms before responding to simulate a
// slow API endpoint as the bottleneck of your application.
func Endpoint(w http.ResponseWriter, r *http.Request) {
	time.Sleep(200 * time.Millisecond)
	fmt.Fprintf(w, "Slept for 200ms!")
}
<|endoftext|>


<commit_before>package btelegram

import (
	"regexp"
	"strconv"

	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/bridge/helper"
	log "github.com/Sirupsen/logrus"
	"github.com/go-telegram-bot-api/telegram-bot-api"
)

type Btelegram struct {
	c       *tgbotapi.BotAPI
	Config  *config.Protocol
	Remote  chan config.Message
	Account string
}

var flog *log.Entry
var protocol = "telegram"

func init() {
	flog = log.WithFields(log.Fields{"module": protocol})
}

func New(cfg config.Protocol, account string, c chan config.Message) *Btelegram {
	b := &Btelegram{}
	b.Config = &cfg
	b.Remote = c
	b.Account = account
	return b
}

func (b *Btelegram) Connect() error {
	var err error
	flog.Info("Connecting")
	b.c, err = tgbotapi.NewBotAPI(b.Config.Token)
	if err != nil {
		flog.Debugf("%#v", err)
		return err
	}
	updates, err := b.c.GetUpdatesChan(tgbotapi.NewUpdate(0))
	if err != nil {
		flog.Debugf("%#v", err)
		return err
	}
	flog.Info("Connection succeeded")
	go b.handleRecv(updates)
	return nil
}

func (b *Btelegram) Disconnect() error {
	return nil

}

func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error {
	return nil
}

func (b *Btelegram) Send(msg config.Message) (string, error) {
	flog.Debugf("Receiving %#v", msg)
	chatid, err := strconv.ParseInt(msg.Channel, 10, 64)
	if err != nil {
		return "", err
	}

	if b.Config.MessageFormat == "HTML" {
		msg.Text = makeHTML(msg.Text)
	}

	if msg.Event == config.EVENT_MSG_DELETE {
		if msg.ID == "" {
			return "", nil
		}
		msgid, err := strconv.Atoi(msg.ID)
		if err != nil {
			return "", err
		}
		_, err = b.c.DeleteMessage(tgbotapi.DeleteMessageConfig{ChatID: chatid, MessageID: msgid})
		return "", err
	}

	// edit the message if we have a msg ID
	if msg.ID != "" {
		msgid, err := strconv.Atoi(msg.ID)
		if err != nil {
			return "", err
		}
		m := tgbotapi.NewEditMessageText(chatid, msgid, msg.Username+msg.Text)
		_, err = b.c.Send(m)
		if err != nil {
			return "", err
		}
		return "", nil
	}

	if msg.Extra != nil {
		// check if we have files to upload (from slack, telegram or mattermost)
		if len(msg.Extra["file"]) > 0 {
			var c tgbotapi.Chattable
			for _, f := range msg.Extra["file"] {
				fi := f.(config.FileInfo)
				file := tgbotapi.FileBytes{fi.Name, *fi.Data}
				re := regexp.MustCompile(".(jpg|png)$")
				if re.MatchString(fi.Name) {
					c = tgbotapi.NewPhotoUpload(chatid, file)
				} else {
					c = tgbotapi.NewDocumentUpload(chatid, file)
				}
				_, err := b.c.Send(c)
				if err != nil {
					log.Errorf("file upload failed: %#v", err)
				}
			}
		}
	}

	m := tgbotapi.NewMessage(chatid, msg.Username+msg.Text)
	if b.Config.MessageFormat == "HTML" {
		m.ParseMode = tgbotapi.ModeHTML
	}
	res, err := b.c.Send(m)
	if err != nil {
		return "", err
	}
	return strconv.Itoa(res.MessageID), nil

}

func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
	for update := range updates {
		flog.Debugf("Receiving from telegram: %#v", update.Message)
		var message *tgbotapi.Message
		username := ""
		channel := ""
		text := ""

		fmsg := config.Message{Extra: make(map[string][]interface{})}

		// handle channels
		if update.ChannelPost != nil {
			message = update.ChannelPost
		}
		if update.EditedChannelPost != nil && !b.Config.EditDisable {
			message = update.EditedChannelPost
			message.Text = message.Text + b.Config.EditSuffix
		}
		// handle groups
		if update.Message != nil {
			message = update.Message
		}
		if update.EditedMessage != nil && !b.Config.EditDisable {
			message = update.EditedMessage
			message.Text = message.Text + b.Config.EditSuffix
		}
		if message.From != nil {
			if b.Config.UseFirstName {
				username = message.From.FirstName
			}
			if username == "" {
				username = message.From.UserName
				if username == "" {
					username = message.From.FirstName
				}
			}
			text = message.Text
			channel = strconv.FormatInt(message.Chat.ID, 10)
		}

		if username == "" {
			username = "unknown"
		}
		if message.Sticker != nil {
			b.handleDownload(message.Sticker, &fmsg)
		}
		if message.Video != nil {
			b.handleDownload(message.Video, &fmsg)
		}
		if message.Photo != nil {
			b.handleDownload(message.Photo, &fmsg)
		}
		if message.Document != nil {
			b.handleDownload(message.Document, &fmsg)
		}

		// quote the previous message
		if message.ReplyToMessage != nil {
			usernameReply := ""
			if message.ReplyToMessage.From != nil {
				if b.Config.UseFirstName {
					usernameReply = message.ReplyToMessage.From.FirstName
				}
				if usernameReply == "" {
					usernameReply = message.ReplyToMessage.From.UserName
					if usernameReply == "" {
						usernameReply = message.ReplyToMessage.From.FirstName
					}
				}
			}
			if usernameReply == "" {
				usernameReply = "unknown"
			}
			text = text + " (re @" + usernameReply + ":" + message.ReplyToMessage.Text + ")"
		}

		if text != "" || len(fmsg.Extra) > 0 {
			flog.Debugf("Sending message from %s on %s to gateway", username, b.Account)
			msg := config.Message{Username: username, Text: text, Channel: channel, Account: b.Account, UserID: strconv.Itoa(message.From.ID), ID: strconv.Itoa(message.MessageID)}
			flog.Debugf("Message is %#v", msg)
			b.Remote <- msg
		}
	}
}

func (b *Btelegram) getFileDirectURL(id string) string {
	res, err := b.c.GetFileDirectURL(id)
	if err != nil {
		return ""
	}
	return res
}

func (b *Btelegram) handleDownload(file interface{}, msg *config.Message) {
	size := 0
	url := ""
	name := ""
	text := ""
	fileid := ""
	switch v := file.(type) {
	case *tgbotapi.Sticker:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		name = "sticker"
		text = " " + url
		fileid = v.FileID
	case *tgbotapi.Video:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		name = "video"
		text = " " + url
		fileid = v.FileID
	case *[]tgbotapi.PhotoSize:
		photos := *v
		size = photos[len(photos)-1].FileSize
		url = b.getFileDirectURL(photos[len(photos)-1].FileID)
		name = "photo"
		text = " " + url
		fileid = v.FileID
	case *tgbotapi.Document:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		name = v.FileName
		text = " " + v.FileName + " : " + url
		fileid = v.FileID
	}
	if b.Config.UseInsecureURL {
		msg.Text = text
		return
	}
	// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra
	// limit to 1MB for now
	flog.Debugf("trying to download %#v fileid %#v with size %#v", name, fileid, size)
	if size <= 1000000 {
		data, err := helper.DownloadFile(url)
		if err != nil {
			flog.Errorf("download %s failed %#v", url, err)
		} else {
			flog.Debugf("download OK %#v %#v %#v", name, len(data), len(url))
			msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: name, Data: data})
		}
	}
}
<commit_msg>Add more debug info (telegram)<commit_after>package btelegram

import (
	"regexp"
	"strconv"

	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/bridge/helper"
	log "github.com/Sirupsen/logrus"
	"github.com/go-telegram-bot-api/telegram-bot-api"
)

type Btelegram struct {
	c       *tgbotapi.BotAPI
	Config  *config.Protocol
	Remote  chan config.Message
	Account string
}

var flog *log.Entry
var protocol = "telegram"

func init() {
	flog = log.WithFields(log.Fields{"module": protocol})
}

func New(cfg config.Protocol, account string, c chan config.Message) *Btelegram {
	b := &Btelegram{}
	b.Config = &cfg
	b.Remote = c
	b.Account = account
	return b
}

func (b *Btelegram) Connect() error {
	var err error
	flog.Info("Connecting")
	b.c, err = tgbotapi.NewBotAPI(b.Config.Token)
	if err != nil {
		flog.Debugf("%#v", err)
		return err
	}
	updates, err := b.c.GetUpdatesChan(tgbotapi.NewUpdate(0))
	if err != nil {
		flog.Debugf("%#v", err)
		return err
	}
	flog.Info("Connection succeeded")
	go b.handleRecv(updates)
	return nil
}

func (b *Btelegram) Disconnect() error {
	return nil

}

func (b *Btelegram) JoinChannel(channel config.ChannelInfo) error {
	return nil
}

func (b *Btelegram) Send(msg config.Message) (string, error) {
	flog.Debugf("Receiving %#v", msg)
	chatid, err := strconv.ParseInt(msg.Channel, 10, 64)
	if err != nil {
		return "", err
	}

	if b.Config.MessageFormat == "HTML" {
		msg.Text = makeHTML(msg.Text)
	}

	if msg.Event == config.EVENT_MSG_DELETE {
		if msg.ID == "" {
			return "", nil
		}
		msgid, err := strconv.Atoi(msg.ID)
		if err != nil {
			return "", err
		}
		_, err = b.c.DeleteMessage(tgbotapi.DeleteMessageConfig{ChatID: chatid, MessageID: msgid})
		return "", err
	}

	// edit the message if we have a msg ID
	if msg.ID != "" {
		msgid, err := strconv.Atoi(msg.ID)
		if err != nil {
			return "", err
		}
		m := tgbotapi.NewEditMessageText(chatid, msgid, msg.Username+msg.Text)
		_, err = b.c.Send(m)
		if err != nil {
			return "", err
		}
		return "", nil
	}

	if msg.Extra != nil {
		// check if we have files to upload (from slack, telegram or mattermost)
		if len(msg.Extra["file"]) > 0 {
			var c tgbotapi.Chattable
			for _, f := range msg.Extra["file"] {
				fi := f.(config.FileInfo)
				file := tgbotapi.FileBytes{fi.Name, *fi.Data}
				re := regexp.MustCompile(".(jpg|png)$")
				if re.MatchString(fi.Name) {
					c = tgbotapi.NewPhotoUpload(chatid, file)
				} else {
					c = tgbotapi.NewDocumentUpload(chatid, file)
				}
				_, err := b.c.Send(c)
				if err != nil {
					log.Errorf("file upload failed: %#v", err)
				}
			}
		}
	}

	m := tgbotapi.NewMessage(chatid, msg.Username+msg.Text)
	if b.Config.MessageFormat == "HTML" {
		m.ParseMode = tgbotapi.ModeHTML
	}
	res, err := b.c.Send(m)
	if err != nil {
		return "", err
	}
	return strconv.Itoa(res.MessageID), nil

}

func (b *Btelegram) handleRecv(updates <-chan tgbotapi.Update) {
	for update := range updates {
		flog.Debugf("Receiving from telegram: %#v", update.Message)
		var message *tgbotapi.Message
		username := ""
		channel := ""
		text := ""

		fmsg := config.Message{Extra: make(map[string][]interface{})}

		// handle channels
		if update.ChannelPost != nil {
			message = update.ChannelPost
		}
		if update.EditedChannelPost != nil && !b.Config.EditDisable {
			message = update.EditedChannelPost
			message.Text = message.Text + b.Config.EditSuffix
		}
		// handle groups
		if update.Message != nil {
			message = update.Message
		}
		if update.EditedMessage != nil && !b.Config.EditDisable {
			message = update.EditedMessage
			message.Text = message.Text + b.Config.EditSuffix
		}
		if message.From != nil {
			if b.Config.UseFirstName {
				username = message.From.FirstName
			}
			if username == "" {
				username = message.From.UserName
				if username == "" {
					username = message.From.FirstName
				}
			}
			text = message.Text
			channel = strconv.FormatInt(message.Chat.ID, 10)
		}

		if username == "" {
			username = "unknown"
		}
		if message.Sticker != nil {
			b.handleDownload(message.Sticker, &fmsg)
		}
		if message.Video != nil {
			b.handleDownload(message.Video, &fmsg)
		}
		if message.Photo != nil {
			b.handleDownload(message.Photo, &fmsg)
		}
		if message.Document != nil {
			b.handleDownload(message.Document, &fmsg)
		}

		// quote the previous message
		if message.ReplyToMessage != nil {
			usernameReply := ""
			if message.ReplyToMessage.From != nil {
				if b.Config.UseFirstName {
					usernameReply = message.ReplyToMessage.From.FirstName
				}
				if usernameReply == "" {
					usernameReply = message.ReplyToMessage.From.UserName
					if usernameReply == "" {
						usernameReply = message.ReplyToMessage.From.FirstName
					}
				}
			}
			if usernameReply == "" {
				usernameReply = "unknown"
			}
			text = text + " (re @" + usernameReply + ":" + message.ReplyToMessage.Text + ")"
		}

		if text != "" || len(fmsg.Extra) > 0 {
			flog.Debugf("Sending message from %s on %s to gateway", username, b.Account)
			msg := config.Message{Username: username, Text: text, Channel: channel, Account: b.Account, UserID: strconv.Itoa(message.From.ID), ID: strconv.Itoa(message.MessageID)}
			flog.Debugf("Message is %#v", msg)
			b.Remote <- msg
		}
	}
}

func (b *Btelegram) getFileDirectURL(id string) string {
	res, err := b.c.GetFileDirectURL(id)
	if err != nil {
		return ""
	}
	return res
}

func (b *Btelegram) handleDownload(file interface{}, msg *config.Message) {
	size := 0
	url := ""
	name := ""
	text := ""
	fileid := ""
	switch v := file.(type) {
	case *tgbotapi.Sticker:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		name = "sticker"
		text = " " + url
		fileid = v.FileID
	case *tgbotapi.Video:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		name = "video"
		text = " " + url
		fileid = v.FileID
	case *[]tgbotapi.PhotoSize:
		photos := *v
		size = photos[len(photos)-1].FileSize
		url = b.getFileDirectURL(photos[len(photos)-1].FileID)
		name = "photo"
		text = " " + url
	case *tgbotapi.Document:
		size = v.FileSize
		url = b.getFileDirectURL(v.FileID)
		name = v.FileName
		text = " " + v.FileName + " : " + url
		fileid = v.FileID
	}
	if b.Config.UseInsecureURL {
		msg.Text = text
		return
	}
	// if we have a file attached, download it (in memory) and put a pointer to it in msg.Extra
	// limit to 1MB for now
	flog.Debugf("trying to download %#v fileid %#v with size %#v", name, fileid, size)
	if size <= 1000000 {
		data, err := helper.DownloadFile(url)
		if err != nil {
			flog.Errorf("download %s failed %#v", url, err)
		} else {
			flog.Debugf("download OK %#v %#v %#v", name, len(*data), len(url))
			msg.Extra["file"] = append(msg.Extra["file"], config.FileInfo{Name: name, Data: data})
		}
	}
}
<|endoftext|>


<commit_before>package mastodon

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"path"
	"strings"
	"time"
)

// Config is a setting for access mastodon APIs.
type Config struct {
	Server       string
	ClientID     string
	ClientSecret string
	AccessToken  string
}

// Client is a API client for mastodon.
type Client struct {
	http.Client
	config *Config
}

func (c *Client) doAPI(method string, uri string, params url.Values, res interface{}) error {
	url, err := url.Parse(c.config.Server)
	if err != nil {
		return err
	}
	url.Path = path.Join(url.Path, uri)

	var resp *http.Response
	req, err := http.NewRequest(method, url.String(), strings.NewReader(params.Encode()))
	if err != nil {
		return err
	}
	req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)
	resp, err = c.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if res == nil {
		return nil
	}

	if method == "GET" && resp.StatusCode != http.StatusOK {
		return fmt.Errorf("bad request: %v", resp.Status)
	}

	return json.NewDecoder(resp.Body).Decode(&res)
}

// NewClient return new mastodon API client.
func NewClient(config *Config) *Client {
	return &Client{
		Client: *http.DefaultClient,
		config: config,
	}
}

// Authenticate get access-token to the API.
func (c *Client) Authenticate(username, password string) error {
	params := url.Values{}
	params.Set("client_id", c.config.ClientID)
	params.Set("client_secret", c.config.ClientSecret)
	params.Set("grant_type", "password")
	params.Set("username", username)
	params.Set("password", password)
	params.Set("scope", "read write follow")

	url, err := url.Parse(c.config.Server)
	if err != nil {
		return err
	}
	url.Path = path.Join(url.Path, "/oauth/token")

	req, err := http.NewRequest("POST", url.String(), strings.NewReader(params.Encode()))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp, err := c.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("bad authorization: %v", resp.Status)
	}

	res := struct {
		AccessToken string `json:"access_token"`
	}{}
	err = json.NewDecoder(resp.Body).Decode(&res)
	if err != nil {
		return err
	}
	c.config.AccessToken = res.AccessToken
	return nil
}

// AppConfig is a setting for registering applications.
type AppConfig struct {
	http.Client
	Server     string
	ClientName string

	// Where the user should be redirected after authorization (for no redirect, use urn:ietf:wg:oauth:2.0:oob)
	RedirectURIs string

	// This can be a space-separated list of the following items: "read", "write" and "follow".
	Scopes string

	// Optional.
	Website string
}

// Application is mastodon application.
type Application struct {
	ID           int64  `json:"id"`
	RedirectURI  string `json:"redirect_uri"`
	ClientID     string `json:"client_id"`
	ClientSecret string `json:"client_secret"`
}

// RegisterApp returns the mastodon application.
func RegisterApp(appConfig *AppConfig) (*Application, error) {
	params := url.Values{}
	params.Set("client_name", appConfig.ClientName)
	if appConfig.RedirectURIs == "" {
		params.Set("redirect_uris", "urn:ietf:wg:oauth:2.0:oob")
	} else {
		params.Set("redirect_uris", appConfig.RedirectURIs)
	}
	params.Set("scopes", appConfig.Scopes)
	params.Set("website", appConfig.Website)

	url, err := url.Parse(appConfig.Server)
	if err != nil {
		return nil, err
	}
	url.Path = path.Join(url.Path, "/api/v1/apps")

	req, err := http.NewRequest("POST", url.String(), strings.NewReader(params.Encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp, err := appConfig.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("bad request: %v", resp.Status)
	}

	var app Application
	err = json.NewDecoder(resp.Body).Decode(&app)
	if err != nil {
		return nil, err
	}

	return &app, nil
}

// Account hold information for mastodon account.
type Account struct {
	ID             int64     `json:"id"`
	Username       string    `json:"username"`
	Acct           string    `json:"acct"`
	DisplayName    string    `json:"display_name"`
	Locked         bool      `json:"locked"`
	CreatedAt      time.Time `json:"created_at"`
	FollowersCount int64     `json:"followers_count"`
	FollowingCount int64     `json:"following_count"`
	StatusesCount  int64     `json:"statuses_count"`
	Note           string    `json:"note"`
	URL            string    `json:"url"`
	Avatar         string    `json:"avatar"`
	AvatarStatic   string    `json:"avatar_static"`
	Header         string    `json:"header"`
	HeaderStatic   string    `json:"header_static"`
}

// Toot is struct to post status.
type Toot struct {
	Status      string  `json:"status"`
	InReplyToID int64   `json:"in_reply_to_id"`
	MediaIDs    []int64 `json:"media_ids"`
	Sensitive   bool    `json:"sensitive"`
	SpoilerText string  `json:"spoiler_text"`
	Visibility  string  `json:"visibility"`
}

// Status is struct to hold status.
type Status struct {
	ID                 int64         `json:"id"`
	CreatedAt          time.Time     `json:"created_at"`
	InReplyToID        interface{}   `json:"in_reply_to_id"`
	InReplyToAccountID interface{}   `json:"in_reply_to_account_id"`
	Sensitive          bool          `json:"sensitive"`
	SpoilerText        string        `json:"spoiler_text"`
	Visibility         string        `json:"visibility"`
	Application        interface{}   `json:"application"`
	Account            Account       `json:"account"`
	MediaAttachments   []interface{} `json:"media_attachments"`
	Mentions           []interface{} `json:"mentions"`
	Tags               []interface{} `json:"tags"`
	URI                string        `json:"uri"`
	Content            string        `json:"content"`
	URL                string        `json:"url"`
	ReblogsCount       int64         `json:"reblogs_count"`
	FavouritesCount    int64         `json:"favourites_count"`
	Reblog             interface{}   `json:"reblog"`
	Favourited         interface{}   `json:"favourited"`
	Reblogged          interface{}   `json:"reblogged"`
}

// GetAccount return Account.
func (c *Client) GetAccount(id int) (*Account, error) {
	var account Account
	err := c.doAPI("GET", fmt.Sprintf("/api/v1/accounts/%d", id), nil, &account)
	if err != nil {
		return nil, err
	}
	return &account, nil
}

// GetAccountCurrentUser return Account of current user.
func (c *Client) GetAccountCurrentUser() (*Account, error) {
	var account Account
	err := c.doAPI("GET", "/api/v1/accounts/verify_credentials", nil, &account)
	if err != nil {
		return nil, err
	}
	return &account, nil
}

// GetAccountFollowers return followers list.
func (c *Client) GetAccountFollowers(id int64) ([]*Account, error) {
	var accounts []*Account
	err := c.doAPI("GET", fmt.Sprintf("/api/v1/accounts/%d/followers", id), nil, &accounts)
	if err != nil {
		return nil, err
	}
	return accounts, nil
}

// GetAccountFollowing return following list.
func (c *Client) GetAccountFollowing(id int64) ([]*Account, error) {
	var accounts []*Account
	err := c.doAPI("GET", fmt.Sprintf("/api/v1/accounts/%d/following", id), nil, &accounts)
	if err != nil {
		return nil, err
	}
	return accounts, nil
}

// GetTimelineHome return statuses from home timeline.
func (c *Client) GetTimelineHome() ([]*Status, error) {
	var statuses []*Status
	err := c.doAPI("GET", "/api/v1/timelines/home", nil, &statuses)
	if err != nil {
		return nil, err
	}
	return statuses, nil
}

// PostStatus post the toot.
func (c *Client) PostStatus(toot *Toot) (*Status, error) {
	params := url.Values{}
	params.Set("status", toot.Status)
	if toot.InReplyToID > 0 {
		params.Set("in_reply_to_id", fmt.Sprint(toot.InReplyToID))
	}
	// TODO: media_ids, senstitive, spoiler_text, visibility
	//params.Set("visibility", "public")

	var status Status
	err := c.doAPI("POST", "/api/v1/statuses", params, &status)
	if err != nil {
		return nil, err
	}
	return &status, nil
}

// UpdateEvent is struct for passing status event to app.
type UpdateEvent struct{ Status *Status }

func (e *UpdateEvent) event() {}

// NotificationEvent is struct for passing notification event to app.
type NotificationEvent struct{}

func (e *NotificationEvent) event() {}

// DeleteEvent is struct for passing deletion event to app.
type DeleteEvent struct{ ID int64 }

func (e *DeleteEvent) event() {}

// ErrorEvent is struct for passing errors to app.
type ErrorEvent struct{ err error }

func (e *ErrorEvent) event()        {}
func (e *ErrorEvent) Error() string { return e.err.Error() }

// Event is interface passing events to app.
type Event interface {
	event()
}

func handleReader(q chan Event, r io.Reader) error {
	name := ""
	s := bufio.NewScanner(r)
	for s.Scan() {
		line := s.Text()
		token := strings.SplitN(line, ":", 2)
		if len(token) != 2 {
			continue
		}
		switch strings.TrimSpace(token[0]) {
		case "event":
			name = strings.TrimSpace(token[1])
		case "data":
			switch name {
			case "update":
				var status Status
				err = json.Unmarshal([]byte(token[1]), &status)
				if err == nil {
					q <- &UpdateEvent{&status}
				}
			case "notification":
			case "delete":
			}
		default:
		}
	}
	return ctx.Err()
}

// StreamingPublic return channel to read events.
func (c *Client) StreamingPublic(ctx context.Context) (chan Event, error) {
	url, err := url.Parse(c.config.Server)
	if err != nil {
		return nil, err
	}
	url.Path = path.Join(url.Path, "/api/v1/streaming/public")

	var resp *http.Response

	q := make(chan Event, 10)
	go func() {
		defer ctx.Done()

		for {
			req, err := http.NewRequest("GET", url.String(), nil)
			if err == nil {
				req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)
				resp, err = c.Do(req)
			}
			if err == nil {
				err = handleReader(resp.Body)
				if err == nil {
					break
				}
				resp.Body.Close()
				return err
			} else {
				q <- &ErrorEvent{err}
			}
			time.Sleep(3 * time.Second)
		}
	}()
	go func() {
		<-ctx.Done()
		if resp != nil && resp.Body != nil {
			resp.Body.Close()
		}
	}()
	return q, nil
}

// Follow send follow-request.
func (c *Client) Follow(uri string) (*Account, error) {
	params := url.Values{}
	params.Set("uri", uri)

	var account Account
	err := c.doAPI("POST", "/api/v1/follows", params, &account)
	if err != nil {
		return nil, err
	}
	return &account, nil
}

// GetFollowRequests return follow-requests.
func (c *Client) GetFollowRequests(uri string) ([]*Account, error) {
	params := url.Values{}
	params.Set("uri", uri)

	var accounts []*Account
	err := c.doAPI("GET", "/api/v1/follow_requests", params, &accounts)
	if err != nil {
		return nil, err
	}
	return accounts, nil
}
<commit_msg>should not return<commit_after>package mastodon

import (
	"bufio"
	"context"
	"encoding/json"
	"fmt"
	"io"
	"net/http"
	"net/url"
	"path"
	"strings"
	"time"
)

// Config is a setting for access mastodon APIs.
type Config struct {
	Server       string
	ClientID     string
	ClientSecret string
	AccessToken  string
}

// Client is a API client for mastodon.
type Client struct {
	http.Client
	config *Config
}

func (c *Client) doAPI(method string, uri string, params url.Values, res interface{}) error {
	url, err := url.Parse(c.config.Server)
	if err != nil {
		return err
	}
	url.Path = path.Join(url.Path, uri)

	var resp *http.Response
	req, err := http.NewRequest(method, url.String(), strings.NewReader(params.Encode()))
	if err != nil {
		return err
	}
	req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)
	resp, err = c.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	if res == nil {
		return nil
	}

	if method == "GET" && resp.StatusCode != http.StatusOK {
		return fmt.Errorf("bad request: %v", resp.Status)
	}

	return json.NewDecoder(resp.Body).Decode(&res)
}

// NewClient return new mastodon API client.
func NewClient(config *Config) *Client {
	return &Client{
		Client: *http.DefaultClient,
		config: config,
	}
}

// Authenticate get access-token to the API.
func (c *Client) Authenticate(username, password string) error {
	params := url.Values{}
	params.Set("client_id", c.config.ClientID)
	params.Set("client_secret", c.config.ClientSecret)
	params.Set("grant_type", "password")
	params.Set("username", username)
	params.Set("password", password)
	params.Set("scope", "read write follow")

	url, err := url.Parse(c.config.Server)
	if err != nil {
		return err
	}
	url.Path = path.Join(url.Path, "/oauth/token")

	req, err := http.NewRequest("POST", url.String(), strings.NewReader(params.Encode()))
	if err != nil {
		return err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp, err := c.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("bad authorization: %v", resp.Status)
	}

	res := struct {
		AccessToken string `json:"access_token"`
	}{}
	err = json.NewDecoder(resp.Body).Decode(&res)
	if err != nil {
		return err
	}
	c.config.AccessToken = res.AccessToken
	return nil
}

// AppConfig is a setting for registering applications.
type AppConfig struct {
	http.Client
	Server     string
	ClientName string

	// Where the user should be redirected after authorization (for no redirect, use urn:ietf:wg:oauth:2.0:oob)
	RedirectURIs string

	// This can be a space-separated list of the following items: "read", "write" and "follow".
	Scopes string

	// Optional.
	Website string
}

// Application is mastodon application.
type Application struct {
	ID           int64  `json:"id"`
	RedirectURI  string `json:"redirect_uri"`
	ClientID     string `json:"client_id"`
	ClientSecret string `json:"client_secret"`
}

// RegisterApp returns the mastodon application.
func RegisterApp(appConfig *AppConfig) (*Application, error) {
	params := url.Values{}
	params.Set("client_name", appConfig.ClientName)
	if appConfig.RedirectURIs == "" {
		params.Set("redirect_uris", "urn:ietf:wg:oauth:2.0:oob")
	} else {
		params.Set("redirect_uris", appConfig.RedirectURIs)
	}
	params.Set("scopes", appConfig.Scopes)
	params.Set("website", appConfig.Website)

	url, err := url.Parse(appConfig.Server)
	if err != nil {
		return nil, err
	}
	url.Path = path.Join(url.Path, "/api/v1/apps")

	req, err := http.NewRequest("POST", url.String(), strings.NewReader(params.Encode()))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
	resp, err := appConfig.Do(req)
	if err != nil {
		return nil, err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("bad request: %v", resp.Status)
	}

	var app Application
	err = json.NewDecoder(resp.Body).Decode(&app)
	if err != nil {
		return nil, err
	}

	return &app, nil
}

// Account hold information for mastodon account.
type Account struct {
	ID             int64     `json:"id"`
	Username       string    `json:"username"`
	Acct           string    `json:"acct"`
	DisplayName    string    `json:"display_name"`
	Locked         bool      `json:"locked"`
	CreatedAt      time.Time `json:"created_at"`
	FollowersCount int64     `json:"followers_count"`
	FollowingCount int64     `json:"following_count"`
	StatusesCount  int64     `json:"statuses_count"`
	Note           string    `json:"note"`
	URL            string    `json:"url"`
	Avatar         string    `json:"avatar"`
	AvatarStatic   string    `json:"avatar_static"`
	Header         string    `json:"header"`
	HeaderStatic   string    `json:"header_static"`
}

// Toot is struct to post status.
type Toot struct {
	Status      string  `json:"status"`
	InReplyToID int64   `json:"in_reply_to_id"`
	MediaIDs    []int64 `json:"media_ids"`
	Sensitive   bool    `json:"sensitive"`
	SpoilerText string  `json:"spoiler_text"`
	Visibility  string  `json:"visibility"`
}

// Status is struct to hold status.
type Status struct {
	ID                 int64         `json:"id"`
	CreatedAt          time.Time     `json:"created_at"`
	InReplyToID        interface{}   `json:"in_reply_to_id"`
	InReplyToAccountID interface{}   `json:"in_reply_to_account_id"`
	Sensitive          bool          `json:"sensitive"`
	SpoilerText        string        `json:"spoiler_text"`
	Visibility         string        `json:"visibility"`
	Application        interface{}   `json:"application"`
	Account            Account       `json:"account"`
	MediaAttachments   []interface{} `json:"media_attachments"`
	Mentions           []interface{} `json:"mentions"`
	Tags               []interface{} `json:"tags"`
	URI                string        `json:"uri"`
	Content            string        `json:"content"`
	URL                string        `json:"url"`
	ReblogsCount       int64         `json:"reblogs_count"`
	FavouritesCount    int64         `json:"favourites_count"`
	Reblog             interface{}   `json:"reblog"`
	Favourited         interface{}   `json:"favourited"`
	Reblogged          interface{}   `json:"reblogged"`
}

// GetAccount return Account.
func (c *Client) GetAccount(id int) (*Account, error) {
	var account Account
	err := c.doAPI("GET", fmt.Sprintf("/api/v1/accounts/%d", id), nil, &account)
	if err != nil {
		return nil, err
	}
	return &account, nil
}

// GetAccountCurrentUser return Account of current user.
func (c *Client) GetAccountCurrentUser() (*Account, error) {
	var account Account
	err := c.doAPI("GET", "/api/v1/accounts/verify_credentials", nil, &account)
	if err != nil {
		return nil, err
	}
	return &account, nil
}

// GetAccountFollowers return followers list.
func (c *Client) GetAccountFollowers(id int64) ([]*Account, error) {
	var accounts []*Account
	err := c.doAPI("GET", fmt.Sprintf("/api/v1/accounts/%d/followers", id), nil, &accounts)
	if err != nil {
		return nil, err
	}
	return accounts, nil
}

// GetAccountFollowing return following list.
func (c *Client) GetAccountFollowing(id int64) ([]*Account, error) {
	var accounts []*Account
	err := c.doAPI("GET", fmt.Sprintf("/api/v1/accounts/%d/following", id), nil, &accounts)
	if err != nil {
		return nil, err
	}
	return accounts, nil
}

// GetTimelineHome return statuses from home timeline.
func (c *Client) GetTimelineHome() ([]*Status, error) {
	var statuses []*Status
	err := c.doAPI("GET", "/api/v1/timelines/home", nil, &statuses)
	if err != nil {
		return nil, err
	}
	return statuses, nil
}

// PostStatus post the toot.
func (c *Client) PostStatus(toot *Toot) (*Status, error) {
	params := url.Values{}
	params.Set("status", toot.Status)
	if toot.InReplyToID > 0 {
		params.Set("in_reply_to_id", fmt.Sprint(toot.InReplyToID))
	}
	// TODO: media_ids, senstitive, spoiler_text, visibility
	//params.Set("visibility", "public")

	var status Status
	err := c.doAPI("POST", "/api/v1/statuses", params, &status)
	if err != nil {
		return nil, err
	}
	return &status, nil
}

// UpdateEvent is struct for passing status event to app.
type UpdateEvent struct{ Status *Status }

func (e *UpdateEvent) event() {}

// NotificationEvent is struct for passing notification event to app.
type NotificationEvent struct{}

func (e *NotificationEvent) event() {}

// DeleteEvent is struct for passing deletion event to app.
type DeleteEvent struct{ ID int64 }

func (e *DeleteEvent) event() {}

// ErrorEvent is struct for passing errors to app.
type ErrorEvent struct{ err error }

func (e *ErrorEvent) event()        {}
func (e *ErrorEvent) Error() string { return e.err.Error() }

// Event is interface passing events to app.
type Event interface {
	event()
}

func handleReader(ctx context.Context, q chan Event, r io.Reader) error {
	name := ""
	s := bufio.NewScanner(r)
	for s.Scan() {
		line := s.Text()
		token := strings.SplitN(line, ":", 2)
		if len(token) != 2 {
			continue
		}
		switch strings.TrimSpace(token[0]) {
		case "event":
			name = strings.TrimSpace(token[1])
		case "data":
			switch name {
			case "update":
				var status Status
				err := json.Unmarshal([]byte(token[1]), &status)
				if err == nil {
					q <- &UpdateEvent{&status}
				}
			case "notification":
			case "delete":
			}
		default:
		}
	}
	return ctx.Err()
}

// StreamingPublic return channel to read events.
func (c *Client) StreamingPublic(ctx context.Context) (chan Event, error) {
	url, err := url.Parse(c.config.Server)
	if err != nil {
		return nil, err
	}
	url.Path = path.Join(url.Path, "/api/v1/streaming/public")

	var resp *http.Response

	q := make(chan Event, 10)
	go func() {
		defer ctx.Done()

		for {
			req, err := http.NewRequest("GET", url.String(), nil)
			if err == nil {
				req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)
				resp, err = c.Do(req)
			}
			if err == nil {
				err = handleReader(ctx, q, resp.Body)
				resp.Body.Close()
				if err == nil {
					break
				}
			} else {
				q <- &ErrorEvent{err}
			}
			time.Sleep(3 * time.Second)
		}
	}()
	go func() {
		<-ctx.Done()
		if resp != nil && resp.Body != nil {
			resp.Body.Close()
		}
	}()
	return q, nil
}

// Follow send follow-request.
func (c *Client) Follow(uri string) (*Account, error) {
	params := url.Values{}
	params.Set("uri", uri)

	var account Account
	err := c.doAPI("POST", "/api/v1/follows", params, &account)
	if err != nil {
		return nil, err
	}
	return &account, nil
}

// GetFollowRequests return follow-requests.
func (c *Client) GetFollowRequests(uri string) ([]*Account, error) {
	params := url.Values{}
	params.Set("uri", uri)

	var accounts []*Account
	err := c.doAPI("GET", "/api/v1/follow_requests", params, &accounts)
	if err != nil {
		return nil, err
	}
	return accounts, nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/cubicdaiya/nginx-build/builder"
	"github.com/cubicdaiya/nginx-build/command"
	"github.com/cubicdaiya/nginx-build/util"
)

const DefaultTimeout = time.Duration(900) * time.Second

func extractArchive(path string) error {
	return command.Run([]string{"tar", "zxvf", path})
}

func download(b *builder.Builder) error {
	c := &http.Client{
		Timeout: DefaultTimeout,
	}

	res, err := c.Get(b.DownloadURL())
	if err != nil {
		return err
	}
	defer res.Body.Close()

	f, err := os.Create(b.ArchivePath())
	if err != nil {
		return err
	}
	defer f.Close()

	_, err = io.Copy(f, res.Body)
	if err != nil && err != io.EOF {
		return err
	}

	return nil
}

func downloadAndExtract(b *builder.Builder) error {
	if !util.FileExists(b.SourcePath()) {
		if !util.FileExists(b.ArchivePath()) {

			log.Printf("Download %s.....", b.SourcePath())

			err := download(b)
			if err != nil {
				return fmt.Errorf("Failed to download %s. %s", b.SourcePath(), err.Error())
			}
		}

		log.Printf("Extract %s.....", b.ArchivePath())

		err := extractArchive(b.ArchivePath())
		if err != nil {
			return fmt.Errorf("Failed to extract %s. %s", b.ArchivePath(), err.Error())
		}
	} else {
		log.Printf("%s already exists.", b.SourcePath())
	}
	return nil
}

func downloadAndExtractParallel(b *builder.Builder) {
	err := downloadAndExtract(b)
	if err != nil {
		util.PrintFatalMsg(err, b.LogPath())
	}
}
<commit_msg>change constant name<commit_after>package main

import (
	"fmt"
	"io"
	"log"
	"net/http"
	"os"
	"time"

	"github.com/cubicdaiya/nginx-build/builder"
	"github.com/cubicdaiya/nginx-build/command"
	"github.com/cubicdaiya/nginx-build/util"
)

const DefaultDownloadTimeout = time.Duration(900) * time.Second

func extractArchive(path string) error {
	return command.Run([]string{"tar", "zxvf", path})
}

func download(b *builder.Builder) error {
	c := &http.Client{
		Timeout: DefaultDownloadTimeout,
	}

	res, err := c.Get(b.DownloadURL())
	if err != nil {
		return err
	}
	defer res.Body.Close()

	f, err := os.Create(b.ArchivePath())
	if err != nil {
		return err
	}
	defer f.Close()

	_, err = io.Copy(f, res.Body)
	if err != nil && err != io.EOF {
		return err
	}

	return nil
}

func downloadAndExtract(b *builder.Builder) error {
	if !util.FileExists(b.SourcePath()) {
		if !util.FileExists(b.ArchivePath()) {

			log.Printf("Download %s.....", b.SourcePath())

			err := download(b)
			if err != nil {
				return fmt.Errorf("Failed to download %s. %s", b.SourcePath(), err.Error())
			}
		}

		log.Printf("Extract %s.....", b.ArchivePath())

		err := extractArchive(b.ArchivePath())
		if err != nil {
			return fmt.Errorf("Failed to extract %s. %s", b.ArchivePath(), err.Error())
		}
	} else {
		log.Printf("%s already exists.", b.SourcePath())
	}
	return nil
}

func downloadAndExtractParallel(b *builder.Builder) {
	err := downloadAndExtract(b)
	if err != nil {
		util.PrintFatalMsg(err, b.LogPath())
	}
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"flag"
	"fmt"
	"github.com/gmacd/container/set"
	"github.com/russross/blackfriday"
	"html/template"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

// Globals
var (
	srcRoot string
	cssPath string

	pageSplitterRegex *regexp.Regexp
)

func main() {
	fmt.Printf("\n...chanzero...\n\n")

	// Split on at least 3 '/'
	pageSplitterRegex = regexp.MustCompile(`[/]{3,}`)

	// TODO Specfiy output folder
	flag.StringVar(&srcRoot, "src", "", "Path to root src file for site to build.")
	flag.Parse()

	if _, err := os.Stat(srcRoot); err != nil {
		fmt.Printf("Couldn't open file \"%v\" (%v)\n", srcRoot, err.Error())
		os.Exit(-1)
	}

	fmt.Printf("Building site with root: %v\n\n", srcRoot)

	exportSite(srcRoot)
}

type page struct {
	srcPath    string
	destPath   string
	html       []byte
	linkedUrls []string
	settings   map[string]string
}

func NewPage(srcPath, destPath string) *page {
	return &page{srcPath, destPath, nil, make([]string, 0), make(map[string]string)}
}

func (page *page) AddLink(url string) {
	page.linkedUrls = append(page.linkedUrls, url)
}

func replaceExtension(path, newExtention string) string {
	basePath := strings.TrimSuffix(path, filepath.Ext(path))
	return basePath + "." + newExtention
}

// Wrapped HtmlRenderer which gathers all links in markdown
// TODO Write post about wrapping/oeverriding functionality in Go.
type LinkGatheringHtmlRenderer struct {
	*blackfriday.Html

	page *page
}

func NewLinkGatheringHtmlRenderer(renderer blackfriday.Renderer, page *page) *LinkGatheringHtmlRenderer {
	return &LinkGatheringHtmlRenderer{renderer.(*blackfriday.Html), page}
}

func (html *LinkGatheringHtmlRenderer) AutoLink(out *bytes.Buffer, link []byte, kind int) {
	html.page.AddLink(string(link))
	html.Html.AutoLink(out, link, kind)
}

func (html *LinkGatheringHtmlRenderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
	html.page.AddLink(string(link))
	html.Html.Link(out, link, title, content)
}

// Given a single root page, load the page and follow all local src links,
// loading each page recursively, linked to the root.
func (page *page) importPage() {
	fileContents, err := ioutil.ReadFile(page.srcPath)
	if err != nil {
		fmt.Printf("Couldn't load \"%v\": %v\n", page.srcPath, err.Error())
	}

	mdsrc := fileContents
	strs := pageSplitterRegex.Split(string(fileContents), -1)
	if len(strs) > 1 {
		parseSettings(strs[0], page.settings)
		handleGlobalSettings(page.settings)
		mdsrc = []byte(strs[1])
	}

	// Set up a 'common' converter
	htmlFlags := 0
	htmlFlags |= blackfriday.HTML_USE_SMARTYPANTS
	htmlFlags |= blackfriday.HTML_SMARTYPANTS_FRACTIONS
	htmlFlags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES
	htmlFlags |= blackfriday.HTML_COMPLETE_PAGE
	title := page.settings["Title"]
	renderer := blackfriday.HtmlRenderer(htmlFlags, title, cssPath)

	linkGatheringRenderer := NewLinkGatheringHtmlRenderer(renderer, page)

	extensions := 0
	//extensions |= blackfriday.EXTENSION_NO_INTRA_EMPHASIS
	extensions |= blackfriday.EXTENSION_TABLES
	extensions |= blackfriday.EXTENSION_FENCED_CODE
	extensions |= blackfriday.EXTENSION_AUTOLINK
	extensions |= blackfriday.EXTENSION_STRIKETHROUGH
	extensions |= blackfriday.EXTENSION_SPACE_HEADERS
	extensions |= blackfriday.EXTENSION_HEADER_IDS
	extensions |= blackfriday.EXTENSION_HARD_LINE_BREAK

	page.html = blackfriday.Markdown(mdsrc, linkGatheringRenderer, extensions)
}

// Given a root page, export the entire site.
func exportSite(rootSrc string) {
	rootFile := filepath.Base(rootSrc)
	rootSrcPath := filepath.Dir(rootSrc)
	destSrcPath := filepath.Dir(rootSrcPath)

	exportedPages := set.NewSetOfValues()
	exportPage(rootFile, rootSrcPath, destSrcPath, exportedPages)
}

// TEMP
type Foo struct {
	Title string
}

func exportPage(pageSrcPath, rootSrcPath, destSrcPath string, previouslyExportedPaths *set.Set) {
	if !previouslyExportedPaths.Contains(pageSrcPath) {
		previouslyExportedPaths.Add(pageSrcPath)

		page := NewPage(
			rootSrcPath+"/"+pageSrcPath,
			destSrcPath+"/"+replaceExtension(pageSrcPath, "html"))

		fmt.Printf(" Exporting page  src: %v\n", page.srcPath)
		fmt.Printf("                dest: %v\n\n", page.destPath)

		// Ensure destination exists
		os.MkdirAll(filepath.Dir(page.destPath), 0755)

		page.importPage()

		// Prepare exported HTML as a template
		template, err := template.New("page").Parse(string(page.html))
		if err != nil {
			fmt.Println("Couldn't parse file.  ", err.Error())
			return
		}

		// Expand any templates and write out the file
		file, err := os.Create(page.destPath)
		if err != nil {
			fmt.Println("Couldn't write to file.  ", err.Error())
			return
		}
		defer file.Close()
		template.Execute(file, &Foo{"foo"})

		// Export local, valid markdown links
		for _, linkUrl := range page.linkedUrls {
			linkSrc := replaceExtension(linkUrl, "md")

			if canOpen(rootSrcPath + "/" + linkSrc) {
				exportPage(linkSrc, rootSrcPath, destSrcPath, previouslyExportedPaths)
			}
		}
	}
}

func parseSettings(str string, settings map[string]string) {
	for _, line := range strings.Split(str, "\n") {
		tokens := strings.SplitN(line, ":", 2)
		if len(tokens) == 2 {
			key := strings.TrimSpace(tokens[0])
			value := strings.TrimSpace(tokens[1])
			settings[key] = value
		}
	}
}

func handleGlobalSettings(pageSettings map[string]string) {
	if value, ok := pageSettings["SiteCss"]; ok {
		cssPath = value
	}
}

func canOpen(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}
<commit_msg>Skip drafts<commit_after>package main

import (
	"bytes"
	"flag"
	"fmt"
	"github.com/gmacd/container/set"
	"github.com/russross/blackfriday"
	"html/template"
	"io/ioutil"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

// Globals
var (
	srcRoot string
	cssPath string

	pageSplitterRegex *regexp.Regexp
)

func main() {
	fmt.Printf("\n...chanzero...\n\n")

	// Split on at least 3 '/'
	pageSplitterRegex = regexp.MustCompile(`[/]{3,}`)

	// TODO Specfiy output folder
	flag.StringVar(&srcRoot, "src", "", "Path to root src file for site to build.")
	flag.Parse()

	if _, err := os.Stat(srcRoot); err != nil {
		fmt.Printf("Couldn't open file \"%v\" (%v)\n", srcRoot, err.Error())
		os.Exit(-1)
	}

	fmt.Printf("Building site with root: %v\n", srcRoot)

	exportSite(srcRoot)
}

type page struct {
	srcPath    string
	destPath   string
	html       []byte
	linkedUrls []string
	settings   map[string]string
}

func NewPage(srcPath, destPath string) *page {
	return &page{srcPath, destPath, nil, make([]string, 0), make(map[string]string)}
}

func (page *page) AddLink(url string) {
	page.linkedUrls = append(page.linkedUrls, url)
}

func (page *page) IsDraft() bool {
	value, ok := page.settings["Draft"]
	return ok && value == "true"
}

func replaceExtension(path, newExtention string) string {
	basePath := strings.TrimSuffix(path, filepath.Ext(path))
	return basePath + "." + newExtention
}

// Wrapped HtmlRenderer which gathers all links in markdown
// TODO Write post about wrapping/oeverriding functionality in Go.
type LinkGatheringHtmlRenderer struct {
	*blackfriday.Html

	page *page
}

func NewLinkGatheringHtmlRenderer(renderer blackfriday.Renderer, page *page) *LinkGatheringHtmlRenderer {
	return &LinkGatheringHtmlRenderer{renderer.(*blackfriday.Html), page}
}

func (html *LinkGatheringHtmlRenderer) AutoLink(out *bytes.Buffer, link []byte, kind int) {
	html.page.AddLink(string(link))
	html.Html.AutoLink(out, link, kind)
}

func (html *LinkGatheringHtmlRenderer) Link(out *bytes.Buffer, link []byte, title []byte, content []byte) {
	html.page.AddLink(string(link))
	html.Html.Link(out, link, title, content)
}

// Given a single root page, load the page and follow all local src links,
// loading each page recursively, linked to the root.
func (page *page) importPage() {
	fileContents, err := ioutil.ReadFile(page.srcPath)
	if err != nil {
		fmt.Printf("Couldn't load \"%v\": %v\n", page.srcPath, err.Error())
	}

	mdsrc := fileContents
	strs := pageSplitterRegex.Split(string(fileContents), -1)
	if len(strs) > 1 {
		parseSettings(strs[0], page.settings)
		handleGlobalSettings(page.settings)
		mdsrc = []byte(strs[1])
	}

	// Set up a 'common' converter
	htmlFlags := 0
	htmlFlags |= blackfriday.HTML_USE_SMARTYPANTS
	htmlFlags |= blackfriday.HTML_SMARTYPANTS_FRACTIONS
	htmlFlags |= blackfriday.HTML_SMARTYPANTS_LATEX_DASHES
	htmlFlags |= blackfriday.HTML_COMPLETE_PAGE
	title := page.settings["Title"]
	renderer := blackfriday.HtmlRenderer(htmlFlags, title, cssPath)

	linkGatheringRenderer := NewLinkGatheringHtmlRenderer(renderer, page)

	extensions := 0
	//extensions |= blackfriday.EXTENSION_NO_INTRA_EMPHASIS
	extensions |= blackfriday.EXTENSION_TABLES
	extensions |= blackfriday.EXTENSION_FENCED_CODE
	extensions |= blackfriday.EXTENSION_AUTOLINK
	extensions |= blackfriday.EXTENSION_STRIKETHROUGH
	extensions |= blackfriday.EXTENSION_SPACE_HEADERS
	extensions |= blackfriday.EXTENSION_HEADER_IDS
	extensions |= blackfriday.EXTENSION_HARD_LINE_BREAK

	page.html = blackfriday.Markdown(mdsrc, linkGatheringRenderer, extensions)
}

// Given a root page, export the entire site.
func exportSite(rootSrc string) {
	rootFile := filepath.Base(rootSrc)
	rootSrcPath := filepath.Dir(rootSrc)
	destSrcPath := filepath.Dir(rootSrcPath)

	exportedPages := set.NewSetOfValues()
	exportPage(rootFile, rootSrcPath, destSrcPath, exportedPages)
}

// TEMP
type Foo struct {
	Title string
}

func exportPage(pageSrcPath, rootSrcPath, destSrcPath string, previouslyExportedPaths *set.Set) {
	if !previouslyExportedPaths.Contains(pageSrcPath) {
		previouslyExportedPaths.Add(pageSrcPath)

		page := NewPage(
			rootSrcPath+"/"+pageSrcPath,
			destSrcPath+"/"+replaceExtension(pageSrcPath, "html"))

		fmt.Printf("\n Exporting page  src: %v\n", page.srcPath)
		fmt.Printf("                dest: %v\n", page.destPath)

		// Ensure destination exists
		os.MkdirAll(filepath.Dir(page.destPath), 0755)

		page.importPage()
		if page.IsDraft() {
			fmt.Println("   [Skipping draft]")
			return
		}

		// Prepare exported HTML as a template
		template, err := template.New("page").Parse(string(page.html))
		if err != nil {
			fmt.Println("Couldn't parse file.  ", err.Error())
			return
		}

		// Expand any templates and write out the file
		file, err := os.Create(page.destPath)
		if err != nil {
			fmt.Println("Couldn't write to file.  ", err.Error())
			return
		}
		defer file.Close()
		template.Execute(file, &Foo{"foo"})

		// Export local, valid markdown links
		for _, linkUrl := range page.linkedUrls {
			linkSrc := replaceExtension(linkUrl, "md")

			if canOpen(rootSrcPath + "/" + linkSrc) {
				exportPage(linkSrc, rootSrcPath, destSrcPath, previouslyExportedPaths)
			}
		}
	}
}

func parseSettings(str string, settings map[string]string) {
	for _, line := range strings.Split(str, "\n") {
		tokens := strings.SplitN(line, ":", 2)
		if len(tokens) == 2 {
			key := strings.TrimSpace(tokens[0])
			value := strings.TrimSpace(tokens[1])
			settings[key] = value
		}
	}
}

func handleGlobalSettings(pageSettings map[string]string) {
	if value, ok := pageSettings["SiteCss"]; ok {
		cssPath = value
	}
}

func canOpen(path string) bool {
	_, err := os.Stat(path)
	return err == nil
}
<|endoftext|>


<commit_before>package spec

import (
	"fmt"
	"regexp"
	"strconv"

	"github.com/libopenstorage/openstorage/api"
	"github.com/libopenstorage/openstorage/pkg/units"
)

// SpecHandler provides conversion function from what gets passed in over the
// plugin API to an api.VolumeSpec object.
type SpecHandler interface {
	// SpecFromString parses options from the name.
	// If the scheduler was unable to pass in the volume spec via the API,
	// the spec can be passed in via the name in the format:
	// "key=value;key=value;name=volname"
	// source is populated if key parent=<volume_id> is specified.
	// If the spec was parsed, it returns:
	//  	(true, parsed_spec, source, parsed_name)
	// If the input string didn't contain the string, it returns:
	// 	(false, DefaultSpec(), nil, inputString)
	SpecFromString(inputString string) (bool, *api.VolumeSpec, *api.Source, string)

	// SpecFromOpts parses in docker options passed in the the docker run
	// command of the form --opt name=value
	// source is populated if --opt parent=<volume_id> is specified.
	// If the options are validated then it returns:
	// 	(resultant_VolumeSpec, source, nil)
	// If the options have invalid values then it returns:
	//	(nil, nil, error)

	SpecFromOpts(opts map[string]string) (*api.VolumeSpec, *api.Source, error)
	// Returns a default VolumeSpec if no docker options or string encoding
	// was provided.
	DefaultSpec() *api.VolumeSpec
}

var (
	nameRegex       = regexp.MustCompile(api.Name + "=([0-9A-Za-z_-]+),?")
	sizeRegex       = regexp.MustCompile(api.SpecSize + "=([0-9A-Za-z]+),?")
	scaleRegex      = regexp.MustCompile(api.SpecScale + "=([0-9]+),?")
	fsRegex         = regexp.MustCompile(api.SpecFilesystem + "=([0-9A-Za-z]+),?")
	bsRegex         = regexp.MustCompile(api.SpecBlockSize + "=([0-9]+),?")
	haRegex         = regexp.MustCompile(api.SpecHaLevel + "=([0-9]+),?")
	cosRegex        = regexp.MustCompile(api.SpecPriority + "=([A-Za-z]+),?")
	sharedRegex     = regexp.MustCompile(api.SpecShared + "=([A-Za-z]+),?")
	passphraseRegex = regexp.MustCompile(api.SpecPassphrase + "=([0-9A-Za-z_@./#&+-]+),?")
	stickyRegex     = regexp.MustCompile(api.SpecSticky + "=([A-Za-z]+),?")
)

type specHandler struct {
}

// NewSpecHandler returns a new SpecHandler interface
func NewSpecHandler() SpecHandler {
	return &specHandler{}
}

func (d *specHandler) cosLevel(cos string) (uint32, error) {
	switch cos {
	case "high", "3":
		return uint32(api.CosType_HIGH), nil
	case "medium", "2":
		return uint32(api.CosType_MEDIUM), nil
	case "low", "1", "":
		return uint32(api.CosType_LOW), nil
	}
	return uint32(api.CosType_LOW),
		fmt.Errorf("Cos must be one of %q | %q | %q", "high", "medium", "low")
}

func (d *specHandler) getVal(r *regexp.Regexp, str string) (bool, string) {
	found := r.FindString(str)
	if found == "" {
		return false, ""
	}

	submatches := r.FindStringSubmatch(str)
	if len(submatches) < 2 {
		return false, ""
	}

	val := submatches[1]

	return true, val
}

func (d *specHandler) DefaultSpec() *api.VolumeSpec {
	return &api.VolumeSpec{
		VolumeLabels: make(map[string]string),
		Format:       api.FSType_FS_TYPE_EXT4,
		HaLevel:      1,
	}
}

func (d *specHandler) SpecFromOpts(
	opts map[string]string,
) (*api.VolumeSpec, *api.Source, error) {
	var source *api.Source
	spec := d.DefaultSpec()

	for k, v := range opts {
		switch k {
		case api.SpecParent:
			source = &api.Source{Parent: v}
		case api.SpecEphemeral:
			spec.Ephemeral, _ = strconv.ParseBool(v)
		case api.SpecSize:
			if size, err := units.Parse(v); err != nil {
				return nil, nil, err
			} else {
				spec.Size = uint64(size)
			}
		case api.SpecScale:
			if scale, err := strconv.ParseUint(v, 10, 64); err == nil {
				spec.Scale = uint32(scale)
			}

		case api.SpecFilesystem:
			if value, err := api.FSTypeSimpleValueOf(v); err != nil {
				return nil, nil, err
			} else {
				spec.Format = value
			}
		case api.SpecBlockSize:
			if blockSize, err := units.Parse(v); err != nil {
				return nil, nil, err
			} else {
				spec.BlockSize = blockSize
			}
		case api.SpecHaLevel:
			haLevel, _ := strconv.ParseInt(v, 10, 64)
			spec.HaLevel = haLevel
		case api.SpecPriority:
			cos, err := api.CosTypeSimpleValueOf(v)
			if err != nil {
				return nil, nil, err
			}
			spec.Cos = cos
		case api.SpecDedupe:
			spec.Dedupe, _ = strconv.ParseBool(v)
		case api.SpecSnapshotInterval:
			snapshotInterval, _ := strconv.ParseUint(v, 10, 32)
			spec.SnapshotInterval = uint32(snapshotInterval)
		case api.SpecAggregationLevel:
			if v == api.SpecAutoAggregationValue {
				spec.AggregationLevel = api.AutoAggregation
			} else {
				aggregationLevel, _ := strconv.ParseUint(v, 10, 32)
				spec.AggregationLevel = uint32(aggregationLevel)
			}
		case api.SpecShared:
			if shared, err := strconv.ParseBool(v); err != nil {
				return nil, nil, err
			} else {
				spec.Shared = shared
			}
		case api.SpecSticky:
			if sticky, err := strconv.ParseBool(v); err != nil {
				return nil, nil, err
			} else {
				spec.Sticky = sticky
			}
		case api.SpecPassphrase:
			spec.Encrypted = true
			spec.Passphrase = v
		case api.SpecGroup:
			spec.Group = &api.Group{Id: v}
		case api.SpecGroupEnforce:
			if groupEnforced, err := strconv.ParseBool(v); err != nil {
				return nil, nil, err
			} else {
				spec.GroupEnforced = groupEnforced
			}
		default:
			spec.VolumeLabels[k] = v
		}
	}
	return spec, source, nil
}

func (d *specHandler) SpecFromString(
	str string,
) (bool, *api.VolumeSpec, *api.Source, string) {
	// If we can't parse the name, the rest of the spec is invalid.
	ok, name := d.getVal(nameRegex, str)
	if !ok {
		return false, d.DefaultSpec(), nil, str
	}

	opts := make(map[string]string)

	if ok, sz := d.getVal(sizeRegex, str); ok {
		opts[api.SpecSize] = sz
	}
	if ok, scale := d.getVal(scaleRegex, str); ok {
		opts[api.SpecScale] = scale
	}
	if ok, fs := d.getVal(fsRegex, str); ok {
		opts[api.SpecFilesystem] = fs
	}
	if ok, bs := d.getVal(bsRegex, str); ok {
		opts[api.SpecBlockSize] = bs
	}
	if ok, ha := d.getVal(haRegex, str); ok {
		opts[api.SpecHaLevel] = ha
	}
	if ok, priority := d.getVal(cosRegex, str); ok {
		opts[api.SpecPriority] = priority
	}
	if ok, shared := d.getVal(sharedRegex, str); ok {
		opts[api.SpecShared] = shared
	}
	if ok, sticky := d.getVal(stickyRegex, str); ok {
		opts[api.SpecSticky] = sticky
	}
	if ok, passphrase := d.getVal(passphraseRegex, str); ok {
		opts[api.SpecPassphrase] = passphrase
	}

	spec, source, err := d.SpecFromOpts(opts)
	if err != nil {
		return false, d.DefaultSpec(), nil, name
	}
	return true, spec, source, name
}
<commit_msg>Allow strings as well as numeric values for IO priority in spec handler<commit_after>package spec

import (
	"fmt"
	"regexp"
	"strconv"
	"strings"

	"github.com/libopenstorage/openstorage/api"
	"github.com/libopenstorage/openstorage/pkg/units"
)

// SpecHandler provides conversion function from what gets passed in over the
// plugin API to an api.VolumeSpec object.
type SpecHandler interface {
	// SpecFromString parses options from the name.
	// If the scheduler was unable to pass in the volume spec via the API,
	// the spec can be passed in via the name in the format:
	// "key=value;key=value;name=volname"
	// source is populated if key parent=<volume_id> is specified.
	// If the spec was parsed, it returns:
	//  	(true, parsed_spec, source, parsed_name)
	// If the input string didn't contain the string, it returns:
	// 	(false, DefaultSpec(), nil, inputString)
	SpecFromString(inputString string) (bool, *api.VolumeSpec, *api.Source, string)

	// SpecFromOpts parses in docker options passed in the the docker run
	// command of the form --opt name=value
	// source is populated if --opt parent=<volume_id> is specified.
	// If the options are validated then it returns:
	// 	(resultant_VolumeSpec, source, nil)
	// If the options have invalid values then it returns:
	//	(nil, nil, error)

	SpecFromOpts(opts map[string]string) (*api.VolumeSpec, *api.Source, error)
	// Returns a default VolumeSpec if no docker options or string encoding
	// was provided.
	DefaultSpec() *api.VolumeSpec
}

var (
	nameRegex       = regexp.MustCompile(api.Name + "=([0-9A-Za-z_-]+),?")
	sizeRegex       = regexp.MustCompile(api.SpecSize + "=([0-9A-Za-z]+),?")
	scaleRegex      = regexp.MustCompile(api.SpecScale + "=([0-9]+),?")
	fsRegex         = regexp.MustCompile(api.SpecFilesystem + "=([0-9A-Za-z]+),?")
	bsRegex         = regexp.MustCompile(api.SpecBlockSize + "=([0-9]+),?")
	haRegex         = regexp.MustCompile(api.SpecHaLevel + "=([0-9]+),?")
	cosRegex        = regexp.MustCompile(api.SpecPriority + "=([A-Za-z]+),?")
	sharedRegex     = regexp.MustCompile(api.SpecShared + "=([A-Za-z]+),?")
	passphraseRegex = regexp.MustCompile(api.SpecPassphrase + "=([0-9A-Za-z_@./#&+-]+),?")
	stickyRegex     = regexp.MustCompile(api.SpecSticky + "=([A-Za-z]+),?")
)

type specHandler struct {
}

// NewSpecHandler returns a new SpecHandler interface
func NewSpecHandler() SpecHandler {
	return &specHandler{}
}

func (d *specHandler) cosLevel(cos string) (api.CosType, error) {
	cos = strings.ToLower(cos)
	switch cos {
	case "high", "3":
		return api.CosType_HIGH, nil
	case "medium", "2":
		return api.CosType_MEDIUM, nil
	case "low", "1", "":
		return api.CosType_LOW, nil
	}
	return api.CosType_NONE,
		fmt.Errorf("Cos must be one of %q | %q | %q", "high", "medium", "low")
}

func (d *specHandler) getVal(r *regexp.Regexp, str string) (bool, string) {
	found := r.FindString(str)
	if found == "" {
		return false, ""
	}

	submatches := r.FindStringSubmatch(str)
	if len(submatches) < 2 {
		return false, ""
	}

	val := submatches[1]

	return true, val
}

func (d *specHandler) DefaultSpec() *api.VolumeSpec {
	return &api.VolumeSpec{
		VolumeLabels: make(map[string]string),
		Format:       api.FSType_FS_TYPE_EXT4,
		HaLevel:      1,
	}
}

func (d *specHandler) SpecFromOpts(
	opts map[string]string,
) (*api.VolumeSpec, *api.Source, error) {
	var source *api.Source
	spec := d.DefaultSpec()

	for k, v := range opts {
		switch k {
		case api.SpecParent:
			source = &api.Source{Parent: v}
		case api.SpecEphemeral:
			spec.Ephemeral, _ = strconv.ParseBool(v)
		case api.SpecSize:
			if size, err := units.Parse(v); err != nil {
				return nil, nil, err
			} else {
				spec.Size = uint64(size)
			}
		case api.SpecScale:
			if scale, err := strconv.ParseUint(v, 10, 64); err == nil {
				spec.Scale = uint32(scale)
			}

		case api.SpecFilesystem:
			if value, err := api.FSTypeSimpleValueOf(v); err != nil {
				return nil, nil, err
			} else {
				spec.Format = value
			}
		case api.SpecBlockSize:
			if blockSize, err := units.Parse(v); err != nil {
				return nil, nil, err
			} else {
				spec.BlockSize = blockSize
			}
		case api.SpecHaLevel:
			haLevel, _ := strconv.ParseInt(v, 10, 64)
			spec.HaLevel = haLevel
		case api.SpecPriority:
			cos, err := d.cosLevel(v)
			if err != nil {
				return nil, nil, err
			}
			spec.Cos = cos
		case api.SpecDedupe:
			spec.Dedupe, _ = strconv.ParseBool(v)
		case api.SpecSnapshotInterval:
			snapshotInterval, _ := strconv.ParseUint(v, 10, 32)
			spec.SnapshotInterval = uint32(snapshotInterval)
		case api.SpecAggregationLevel:
			if v == api.SpecAutoAggregationValue {
				spec.AggregationLevel = api.AutoAggregation
			} else {
				aggregationLevel, _ := strconv.ParseUint(v, 10, 32)
				spec.AggregationLevel = uint32(aggregationLevel)
			}
		case api.SpecShared:
			if shared, err := strconv.ParseBool(v); err != nil {
				return nil, nil, err
			} else {
				spec.Shared = shared
			}
		case api.SpecSticky:
			if sticky, err := strconv.ParseBool(v); err != nil {
				return nil, nil, err
			} else {
				spec.Sticky = sticky
			}
		case api.SpecPassphrase:
			spec.Encrypted = true
			spec.Passphrase = v
		case api.SpecGroup:
			spec.Group = &api.Group{Id: v}
		case api.SpecGroupEnforce:
			if groupEnforced, err := strconv.ParseBool(v); err != nil {
				return nil, nil, err
			} else {
				spec.GroupEnforced = groupEnforced
			}
		default:
			spec.VolumeLabels[k] = v
		}
	}
	return spec, source, nil
}

func (d *specHandler) SpecFromString(
	str string,
) (bool, *api.VolumeSpec, *api.Source, string) {
	// If we can't parse the name, the rest of the spec is invalid.
	ok, name := d.getVal(nameRegex, str)
	if !ok {
		return false, d.DefaultSpec(), nil, str
	}

	opts := make(map[string]string)

	if ok, sz := d.getVal(sizeRegex, str); ok {
		opts[api.SpecSize] = sz
	}
	if ok, scale := d.getVal(scaleRegex, str); ok {
		opts[api.SpecScale] = scale
	}
	if ok, fs := d.getVal(fsRegex, str); ok {
		opts[api.SpecFilesystem] = fs
	}
	if ok, bs := d.getVal(bsRegex, str); ok {
		opts[api.SpecBlockSize] = bs
	}
	if ok, ha := d.getVal(haRegex, str); ok {
		opts[api.SpecHaLevel] = ha
	}
	if ok, priority := d.getVal(cosRegex, str); ok {
		opts[api.SpecPriority] = priority
	}
	if ok, shared := d.getVal(sharedRegex, str); ok {
		opts[api.SpecShared] = shared
	}
	if ok, sticky := d.getVal(stickyRegex, str); ok {
		opts[api.SpecSticky] = sticky
	}
	if ok, passphrase := d.getVal(passphraseRegex, str); ok {
		opts[api.SpecPassphrase] = passphrase
	}

	spec, source, err := d.SpecFromOpts(opts)
	if err != nil {
		return false, d.DefaultSpec(), nil, name
	}
	return true, spec, source, name
}
<|endoftext|>


<commit_before>package main_test

import (
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/LewisWatson/carshare-back/model"
	"github.com/LewisWatson/carshare-back/resource"
	"github.com/LewisWatson/carshare-back/storage"
	"github.com/manyminds/api2go"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

// there are a lot of functions because each test can be run individually and sets up the complete
// environment. That is because we run all the specs randomized.
var _ = Describe("CrudExample", func() {
	var rec *httptest.ResponseRecorder

	BeforeEach(func() {
		api = api2go.NewAPIWithBaseURL("v0", "http://localhost:31415")
		tripStorage := storage.NewTripStorage()
		userStorage := storage.NewUserStorage()
		carShareStorage := storage.NewCarShareStorage()
		api.AddResource(model.User{}, resource.UserResource{UserStorage: userStorage})
		api.AddResource(model.Trip{}, resource.TripResource{TripStorage: tripStorage})
		api.AddResource(model.CarShare{}, resource.CarShareResource{CarShareStorage: carShareStorage, TripStorage: tripStorage, UserStorage: userStorage})
		rec = httptest.NewRecorder()
	})

	var createUser = func() {
		rec = httptest.NewRecorder()
		req, err := http.NewRequest("POST", "/v0/users", strings.NewReader(`
		{
			"data": {
				"type": "users",
				"attributes": {
					"user-name": "marvin"
				}
			}
		}
		`))
		Expect(err).ToNot(HaveOccurred())
		api.Handler().ServeHTTP(rec, req)
		Expect(rec.Code).To(Equal(http.StatusCreated))
		Expect(rec.Body.String()).To(MatchJSON(`
    {
      "data": {
        "type": "users",
        "id": "1",
        "attributes": {
          "user-name": "marvin"
        }
      },
      "meta": {
        "author": "Lewis Watson"
      }
    }
		`))
	}

	It("Creates a new user", func() {
		createUser()
	})

	var createCarShare = func() {
		rec = httptest.NewRecorder()
		req, err := http.NewRequest("POST", "/v0/carShares", strings.NewReader(`
		{
			"data": {
				"type": "carShares",
				"attributes": {
					"name": "carShare1",
					"metres": 1000
				}
			}
		}
		`))
		Expect(err).ToNot(HaveOccurred())
		api.Handler().ServeHTTP(rec, req)
		Expect(rec.Code).To(Equal(http.StatusCreated))
		Expect(rec.Body.String()).To(MatchJSON(`
			{
       "data": {
         "type": "carShares",
         "id": "1",
         "attributes": {
           "name": "carShare1",
           "metres": 1000
         },
         "relationships": {
           "trips": {
             "links": {
               "self": "http://localhost:31415/v0/carShares/1/relationships/trips",
               "related": "http://localhost:31415/v0/carShares/1/trips"
             },
             "data": []
           }
         }
       },
       "meta": {
         "author": "Lewis Watson"
       }
     }
		`))
	}

	It("Creates a new car share", func() {
		createCarShare()
	})

	var createTrip = func() {
		rec = httptest.NewRecorder()
		req, err := http.NewRequest("POST", "/v0/trips", strings.NewReader(`
		{
			"data": {
				"type": "trips",
				"attributes": {
					"meters-as-driver": 1000,
					"meters-as-passenger": 1000
				}
			}
		}
		`))
		Expect(err).ToNot(HaveOccurred())
		api.Handler().ServeHTTP(rec, req)
		Expect(rec.Code).To(Equal(http.StatusCreated))
		Expect(rec.Body.String()).To(MatchJSON(`
			{
        "data": {
          "type": "trips",
          "id": "1",
          "attributes": {
            "meters-as-driver": 1000,
            "meters-as-passenger": 1000
          }
        },
        "meta": {
          "author": "Lewis Watson"
        }
      }
		`))
	}

	It("Creates a trip", func() {
		createTrip()
	})

	It("Adds a trip to a car share", func() {
		createUser()
		createCarShare()
		createTrip()
		rec = httptest.NewRecorder()

		By("Adding a trip with POST")

		req, err := http.NewRequest("POST", "/v0/carShares/1/relationships/trips", strings.NewReader(`
		{
			"data": [{
				"type": "trips",
				"id": "1"
			}]
		}
		`))
		Expect(err).ToNot(HaveOccurred())
		api.Handler().ServeHTTP(rec, req)
		Expect(rec.Code).To(Equal(http.StatusNoContent))

		By("Loading the car share from the backend, it should have the trip")

		rec = httptest.NewRecorder()
		req, err = http.NewRequest("GET", "/v0/carShares/1", nil)
		api.Handler().ServeHTTP(rec, req)
		Expect(err).ToNot(HaveOccurred())
		Expect(rec.Body.String()).To(MatchJSON(`
			{
			  "data": {
			    "type": "carShares",
			    "id": "1",
			    "attributes": {
			      "name": "carShare1",
			      "metres": 1000
			    },
			    "relationships": {
			      "trips": {
			        "links": {
			          "self": "http://localhost:31415/v0/carShares/1/relationships/trips",
			          "related": "http://localhost:31415/v0/carShares/1/trips"
			        },
			        "data": [
			          {
			            "type": "trips",
			            "id": "1"
			          }
			        ]
			      }
			    }
			  },
			  "included": [
			    {
			      "type": "trips",
			      "id": "1",
			      "attributes": {
			        "meters-as-driver": 1000,
			        "meters-as-passenger": 1000
			      }
			    }
			  ],
			  "meta": {
			    "author": "Lewis Watson"
			  }
			}
		`))
	})

	var replaceTrips = func() {
		rec = httptest.NewRecorder()
		By("Replacing trip relationship with PATCH")

		req, err := http.NewRequest("PATCH", "/v0/carShares/1/relationships/trips", strings.NewReader(`
		{
			"data": [{
				"type": "trips",
				"id": "1"
			}]
		}
		`))
		Expect(err).ToNot(HaveOccurred())
		api.Handler().ServeHTTP(rec, req)
		Expect(rec.Code).To(Equal(http.StatusNoContent))

		By("Loading the car share from the backend, it should have the relationship")

		rec = httptest.NewRecorder()
		req, err = http.NewRequest("GET", "/v0/carShares/1", nil)
		api.Handler().ServeHTTP(rec, req)
		Expect(err).ToNot(HaveOccurred())
		Expect(rec.Body.String()).To(MatchJSON(`
			{
			  "data": {
			    "type": "carShares",
			    "id": "1",
			    "attributes": {
			      "name": "carShare1",
			      "metres": 1000
			    },
			    "relationships": {
			      "trips": {
			        "links": {
			          "self": "http://localhost:31415/v0/carShares/1/relationships/trips",
			          "related": "http://localhost:31415/v0/carShares/1/trips"
			        },
			        "data": [
			          {
			            "type": "trips",
			            "id": "1"
			          }
			        ]
			      }
			    }
			  },
			  "included": [
			    {
			      "type": "trips",
			      "id": "1",
			      "attributes": {
			        "meters-as-driver": 1000,
			        "meters-as-passenger": 1000
			      }
			    }
			  ],
			  "meta": {
			    "author": "Lewis Watson"
			  }
			}
		`))
	}

	It("Replaces car share's trips", func() {
		createUser()
		createCarShare()
		createTrip()
		replaceTrips()
	})

	It("Deletes a car share trip", func() {
		createUser()
		createCarShare()
		createTrip()
		replaceTrips()
		rec = httptest.NewRecorder()

		By("Deleting the car shares only trip with ID 1")

		req, err := http.NewRequest("DELETE", "/v0/carShares/1/relationships/trips", strings.NewReader(`
		{
			"data": [{
				"type": "trips",
				"id": "1"
			}]
		}
		`))
		Expect(err).ToNot(HaveOccurred())
		api.Handler().ServeHTTP(rec, req)
		Expect(rec.Code).To(Equal(http.StatusNoContent))

		By("Loading the car share from the backend, it should not have any relations")

		rec = httptest.NewRecorder()
		req, err = http.NewRequest("GET", "/v0/carShares/1", nil)
		api.Handler().ServeHTTP(rec, req)
		Expect(err).ToNot(HaveOccurred())
		Expect(rec.Body.String()).To(MatchJSON(`
			{
			  "data": {
			    "type": "carShares",
			    "id": "1",
			    "attributes": {
			      "name": "carShare1",
			      "metres": 1000
			    },
			    "relationships": {
			      "trips": {
			        "links": {
			          "self": "http://localhost:31415/v0/carShares/1/relationships/trips",
			          "related": "http://localhost:31415/v0/carShares/1/trips"
			        },
			        "data": []
			      }
			    }
			  },
			  "meta": {
			    "author": "Lewis Watson"
			  }
			}
		`))
	})
})
<commit_msg>convert spaces to tabs<commit_after>package main_test

import (
	"net/http"
	"net/http/httptest"
	"strings"

	"github.com/LewisWatson/carshare-back/model"
	"github.com/LewisWatson/carshare-back/resource"
	"github.com/LewisWatson/carshare-back/storage"
	"github.com/manyminds/api2go"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
)

// there are a lot of functions because each test can be run individually and sets up the complete
// environment. That is because we run all the specs randomized.
var _ = Describe("CrudExample", func() {
	var rec *httptest.ResponseRecorder

	BeforeEach(func() {
		api = api2go.NewAPIWithBaseURL("v0", "http://localhost:31415")
		tripStorage := storage.NewTripStorage()
		userStorage := storage.NewUserStorage()
		carShareStorage := storage.NewCarShareStorage()
		api.AddResource(model.User{}, resource.UserResource{UserStorage: userStorage})
		api.AddResource(model.Trip{}, resource.TripResource{TripStorage: tripStorage})
		api.AddResource(model.CarShare{}, resource.CarShareResource{CarShareStorage: carShareStorage, TripStorage: tripStorage, UserStorage: userStorage})
		rec = httptest.NewRecorder()
	})

	var createUser = func() {
		rec = httptest.NewRecorder()
		req, err := http.NewRequest("POST", "/v0/users", strings.NewReader(`
		{
			"data": {
				"type": "users",
				"attributes": {
					"user-name": "marvin"
				}
			}
		}
		`))
		Expect(err).ToNot(HaveOccurred())
		api.Handler().ServeHTTP(rec, req)
		Expect(rec.Code).To(Equal(http.StatusCreated))
		Expect(rec.Body.String()).To(MatchJSON(`
		{
			"data": {
				"type": "users",
				"id": "1",
				"attributes": {
					"user-name": "marvin"
				}
			},
			"meta": {
				"author": "Lewis Watson"
			}
		}
		`))
	}

	It("Creates a new user", func() {
		createUser()
	})

	var createCarShare = func() {
		rec = httptest.NewRecorder()
		req, err := http.NewRequest("POST", "/v0/carShares", strings.NewReader(`
		{
			"data": {
				"type": "carShares",
				"attributes": {
					"name": "carShare1",
					"metres": 1000
				}
			}
		}
		`))
		Expect(err).ToNot(HaveOccurred())
		api.Handler().ServeHTTP(rec, req)
		Expect(rec.Code).To(Equal(http.StatusCreated))
		Expect(rec.Body.String()).To(MatchJSON(`
		{
			 "data": {
				 "type": "carShares",
				 "id": "1",
				 "attributes": {
					 "name": "carShare1",
					 "metres": 1000
				 },
				 "relationships": {
					 "trips": {
						 "links": {
							 "self": "http://localhost:31415/v0/carShares/1/relationships/trips",
							 "related": "http://localhost:31415/v0/carShares/1/trips"
						 },
						 "data": []
					 }
				 }
			 },
			 "meta": {
				 "author": "Lewis Watson"
			 }
		 }
		`))
	}

	It("Creates a new car share", func() {
		createCarShare()
	})

	var createTrip = func() {
		rec = httptest.NewRecorder()
		req, err := http.NewRequest("POST", "/v0/trips", strings.NewReader(`
		{
			"data": {
				"type": "trips",
				"attributes": {
					"meters-as-driver": 1000,
					"meters-as-passenger": 1000
				}
			}
		}
		`))
		Expect(err).ToNot(HaveOccurred())
		api.Handler().ServeHTTP(rec, req)
		Expect(rec.Code).To(Equal(http.StatusCreated))
		Expect(rec.Body.String()).To(MatchJSON(`
		{
			"data": {
				"type": "trips",
				"id": "1",
				"attributes": {
					"meters-as-driver": 1000,
					"meters-as-passenger": 1000
				}
			},
			"meta": {
				"author": "Lewis Watson"
			}
		}
		`))
	}

	It("Creates a trip", func() {
		createTrip()
	})

	It("Adds a trip to a car share", func() {
		createUser()
		createCarShare()
		createTrip()
		rec = httptest.NewRecorder()

		By("Adding a trip with POST")

		req, err := http.NewRequest("POST", "/v0/carShares/1/relationships/trips", strings.NewReader(`
		{
			"data": [{
				"type": "trips",
				"id": "1"
			}]
		}
		`))
		Expect(err).ToNot(HaveOccurred())
		api.Handler().ServeHTTP(rec, req)
		Expect(rec.Code).To(Equal(http.StatusNoContent))

		By("Loading the car share from the backend, it should have the trip")

		rec = httptest.NewRecorder()
		req, err = http.NewRequest("GET", "/v0/carShares/1", nil)
		api.Handler().ServeHTTP(rec, req)
		Expect(err).ToNot(HaveOccurred())
		Expect(rec.Body.String()).To(MatchJSON(`
		{
			"data": {
				"type": "carShares",
				"id": "1",
				"attributes": {
					"name": "carShare1",
					"metres": 1000
				},
				"relationships": {
					"trips": {
						"links": {
							"self": "http://localhost:31415/v0/carShares/1/relationships/trips",
							"related": "http://localhost:31415/v0/carShares/1/trips"
						},
						"data": [
							{
								"type": "trips",
								"id": "1"
							}
						]
					}
				}
			},
			"included": [
				{
					"type": "trips",
					"id": "1",
					"attributes": {
						"meters-as-driver": 1000,
						"meters-as-passenger": 1000
					}
				}
			],
			"meta": {
				"author": "Lewis Watson"
			}
		}
		`))
	})

	var replaceTrips = func() {
		rec = httptest.NewRecorder()
		By("Replacing trip relationship with PATCH")

		req, err := http.NewRequest("PATCH", "/v0/carShares/1/relationships/trips", strings.NewReader(`
		{
			"data": [{
				"type": "trips",
				"id": "1"
			}]
		}
		`))
		Expect(err).ToNot(HaveOccurred())
		api.Handler().ServeHTTP(rec, req)
		Expect(rec.Code).To(Equal(http.StatusNoContent))

		By("Loading the car share from the backend, it should have the relationship")

		rec = httptest.NewRecorder()
		req, err = http.NewRequest("GET", "/v0/carShares/1", nil)
		api.Handler().ServeHTTP(rec, req)
		Expect(err).ToNot(HaveOccurred())
		Expect(rec.Body.String()).To(MatchJSON(`
		{
			"data": {
				"type": "carShares",
				"id": "1",
				"attributes": {
					"name": "carShare1",
					"metres": 1000
				},
				"relationships": {
					"trips": {
						"links": {
							"self": "http://localhost:31415/v0/carShares/1/relationships/trips",
							"related": "http://localhost:31415/v0/carShares/1/trips"
						},
						"data": [
							{
								"type": "trips",
								"id": "1"
							}
						]
					}
				}
			},
			"included": [
				{
					"type": "trips",
					"id": "1",
					"attributes": {
						"meters-as-driver": 1000,
						"meters-as-passenger": 1000
					}
				}
			],
			"meta": {
				"author": "Lewis Watson"
			}
		}
		`))
	}

	It("Replaces car share's trips", func() {
		createUser()
		createCarShare()
		createTrip()
		replaceTrips()
	})

	It("Deletes a car share trip", func() {
		createUser()
		createCarShare()
		createTrip()
		replaceTrips()
		rec = httptest.NewRecorder()

		By("Deleting the car shares only trip with ID 1")

		req, err := http.NewRequest("DELETE", "/v0/carShares/1/relationships/trips", strings.NewReader(`
		{
			"data": [{
				"type": "trips",
				"id": "1"
			}]
		}
		`))
		Expect(err).ToNot(HaveOccurred())
		api.Handler().ServeHTTP(rec, req)
		Expect(rec.Code).To(Equal(http.StatusNoContent))

		By("Loading the car share from the backend, it should not have any relations")

		rec = httptest.NewRecorder()
		req, err = http.NewRequest("GET", "/v0/carShares/1", nil)
		api.Handler().ServeHTTP(rec, req)
		Expect(err).ToNot(HaveOccurred())
		Expect(rec.Body.String()).To(MatchJSON(`
		{
			"data": {
				"type": "carShares",
				"id": "1",
				"attributes": {
					"name": "carShare1",
					"metres": 1000
				},
				"relationships": {
					"trips": {
						"links": {
							"self": "http://localhost:31415/v0/carShares/1/relationships/trips",
							"related": "http://localhost:31415/v0/carShares/1/trips"
						},
						"data": []
					}
				}
			},
			"meta": {
				"author": "Lewis Watson"
			}
		}
		`))
	})
})
<|endoftext|>