<commit_before>package main

import (
	"bufio"
	"bytes"
	"context"
	"encoding/json"
	"mime"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"time"

	"github.com/ONSdigital/florence/assets"
	"github.com/ONSdigital/florence/config"
	"github.com/ONSdigital/florence/healthcheck"
	"github.com/ONSdigital/florence/upload"
	"github.com/ONSdigital/go-ns/handlers/reverseProxy"
	hc "github.com/ONSdigital/go-ns/healthcheck"
	"github.com/ONSdigital/go-ns/log"
	"github.com/ONSdigital/go-ns/server"
	"github.com/ONSdigital/go-ns/vault"
	"github.com/gorilla/pat"
	"github.com/gorilla/websocket"
)

var getAsset = assets.Asset
var upgrader = websocket.Upgrader{}

// Version is set by the make target
var Version string

func main() {
	log.Debug("florence version", log.Data{"version": Version})

	cfg, err := config.Get()
	if err != nil {
		log.Error(err, nil)
		os.Exit(1)
	}

	log.Namespace = "florence"

	zc := healthcheck.New(cfg.ZebedeeURL, "zebedee")
	rtrc := healthcheck.New(cfg.RouterURL, "router")
	dc := healthcheck.New(cfg.DatasetAPIURL, "dataset-api")
	rc := healthcheck.New(cfg.RecipeAPIURL, "recipe-api")
	ic := healthcheck.New(cfg.ImportAPIURL, "import-api")

	/*
		NOTE:
		If there's any issues with this Florence server proxying redirects
		from either Babbage or Zebedee then the code in the previous Java
		Florence server might give some clues for a solution: https://github.com/ONSdigital/florence/blob/b13df0708b30493b98e9ce239103c59d7f409f98/src/main/java/com/github/onsdigital/florence/filter/Proxy.java#L125-L135

		The code has purposefully not been included in this Go replacement
		because we can't see what issue it's fixing and whether it's necessary.
	*/

	routerURL, err := url.Parse(cfg.RouterURL)
	if err != nil {
		log.Error(err, nil)
		os.Exit(1)
	}
	routerProxy := reverseProxy.Create(routerURL, director)

	zebedeeURL, err := url.Parse(cfg.ZebedeeURL)
	if err != nil {
		log.Error(err, nil)
		os.Exit(1)
	}
	zebedeeProxy := reverseProxy.Create(zebedeeURL, zebedeeDirector)

	recipeAPIURL, err := url.Parse(cfg.RecipeAPIURL)
	if err != nil {
		log.Error(err, nil)
		os.Exit(1)
	}
	recipeAPIProxy := reverseProxy.Create(recipeAPIURL, nil)

	tableURL, err := url.Parse(cfg.TableRendererURL)
	if err != nil {
		log.Error(err, nil)
		os.Exit(1)
	}
	tableProxy := reverseProxy.Create(tableURL, tableDirector)

	importAPIURL, err := url.Parse(cfg.ImportAPIURL)
	if err != nil {
		log.Error(err, nil)
		os.Exit(1)
	}
	importAPIProxy := reverseProxy.Create(importAPIURL, importAPIDirector)

	datasetAPIURL, err := url.Parse(cfg.DatasetAPIURL)
	if err != nil {
		log.Error(err, nil)
		os.Exit(1)
	}
	datasetAPIProxy := reverseProxy.Create(datasetAPIURL, datasetAPIDirector)

	router := pat.New()

	var vc upload.VaultClient
	if !cfg.EncryptionDisabled {
		vc, err = vault.CreateVaultClient(cfg.VaultToken, cfg.VaultAddr, 3)
		if err != nil {
			log.Error(err, nil)
			os.Exit(1)
		}
	}

	uploader, err := upload.New(cfg.UploadBucketName, cfg.VaultPath, vc)
	if err != nil {
		log.Error(err, nil)
		os.Exit(1)
	}

	router.Path("/healthcheck").HandlerFunc(hc.Do)

	router.Path("/upload").Methods("GET").HandlerFunc(uploader.CheckUploaded)
	router.Path("/upload").Methods("POST").HandlerFunc(uploader.Upload)
	router.Path("/upload/{id}").Methods("GET").HandlerFunc(uploader.GetS3URL)

	router.Handle("/zebedee{uri:/.*}", zebedeeProxy)
	router.Handle("/recipes{uri:.*}", recipeAPIProxy)
	router.Handle("/import{uri:.*}", importAPIProxy)
	router.Handle("/dataset/{uri:.*}", datasetAPIProxy)
	router.Handle("/instances/{uri:.*}", datasetAPIProxy)
	router.Handle("/table/{uri:.*}", tableProxy)
	router.HandleFunc("/florence/dist/{uri:.*}", staticFiles)
	router.HandleFunc("/florence/", redirectToFlorence)
	router.HandleFunc("/florence/index.html", redirectToFlorence)
	router.HandleFunc("/florence/publishing-queue", legacyIndexFile)
	router.HandleFunc("/florence/reports", legacyIndexFile)
	router.HandleFunc("/florence/users-and-access", legacyIndexFile)
	router.HandleFunc("/florence/workspace", legacyIndexFile)
	router.HandleFunc("/florence/websocket", websocketHandler)
	router.HandleFunc("/florence{uri:/.*}", refactoredIndexFile)
	router.Handle("/{uri:.*}", routerProxy)

	log.Debug("Starting server", log.Data{"config": cfg})

	s := server.New(cfg.BindAddr, router)
	// TODO need to reconsider default go-ns server timeouts
	s.Server.IdleTimeout = 120 * time.Second
	s.Server.WriteTimeout = 120 * time.Second
	s.Server.ReadTimeout = 30 * time.Second
	s.HandleOSSignals = false
	s.MiddlewareOrder = []string{"RequestID", "Log"}

	// FIXME temporary hack to remove timeout middleware (doesn't support hijacker interface)
	mo := s.MiddlewareOrder
	var newMo []string
	for _, mw := range mo {
		if mw != "Timeout" {
			newMo = append(newMo, mw)
		}
	}
	s.MiddlewareOrder = newMo

	go func() {
		if err := s.ListenAndServe(); err != nil {
			log.Error(err, nil)
			os.Exit(2)
		}
	}()

	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt, os.Kill)

	for {
		hc.MonitorExternal(rtrc, zc, ic, rc, dc)

		timer := time.NewTimer(time.Second * 60)

		select {
		case <-timer.C:
			continue
		case <-stop:
			log.Info("shutting service down gracefully", nil)
			timer.Stop()
			ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
			defer cancel()
			if err := s.Server.Shutdown(ctx); err != nil {
				log.Error(err, nil)
			}
			return
		}
	}
}

func redirectToFlorence(w http.ResponseWriter, req *http.Request) {
	http.Redirect(w, req, "/florence", 301)
}

func staticFiles(w http.ResponseWriter, req *http.Request) {
	path := req.URL.Query().Get(":uri")

	b, err := getAsset("../dist/" + path)
	if err != nil {
		log.Error(err, nil)
		w.WriteHeader(404)
		return
	}

	w.Header().Set(`Content-Type`, mime.TypeByExtension(filepath.Ext(path)))
	w.WriteHeader(200)
	w.Write(b)
}

func legacyIndexFile(w http.ResponseWriter, req *http.Request) {
	log.Debug("Getting legacy HTML file", nil)

	b, err := getAsset("../dist/legacy-assets/index.html")
	if err != nil {
		log.Error(err, nil)
		w.WriteHeader(404)
		return
	}

	w.Header().Set(`Content-Type`, "text/html")
	w.WriteHeader(200)
	w.Write(b)
}

func refactoredIndexFile(w http.ResponseWriter, req *http.Request) {
	log.Debug("Getting refactored HTML file", nil)

	b, err := getAsset("../dist/refactored.html")
	if err != nil {
		log.Error(err, nil)
		w.WriteHeader(404)
		return
	}

	w.Header().Set(`Content-Type`, "text/html")
	w.WriteHeader(200)
	w.Write(b)
}

func zebedeeDirector(req *http.Request) {
	if c, err := req.Cookie(`access_token`); err == nil && len(c.Value) > 0 {
		req.Header.Set(`X-Florence-Token`, c.Value)
	}
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/zebedee")
}

func director(req *http.Request) {
	if c, err := req.Cookie(`access_token`); err == nil && len(c.Value) > 0 {
		req.Header.Set(`X-Florence-Token`, c.Value)
	}
}

func importAPIDirector(req *http.Request) {
	director(req)
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/import")
}

func tableDirector(req *http.Request) {
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/table")
}

func datasetAPIDirector(req *http.Request) {
	director(req)
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/dataset")
}

func websocketHandler(w http.ResponseWriter, req *http.Request) {
	c, err := upgrader.Upgrade(w, req, nil)
	if err != nil {
		log.ErrorR(req, err, nil)
		return
	}

	defer c.Close()

	err = c.WriteJSON(florenceServerEvent{"version", florenceVersionPayload{Version: Version}})
	if err != nil {
		log.ErrorR(req, err, nil)
		return
	}

	for {
		_, message, err := c.ReadMessage()
		if err != nil {
			log.ErrorR(req, err, nil)
			break
		}

		rdr := bufio.NewReader(bytes.NewReader(message))
		b, err := rdr.ReadBytes('{')
		if err != nil {
			log.ErrorR(req, err, log.Data{"bytes": string(b)})
			continue
		}

		tags := strings.Split(string(b), ":")
		eventID := tags[0]
		eventType := tags[1]
		eventData := message[len(eventID)+len(eventType)+2:]

		switch eventType {
		case "log":
			var e florenceLogEvent
			e.ServerTimestamp = time.Now().UTC().Format("2006-01-02T15:04:05.000-0700Z")
			err = json.Unmarshal(eventData, &e)
			if err != nil {
				log.ErrorR(req, err, log.Data{"data": string(eventData)})
				continue
			}
			log.Debug("client log", log.Data{"data": e})

			err = c.WriteJSON(florenceServerEvent{"ack", eventID})
			if err != nil {
				log.ErrorR(req, err, nil)
			}
		default:
			log.DebugR(req, "unknown event type", log.Data{"type": eventType, "data": string(eventData)})
		}

		// err = c.WriteMessage(mt, message)
		// if err != nil {
		// 	log.ErrorR(req, err, nil)
		// 	break
		// }
	}
}

type florenceLogEvent struct {
	ServerTimestamp string      `json:"-"`
	ClientTimestamp time.Time   `json:"clientTimestamp"`
	Type            string      `json:"type"`
	Location        string      `json:"location"`
	InstanceID      string      `json:"instanceID"`
	Payload         interface{} `json:"payload"`
}

type florenceServerEvent struct {
	Type    string      `json:"type"`
	Payload interface{} `json:"payload"`
}

type florenceVersionPayload struct {
	Version string `json:"version"`
}
<commit_msg>Fix route to Florence going to preview instead of Florence app<commit_after>package main

import (
	"bufio"
	"bytes"
	"context"
	"encoding/json"
	"mime"
	"net/http"
	"net/url"
	"os"
	"os/signal"
	"path/filepath"
	"strings"
	"time"

	"github.com/ONSdigital/florence/assets"
	"github.com/ONSdigital/florence/config"
	"github.com/ONSdigital/florence/healthcheck"
	"github.com/ONSdigital/florence/upload"
	"github.com/ONSdigital/go-ns/handlers/reverseProxy"
	hc "github.com/ONSdigital/go-ns/healthcheck"
	"github.com/ONSdigital/go-ns/log"
	"github.com/ONSdigital/go-ns/server"
	"github.com/ONSdigital/go-ns/vault"
	"github.com/gorilla/pat"
	"github.com/gorilla/websocket"
)

var getAsset = assets.Asset
var upgrader = websocket.Upgrader{}

// Version is set by the make target
var Version string

func main() {
	log.Debug("florence version", log.Data{"version": Version})

	cfg, err := config.Get()
	if err != nil {
		log.Error(err, nil)
		os.Exit(1)
	}

	log.Namespace = "florence"

	zc := healthcheck.New(cfg.ZebedeeURL, "zebedee")
	rtrc := healthcheck.New(cfg.RouterURL, "router")
	dc := healthcheck.New(cfg.DatasetAPIURL, "dataset-api")
	rc := healthcheck.New(cfg.RecipeAPIURL, "recipe-api")
	ic := healthcheck.New(cfg.ImportAPIURL, "import-api")

	/*
		NOTE:
		If there's any issues with this Florence server proxying redirects
		from either Babbage or Zebedee then the code in the previous Java
		Florence server might give some clues for a solution: https://github.com/ONSdigital/florence/blob/b13df0708b30493b98e9ce239103c59d7f409f98/src/main/java/com/github/onsdigital/florence/filter/Proxy.java#L125-L135

		The code has purposefully not been included in this Go replacement
		because we can't see what issue it's fixing and whether it's necessary.
	*/

	routerURL, err := url.Parse(cfg.RouterURL)
	if err != nil {
		log.Error(err, nil)
		os.Exit(1)
	}
	routerProxy := reverseProxy.Create(routerURL, director)

	zebedeeURL, err := url.Parse(cfg.ZebedeeURL)
	if err != nil {
		log.Error(err, nil)
		os.Exit(1)
	}
	zebedeeProxy := reverseProxy.Create(zebedeeURL, zebedeeDirector)

	recipeAPIURL, err := url.Parse(cfg.RecipeAPIURL)
	if err != nil {
		log.Error(err, nil)
		os.Exit(1)
	}
	recipeAPIProxy := reverseProxy.Create(recipeAPIURL, nil)

	tableURL, err := url.Parse(cfg.TableRendererURL)
	if err != nil {
		log.Error(err, nil)
		os.Exit(1)
	}
	tableProxy := reverseProxy.Create(tableURL, tableDirector)

	importAPIURL, err := url.Parse(cfg.ImportAPIURL)
	if err != nil {
		log.Error(err, nil)
		os.Exit(1)
	}
	importAPIProxy := reverseProxy.Create(importAPIURL, importAPIDirector)

	datasetAPIURL, err := url.Parse(cfg.DatasetAPIURL)
	if err != nil {
		log.Error(err, nil)
		os.Exit(1)
	}
	datasetAPIProxy := reverseProxy.Create(datasetAPIURL, datasetAPIDirector)

	router := pat.New()

	var vc upload.VaultClient
	if !cfg.EncryptionDisabled {
		vc, err = vault.CreateVaultClient(cfg.VaultToken, cfg.VaultAddr, 3)
		if err != nil {
			log.Error(err, nil)
			os.Exit(1)
		}
	}

	uploader, err := upload.New(cfg.UploadBucketName, cfg.VaultPath, vc)
	if err != nil {
		log.Error(err, nil)
		os.Exit(1)
	}

	router.Path("/healthcheck").HandlerFunc(hc.Do)

	router.Path("/upload").Methods("GET").HandlerFunc(uploader.CheckUploaded)
	router.Path("/upload").Methods("POST").HandlerFunc(uploader.Upload)
	router.Path("/upload/{id}").Methods("GET").HandlerFunc(uploader.GetS3URL)

	router.Handle("/zebedee{uri:/.*}", zebedeeProxy)
	router.Handle("/recipes{uri:.*}", recipeAPIProxy)
	router.Handle("/import{uri:.*}", importAPIProxy)
	router.Handle("/dataset/{uri:.*}", datasetAPIProxy)
	router.Handle("/instances/{uri:.*}", datasetAPIProxy)
	router.Handle("/table/{uri:.*}", tableProxy)
	router.HandleFunc("/florence/dist/{uri:.*}", staticFiles)
	router.HandleFunc("/florence/", redirectToFlorence)
	router.HandleFunc("/florence/index.html", redirectToFlorence)
	router.HandleFunc("/florence/publishing-queue", legacyIndexFile)
	router.HandleFunc("/florence/reports", legacyIndexFile)
	router.HandleFunc("/florence/users-and-access", legacyIndexFile)
	router.HandleFunc("/florence/workspace", legacyIndexFile)
	router.HandleFunc("/florence/websocket", websocketHandler)
	router.HandleFunc("/florence{uri:.*}", refactoredIndexFile)
	router.Handle("/{uri:.*}", routerProxy)

	log.Debug("Starting server", log.Data{"config": cfg})

	s := server.New(cfg.BindAddr, router)
	// TODO need to reconsider default go-ns server timeouts
	s.Server.IdleTimeout = 120 * time.Second
	s.Server.WriteTimeout = 120 * time.Second
	s.Server.ReadTimeout = 30 * time.Second
	s.HandleOSSignals = false
	s.MiddlewareOrder = []string{"RequestID", "Log"}

	// FIXME temporary hack to remove timeout middleware (doesn't support hijacker interface)
	mo := s.MiddlewareOrder
	var newMo []string
	for _, mw := range mo {
		if mw != "Timeout" {
			newMo = append(newMo, mw)
		}
	}
	s.MiddlewareOrder = newMo

	go func() {
		if err := s.ListenAndServe(); err != nil {
			log.Error(err, nil)
			os.Exit(2)
		}
	}()

	stop := make(chan os.Signal, 1)
	signal.Notify(stop, os.Interrupt, os.Kill)

	for {
		hc.MonitorExternal(rtrc, zc, ic, rc, dc)

		timer := time.NewTimer(time.Second * 60)

		select {
		case <-timer.C:
			continue
		case <-stop:
			log.Info("shutting service down gracefully", nil)
			timer.Stop()
			ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
			defer cancel()
			if err := s.Server.Shutdown(ctx); err != nil {
				log.Error(err, nil)
			}
			return
		}
	}
}

func redirectToFlorence(w http.ResponseWriter, req *http.Request) {
	http.Redirect(w, req, "/florence", 301)
}

func staticFiles(w http.ResponseWriter, req *http.Request) {
	path := req.URL.Query().Get(":uri")

	b, err := getAsset("../dist/" + path)
	if err != nil {
		log.Error(err, nil)
		w.WriteHeader(404)
		return
	}

	w.Header().Set(`Content-Type`, mime.TypeByExtension(filepath.Ext(path)))
	w.WriteHeader(200)
	w.Write(b)
}

func legacyIndexFile(w http.ResponseWriter, req *http.Request) {
	log.Debug("Getting legacy HTML file", nil)

	b, err := getAsset("../dist/legacy-assets/index.html")
	if err != nil {
		log.Error(err, nil)
		w.WriteHeader(404)
		return
	}

	w.Header().Set(`Content-Type`, "text/html")
	w.WriteHeader(200)
	w.Write(b)
}

func refactoredIndexFile(w http.ResponseWriter, req *http.Request) {
	log.Debug("Getting refactored HTML file", nil)

	b, err := getAsset("../dist/refactored.html")
	if err != nil {
		log.Error(err, nil)
		w.WriteHeader(404)
		return
	}

	w.Header().Set(`Content-Type`, "text/html")
	w.WriteHeader(200)
	w.Write(b)
}

func zebedeeDirector(req *http.Request) {
	if c, err := req.Cookie(`access_token`); err == nil && len(c.Value) > 0 {
		req.Header.Set(`X-Florence-Token`, c.Value)
	}
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/zebedee")
}

func director(req *http.Request) {
	if c, err := req.Cookie(`access_token`); err == nil && len(c.Value) > 0 {
		req.Header.Set(`X-Florence-Token`, c.Value)
	}
}

func importAPIDirector(req *http.Request) {
	director(req)
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/import")
}

func tableDirector(req *http.Request) {
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/table")
}

func datasetAPIDirector(req *http.Request) {
	director(req)
	req.URL.Path = strings.TrimPrefix(req.URL.Path, "/dataset")
}

func websocketHandler(w http.ResponseWriter, req *http.Request) {
	c, err := upgrader.Upgrade(w, req, nil)
	if err != nil {
		log.ErrorR(req, err, nil)
		return
	}

	defer c.Close()

	err = c.WriteJSON(florenceServerEvent{"version", florenceVersionPayload{Version: Version}})
	if err != nil {
		log.ErrorR(req, err, nil)
		return
	}

	for {
		_, message, err := c.ReadMessage()
		if err != nil {
			log.ErrorR(req, err, nil)
			break
		}

		rdr := bufio.NewReader(bytes.NewReader(message))
		b, err := rdr.ReadBytes('{')
		if err != nil {
			log.ErrorR(req, err, log.Data{"bytes": string(b)})
			continue
		}

		tags := strings.Split(string(b), ":")
		eventID := tags[0]
		eventType := tags[1]
		eventData := message[len(eventID)+len(eventType)+2:]

		switch eventType {
		case "log":
			var e florenceLogEvent
			e.ServerTimestamp = time.Now().UTC().Format("2006-01-02T15:04:05.000-0700Z")
			err = json.Unmarshal(eventData, &e)
			if err != nil {
				log.ErrorR(req, err, log.Data{"data": string(eventData)})
				continue
			}
			log.Debug("client log", log.Data{"data": e})

			err = c.WriteJSON(florenceServerEvent{"ack", eventID})
			if err != nil {
				log.ErrorR(req, err, nil)
			}
		default:
			log.DebugR(req, "unknown event type", log.Data{"type": eventType, "data": string(eventData)})
		}

		// err = c.WriteMessage(mt, message)
		// if err != nil {
		// 	log.ErrorR(req, err, nil)
		// 	break
		// }
	}
}

type florenceLogEvent struct {
	ServerTimestamp string      `json:"-"`
	ClientTimestamp time.Time   `json:"clientTimestamp"`
	Type            string      `json:"type"`
	Location        string      `json:"location"`
	InstanceID      string      `json:"instanceID"`
	Payload         interface{} `json:"payload"`
}

type florenceServerEvent struct {
	Type    string      `json:"type"`
	Payload interface{} `json:"payload"`
}

type florenceVersionPayload struct {
	Version string `json:"version"`
}
<|endoftext|>


<commit_before>package main

import "github.com/codegangsta/martini"

import (
	"fmt"
	"net/http"
	"io/ioutil"
	"encoding/hex"
	"crypto/sha256"
	"encoding/json"
	//"container/list"
	"time"
)

type FileStore struct {
	file_by_hash	map[string] *File
	hash_by_key		map[string] string
	file_fifo		[]*File

	memory_used		int
	memory_limit	int
}

func (fs *FileStore) MakeFile(content []byte, content_type, file_name string) *File {
	file := new (File)

	file.content		= content
	file.CreatedAt		= time.Now()
	file.ContentType	= content_type
	file.Name			= file_name
	file.Length			= len(file.content)
	file.Hash			= Hash(content)

	fs.memory_used += file.Length

	// Catch all
	if file.ContentType == "application/x-www-form-urlencoded" || len(file.ContentType) == 0 {
		file.ContentType = "application/octet-stream"
	}

	return file
}

func (fs *FileStore) PutFileByHash(file *File) bool {
	fs.file_fifo = append(fs.file_fifo, file)
	fs.file_by_hash[file.Hash] = file
	_, ok := fs.file_by_hash[file.Hash]
	return ok;
}

func (fs *FileStore) LinkFileToKey(key string, hash string) bool {

	f, ok := fs.file_by_hash[hash]
	f.Key = key

	fs.hash_by_key[key] = hash
	hash, ok  = fs.hash_by_key[key]
	return ok;
}

func (fs *FileStore) GetFileByHash(hash string) (*File, bool) {
	f, ok := fs.file_by_hash[hash]
	return f, ok
}

func (fs *FileStore) GetFileByKey(key string) (*File, bool) {
	hash, ok := fs.GetHashByKey(key)

	if !ok {
		return nil, ok
	}

	return fs.GetFileByHash(hash)
}

func (fs *FileStore) MakeRoomFor(size int) {
	for  fs.memory_used + size > fs.memory_limit {
		f := fs.file_fifo[0];

		delete(fs.file_by_hash, f.Hash)
		delete(fs.hash_by_key, f.Key)
	}
}

func (fs *FileStore) GetHashByKey(key string) (string, bool) {
	hash, ok := fs.hash_by_key[key]
	return hash, ok
}

type File struct {
	ContentType string
	Name string
	CreatedAt time.Time
	content []byte
	Length int
	Hash string
	Key string
}

func Hash(data []byte) string {
	h := sha256.New()
	h.Write(data)
	return hex.EncodeToString(h.Sum(nil))
}

type JM map[string] string

func Stringify (data JM) string {
	b,_ := json.Marshal(data)
	return string(b)
}

const (
	B  = (1)
	KB = (1024)
	MB = (1024 * 1024)
	GB = (1024 * 1024 * 1024)
)

func main() {

	fs := new (FileStore)

	fs.file_by_hash	= make(map[string] *File)
	fs.hash_by_key	= make(map[string] string)
	fs.file_fifo	= make([]*File, 128)

	m := martini.Classic()

	m.Get("/", func() string {
		return Stringify(JM{"Message": "Hello World"})
	})

	m.Get("/:id", func(res http.ResponseWriter, params martini.Params) (int, []byte) {

		file, ok := fs.GetFileByHash(params["id"])

		if !ok {
			file, ok = fs.GetFileByKey(params["id"])
			if !ok {
				return 404, []byte(Stringify(JM{"Message": "The file you are trying to access does not exists."}))
			}
		}

		res.Header().Set("Content-Type", file.ContentType)
		res.Header().Set("Content-Disposition", "Attachment;filename=" + file.Name )

		return 200, file.content
	})

	m.Put("/:id", func(params martini.Params, request *http.Request) (int, []byte) {
		_, ok := fs.GetFileByKey(params["id"])

		if ok {
			return 404, []byte(Stringify(JM{"Message": "A file already exists at this location."}))
		}

		content, err := ioutil.ReadAll(request.Body);

		if err != nil {
			fmt.Println(err)
		}

		file := fs.MakeFile(content, request.Header.Get("Content-Type"), request.Header.Get("File-Name"))

		if len(file.Name) == 0 {
			file.Name = params["id"]
		}

		fs.PutFileByHash(file)
		fs.LinkFileToKey(params["id"], file.Hash)

		b, err := json.Marshal(file)
		if err != nil {
			fmt.Println(err)
			return 500, make([]byte,0)
		}

		return 200, b

	})

	m.Post("/", func(request *http.Request) (int, []byte) {
		content, err := ioutil.ReadAll(request.Body);

		if err != nil {
			fmt.Println(err)
		}

		file := fs.MakeFile(content, request.Header.Get("Content-Type"), request.Header.Get("File-Name"))

		if len(file.Name) == 0 {
			file.Name = file.Hash[0:15]
		}

		fs.PutFileByHash(file)

		b, err := json.Marshal(file)
		if err != nil {
			fmt.Println(err)
			return 500, make([]byte,0)
		}

		return 200, b
	})

  m.Run()
}
<commit_msg>Renamed main file for pretty installation<commit_after><|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	_ "github.com/lib/pq"
)

func main() {
	if len(os.Args) != 4 {
		fmt.Fprintln(os.Stderr, "usage: pgmigrate DSN COMMAND DIRECTORY")
		os.Exit(1)
	}

	dsn := os.Args[1]

	cmd := os.Args[2]
	if cmd != "up" && cmd != "down" {
		fmt.Fprintln(os.Stderr, "valid commands are 'up' and 'down'")
		os.Exit(1)
	}

	dir := os.Args[3]
	if stat, err := os.Stat(dir); err != nil || !stat.IsDir() {
		fmt.Fprintf(os.Stderr, "%s does not exist or isn't a directory: %s\n", dir, err)
		os.Exit(1)
	}

	db, err := sql.Open("postgres", dsn)
	if err != nil {
		fmt.Fprintf(os.Stderr, "could not connect to database: %s\n", err)
		os.Exit(1)
	}
	defer db.Close()
	err = db.Ping()
	if err != nil {
		fmt.Fprintf(os.Stderr, "could not ping database: %s\n", err)
		os.Exit(1)
	}

	var exists bool
	eq := `SELECT EXISTS (SELECT 1 FROM information_schema.tables WHERE table_name = 'pgmigrate')`
	err = db.QueryRow(eq).Scan(&exists)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error querying the database: %s\n", err)
		os.Exit(1)
	}

	if !exists {
		cq := `CREATE TABLE pgmigrate(id SERIAL, migration TEXT,
		created_at TIMESTAMP DEFAULT now());`
		_, err = db.Exec(cq)
		if err != nil {
			fmt.Fprintf(os.Stderr, "could not create migrations table: %s\n", err)
			os.Exit(1)
		}
	}

	var latestMigration string
	lmq := `SELECT migration FROM pgmigrate ORDER BY migration DESC`
	err = db.QueryRow(lmq).Scan(&latestMigration)
	if err != nil && err != sql.ErrNoRows {
		fmt.Fprintf(os.Stderr, "error querying the database: %s\n", err)
		os.Exit(1)
	}

	if cmd == "up" {
		migrations := make([]string, 0)
		files, err := ioutil.ReadDir(dir)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error reading the migrations: %s\n", err)
			os.Exit(1)
		}
		for _, f := range files {
			n := f.Name()
			if strings.Contains(n, "up.sql") {
				if strings.TrimSuffix(n, ".up.sql") > latestMigration {
					migrations = append(migrations, n)
				}
			}
		}
		for _, m := range migrations {
			fq, err := ioutil.ReadFile(filepath.Join(dir, m))
			if err != nil {
				fmt.Fprintf(os.Stderr, "error reading the migration: %s\n", err)
				os.Exit(1)
			}
			_, err = db.Exec(string(fq))
			if err != nil {
				fmt.Fprintf(os.Stderr, "error querying the database: %s\n", err)
				os.Exit(1)
			}
			mn := strings.TrimSuffix(m, ".up.sql")
			cmq := `INSERT INTO pgmigrate(migration) VALUES($1)`
			_, err = db.Exec(string(cmq), mn)
			if err != nil {
				fmt.Fprintf(os.Stderr, "error saving migration metadata: %s\n", err)
				fmt.Fprintln(os.Stderr, "[WARNING] database in inconsistent state")
				os.Exit(1)
			}
		}
	} else if cmd == "down" {
		if latestMigration == "" {
			os.Exit(0)
		}
		fn := filepath.Join(dir, latestMigration+".down.sql")
		if _, err := os.Stat(fn); err != nil {
			fmt.Fprintf(os.Stderr, "%s does not exist: %s\n", fn, err)
			os.Exit(1)
		}
		fq, err := ioutil.ReadFile(fn)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error reading the migration: %s\n", err)
			os.Exit(1)
		}
		_, err = db.Exec(string(fq))
		if err != nil {
			fmt.Fprintf(os.Stderr, "error querying the database: %s\n", err)
			os.Exit(1)
		}
		rmq := `DELETE FROM pgmigrate WHERE migration=$1`
		_, err = db.Exec(string(rmq), latestMigration)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error querying the database: %s\n", err)
			fmt.Fprintln(os.Stderr, "[WARNING] database in inconsistent state")
			os.Exit(1)
		}
		os.Exit(0)
	}
}
<commit_msg>Cleanup queries and flags<commit_after>package main

import (
	"database/sql"
	"flag"
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"strings"

	_ "github.com/lib/pq"
)

const (
	migrationsTableExists = `SELECT EXISTS (SELECT 1 FROM information_schema.tables
		WHERE table_name = 'pgmigrate')`
	createMigrationsTable = `CREATE TABLE pgmigrate(id SERIAL, migration TEXT,
		created_at TIMESTAMP DEFAULT now());`
	latestMigration = `SELECT migration FROM pgmigrate ORDER BY migration DESC`
	addMigration    = `INSERT INTO pgmigrate(migration) VALUES($1)`
	removeMigration = `DELETE FROM pgmigrate WHERE migration=$1`
)

const (
	upCommand   = "up"
	downCommand = "down"
)

func main() {
	var dsn string
	var dir string
	flag.StringVar(&dsn, "dsn",
		"postgres://postgres:@localhost/postgres?sslmode=disable",
		"The DSN to use to connect to the database")
	flag.StringVar(&dir, "dir", "sql", "The path to the directory of migration files")
	flag.Parse()

	if len(flag.Args()) != 1 {
		fmt.Fprintln(os.Stderr, "usage: pgmigrate [OPTIONS] COMMAND")
		flag.PrintDefaults()
		os.Exit(1)
	}

	cmd := flag.Args()[0]
	if cmd != upCommand && cmd != downCommand {
		fmt.Fprintf(os.Stderr, "valid commands are '%s' and '%s'\n", upCommand, downCommand)
		os.Exit(1)
	}

	if stat, err := os.Stat(dir); err != nil || !stat.IsDir() {
		fmt.Fprintf(os.Stderr, "%s does not exist or isn't a directory: %s\n", dir, err)
		os.Exit(1)
	}

	db, err := sql.Open("postgres", dsn)
	if err != nil {
		fmt.Fprintf(os.Stderr, "could not connect to database: %s\n", err)
		os.Exit(1)
	}
	defer db.Close()
	err = db.Ping()
	if err != nil {
		fmt.Fprintf(os.Stderr, "could not ping database: %s\n", err)
		os.Exit(1)
	}

	var exists bool
	err = db.QueryRow(migrationsTableExists).Scan(&exists)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error querying the database: %s\n", err)
		os.Exit(1)
	}

	if !exists {
		_, err = db.Exec(createMigrationsTable)
		if err != nil {
			fmt.Fprintf(os.Stderr, "could not create migrations table: %s\n", err)
			os.Exit(1)
		}
	}

	var lastMigration string
	err = db.QueryRow(latestMigration).Scan(&lastMigration)
	if err != nil && err != sql.ErrNoRows {
		fmt.Fprintf(os.Stderr, "error querying the database: %s\n", err)
		os.Exit(1)
	}

	if cmd == upCommand {
		migrations := make([]string, 0)
		files, err := ioutil.ReadDir(dir)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error reading the migrations: %s\n", err)
			os.Exit(1)
		}
		for _, f := range files {
			n := f.Name()
			if strings.Contains(n, "up.sql") {
				if strings.TrimSuffix(n, ".up.sql") > lastMigration {
					migrations = append(migrations, n)
				}
			}
		}
		for _, m := range migrations {
			fq, err := ioutil.ReadFile(filepath.Join(dir, m))
			if err != nil {
				fmt.Fprintf(os.Stderr, "error reading the migration: %s\n", err)
				os.Exit(1)
			}
			_, err = db.Exec(string(fq))
			if err != nil {
				fmt.Fprintf(os.Stderr, "error querying the database: %s\n", err)
				os.Exit(1)
			}
			mn := strings.TrimSuffix(m, ".up.sql")
			_, err = db.Exec(string(addMigration), mn)
			if err != nil {
				fmt.Fprintf(os.Stderr, "error saving migration metadata: %s\n", err)
				fmt.Fprintln(os.Stderr, "[WARNING] database in inconsistent state")
				os.Exit(1)
			}
		}
		fmt.Fprintf(os.Stdout, "Applied %d migrations\n", len(migrations))
	} else if cmd == downCommand {
		if lastMigration == "" {
			os.Exit(0)
		}
		fn := filepath.Join(dir, lastMigration+".down.sql")
		if _, err := os.Stat(fn); err != nil {
			fmt.Fprintf(os.Stderr, "%s does not exist: %s\n", fn, err)
			os.Exit(1)
		}
		fq, err := ioutil.ReadFile(fn)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error reading the migration: %s\n", err)
			os.Exit(1)
		}
		_, err = db.Exec(string(fq))
		if err != nil {
			fmt.Fprintf(os.Stderr, "error querying the database: %s\n", err)
			os.Exit(1)
		}
		_, err = db.Exec(string(removeMigration), lastMigration)
		if err != nil {
			fmt.Fprintf(os.Stderr, "error querying the database: %s\n", err)
			fmt.Fprintln(os.Stderr, "[WARNING] database in inconsistent state")
			os.Exit(1)
		}
		fmt.Fprintf(os.Stdout, "Migration %s successfully removed\n", lastMigration)
		os.Exit(0)
	}
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"net/http"
	_ "net/http/pprof"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/prometheus/common/log"
)

var (
	configFile = flag.String(
		"config.file", "snmp.yml",
		"Path to configuration file.",
	)
	listenAddress = flag.String(
		"web.listen-address", ":9116",
		"Address to listen on for web interface and telemetry.",
	)

	// Metrics about the SNMP exporter itself.
	snmpDuration = prometheus.NewSummaryVec(
		prometheus.SummaryOpts{
			Name: "snmp_collection_duration_seconds",
			Help: "Duration of collections by the SNMP exporter",
		},
		[]string{"module"},
	)
	snmpRequestErrors = prometheus.NewCounter(
		prometheus.CounterOpts{
			Name: "snmp_request_errors_total",
			Help: "Errors in requests to the SNMP exporter",
		},
	)
)

func init() {
	prometheus.MustRegister(snmpDuration)
	prometheus.MustRegister(snmpRequestErrors)
}

func handler(w http.ResponseWriter, r *http.Request) {
	cfg, err := LoadFile(*configFile)
	if err != nil {
		msg := fmt.Sprintf("Error parsing config file: %s", err)
		http.Error(w, msg, 400)
		log.Errorf(msg)
		return
	}

	target := r.URL.Query().Get("target")
	if target == "" {
		http.Error(w, "'target' parameter must be specified", 400)
		snmpRequestErrors.Inc()
		return
	}
	moduleName := r.URL.Query().Get("module")
	if moduleName == "" {
		moduleName = "default"
	}
	module, ok := (*cfg)[moduleName]
	if !ok {
		http.Error(w, fmt.Sprintf("Unkown module '%s'", moduleName), 400)
		snmpRequestErrors.Inc()
		return
	}
	log.Debugf("Scraping target '%s' with module '%s'", target, moduleName)

	start := time.Now()
	registry := prometheus.NewRegistry()
	collector := collector{target: target, module: module}
	registry.MustRegister(collector)
	// Delegate http serving to Promethues client library, which will call collector.Collect.
	h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{})
	h.ServeHTTP(w, r)
	duration := float64(time.Since(start).Seconds())
	snmpDuration.WithLabelValues(moduleName).Observe(duration)
	log.Debugf("Scrape of target '%s' with module '%s' took %f seconds", target, moduleName, duration)
}

func main() {
	flag.Parse()

	// Bail early if the config is bad.
	c, err := LoadFile(*configFile)
	if err != nil {
		log.Fatalf("Error parsing config file: %s", err)
	}
	// Initilise metrics.
	for module, _ := range *c {
		snmpDuration.WithLabelValues(module)
	}

	http.Handle("/metrics", promhttp.Handler()) // Normal metrics endpoint for SNMP exporter itself.
	http.HandleFunc("/snmp", handler)           // Endpoint to do SNMP scrapes.
	log.Infof("Listening on %s", *listenAddress)
	log.Fatal(http.ListenAndServe(*listenAddress, nil))
}
<commit_msg>Add build info<commit_after>package main

import (
	"flag"
	"fmt"
	"net/http"
	_ "net/http/pprof"
	"time"

	"github.com/prometheus/client_golang/prometheus"
	"github.com/prometheus/client_golang/prometheus/promhttp"
	"github.com/prometheus/common/log"
	"github.com/prometheus/common/version"
)

var (
	configFile = flag.String(
		"config.file", "snmp.yml",
		"Path to configuration file.",
	)
	listenAddress = flag.String(
		"web.listen-address", ":9116",
		"Address to listen on for web interface and telemetry.",
	)

	// Metrics about the SNMP exporter itself.
	snmpDuration = prometheus.NewSummaryVec(
		prometheus.SummaryOpts{
			Name: "snmp_collection_duration_seconds",
			Help: "Duration of collections by the SNMP exporter",
		},
		[]string{"module"},
	)
	snmpRequestErrors = prometheus.NewCounter(
		prometheus.CounterOpts{
			Name: "snmp_request_errors_total",
			Help: "Errors in requests to the SNMP exporter",
		},
	)
)

func init() {
	prometheus.MustRegister(snmpDuration)
	prometheus.MustRegister(snmpRequestErrors)
	prometheus.MustRegister(version.NewCollector("snmp_exporter"))
}

func handler(w http.ResponseWriter, r *http.Request) {
	cfg, err := LoadFile(*configFile)
	if err != nil {
		msg := fmt.Sprintf("Error parsing config file: %s", err)
		http.Error(w, msg, 400)
		log.Errorf(msg)
		return
	}

	target := r.URL.Query().Get("target")
	if target == "" {
		http.Error(w, "'target' parameter must be specified", 400)
		snmpRequestErrors.Inc()
		return
	}
	moduleName := r.URL.Query().Get("module")
	if moduleName == "" {
		moduleName = "default"
	}
	module, ok := (*cfg)[moduleName]
	if !ok {
		http.Error(w, fmt.Sprintf("Unkown module '%s'", moduleName), 400)
		snmpRequestErrors.Inc()
		return
	}
	log.Debugf("Scraping target '%s' with module '%s'", target, moduleName)

	start := time.Now()
	registry := prometheus.NewRegistry()
	collector := collector{target: target, module: module}
	registry.MustRegister(collector)
	// Delegate http serving to Promethues client library, which will call collector.Collect.
	h := promhttp.HandlerFor(registry, promhttp.HandlerOpts{})
	h.ServeHTTP(w, r)
	duration := float64(time.Since(start).Seconds())
	snmpDuration.WithLabelValues(moduleName).Observe(duration)
	log.Debugf("Scrape of target '%s' with module '%s' took %f seconds", target, moduleName, duration)
}

func main() {
	flag.Parse()

	log.Infoln("Starting snmp exporter", version.Info())
	log.Infoln("Build context", version.BuildContext())

	// Bail early if the config is bad.
	c, err := LoadFile(*configFile)
	if err != nil {
		log.Fatalf("Error parsing config file: %s", err)
	}
	// Initilise metrics.
	for module, _ := range *c {
		snmpDuration.WithLabelValues(module)
	}

	http.Handle("/metrics", promhttp.Handler()) // Normal metrics endpoint for SNMP exporter itself.
	http.HandleFunc("/snmp", handler)           // Endpoint to do SNMP scrapes.
	log.Infof("Listening on %s", *listenAddress)
	log.Fatal(http.ListenAndServe(*listenAddress, nil))
}
<|endoftext|>


<commit_before>// main.go
package main

import (
	"github.com/jessevdk/go-flags"
	"os"
	"log"
	"strings"
	"bufio"
	"encoding/base64"
	"github.com/beplus/makeicon/app/helper/image-helper"
	"fmt"
)

var args struct {
	Filename string `short:"f" long:"file" description:"filename to make assets"`
}
var version = "master"

func main() {
	println(version)

	_, err := flags.ParseArgs(&args, os.Args)
	if err == nil {

		if args.Filename == "" {
			log.Fatal("No file set use -n or --file to set it")
		}

		// todo windows \
		s := strings.Split(args.Filename, "/")
		filename := s[len(s)-1]
		// prepare json body

		filenameArray := strings.Split(filename, ".")
		name, extension := filenameArray[0], filenameArray[1]

		myImage, err := image_helper.NewMyImageFromBase64(getImageBase64(args.Filename), name, extension)
		if err != nil {
			log.Fatal(err)
		}

		fmt.Println("Processing icons... It could few seconds...")

		_, err = myImage.Upload()
		if err != nil {
			log.Println(err)
		} else {
			fmt.Println("Icons saved to folder 'AppIcon'.")
		}
	}
}

func getImageBase64(filename string) string {
	imgFile, err := os.Open(filename)
	if err != nil {
		log.Fatal("Open file fail")
	}
	defer imgFile.Close()

	// create a new buffer base on file size
	fInfo, _ := imgFile.Stat()
	var size int64 = fInfo.Size()
	buf := make([]byte, size)

	// read file content into buffer
	fReader := bufio.NewReader(imgFile)
	fReader.Read(buf)

	// convert the buffer bytes to base64 string - use buf.Bytes() for new image
	return base64.StdEncoding.EncodeToString(buf)
}
<commit_msg>test<commit_after>// main.go
package main

import (
	//"github.com/jessevdk/go-flags"
	"os"
	"log"
	//"strings"
	"bufio"
	"encoding/base64"
	//"github.com/beplus/makeicon/app/helper/image-helper"
	//"fmt"
)

var version = "master"

//
//var args struct {
//	Filename string `short:"f" long:"file" description:"filename to make assets"`
//}

func main() {
	log.Printf("running goreleaser %v", version)

	//_, err := flags.ParseArgs(&args, os.Args)
	//if err == nil {
	//
	//	if args.Filename == "" {
	//		log.Fatal("No file set use -n or --file to set it")
	//	}
	//
	//	// todo windows \
	//	s := strings.Split(args.Filename, "/")
	//	filename := s[len(s)-1]
	//	// prepare json body
	//
	//	filenameArray := strings.Split(filename, ".")
	//	name, extension := filenameArray[0], filenameArray[1]
	//
	//	myImage, err := image_helper.NewMyImageFromBase64(getImageBase64(args.Filename), name, extension)
	//	if err != nil {
	//		log.Fatal(err)
	//	}
	//
	//	fmt.Println("Processing icons... It could few seconds...")
	//
	//	_, err = myImage.Upload()
	//	if err != nil {
	//		log.Println(err)
	//	} else {
	//		fmt.Println("Icons saved to folder 'AppIcon'.")
	//	}
	//}
}

func getImageBase64(filename string) string {
	imgFile, err := os.Open(filename)
	if err != nil {
		log.Fatal("Open file fail")
	}
	defer imgFile.Close()

	// create a new buffer base on file size
	fInfo, _ := imgFile.Stat()
	var size int64 = fInfo.Size()
	buf := make([]byte, size)

	// read file content into buffer
	fReader := bufio.NewReader(imgFile)
	fReader.Read(buf)

	// convert the buffer bytes to base64 string - use buf.Bytes() for new image
	return base64.StdEncoding.EncodeToString(buf)
}
<|endoftext|>


<commit_before>package gosingleton

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"runtime"
	"strconv"

	"gopkg.in/fatih/set.v0"
)

const version = "v0.0.3"

// Check if we're the only process running with this path.
func UniquePath(path string) bool {
	return false // not implemented.
}

// Check if we're the only process running with this name.
func UniqueName(path string) bool {
	return false // not implemented
}

// Check if an instance of the same binary is running.
func UniqueBinary(name string, pid int) error {
	// This was only developed for Linux so exit early if we're running in some other OS.
	if runtime.GOOS != "linux" {
		// fmt.Printf("%s is not a supported platform failing open\n", runtime.GOOS)

		return resolveUsingPs(name)
	}

	// Resolve the exe symlink for this PID.
	path, err := resolveExeSymlink(pid)
	if err != nil {
		return err
	}

	// Build an inverted index of all /proc/PID/exe symlinks.
	index, err := buildInvertedIndex()
	if err != nil {
		return err
	}

	// Query the index to see if there are multiple instances.
	bin, ok := index[path]
	if !ok {
		return fmt.Errorf("Unknown binary path %s", path)
	}

	if bin.Size() > 1 {
		return fmt.Errorf("Existing binary %s detected!", path)
	}

	return nil
}

// resolveUsingPs does a UNIX _ps -axw | grep $name | grep -v grep_ to check if the named proc is running.
func resolveUsingPs(name string) error {
	var b bytes.Buffer

	c1 := exec.Command("ps", "-axw")
	c2 := exec.Command("grep", name)
	c3 := exec.Command("grep", "-v", "grep")

	c3.Stdin, _ = c2.StdoutPipe()
	c2.Stdin, _ = c1.StdoutPipe()
	c3.Stdout = os.Stdout

	_ = c3.Start()
	_ = c2.Start()
	_ = c1.Run()
	_ = c2.Wait()
	_ = c2.Run()
	_ = c3.Wait()

	io.Copy(os.Stdout, &b)

	if b.String() != "" {
		return fmt.Errorf("Existing binary %s detected!", name)
	}

	return nil
}

// Resole the exe symlink for a PID.
func resolveExeSymlink(pid int) (string, error) {
	path, err := os.Readlink(fmt.Sprintf("/proc/%d/exe", pid))
	if err != nil {
		return "", err
	}

	return path, nil
}

// Build an inverted index of paths as keys with sets of PIDs as values.
func buildInvertedIndex() (map[string]*set.Set, error) {
	index := make(map[string]*set.Set)

	// Find all entries under /proc.
	entries, err := ioutil.ReadDir("/proc")
	if err != nil {
		return nil, err
	}

	// Filter for PIDs and resolve their exe symlinks.
	for _, e := range entries {
		pid, err := strconv.Atoi(e.Name())
		if err != nil {
			continue
		}

		// Skip this path if the exe symlink does not resolve properly.
		path, err := resolveExeSymlink(pid)
		if err != nil {
			continue
		}

		if bin, ok := index[path]; ok {
			bin.Add(pid)
		} else {
			index[path] = set.New(pid)
		}
	}

	return index, nil
}
<commit_msg>* Set the pacakge version to v0.0.4. * Removed some debug.<commit_after>package gosingleton

import (
	"bytes"
	"fmt"
	"io"
	"io/ioutil"
	"os"
	"os/exec"
	"runtime"
	"strconv"

	"gopkg.in/fatih/set.v0"
)

const version = "v0.0.4"

// Check if we're the only process running with this path.
func UniquePath(path string) bool {
	return false // not implemented.
}

// Check if we're the only process running with this name.
func UniqueName(path string) bool {
	return false // not implemented
}

// Check if an instance of the same binary is running.
func UniqueBinary(name string, pid int) error {
	// This was only developed for Linux so exit early if we're running in some other OS.
	if runtime.GOOS != "linux" {
		return resolveUsingPs(name)
	}

	// Resolve the exe symlink for this PID.
	path, err := resolveExeSymlink(pid)
	if err != nil {
		return err
	}

	// Build an inverted index of all /proc/PID/exe symlinks.
	index, err := buildInvertedIndex()
	if err != nil {
		return err
	}

	// Query the index to see if there are multiple instances.
	bin, ok := index[path]
	if !ok {
		return fmt.Errorf("Unknown binary path %s", path)
	}

	if bin.Size() > 1 {
		return fmt.Errorf("Existing binary %s detected!", path)
	}

	return nil
}

// resolveUsingPs does a UNIX _ps -axw | grep $name | grep -v grep_ to check if the named proc is running.
func resolveUsingPs(name string) error {
	var b bytes.Buffer

	c1 := exec.Command("ps", "-axw")
	c2 := exec.Command("grep", name)
	c3 := exec.Command("grep", "-v", "grep")

	c3.Stdin, _ = c2.StdoutPipe()
	c2.Stdin, _ = c1.StdoutPipe()
	c3.Stdout = os.Stdout

	_ = c3.Start()
	_ = c2.Start()
	_ = c1.Run()
	_ = c2.Wait()
	_ = c2.Run()
	_ = c3.Wait()

	io.Copy(os.Stdout, &b)

	if b.String() != "" {
		return fmt.Errorf("Existing binary %s detected!", name)
	}

	return nil
}

// Resole the exe symlink for a PID.
func resolveExeSymlink(pid int) (string, error) {
	path, err := os.Readlink(fmt.Sprintf("/proc/%d/exe", pid))
	if err != nil {
		return "", err
	}

	return path, nil
}

// Build an inverted index of paths as keys with sets of PIDs as values.
func buildInvertedIndex() (map[string]*set.Set, error) {
	index := make(map[string]*set.Set)

	// Find all entries under /proc.
	entries, err := ioutil.ReadDir("/proc")
	if err != nil {
		return nil, err
	}

	// Filter for PIDs and resolve their exe symlinks.
	for _, e := range entries {
		pid, err := strconv.Atoi(e.Name())
		if err != nil {
			continue
		}

		// Skip this path if the exe symlink does not resolve properly.
		path, err := resolveExeSymlink(pid)
		if err != nil {
			continue
		}

		if bin, ok := index[path]; ok {
			bin.Add(pid)
		} else {
			index[path] = set.New(pid)
		}
	}

	return index, nil
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"github.com/cernbox/cboxswanapid/handlers"
	gh "github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
	"go.uber.org/zap"
	"log"
	"net/http"
	"os"
)

// Build information obtained with the help of -ldflags
var (
	appName       string
	buildDate     string // date -u
	gitTag        string // git describe --exact-match HEAD
	gitNearestTag string // git describe --abbrev=0 --tags HEAD
	gitCommit     string // git rev-parse HEAD
)

var fVersion bool

func init() {
	flag.BoolVar(&fVersion, "version", false, "Show version")

	viper.SetDefault("port", 2005)
	viper.SetDefault("applog", "stderr")
	viper.SetDefault("httplog", "stderr")
	viper.SetDefault("secret", "changeme")
	viper.SetDefault("signkey", "changeme")
	viper.SetDefault("allowfrom", "swan[a-z0-9-]*.cern.ch")
	viper.SetDefault("shibreferer", "https://login.cern.ch")
	viper.SetDefault("cboxgroupdurl", "http://localhost:2002/api/v1/search")
	viper.SetDefault("cboxgroupdsecret", "changeme")

	viper.SetConfigName("cboxswanapid")
	viper.AddConfigPath("/etc/cboxswanapid/")
	flag.Int("port", 2005, "Port to listen for connections")
	flag.String("applog", "stderr", "File to log application data")
	flag.String("httplog", "stderr", "File to log HTTP requests")
	flag.String("secret", "changeme", "Shared secret with SWAN")
	flag.String("signkey", "changeme", "Secret to sign JWT tokens")
	flag.String("allowfrom", "swan[a-z0-9-]*.cern.ch", "Check the Referer/Origin request header (depending on the endpoint) and return Bad Request if no match.")
	flag.String("shibreferer", "https://login.cern.ch", "Shibolleth referer for /authenticate request.")
	flag.String("cboxgroupdsecret", "", "Shared secret to communicate with the cboxgroupd daemon")
	flag.String("cboxgroupdurl", "http://localhost:2002/api/v1/search", "URL to address the cboxgroupd daemon")
	flag.String("config", "", "Configuration file to use")
	flag.Parse()

	pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
	pflag.Parse()
	viper.BindPFlags(pflag.CommandLine)
}

func main() {

	if fVersion {
		showVersion()
	}

	if viper.GetString("config") != "" {
		viper.SetConfigFile(viper.GetString("config"))
	}

	err := viper.ReadInConfig()
	if err != nil {
		panic(fmt.Errorf("error reading config file: %s", err))
	}

	config := zap.NewProductionConfig()
	config.OutputPaths = []string{viper.GetString("applog")}
	logger, _ := config.Build()

	router := mux.NewRouter()

	tokenHandler := handlers.CheckNothing(logger, handlers.Token(logger, viper.GetString("signkey"), viper.GetString("allowfrom"), viper.GetString("shibreferer")))

	sharedHandler := handlers.CheckJWTToken(logger, viper.GetString("signkey"), handlers.Shared(logger, viper.GetString("allowfrom"), "list-shared-with", false))
	sharingHandler := handlers.CheckJWTToken(logger, viper.GetString("signkey"), handlers.Shared(logger, viper.GetString("allowfrom"), "list-shared-by", false))
	getIndividualShareHandler := handlers.CheckJWTToken(logger, viper.GetString("signkey"), handlers.Shared(logger, viper.GetString("allowfrom"), "list-shared-by", true))
	updateShareHandler := handlers.CheckJWTToken(logger, viper.GetString("signkey"), handlers.UpdateShare(logger, viper.GetString("allowfrom")))
	deleteShareHandler := handlers.CheckJWTToken(logger, viper.GetString("signkey"), handlers.DeleteShare(logger, viper.GetString("allowfrom")))
	searchHandler := handlers.CheckJWTToken(logger, viper.GetString("signkey"), handlers.Search(logger, viper.GetString("cboxgroupdurl"), viper.GetString("cboxgroupdsecret")))
	cloneShareHandler := handlers.CheckJWTToken(logger, viper.GetString("signkey"), handlers.CloneShare(logger, viper.GetString("allowfrom")))
	notFoundHandler := handlers.CheckJWTToken(logger, viper.GetString("signkey"), handlers.Handle404(logger))

	router.NotFoundHandler = notFoundHandler // default protection for non-existing resources is JWT

	router.Handle("/swanapi/v1/authenticate", tokenHandler).Methods("GET")
	router.Handle("/swanapi/v1/shared", sharedHandler).Methods("GET")
	router.Handle("/swanapi/v1/sharing", sharingHandler).Methods("GET")
	router.Handle("/swanapi/v1/share", getIndividualShareHandler).Methods("GET")
	router.Handle("/swanapi/v1/share", updateShareHandler).Methods("PUT")
	router.Handle("/swanapi/v1/share", deleteShareHandler).Methods("DELETE")
	router.Handle("/swanapi/v1/search", searchHandler).Methods("GET")

	router.Handle("/swanapi/v1/shared", handlers.Options(logger, []string{"GET"}, viper.GetString("allowfrom"))).Methods("OPTIONS")
	router.Handle("/swanapi/v1/sharing", handlers.Options(logger, []string{"GET"}, viper.GetString("allowfrom"))).Methods("OPTIONS")
	router.Handle("/swanapi/v1/share", handlers.Options(logger, []string{"GET", "PUT", "DELETE"}, viper.GetString("allowfrom"))).Methods("OPTIONS")

	router.Handle("/swanapi/v1/clone", cloneShareHandler).Methods("POST")
	router.Handle("/swanapi/v1/clone", handlers.Options(logger, []string{"POST"}, viper.GetString("allowfrom"))).Methods("OPTIONS")

	out := getHTTPLoggerOut(viper.GetString("httplog"))
	loggedRouter := gh.LoggingHandler(out, router)

	logger.Info("server is listening", zap.Int("port", viper.GetInt("port")))
	logger.Warn("server stopped", zap.Error(http.ListenAndServe(fmt.Sprintf(":%d", viper.GetInt("port")), loggedRouter)))
}

func getHTTPLoggerOut(filename string) *os.File {
	if filename == "stderr" {
		return os.Stderr
	} else if filename == "stdout" {
		return os.Stdout
	} else {
		fd, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
		if err != nil {
			log.Fatal(err)
		}
		return fd
	}
}

func showVersion() {
	// if gitTag is not empty we are on release build
	if gitTag != "" {
		fmt.Printf("%s %s commit:%s release-build\n", appName, gitNearestTag, gitCommit)
		os.Exit(0)
	}
	fmt.Printf("%s %s commit:%s dev-build\n", appName, gitNearestTag, gitCommit)
	os.Exit(0)
}
<commit_msg>Add OPTIONS for search endpoint<commit_after>package main

import (
	"flag"
	"fmt"
	"github.com/cernbox/cboxswanapid/handlers"
	gh "github.com/gorilla/handlers"
	"github.com/gorilla/mux"
	"github.com/spf13/pflag"
	"github.com/spf13/viper"
	"go.uber.org/zap"
	"log"
	"net/http"
	"os"
)

// Build information obtained with the help of -ldflags
var (
	appName       string
	buildDate     string // date -u
	gitTag        string // git describe --exact-match HEAD
	gitNearestTag string // git describe --abbrev=0 --tags HEAD
	gitCommit     string // git rev-parse HEAD
)

var fVersion bool

func init() {
	flag.BoolVar(&fVersion, "version", false, "Show version")

	viper.SetDefault("port", 2005)
	viper.SetDefault("applog", "stderr")
	viper.SetDefault("httplog", "stderr")
	viper.SetDefault("secret", "changeme")
	viper.SetDefault("signkey", "changeme")
	viper.SetDefault("allowfrom", "swan[a-z0-9-]*.cern.ch")
	viper.SetDefault("shibreferer", "https://login.cern.ch")
	viper.SetDefault("cboxgroupdurl", "http://localhost:2002/api/v1/search")
	viper.SetDefault("cboxgroupdsecret", "changeme")

	viper.SetConfigName("cboxswanapid")
	viper.AddConfigPath("/etc/cboxswanapid/")
	flag.Int("port", 2005, "Port to listen for connections")
	flag.String("applog", "stderr", "File to log application data")
	flag.String("httplog", "stderr", "File to log HTTP requests")
	flag.String("secret", "changeme", "Shared secret with SWAN")
	flag.String("signkey", "changeme", "Secret to sign JWT tokens")
	flag.String("allowfrom", "swan[a-z0-9-]*.cern.ch", "Check the Referer/Origin request header (depending on the endpoint) and return Bad Request if no match.")
	flag.String("shibreferer", "https://login.cern.ch", "Shibolleth referer for /authenticate request.")
	flag.String("cboxgroupdsecret", "", "Shared secret to communicate with the cboxgroupd daemon")
	flag.String("cboxgroupdurl", "http://localhost:2002/api/v1/search", "URL to address the cboxgroupd daemon")
	flag.String("config", "", "Configuration file to use")
	flag.Parse()

	pflag.CommandLine.AddGoFlagSet(flag.CommandLine)
	pflag.Parse()
	viper.BindPFlags(pflag.CommandLine)
}

func main() {

	if fVersion {
		showVersion()
	}

	if viper.GetString("config") != "" {
		viper.SetConfigFile(viper.GetString("config"))
	}

	err := viper.ReadInConfig()
	if err != nil {
		panic(fmt.Errorf("error reading config file: %s", err))
	}

	config := zap.NewProductionConfig()
	config.OutputPaths = []string{viper.GetString("applog")}
	logger, _ := config.Build()

	router := mux.NewRouter()

	tokenHandler := handlers.CheckNothing(logger, handlers.Token(logger, viper.GetString("signkey"), viper.GetString("allowfrom"), viper.GetString("shibreferer")))

	sharedHandler := handlers.CheckJWTToken(logger, viper.GetString("signkey"), handlers.Shared(logger, viper.GetString("allowfrom"), "list-shared-with", false))
	sharingHandler := handlers.CheckJWTToken(logger, viper.GetString("signkey"), handlers.Shared(logger, viper.GetString("allowfrom"), "list-shared-by", false))
	getIndividualShareHandler := handlers.CheckJWTToken(logger, viper.GetString("signkey"), handlers.Shared(logger, viper.GetString("allowfrom"), "list-shared-by", true))
	updateShareHandler := handlers.CheckJWTToken(logger, viper.GetString("signkey"), handlers.UpdateShare(logger, viper.GetString("allowfrom")))
	deleteShareHandler := handlers.CheckJWTToken(logger, viper.GetString("signkey"), handlers.DeleteShare(logger, viper.GetString("allowfrom")))
	searchHandler := handlers.CheckJWTToken(logger, viper.GetString("signkey"), handlers.Search(logger, viper.GetString("cboxgroupdurl"), viper.GetString("cboxgroupdsecret")))
	cloneShareHandler := handlers.CheckJWTToken(logger, viper.GetString("signkey"), handlers.CloneShare(logger, viper.GetString("allowfrom")))
	notFoundHandler := handlers.CheckJWTToken(logger, viper.GetString("signkey"), handlers.Handle404(logger))

	router.NotFoundHandler = notFoundHandler // default protection for non-existing resources is JWT

	router.Handle("/swanapi/v1/authenticate", tokenHandler).Methods("GET")
	router.Handle("/swanapi/v1/shared", sharedHandler).Methods("GET")
	router.Handle("/swanapi/v1/sharing", sharingHandler).Methods("GET")
	router.Handle("/swanapi/v1/share", getIndividualShareHandler).Methods("GET")
	router.Handle("/swanapi/v1/share", updateShareHandler).Methods("PUT")
	router.Handle("/swanapi/v1/share", deleteShareHandler).Methods("DELETE")
	router.Handle("/swanapi/v1/search", searchHandler).Methods("GET")
	router.Handle("/swanapi/v1/clone", cloneShareHandler).Methods("POST")

	router.Handle("/swanapi/v1/shared", handlers.Options(logger, []string{"GET"}, viper.GetString("allowfrom"))).Methods("OPTIONS")
	router.Handle("/swanapi/v1/sharing", handlers.Options(logger, []string{"GET"}, viper.GetString("allowfrom"))).Methods("OPTIONS")
	router.Handle("/swanapi/v1/share", handlers.Options(logger, []string{"GET", "PUT", "DELETE"}, viper.GetString("allowfrom"))).Methods("OPTIONS")
	router.Handle("/swanapi/v1/clone", handlers.Options(logger, []string{"POST"}, viper.GetString("allowfrom"))).Methods("OPTIONS")
	router.Handle("/swanapi/v1/search", handlers.Options(logger, []string{"GET"}, viper.GetString("allowfrom"))).Methods("OPTIONS")

	out := getHTTPLoggerOut(viper.GetString("httplog"))
	loggedRouter := gh.LoggingHandler(out, router)

	logger.Info("server is listening", zap.Int("port", viper.GetInt("port")))
	logger.Warn("server stopped", zap.Error(http.ListenAndServe(fmt.Sprintf(":%d", viper.GetInt("port")), loggedRouter)))
}

func getHTTPLoggerOut(filename string) *os.File {
	if filename == "stderr" {
		return os.Stderr
	} else if filename == "stdout" {
		return os.Stdout
	} else {
		fd, err := os.OpenFile(filename, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0644)
		if err != nil {
			log.Fatal(err)
		}
		return fd
	}
}

func showVersion() {
	// if gitTag is not empty we are on release build
	if gitTag != "" {
		fmt.Printf("%s %s commit:%s release-build\n", appName, gitNearestTag, gitCommit)
		os.Exit(0)
	}
	fmt.Printf("%s %s commit:%s dev-build\n", appName, gitNearestTag, gitCommit)
	os.Exit(0)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"flag"
	"os"
	"log"
	"io"
	"strconv"
	"encoding/csv"
	"bytes"
	"os/exec"
	"strings"
	"errors"
	"encoding/json"

	"github.com/labkode/cernboxshareinfo/proto"
	"github.com/syndtr/goleveldb/leveldb"
)

var sqlFile string
var phonebookBinary string
var phonebookdb string

func main() {
	flag.StringVar(&sqlFile, "sqlfile", "shares.sql", "file containing the mysql oc_shares dump")
	flag.StringVar(&phonebookBinary, "phonebookbinary", "/usr/bin/phonebook", "the phonebook binary")
	flag.StringVar(&phonebookdb, "phonebookdb", "phonebook.db", "phonebook cache db file")
	flag.Parse()

	fmt.Printf("Loading shares from: %s\n", sqlFile)
	fmt.Printf("PhoneBook Application: %s\n", phonebookBinary)

	handle, err := os.Open(sqlFile)
	if err != nil {
		log.Fatal(err)
		os.Exit(1)
	}
	defer handle.Close()

	r := csv.NewReader(handle)
	r.Comma = '\t'

	shareInfos := []*proto.ShareInfo{}
	for {
		record, err := r.Read()
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Fatal(os.Stderr, err)
		} else {

			/*
			*
			* These are the attributes from the MySQL oc_share dump:
			*
			* id: unique identifier of the share
			* share_type: the type of the share: 0 is internal, 1 is group and 3 is by link
			* share_with: the receiver of the share
			* uid_owner: the username that started the share
			* parent: the parent file of the shared one
			* item_type: the type of resource being shared: either file or folder
			* item source: inode of file being shared
			* item_target: name of the file when appears in the sharee filesystem
			* file_source: the inode of the file being shared
			* file_target: the name of the file when appears in the sharee filesystem
			* permissions: the permissions for the sharee
			* stime: the time the share was created
			* accepted: the share was acepted: only valid for federated shares
			* expiration: when the share will expire: only valid for link shares
			* token: the token to access the share: only valid for link shares
			* mail_send: whether the sharee has been informed by email of the new share
			*/

			id, _ := strconv.Atoi(record[0])
			shareType,_ := strconv.Atoi(record[1])
			parent,_ := strconv.Atoi(record[4])
			itemSource,_ := strconv.Atoi(record[6])
			fileSource,_ := strconv.Atoi(record[8])
			stime,_ := strconv.Atoi(record[11])
			accepted,_ := strconv.Atoi(record[12])
			mailSend,_ := strconv.Atoi(record[15])

			shareInfo := &proto.ShareInfo{}
			shareInfo.Id = int64(id)
			shareInfo.ShareType = int64(shareType)
			shareInfo.ShareWith = record[2]
			shareInfo.UidOwner= record[3]
			shareInfo.Parent = int64(parent)
			shareInfo.ItemType= record[5]
			shareInfo.ItemSource = int64(itemSource)
			shareInfo.ItemTarget = record[7]
			shareInfo.FileSource = int64(fileSource)
			shareInfo.FileTarget = record[9]
			shareInfo.Permissions= record[10]
			shareInfo.Stime = int64(stime)
			shareInfo.Accepted = int64(accepted)
			shareInfo.Expiration = record[13]
			shareInfo.Token = record[14]
			shareInfo.MailSend = int64(mailSend)

			// we are interested in user shares
			if shareInfo.ShareType == 0 {
				shareInfos = append(shareInfos, shareInfo)
			}
		}
	}

	userMap := map[string]*proto.PersonInfo{}
	db, err := leveldb.OpenFile(phonebookdb, nil)
	if err != nil {
		fmt.Println(err)
		fmt.Println("phonebook calls won't be cached")
	} else {
		fmt.Println("Loading phonebook db into memory...")
		total := 0
		iter := db.NewIterator(nil, nil)
		for iter.Next() {
			key := iter.Key()
		    	value := iter.Value() // value is json
			personInfo := &proto.PersonInfo{}
			err := json.Unmarshal(value, personInfo)
			if err != nil {
				fmt.Println(err)
			} else {
				userMap[string(key)] = personInfo
				total++
			}
		}
		iter.Release()
		fmt.Printf("Loaded %d entries from phonebook cached db\n", total)
	}

	total := len(shareInfos)
	for i, shareInfo := range shareInfos {
			fmt.Printf("Resolving users (%d)/%d\n", i, total)
			if _, ok := userMap[shareInfo.UidOwner]; !ok {
				personInfo, err := getPersonInfo(shareInfo.UidOwner)
				if err != nil {
					//log.Println(err)
					db.Delete([]byte(shareInfo.UidOwner), nil)
				} else {
					userMap[shareInfo.UidOwner] = personInfo
					if db != nil {
						json, err := json.Marshal(personInfo)
						if err != nil {
							fmt.Println(err)
						} else {
							db.Put([]byte(shareInfo.UidOwner), json, nil)
						}
					}
				}
			}
			if _, ok := userMap[shareInfo.ShareWith]; !ok {
				personInfo, err := getPersonInfo(shareInfo.ShareWith)
				if err != nil {
					//log.Println(err)
					db.Delete([]byte(shareInfo.UidOwner), nil)
				} else {
					userMap[shareInfo.ShareWith] = personInfo
					if db != nil {
						json, err := json.Marshal(personInfo)
						if err != nil {
							fmt.Println(err)
						} else {
							db.Put([]byte(shareInfo.ShareWith), json, nil)
						}
					}
				}
			}
	}

	fmt.Printf("Found %d persons with valid information in phonebook\n", len(userMap))

	flatInfos := []*proto.FlatInfo{}
	// create flat infos with owner and sharee together with share info
	for _, shareInfo := range shareInfos {
		_, ownerFound := userMap[shareInfo.UidOwner]
		_, shareeFound := userMap[shareInfo.ShareWith]
		if ownerFound && shareeFound {
			flatInfo := &proto.FlatInfo{}
			flatInfo.ShareInfo = shareInfo
			flatInfo.OwnerInfo = userMap[shareInfo.UidOwner]
			flatInfo.ShareeInfo= userMap[shareInfo.ShareWith]
			fmt.Println(flatInfo)
			flatInfos = append(flatInfos, flatInfo)
		}
	}

}

func getPersonInfo(username string) (*proto.PersonInfo, error) {
	cmd := exec.Command(phonebookBinary, "--login", username, "-t", "login", "-t", "uid", "-t", "department", "-t", "organization", "-t", "company", "-t", "office")
	stdout, _, err := executeCMD(cmd)
	if err != nil {
		return nil, err
	}
	if stdout == "" {
		return nil, errors.New("user " + username + " does not exist anymore")
	}
	personInfo := &proto.PersonInfo{}
	tokens := strings.Split(stdout, ";")
	uid, _ := strconv.Atoi(tokens[1])
	personInfo.Login = tokens[0]
	personInfo.Uid = int64(uid)
	personInfo.Department = tokens[2]
	personInfo.Organization = tokens[3]
	personInfo.Company = tokens[4]
	personInfo.Office = tokens[5]
	return personInfo, nil
}

func executeCMD(cmd *exec.Cmd) (string, string, error) {
	outBuf := &bytes.Buffer{}
	errBuf := &bytes.Buffer{}
	cmd.Stdout = outBuf
	cmd.Stderr = errBuf
	err := cmd.Run()
	return outBuf.String(), errBuf.String(), err
}
<commit_msg>Add SQL conf params<commit_after>package main

import (
	"bytes"
	"encoding/csv"
	"encoding/json"
	"errors"
	"flag"
	"fmt"
	"io"
	"log"
	"os"
	"os/exec"
	"strconv"
	"strings"

	"database/sql"
	_ "github.com/go-sql-driver/mysql"
	"github.com/knq/dburl"
	"github.com/labkode/cernboxshareinfo/models"
	"github.com/labkode/cernboxshareinfo/proto"
	"github.com/syndtr/goleveldb/leveldb"
)

var sqlFile string
var phonebookBinary string
var phonebookdb string
var mysqlhost string
var mysqlusername string
var mysqlpassword string
var mysqldatabase string
var mysqlport int

func main() {
	flag.StringVar(&sqlFile, "sqlfile", "shares.sql", "file containing the mysql oc_shares dump")
	flag.StringVar(&phonebookBinary, "phonebookbinary", "/usr/bin/phonebook", "the phonebook binary")
	flag.StringVar(&phonebookdb, "phonebookdb", "phonebook.db", "phonebook cache db file")
	flag.StringVar(&mysqlhost, "mysqlhost", "localhost", "mysql hostname")
	flag.StringVar(&mysqlusername, "mysqlusername", "admin", "mysql username")
	flag.StringVar(&mysqlpassword, "mysqlpassword", "admin", "mysql password")
	flag.StringVar(&mysqldatabase, "mysqldatabase", "mydb", "mysql database")
	flag.IntVar(&mysqlport, "mysqlport", 3306, "mysql port")
	flag.Parse()

	fmt.Printf("Loading shares from: %s\n", sqlFile)
	fmt.Printf("PhoneBook Application: %s\n", phonebookBinary)

	handle, err := os.Open(sqlFile)
	if err != nil {
		log.Fatal(err)
		os.Exit(1)
	}
	defer handle.Close()

	r := csv.NewReader(handle)
	r.Comma = '\t'

	shareInfos := []*proto.ShareInfo{}
	for {
		record, err := r.Read()
		if err == io.EOF {
			break
		}
		if err != nil {
			log.Fatal(os.Stderr, err)
		} else {

			/*
			*
			* These are the attributes from the MySQL oc_share dump:
			*
			* id: unique identifier of the share
			* share_type: the type of the share: 0 is internal, 1 is group and 3 is by link
			* share_with: the receiver of the share
			* uid_owner: the username that started the share
			* parent: the parent file of the shared one
			* item_type: the type of resource being shared: either file or folder
			* item source: inode of file being shared
			* item_target: name of the file when appears in the sharee filesystem
			* file_source: the inode of the file being shared
			* file_target: the name of the file when appears in the sharee filesystem
			* permissions: the permissions for the sharee
			* stime: the time the share was created
			* accepted: the share was acepted: only valid for federated shares
			* expiration: when the share will expire: only valid for link shares
			* token: the token to access the share: only valid for link shares
			* mail_send: whether the sharee has been informed by email of the new share
			 */

			id, _ := strconv.Atoi(record[0])
			shareType, _ := strconv.Atoi(record[1])
			parent, _ := strconv.Atoi(record[4])
			itemSource, _ := strconv.Atoi(record[6])
			fileSource, _ := strconv.Atoi(record[8])
			stime, _ := strconv.Atoi(record[11])
			accepted, _ := strconv.Atoi(record[12])
			mailSend, _ := strconv.Atoi(record[15])

			shareInfo := &proto.ShareInfo{}
			shareInfo.Id = int64(id)
			shareInfo.ShareType = int64(shareType)
			shareInfo.ShareWith = record[2]
			shareInfo.UidOwner = record[3]
			shareInfo.Parent = int64(parent)
			shareInfo.ItemType = record[5]
			shareInfo.ItemSource = int64(itemSource)
			shareInfo.ItemTarget = record[7]
			shareInfo.FileSource = int64(fileSource)
			shareInfo.FileTarget = record[9]
			shareInfo.Permissions = record[10]
			shareInfo.Stime = int64(stime)
			shareInfo.Accepted = int64(accepted)
			shareInfo.Expiration = record[13]
			shareInfo.Token = record[14]
			shareInfo.MailSend = int64(mailSend)

			// we are interested in user shares
			if shareInfo.ShareType == 0 {
				shareInfos = append(shareInfos, shareInfo)
			}
		}
	}

	userMap := map[string]*proto.PersonInfo{}
	db, err := leveldb.OpenFile(phonebookdb, nil)
	if err != nil {
		fmt.Println(err)
		fmt.Println("phonebook calls won't be cached")
	} else {
		fmt.Println("Loading phonebook db into memory...")
		total := 0
		iter := db.NewIterator(nil, nil)
		for iter.Next() {
			key := iter.Key()
			value := iter.Value() // value is json
			personInfo := &proto.PersonInfo{}
			err := json.Unmarshal(value, personInfo)
			if err != nil {
				fmt.Println(err)
			} else {
				userMap[string(key)] = personInfo
				total++
			}
		}
		iter.Release()
		fmt.Printf("Loaded %d entries from phonebook cached db\n", total)
	}

	total := len(shareInfos)
	for i, shareInfo := range shareInfos {
		fmt.Printf("Resolving users (%d)/%d\n", i, total)
		if _, ok := userMap[shareInfo.UidOwner]; !ok {
			personInfo, err := getPersonInfo(shareInfo.UidOwner)
			if err != nil {
				log.Println(err)
				db.Delete([]byte(shareInfo.UidOwner), nil)
			} else {
				userMap[shareInfo.UidOwner] = personInfo
				if db != nil {
					json, err := json.Marshal(personInfo)
					if err != nil {
						fmt.Println(err)
					} else {
						db.Put([]byte(shareInfo.UidOwner), json, nil)
					}
				}
			}
		}
		if _, ok := userMap[shareInfo.ShareWith]; !ok {
			personInfo, err := getPersonInfo(shareInfo.ShareWith)
			if err != nil {
				log.Println(err)
				db.Delete([]byte(shareInfo.UidOwner), nil)
			} else {
				userMap[shareInfo.ShareWith] = personInfo
				if db != nil {
					json, err := json.Marshal(personInfo)
					if err != nil {
						fmt.Println(err)
					} else {
						db.Put([]byte(shareInfo.ShareWith), json, nil)
					}
				}
			}
		}
	}

	fmt.Printf("Found %d persons with valid information in phonebook\n", len(userMap))

	flatInfos := []*proto.FlatInfo{}
	// create flat infos with owner and sharee together with share info
	for _, shareInfo := range shareInfos {
		_, ownerFound := userMap[shareInfo.UidOwner]
		_, shareeFound := userMap[shareInfo.ShareWith]
		if ownerFound && shareeFound {
			flatInfo := &proto.FlatInfo{}
			flatInfo.ShareInfo = shareInfo
			flatInfo.OwnerInfo = userMap[shareInfo.UidOwner]
			flatInfo.ShareeInfo = userMap[shareInfo.ShareWith]
			flatInfos = append(flatInfos, flatInfo)
		}
	}

	// forward data to sql
	sqldb, err := dburl.Open(fmt.Sprintf("mysql://%s:%s@%s:%d/%s", mysqlusername, mysqlpassword, mysqlhost, mysqlport, mysqldatabase))
	if err != nil {
		log.Fatal(err)
	}

	createCompanies(sqldb, flatInfos)
}

func createCompanies(db *sql.DB, flatInfos []*proto.FlatInfo) {
	companies := map[string]*proto.FlatInfo{}
	for _, flatInfo := range flatInfos {
		if _, ok := companies[flatInfo.GetOwnerInfo().Company]; !ok {
			companies[flatInfo.GetOwnerInfo().Company] = flatInfo
		}
		if _, ok := companies[flatInfo.GetShareeInfo().Company]; !ok {
			companies[flatInfo.GetShareeInfo().Company] = flatInfo
		}
	}
	for pk := range companies {
		company := &models.DimensionCompany{}
		company.Company = pk
		if err := company.Save(db); err != nil {
			log.Fatal(err)
		}
	}
}
func getPersonInfo(username string) (*proto.PersonInfo, error) {
	cmd := exec.Command(phonebookBinary, "--login", username, "-t", "login", "-t", "uid", "-t", "department", "-t", "organization", "-t", "company", "-t", "office")
	stdout, _, err := executeCMD(cmd)
	if err != nil {
		return nil, err
	}
	if stdout == "" {
		return nil, errors.New("user " + username + " does not exist anymore")
	}
	personInfo := &proto.PersonInfo{}
	tokens := strings.Split(stdout, ";")
	uid, _ := strconv.Atoi(tokens[1])
	personInfo.Login = tokens[0]
	personInfo.Uid = int64(uid)
	personInfo.Department = tokens[2]
	personInfo.Organization = tokens[3]
	personInfo.Company = tokens[4]
	personInfo.Office = tokens[5]
	return personInfo, nil
}

func executeCMD(cmd *exec.Cmd) (string, string, error) {
	outBuf := &bytes.Buffer{}
	errBuf := &bytes.Buffer{}
	cmd.Stdout = outBuf
	cmd.Stderr = errBuf
	err := cmd.Run()
	return outBuf.String(), errBuf.String(), err
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"io/ioutil"
	"encoding/json"
	"strings"
	"github.com/line/line-bot-sdk-go/linebot"
	"github.com/go-redis/redis"
	"strconv"
	"math"
	"regexp"
)

type device struct {
	// Gps_num float32 `json:"gps_num"`
	// App string `json:"app"`
	// Gps_alt float32 `json:"gps_alt"`
	// Fmt_opt int `json:"fmt_opt"`
	// Device string `json:"device"`
	// S_d2 float32 `json:"s_d2"`
	S_d0 float32 `json:"s_d0"`
	// S_d1 float32 `json:"s_d1"`
	S_h0 float32 `json:"s_h0"`
	SiteName string `json:"SiteName"`
	// Gps_fix float32 `json:"gps_fix"`
	// Ver_app string `json:"ver_app"`
	Gps_lat float64 `json:"gps_lat"`
	S_t0 float32 `json:"s_t0"`
	Timestamp string `json:"timestamp"`
	Gps_lon float64 `json:"gps_lon"`
	// Date string `json:"date"`
	// Tick float32 `json:"tick"`
	Device_id string `json:"device_id"`
	// S_1 float32 `json:"s_1"`
	// S_0 float32 `json:"s_0"`
	// S_3 float32 `json:"s_3"`
	// S_2 float32 `json:"s_2"`
	// Ver_format string `json:"ver_format"`
	// Time string `json:"time"`
}

type airbox struct {
	Source string `json:"source"`
	Feeds []device `json:"feeds"`
	Version string `json:"version"`
	Num_of_records int `json:"num_of_records"`
}

type subscribeid struct {
	Device_id []string `json:"device_id"`
	Sitename []string `json:"sitename"`
}

var bot *linebot.Client
var airbox_json airbox
var lass_json airbox
var maps_json airbox
var all_device []device
var history_json subscribeid
var	client=redis.NewClient(&redis.Options{
		Addr:"hipposerver.ddns.net:6379",
		Password:"",
		DB:0,
	})

func main() {
	url := "https://data.lass-net.org/data/last-all-airbox.json"
	req, _ := http.NewRequest("GET", url, nil)
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)
	errs := json.Unmarshal(body, &airbox_json)
	if errs != nil {
		fmt.Println(errs)
	}

	url = "https://data.lass-net.org/data/last-all-lass.json"
	req, _ = http.NewRequest("GET", url, nil)
	res, _ = http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ = ioutil.ReadAll(res.Body)
	errs = json.Unmarshal(body, &lass_json)
	if errs != nil {
		fmt.Println(errs)
	}

	url = "https://data.lass-net.org/data/last-all-maps.json"
	req, _ = http.NewRequest("GET", url, nil)
	res, _ = http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ = ioutil.ReadAll(res.Body)
	errs = json.Unmarshal(body, &maps_json)
	if errs != nil {
		fmt.Println(errs)
	}

	all_device=append(maps_json.Feeds,lass_json.Feeds...)
	all_device=append(all_device,airbox_json.Feeds...)

	url = "https://data.lass-net.org/data/airbox_list.json"
	req, _ = http.NewRequest("GET", url, nil)
	res, _ = http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ = ioutil.ReadAll(res.Body)
	errs = json.Unmarshal(body, &history_json)
	if errs != nil {
		fmt.Println(errs)
	}
	// pushmessage()
	// fmt.Println(airbox_json)
	var err error
	bot, err = linebot.New(os.Getenv("ChannelSecret"), os.Getenv("ChannelAccessToken"))
	// _,_=bot.PushMessage("U3617adbdd46283d7e859f36302f4f471", linebot.NewTextMessage("hi!")).Do()
	log.Println("Bot:", bot, " err:", err)
	http.HandleFunc("/callback", callbackHandler)
	port := os.Getenv("PORT")
	addr := fmt.Sprintf(":%s", port)
	http.ListenAndServe(addr, nil)

}

func callbackHandler(w http.ResponseWriter, r *http.Request) {
	events, err := bot.ParseRequest(r)

	if err != nil {
		if err == linebot.ErrInvalidSignature {
			w.WriteHeader(400)
		} else {
			w.WriteHeader(500)
		}
		return
	}

	for _, event := range events {
		if event.Type == linebot.EventTypeMessage {
			switch message := event.Message.(type) {
			case *linebot.TextMessage:
				var txtmessage string
				inText := strings.ToLower(message.Text)
				if strings.Contains(inText,"訂閱"){
					userID:=event.Source.UserID
					// pong, _ := client.Ping().Result()
					// txtmessage=pong
					for i:=0; i<len(history_json.Device_id); i++ {
						if strings.Contains(inText,strings.ToLower(history_json.Device_id[i]))||strings.Contains(inText,history_json.Sitename[i]) {
							val, err:=client.Get(history_json.Device_id[i]).Result()
							if err!=nil{
								if strings.Contains(inText,"取消"){
									txtmessage="你並沒有訂閱此ID。"
									break
								}
								client.Set(history_json.Device_id[i],userID,0)
								txtmessage="訂閱成功!"
								break
							}
							if strings.Contains(inText,"取消"){
								stringSlice:=strings.Split(val,",")
								if stringInSlice(userID,stringSlice){
									if len(stringSlice)==1{
										err=client.Del(history_json.Device_id[i]).Err()
										if err!=nil{
											txtmessage=err.Error()
											break
										}
										txtmessage="取消訂閱成功!"
										break
									}else{
										var s []string
										s = removeStringInSlice(stringSlice, userID)
										client.Set(history_json.Device_id[i],s,0)
										txtmessage="取消訂閱成功!"
										break
									}
								}else{
									txtmessage="你並沒有訂閱此ID。"
									break
								}
							}
							stringSlice:=strings.Split(val,",")
							if stringInSlice(userID,stringSlice){
								txtmessage="您已訂閱過此ID!"
								break
							} else{
								val=val+","+userID
								client.Set(history_json.Device_id[i],val,0)
								txtmessage="訂閱成功!"
								break
							}
						}
					}
				} else if strings.Contains(inText,"門檻值"){
					// 新增門檻值
					userID:=event.Source.UserID
					client2:=redis.NewClient(&redis.Options{
							Addr:"hipposerver.ddns.net:6379",
							Password:"",
							DB:1,
					})
					re:=regexp.MustCompile("[0-9]+")
					number:=re.FindAllString(inText,-1)
					threshold=number[len(number)-1]
					val, err:=client2.Get(userID).Result()
					if err!=nil{
						client2.Set(userID,threshold,0)
						txtmessage="已為您將門檻值設為"+strconv.Itoa(threshold)+"，當您訂閱的AirBox超過這個門檻值將會發出警告！"
					}
					else{
						client2.Set(userID,threshold,0)
						txtmessage="已為您將門檻值從"+val+"改為"+strconv.Itoa(threshold)+"，當您訂閱的AirBox超過這個門檻值將會發出警告！"
					}
				} else{
					for i:=0; i<len(all_device); i++ {
						if strings.Contains(inText,strings.ToLower(all_device[i].Device_id)) {
							txtmessage="Device_id: "+all_device[i].Device_id+"\n"
							txtmessage=txtmessage+"Site Name: "+all_device[i].SiteName+"\n"
							txtmessage=txtmessage+"Location: ("+strconv.FormatFloat(float64(all_device[i].Gps_lon),'f',3,64)+","+strconv.FormatFloat(float64(all_device[i].Gps_lat),'f',3,64)+")"+"\n"
							txtmessage=txtmessage+"Timestamp: "+all_device[i].Timestamp+"\n"
							txtmessage=txtmessage+"PM2.5: "+strconv.FormatFloat(float64(all_device[i].S_d0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Humidity: "+strconv.FormatFloat(float64(all_device[i].S_h0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Temperature: "+strconv.FormatFloat(float64(all_device[i].S_t0),'f',0,64)
							break
						} else if len(all_device[i].SiteName)!=0 && strings.Contains(inText,all_device[i].SiteName){
							txtmessage="Device_id: "+all_device[i].Device_id+"\n"
							txtmessage=txtmessage+"Site Name: "+all_device[i].SiteName+"\n"
							txtmessage=txtmessage+"Location: ("+strconv.FormatFloat(float64(all_device[i].Gps_lon),'f',3,64)+","+strconv.FormatFloat(float64(all_device[i].Gps_lat),'f',3,64)+")"+"\n"
							txtmessage=txtmessage+"Timestamp: "+all_device[i].Timestamp+"\n"
							txtmessage=txtmessage+"PM2.5: "+strconv.FormatFloat(float64(all_device[i].S_d0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Humidity: "+strconv.FormatFloat(float64(all_device[i].S_h0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Temperature: "+strconv.FormatFloat(float64(all_device[i].S_t0),'f',0,64)
							break
						}
					}
				}
				if len(txtmessage)==0{
					txtmessage="很抱歉! 這個AirBox ID不存在或是不提供即時資訊查詢。"
				}
				if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(txtmessage)).Do(); err != nil {
					log.Print(err)
				}
			case *linebot.LocationMessage:
				var txtmessage string
				lat := message.Latitude
				lon := message.Longitude
				minD := math.MaxFloat64
				for i:=0; i<len(all_device); i++ {
					if all_device[i].Gps_lat<=lat+0.05 && all_device[i].Gps_lat>=lat-0.05 && all_device[i].Gps_lon<=lon+0.05 && all_device[i].Gps_lon>=lon-0.05{
						D:=distanceInKmBetweenEarthCoordinates(lat, lon, all_device[i].Gps_lat, all_device[i].Gps_lon)
						if D<minD{
							minD=D
							txtmessage="離您所提供的位置最近的AirBox資訊如以下所示：\n"
							txtmessage=txtmessage+"Device_id: "+all_device[i].Device_id+"\n"
							txtmessage=txtmessage+"Site Name: "+all_device[i].SiteName+"\n"
							txtmessage=txtmessage+"Location: ("+strconv.FormatFloat(float64(all_device[i].Gps_lon),'f',3,64)+","+strconv.FormatFloat(float64(all_device[i].Gps_lat),'f',3,64)+")"+"\n"
							txtmessage=txtmessage+"Timestamp: "+all_device[i].Timestamp+"\n"
							txtmessage=txtmessage+"PM2.5: "+strconv.FormatFloat(float64(all_device[i].S_d0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Humidity: "+strconv.FormatFloat(float64(all_device[i].S_h0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Temperature: "+strconv.FormatFloat(float64(all_device[i].S_t0),'f',0,64)
						}
					} else{
						continue
					}
				}
				if len(txtmessage)==0{
					txtmessage="很抱歉這附近1公里內沒有任何上線的AirBox。"
				}
				if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(txtmessage)).Do(); err != nil {
					log.Print(err)
				}
			}
		}
	}
}

func stringInSlice(a string, list []string) bool {
    for _, b := range list {
        if b == a {
            return true
        }
    }
    return false
}

func removeStringInSlice(s []string, r string) []string {
    for i, v := range s {
        if v == r {
            return append(s[:i], s[i+1:]...)
        }
    }
    return s
}

func degreesToRadians(degrees float64) float64 {
  return degrees * math.Pi / 180;
}

func distanceInKmBetweenEarthCoordinates(lat1 float64, lon1 float64, lat2 float64, lon2 float64) float64 {
  var earthRadiusKm = 6371.0;

  var dLat = degreesToRadians(lat2-lat1);
  var dLon = degreesToRadians(lon2-lon1);

  lat1 = degreesToRadians(lat1);
  lat2 = degreesToRadians(lat2);

  var a = math.Sin(dLat/2) * math.Sin(dLat/2) +
          math.Sin(dLon/2) * math.Sin(dLon/2) * math.Cos(lat1) * math.Cos(lat2);
  var c = 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a));
  return earthRadiusKm * c;
}

<commit_msg>resolve some syntax errors<commit_after>package main

import (
	"fmt"
	"log"
	"net/http"
	"os"
	"io/ioutil"
	"encoding/json"
	"strings"
	"github.com/line/line-bot-sdk-go/linebot"
	"github.com/go-redis/redis"
	"strconv"
	"math"
	"regexp"
)

type device struct {
	// Gps_num float32 `json:"gps_num"`
	// App string `json:"app"`
	// Gps_alt float32 `json:"gps_alt"`
	// Fmt_opt int `json:"fmt_opt"`
	// Device string `json:"device"`
	// S_d2 float32 `json:"s_d2"`
	S_d0 float32 `json:"s_d0"`
	// S_d1 float32 `json:"s_d1"`
	S_h0 float32 `json:"s_h0"`
	SiteName string `json:"SiteName"`
	// Gps_fix float32 `json:"gps_fix"`
	// Ver_app string `json:"ver_app"`
	Gps_lat float64 `json:"gps_lat"`
	S_t0 float32 `json:"s_t0"`
	Timestamp string `json:"timestamp"`
	Gps_lon float64 `json:"gps_lon"`
	// Date string `json:"date"`
	// Tick float32 `json:"tick"`
	Device_id string `json:"device_id"`
	// S_1 float32 `json:"s_1"`
	// S_0 float32 `json:"s_0"`
	// S_3 float32 `json:"s_3"`
	// S_2 float32 `json:"s_2"`
	// Ver_format string `json:"ver_format"`
	// Time string `json:"time"`
}

type airbox struct {
	Source string `json:"source"`
	Feeds []device `json:"feeds"`
	Version string `json:"version"`
	Num_of_records int `json:"num_of_records"`
}

type subscribeid struct {
	Device_id []string `json:"device_id"`
	Sitename []string `json:"sitename"`
}

var bot *linebot.Client
var airbox_json airbox
var lass_json airbox
var maps_json airbox
var all_device []device
var history_json subscribeid
var	client=redis.NewClient(&redis.Options{
		Addr:"hipposerver.ddns.net:6379",
		Password:"",
		DB:0,
	})

func main() {
	url := "https://data.lass-net.org/data/last-all-airbox.json"
	req, _ := http.NewRequest("GET", url, nil)
	res, _ := http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ := ioutil.ReadAll(res.Body)
	errs := json.Unmarshal(body, &airbox_json)
	if errs != nil {
		fmt.Println(errs)
	}

	url = "https://data.lass-net.org/data/last-all-lass.json"
	req, _ = http.NewRequest("GET", url, nil)
	res, _ = http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ = ioutil.ReadAll(res.Body)
	errs = json.Unmarshal(body, &lass_json)
	if errs != nil {
		fmt.Println(errs)
	}

	url = "https://data.lass-net.org/data/last-all-maps.json"
	req, _ = http.NewRequest("GET", url, nil)
	res, _ = http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ = ioutil.ReadAll(res.Body)
	errs = json.Unmarshal(body, &maps_json)
	if errs != nil {
		fmt.Println(errs)
	}

	all_device=append(maps_json.Feeds,lass_json.Feeds...)
	all_device=append(all_device,airbox_json.Feeds...)

	url = "https://data.lass-net.org/data/airbox_list.json"
	req, _ = http.NewRequest("GET", url, nil)
	res, _ = http.DefaultClient.Do(req)
	defer res.Body.Close()
	body, _ = ioutil.ReadAll(res.Body)
	errs = json.Unmarshal(body, &history_json)
	if errs != nil {
		fmt.Println(errs)
	}
	// pushmessage()
	// fmt.Println(airbox_json)
	var err error
	bot, err = linebot.New(os.Getenv("ChannelSecret"), os.Getenv("ChannelAccessToken"))
	// _,_=bot.PushMessage("U3617adbdd46283d7e859f36302f4f471", linebot.NewTextMessage("hi!")).Do()
	log.Println("Bot:", bot, " err:", err)
	http.HandleFunc("/callback", callbackHandler)
	port := os.Getenv("PORT")
	addr := fmt.Sprintf(":%s", port)
	http.ListenAndServe(addr, nil)

}

func callbackHandler(w http.ResponseWriter, r *http.Request) {
	events, err := bot.ParseRequest(r)

	if err != nil {
		if err == linebot.ErrInvalidSignature {
			w.WriteHeader(400)
		} else {
			w.WriteHeader(500)
		}
		return
	}

	for _, event := range events {
		if event.Type == linebot.EventTypeMessage {
			switch message := event.Message.(type) {
			case *linebot.TextMessage:
				var txtmessage string
				inText := strings.ToLower(message.Text)
				if strings.Contains(inText,"訂閱"){
					userID:=event.Source.UserID
					// pong, _ := client.Ping().Result()
					// txtmessage=pong
					for i:=0; i<len(history_json.Device_id); i++ {
						if strings.Contains(inText,strings.ToLower(history_json.Device_id[i]))||strings.Contains(inText,history_json.Sitename[i]) {
							val, err:=client.Get(history_json.Device_id[i]).Result()
							if err!=nil{
								if strings.Contains(inText,"取消"){
									txtmessage="你並沒有訂閱此ID。"
									break
								}
								client.Set(history_json.Device_id[i],userID,0)
								txtmessage="訂閱成功!"
								break
							}
							if strings.Contains(inText,"取消"){
								stringSlice:=strings.Split(val,",")
								if stringInSlice(userID,stringSlice){
									if len(stringSlice)==1{
										err=client.Del(history_json.Device_id[i]).Err()
										if err!=nil{
											txtmessage=err.Error()
											break
										}
										txtmessage="取消訂閱成功!"
										break
									}else{
										var s []string
										s = removeStringInSlice(stringSlice, userID)
										client.Set(history_json.Device_id[i],s,0)
										txtmessage="取消訂閱成功!"
										break
									}
								}else{
									txtmessage="你並沒有訂閱此ID。"
									break
								}
							}
							stringSlice:=strings.Split(val,",")
							if stringInSlice(userID,stringSlice){
								txtmessage="您已訂閱過此ID!"
								break
							} else{
								val=val+","+userID
								client.Set(history_json.Device_id[i],val,0)
								txtmessage="訂閱成功!"
								break
							}
						}
					}
				} else if strings.Contains(inText,"門檻值"){
					// 新增門檻值
					userID:=event.Source.UserID
					client2:=redis.NewClient(&redis.Options{
							Addr:"hipposerver.ddns.net:6379",
							Password:"",
							DB:1,
					})
					re:=regexp.MustCompile("[0-9]+")
					number:=re.FindAllString(inText,-1)
					threshold=number[len(number)-1]
					val, err:=client2.Get(userID).Result()
					if err!=nil{
						client2.Set(userID,threshold,0)
						txtmessage="已為您將門檻值設為"+strconv.Itoa(threshold)+"，當您訂閱的AirBox超過這個門檻值將會發出警告！"
					} else{
						client2.Set(userID,threshold,0)
						txtmessage="已為您將門檻值從"+val+"改為"+strconv.Itoa(threshold)+"，當您訂閱的AirBox超過這個門檻值將會發出警告！"
					}
				} else{
					for i:=0; i<len(all_device); i++ {
						if strings.Contains(inText,strings.ToLower(all_device[i].Device_id)) {
							txtmessage="Device_id: "+all_device[i].Device_id+"\n"
							txtmessage=txtmessage+"Site Name: "+all_device[i].SiteName+"\n"
							txtmessage=txtmessage+"Location: ("+strconv.FormatFloat(float64(all_device[i].Gps_lon),'f',3,64)+","+strconv.FormatFloat(float64(all_device[i].Gps_lat),'f',3,64)+")"+"\n"
							txtmessage=txtmessage+"Timestamp: "+all_device[i].Timestamp+"\n"
							txtmessage=txtmessage+"PM2.5: "+strconv.FormatFloat(float64(all_device[i].S_d0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Humidity: "+strconv.FormatFloat(float64(all_device[i].S_h0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Temperature: "+strconv.FormatFloat(float64(all_device[i].S_t0),'f',0,64)
							break
						} else if len(all_device[i].SiteName)!=0 && strings.Contains(inText,all_device[i].SiteName){
							txtmessage="Device_id: "+all_device[i].Device_id+"\n"
							txtmessage=txtmessage+"Site Name: "+all_device[i].SiteName+"\n"
							txtmessage=txtmessage+"Location: ("+strconv.FormatFloat(float64(all_device[i].Gps_lon),'f',3,64)+","+strconv.FormatFloat(float64(all_device[i].Gps_lat),'f',3,64)+")"+"\n"
							txtmessage=txtmessage+"Timestamp: "+all_device[i].Timestamp+"\n"
							txtmessage=txtmessage+"PM2.5: "+strconv.FormatFloat(float64(all_device[i].S_d0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Humidity: "+strconv.FormatFloat(float64(all_device[i].S_h0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Temperature: "+strconv.FormatFloat(float64(all_device[i].S_t0),'f',0,64)
							break
						}
					}
				}
				if len(txtmessage)==0{
					txtmessage="很抱歉! 這個AirBox ID不存在或是不提供即時資訊查詢。"
				}
				if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(txtmessage)).Do(); err != nil {
					log.Print(err)
				}
			case *linebot.LocationMessage:
				var txtmessage string
				lat := message.Latitude
				lon := message.Longitude
				minD := math.MaxFloat64
				for i:=0; i<len(all_device); i++ {
					if all_device[i].Gps_lat<=lat+0.05 && all_device[i].Gps_lat>=lat-0.05 && all_device[i].Gps_lon<=lon+0.05 && all_device[i].Gps_lon>=lon-0.05{
						D:=distanceInKmBetweenEarthCoordinates(lat, lon, all_device[i].Gps_lat, all_device[i].Gps_lon)
						if D<minD{
							minD=D
							txtmessage="離您所提供的位置最近的AirBox資訊如以下所示：\n"
							txtmessage=txtmessage+"Device_id: "+all_device[i].Device_id+"\n"
							txtmessage=txtmessage+"Site Name: "+all_device[i].SiteName+"\n"
							txtmessage=txtmessage+"Location: ("+strconv.FormatFloat(float64(all_device[i].Gps_lon),'f',3,64)+","+strconv.FormatFloat(float64(all_device[i].Gps_lat),'f',3,64)+")"+"\n"
							txtmessage=txtmessage+"Timestamp: "+all_device[i].Timestamp+"\n"
							txtmessage=txtmessage+"PM2.5: "+strconv.FormatFloat(float64(all_device[i].S_d0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Humidity: "+strconv.FormatFloat(float64(all_device[i].S_h0),'f',0,64)+"\n"
							txtmessage=txtmessage+"Temperature: "+strconv.FormatFloat(float64(all_device[i].S_t0),'f',0,64)
						}
					} else{
						continue
					}
				}
				if len(txtmessage)==0{
					txtmessage="很抱歉這附近1公里內沒有任何上線的AirBox。"
				}
				if _, err = bot.ReplyMessage(event.ReplyToken, linebot.NewTextMessage(txtmessage)).Do(); err != nil {
					log.Print(err)
				}
			}
		}
	}
}

func stringInSlice(a string, list []string) bool {
    for _, b := range list {
        if b == a {
            return true
        }
    }
    return false
}

func removeStringInSlice(s []string, r string) []string {
    for i, v := range s {
        if v == r {
            return append(s[:i], s[i+1:]...)
        }
    }
    return s
}

func degreesToRadians(degrees float64) float64 {
  return degrees * math.Pi / 180;
}

func distanceInKmBetweenEarthCoordinates(lat1 float64, lon1 float64, lat2 float64, lon2 float64) float64 {
  var earthRadiusKm = 6371.0;

  var dLat = degreesToRadians(lat2-lat1);
  var dLon = degreesToRadians(lon2-lon1);

  lat1 = degreesToRadians(lat1);
  lat2 = degreesToRadians(lat2);

  var a = math.Sin(dLat/2) * math.Sin(dLat/2) +
          math.Sin(dLon/2) * math.Sin(dLon/2) * math.Cos(lat1) * math.Cos(lat2);
  var c = 2 * math.Atan2(math.Sqrt(a), math.Sqrt(1-a));
  return earthRadiusKm * c;
}

<|endoftext|>