<commit_before>package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"strings"

	"github.com/brotherlogic/godiscogs"
	"golang.org/x/net/context"

	pbd "github.com/brotherlogic/godiscogs"
)

import "github.com/golang/protobuf/proto"

import "strconv"

import "time"

import pb "github.com/brotherlogic/discogssyncer/server"

// GetRelease Gets the release and metadata for the release
func (syncer *Syncer) GetRelease(id int, folder int) (*pbd.Release, *pb.ReleaseMetadata) {
	releaseData, err := ioutil.ReadFile(syncer.saveLocation + "/" + strconv.Itoa(folder) + "/" + strconv.Itoa(id) + ".release")
	if err != nil {
		return nil, nil
	}

	metadataData, _ := ioutil.ReadFile(syncer.saveLocation + "/static-metadata/" + strconv.Itoa(id) + ".metadata")
	release := &pbd.Release{}
	metadata := &pb.ReleaseMetadata{}

	proto.Unmarshal(releaseData, release)
	proto.Unmarshal(metadataData, metadata)

	return release, metadata
}

func (syncer *Syncer) saveMetadata(rel *godiscogs.Release) {
	metadataRoot := syncer.saveLocation + "/static-metadata/"
	metadataPath := metadataRoot + strconv.Itoa(int(rel.Id)) + ".metadata"
	if _, err := os.Stat(metadataRoot); os.IsNotExist(err) {
		os.MkdirAll(metadataRoot, 0777)
	}

	metadata := &pb.ReleaseMetadata{}
	if _, err := os.Stat(metadataPath); os.IsNotExist(err) {
		metadata.DateAdded = time.Now().Unix()
		metadata.DateRefreshed = time.Now().Unix()
	} else {
		data, _ := ioutil.ReadFile(metadataPath)
		proto.Unmarshal(data, metadata)
		metadata.DateRefreshed = time.Now().Unix()
	}
	data, _ := proto.Marshal(metadata)
	ioutil.WriteFile(metadataPath, data, 0644)
}

func (syncer *Syncer) deleteRelease(rel *godiscogs.Release, folder int) {
	os.Remove(syncer.saveLocation + "/" + strconv.Itoa(folder) + "/" + strconv.Itoa(int(rel.Id)) + ".release")
}

func (syncer *Syncer) saveRelease(rel *godiscogs.Release, folder int) {
	//Check that the save folder exists
	savePath := syncer.saveLocation + "/" + strconv.Itoa(folder) + "/"
	if _, err := os.Stat(savePath); os.IsNotExist(err) {
		os.MkdirAll(savePath, 0777)
	}

	data, _ := proto.Marshal(rel)
	ioutil.WriteFile(savePath+strconv.Itoa(int(rel.Id))+".release", data, 0644)
	syncer.saveMetadata(rel)
}

type saver interface {
	GetCollection() []godiscogs.Release
	GetFolders() []godiscogs.Folder
	GetRelease(id int) (godiscogs.Release, error)
	MoveToFolder(folderID int, releaseID int, instanceID int, newFolderID int)
	AddToFolder(folderID int, releaseID int)
	SetRating(folderID int, releaseID int, instanceID int, rating int)
	GetWantlist() ([]pbd.Release, error)
}

// SaveCollection writes out the full collection to files.
func (syncer *Syncer) SaveCollection(retr saver) {
	releases := retr.GetCollection()
	for _, release := range releases {
		fullRelease, _ := retr.GetRelease(int(release.Id))
		fullRelease.InstanceId = release.InstanceId
		fullRelease.FolderId = release.FolderId
		fullRelease.Rating = release.Rating
		syncer.saveRelease(&fullRelease, int(release.FolderId))
	}
	folders := retr.GetFolders()
	folderList := pb.FolderList{}
	for i := range folders {
		folder := folders[i]
		folderList.Folders = append(folderList.Folders, &folder)
	}
	syncer.SaveFolders(&folderList)
}

// SyncWantlist syncs the wantlist with the server
func (syncer *Syncer) SyncWantlist() {
	wants, _ := syncer.retr.GetWantlist()

	for _, want := range wants {
		seen := false
		var val *pb.Want
		for _, swant := range syncer.wants.Want {
			if swant.ReleaseId == want.Id {
				seen = true
				val = swant
			}
		}

		if seen {
			val.Wanted = true
		} else {
			syncer.wants.Want = append(syncer.wants.Want, &pb.Want{ReleaseId: want.Id, Valued: false, Wanted: true})
		}
	}

	// Cache the want list releases
	for _, want := range syncer.wants.Want {
		release, _ := syncer.retr.GetRelease(int(want.ReleaseId))
		syncer.saveRelease(&release, -5)
	}

	syncer.saveWantList()
}

func (syncer *Syncer) getFolders() *pb.FolderList {
	data, _ := ioutil.ReadFile(syncer.saveLocation + "/metadata/folders")
	folderData := &pb.FolderList{}
	proto.Unmarshal(data, folderData)
	return folderData
}

// GetSingleRelease gets a single release
func (syncer *Syncer) GetSingleRelease(ctx context.Context, in *pbd.Release) (*pbd.Release, error) {
	if val, ok := syncer.relMap[in.Id]; ok {
		return val, nil
	}
	return nil, errors.New("Unable to find release")
}

// MoveToFolder moves a release to the specified folder
func (syncer *Syncer) MoveToFolder(ctx context.Context, in *pb.ReleaseMove) (*pb.Empty, error) {
	syncer.retr.MoveToFolder(int(in.Release.FolderId), int(in.Release.Id), int(in.Release.InstanceId), int(in.NewFolderId))
	oldFolder := int(in.Release.FolderId)
	fullRelease, _ := syncer.retr.GetRelease(int(in.Release.Id))
	fullRelease.FolderId = int32(in.NewFolderId)
	syncer.relMap[fullRelease.Id] = &fullRelease

	syncer.Log(fmt.Sprintf("Moving %v from %v to %v", in.Release.Id, in.Release.FolderId, in.NewFolderId))
	syncer.saveRelease(&fullRelease, int(in.NewFolderId))
	syncer.deleteRelease(&fullRelease, oldFolder)
	return &pb.Empty{}, nil
}

// AddToFolder adds a release to the specified folder
func (syncer *Syncer) AddToFolder(ctx context.Context, in *pb.ReleaseMove) (*pb.Empty, error) {
	syncer.retr.AddToFolder(int(in.NewFolderId), int(in.Release.Id))
	fullRelease, _ := syncer.retr.GetRelease(int(in.Release.Id))
	fullRelease.FolderId = int32(in.NewFolderId)
	syncer.saveRelease(&fullRelease, int(in.NewFolderId))
	syncer.relMap[in.Release.Id] = &fullRelease
	return &pb.Empty{}, nil
}

// UpdateRating updates the rating of a release
func (syncer *Syncer) UpdateRating(ctx context.Context, in *pbd.Release) (*pb.Empty, error) {
	syncer.retr.SetRating(int(in.FolderId), int(in.Id), int(in.InstanceId), int(in.Rating))
	fullRelease, _ := syncer.GetRelease(int(in.Id), int(in.FolderId))
	fullRelease.Rating = int32(in.Rating)
	syncer.relMap[in.Id] = fullRelease
	syncer.saveRelease(fullRelease, int(fullRelease.FolderId))
	return &pb.Empty{}, nil
}

// UpdateMetadata updates the metadata of a given record
func (syncer *Syncer) UpdateMetadata(ctx context.Context, in *pb.MetadataUpdate) (*pb.ReleaseMetadata, error) {
	release, metadata := syncer.GetRelease(int(in.Release.Id), int(in.Release.FolderId))
	if release == nil {
		return nil, errors.New("Unable to locate release")
	}
	proto.Merge(metadata, in.Update)

	metadataRoot := syncer.saveLocation + "/static-metadata/"
	metadataPath := metadataRoot + strconv.Itoa(int(in.Release.Id)) + ".metadata"
	data, _ := proto.Marshal(metadata)
	ioutil.WriteFile(metadataPath, data, 0644)

	return metadata, nil
}

// GetWantlist gets the wantlist
func (syncer *Syncer) GetWantlist(ctx context.Context, in *pb.Empty) (*pb.Wantlist, error) {
	return &syncer.wants, nil
}

// GetMetadata gets the metadata for a given release
func (syncer *Syncer) GetMetadata(ctx context.Context, in *pbd.Release) (*pb.ReleaseMetadata, error) {
	_, metadata := syncer.GetRelease(int(in.Id), int(in.FolderId))
	return metadata, nil
}

// GetReleasesInFolder serves up the releases in a given folder
func (syncer *Syncer) GetReleasesInFolder(ctx context.Context, in *pb.FolderList) (*pb.ReleaseList, error) {

	releases := pb.ReleaseList{}
	for _, folderSpec := range in.Folders {
		folders := syncer.getFolders()
		for _, folder := range folders.Folders {
			if folder.Name == folderSpec.Name || folder.Id == folderSpec.Id {
				innerReleases := syncer.getReleases(int(folder.Id))
				releases.Releases = append(releases.Releases, innerReleases.Releases...)
			}
		}
	}

	return &releases, nil
}

func (syncer *Syncer) getReleases(folderID int) *pb.ReleaseList {
	releases := pb.ReleaseList{}
	files, _ := ioutil.ReadDir(syncer.saveLocation + "/" + strconv.Itoa(folderID) + "/")
	for _, file := range files {
		if strings.HasSuffix(file.Name(), ".release") {
			data, _ := ioutil.ReadFile(syncer.saveLocation + "/" + strconv.Itoa(folderID) + "/" + file.Name())
			release := &pbd.Release{}
			proto.Unmarshal(data, release)
			releases.Releases = append(releases.Releases, release)
		}
	}
	return &releases
}

func (syncer *Syncer) initWantlist() {
	wldata, _ := ioutil.ReadFile(syncer.saveLocation + "/metadata/wantlist")
	proto.Unmarshal(wldata, &syncer.wants)

	for _, want := range syncer.wants.Want {
		rel, _ := syncer.GetRelease(int(want.ReleaseId), -5)
		rel.FolderId = -5
		syncer.relMap[rel.Id] = rel
	}
}

func (syncer *Syncer) saveWantList() {
	data, _ := proto.Marshal(&syncer.wants)
	savePath := syncer.saveLocation + "/metadata/"
	if _, err := os.Stat(savePath); os.IsNotExist(err) {
		os.MkdirAll(savePath, 0777)
	}
	ioutil.WriteFile(savePath+"wantlist", data, 0644)
}

// SaveFolders saves out the list of folders
func (syncer *Syncer) SaveFolders(list *pb.FolderList) {
	savePath := syncer.saveLocation + "/metadata/"
	if _, err := os.Stat(savePath); os.IsNotExist(err) {
		os.MkdirAll(savePath, 0777)
	}

	data, _ := proto.Marshal(list)
	ioutil.WriteFile(savePath+"folders", data, 0644)
}

// GetCollection serves up the whole of the collection
func (syncer *Syncer) GetCollection(ctx context.Context, in *pb.Empty) (*pb.ReleaseList, error) {
	releases := pb.ReleaseList{}
	bfiles, _ := ioutil.ReadDir(syncer.saveLocation)
	for _, bfile := range bfiles {
		if bfile.IsDir() {
			folderID, _ := strconv.Atoi(bfile.Name())
			for _, release := range syncer.getReleases(folderID).Releases {
				releases.Releases = append(releases.Releases, release)
			}
		}
	}
	return &releases, nil
}
<commit_msg>Added logging of sync<commit_after>package main

import (
	"errors"
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"strings"

	"github.com/brotherlogic/godiscogs"
	"golang.org/x/net/context"

	pbd "github.com/brotherlogic/godiscogs"
	"github.com/golang/protobuf/proto"
	"time"
)

import "strconv"

import pb "github.com/brotherlogic/discogssyncer/server"

// GetRelease Gets the release and metadata for the release
func (syncer *Syncer) GetRelease(id int, folder int) (*pbd.Release, *pb.ReleaseMetadata) {
	releaseData, err := ioutil.ReadFile(syncer.saveLocation + "/" + strconv.Itoa(folder) + "/" + strconv.Itoa(id) + ".release")
	if err != nil {
		return nil, nil
	}

	metadataData, _ := ioutil.ReadFile(syncer.saveLocation + "/static-metadata/" + strconv.Itoa(id) + ".metadata")
	release := &pbd.Release{}
	metadata := &pb.ReleaseMetadata{}

	proto.Unmarshal(releaseData, release)
	proto.Unmarshal(metadataData, metadata)

	return release, metadata
}

func (syncer *Syncer) saveMetadata(rel *godiscogs.Release) {
	metadataRoot := syncer.saveLocation + "/static-metadata/"
	metadataPath := metadataRoot + strconv.Itoa(int(rel.Id)) + ".metadata"
	if _, err := os.Stat(metadataRoot); os.IsNotExist(err) {
		os.MkdirAll(metadataRoot, 0777)
	}

	metadata := &pb.ReleaseMetadata{}
	if _, err := os.Stat(metadataPath); os.IsNotExist(err) {
		metadata.DateAdded = time.Now().Unix()
		metadata.DateRefreshed = time.Now().Unix()
	} else {
		data, _ := ioutil.ReadFile(metadataPath)
		proto.Unmarshal(data, metadata)
		metadata.DateRefreshed = time.Now().Unix()
	}
	data, _ := proto.Marshal(metadata)
	ioutil.WriteFile(metadataPath, data, 0644)
}

func (syncer *Syncer) deleteRelease(rel *godiscogs.Release, folder int) {
	os.Remove(syncer.saveLocation + "/" + strconv.Itoa(folder) + "/" + strconv.Itoa(int(rel.Id)) + ".release")
}

func (syncer *Syncer) saveRelease(rel *godiscogs.Release, folder int) {
	//Check that the save folder exists
	savePath := syncer.saveLocation + "/" + strconv.Itoa(folder) + "/"
	if _, err := os.Stat(savePath); os.IsNotExist(err) {
		os.MkdirAll(savePath, 0777)
	}

	data, _ := proto.Marshal(rel)
	ioutil.WriteFile(savePath+strconv.Itoa(int(rel.Id))+".release", data, 0644)
	syncer.saveMetadata(rel)
}

type saver interface {
	GetCollection() []godiscogs.Release
	GetFolders() []godiscogs.Folder
	GetRelease(id int) (godiscogs.Release, error)
	MoveToFolder(folderID int, releaseID int, instanceID int, newFolderID int)
	AddToFolder(folderID int, releaseID int)
	SetRating(folderID int, releaseID int, instanceID int, rating int)
	GetWantlist() ([]pbd.Release, error)
}

// SaveCollection writes out the full collection to files.
func (syncer *Syncer) SaveCollection(retr saver) {
	releases := retr.GetCollection()
	for _, release := range releases {
		fullRelease, _ := retr.GetRelease(int(release.Id))
		fullRelease.InstanceId = release.InstanceId
		fullRelease.FolderId = release.FolderId
		fullRelease.Rating = release.Rating
		syncer.saveRelease(&fullRelease, int(release.FolderId))
	}
	folders := retr.GetFolders()
	folderList := pb.FolderList{}
	for i := range folders {
		folder := folders[i]
		folderList.Folders = append(folderList.Folders, &folder)
	}
	syncer.SaveFolders(&folderList)
}

// SyncWantlist syncs the wantlist with the server
func (syncer *Syncer) SyncWantlist() {
	wants, _ := syncer.retr.GetWantlist()

	for _, want := range wants {
		log.Printf("CHECKING %v", want)
		seen := false
		var val *pb.Want
		for _, swant := range syncer.wants.Want {
			if swant.ReleaseId == want.Id {
				seen = true
				val = swant
			}
		}

		if seen {
			val.Wanted = true
		} else {
			syncer.wants.Want = append(syncer.wants.Want, &pb.Want{ReleaseId: want.Id, Valued: false, Wanted: true})
		}
	}

	// Cache the want list releases
	for _, want := range syncer.wants.Want {
		release, _ := syncer.retr.GetRelease(int(want.ReleaseId))
		syncer.saveRelease(&release, -5)
	}

	syncer.saveWantList()
}

func (syncer *Syncer) getFolders() *pb.FolderList {
	data, _ := ioutil.ReadFile(syncer.saveLocation + "/metadata/folders")
	folderData := &pb.FolderList{}
	proto.Unmarshal(data, folderData)
	return folderData
}

// GetSingleRelease gets a single release
func (syncer *Syncer) GetSingleRelease(ctx context.Context, in *pbd.Release) (*pbd.Release, error) {
	if val, ok := syncer.relMap[in.Id]; ok {
		return val, nil
	}
	return nil, errors.New("Unable to find release")
}

// MoveToFolder moves a release to the specified folder
func (syncer *Syncer) MoveToFolder(ctx context.Context, in *pb.ReleaseMove) (*pb.Empty, error) {
	syncer.retr.MoveToFolder(int(in.Release.FolderId), int(in.Release.Id), int(in.Release.InstanceId), int(in.NewFolderId))
	oldFolder := int(in.Release.FolderId)
	fullRelease, _ := syncer.retr.GetRelease(int(in.Release.Id))
	fullRelease.FolderId = int32(in.NewFolderId)
	syncer.relMap[fullRelease.Id] = &fullRelease

	syncer.Log(fmt.Sprintf("Moving %v from %v to %v", in.Release.Id, in.Release.FolderId, in.NewFolderId))
	syncer.saveRelease(&fullRelease, int(in.NewFolderId))
	syncer.deleteRelease(&fullRelease, oldFolder)
	return &pb.Empty{}, nil
}

// AddToFolder adds a release to the specified folder
func (syncer *Syncer) AddToFolder(ctx context.Context, in *pb.ReleaseMove) (*pb.Empty, error) {
	syncer.retr.AddToFolder(int(in.NewFolderId), int(in.Release.Id))
	fullRelease, _ := syncer.retr.GetRelease(int(in.Release.Id))
	fullRelease.FolderId = int32(in.NewFolderId)
	syncer.saveRelease(&fullRelease, int(in.NewFolderId))
	syncer.relMap[in.Release.Id] = &fullRelease
	return &pb.Empty{}, nil
}

// UpdateRating updates the rating of a release
func (syncer *Syncer) UpdateRating(ctx context.Context, in *pbd.Release) (*pb.Empty, error) {
	syncer.retr.SetRating(int(in.FolderId), int(in.Id), int(in.InstanceId), int(in.Rating))
	fullRelease, _ := syncer.GetRelease(int(in.Id), int(in.FolderId))
	fullRelease.Rating = int32(in.Rating)
	syncer.relMap[in.Id] = fullRelease
	syncer.saveRelease(fullRelease, int(fullRelease.FolderId))
	return &pb.Empty{}, nil
}

// UpdateMetadata updates the metadata of a given record
func (syncer *Syncer) UpdateMetadata(ctx context.Context, in *pb.MetadataUpdate) (*pb.ReleaseMetadata, error) {
	release, metadata := syncer.GetRelease(int(in.Release.Id), int(in.Release.FolderId))
	if release == nil {
		return nil, errors.New("Unable to locate release")
	}
	proto.Merge(metadata, in.Update)

	metadataRoot := syncer.saveLocation + "/static-metadata/"
	metadataPath := metadataRoot + strconv.Itoa(int(in.Release.Id)) + ".metadata"
	data, _ := proto.Marshal(metadata)
	ioutil.WriteFile(metadataPath, data, 0644)

	return metadata, nil
}

// GetWantlist gets the wantlist
func (syncer *Syncer) GetWantlist(ctx context.Context, in *pb.Empty) (*pb.Wantlist, error) {
	return &syncer.wants, nil
}

// GetMetadata gets the metadata for a given release
func (syncer *Syncer) GetMetadata(ctx context.Context, in *pbd.Release) (*pb.ReleaseMetadata, error) {
	_, metadata := syncer.GetRelease(int(in.Id), int(in.FolderId))
	return metadata, nil
}

// GetReleasesInFolder serves up the releases in a given folder
func (syncer *Syncer) GetReleasesInFolder(ctx context.Context, in *pb.FolderList) (*pb.ReleaseList, error) {

	releases := pb.ReleaseList{}
	for _, folderSpec := range in.Folders {
		folders := syncer.getFolders()
		for _, folder := range folders.Folders {
			if folder.Name == folderSpec.Name || folder.Id == folderSpec.Id {
				innerReleases := syncer.getReleases(int(folder.Id))
				releases.Releases = append(releases.Releases, innerReleases.Releases...)
			}
		}
	}

	return &releases, nil
}

func (syncer *Syncer) getReleases(folderID int) *pb.ReleaseList {
	releases := pb.ReleaseList{}
	files, _ := ioutil.ReadDir(syncer.saveLocation + "/" + strconv.Itoa(folderID) + "/")
	for _, file := range files {
		if strings.HasSuffix(file.Name(), ".release") {
			data, _ := ioutil.ReadFile(syncer.saveLocation + "/" + strconv.Itoa(folderID) + "/" + file.Name())
			release := &pbd.Release{}
			proto.Unmarshal(data, release)
			releases.Releases = append(releases.Releases, release)
		}
	}
	return &releases
}

func (syncer *Syncer) initWantlist() {
	wldata, _ := ioutil.ReadFile(syncer.saveLocation + "/metadata/wantlist")
	proto.Unmarshal(wldata, &syncer.wants)

	for _, want := range syncer.wants.Want {
		rel, _ := syncer.GetRelease(int(want.ReleaseId), -5)
		rel.FolderId = -5
		syncer.relMap[rel.Id] = rel
	}
}

func (syncer *Syncer) saveWantList() {
	data, _ := proto.Marshal(&syncer.wants)
	savePath := syncer.saveLocation + "/metadata/"
	if _, err := os.Stat(savePath); os.IsNotExist(err) {
		os.MkdirAll(savePath, 0777)
	}
	ioutil.WriteFile(savePath+"wantlist", data, 0644)
}

// SaveFolders saves out the list of folders
func (syncer *Syncer) SaveFolders(list *pb.FolderList) {
	savePath := syncer.saveLocation + "/metadata/"
	if _, err := os.Stat(savePath); os.IsNotExist(err) {
		os.MkdirAll(savePath, 0777)
	}

	data, _ := proto.Marshal(list)
	ioutil.WriteFile(savePath+"folders", data, 0644)
}

// GetCollection serves up the whole of the collection
func (syncer *Syncer) GetCollection(ctx context.Context, in *pb.Empty) (*pb.ReleaseList, error) {
	releases := pb.ReleaseList{}
	bfiles, _ := ioutil.ReadDir(syncer.saveLocation)
	for _, bfile := range bfiles {
		if bfile.IsDir() {
			folderID, _ := strconv.Atoi(bfile.Name())
			for _, release := range syncer.getReleases(folderID).Releases {
				releases.Releases = append(releases.Releases, release)
			}
		}
	}
	return &releases, nil
}
<|endoftext|>


<commit_before>// Package imapsync provides a single function to download an IMAP folder to a local directory, with each email
// in a plain text file. Emails are downloaded only once, even if the function is run repeatedly.
//
// A command line tool is available at https://github.com/JohannesEbke/go-imap-sync/cmd/go-imap-sync
package imapsync

import (
	"crypto/sha512"
	"encoding/hex"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"

	"github.com/emersion/go-imap"
	"github.com/emersion/go-imap/client"
)

// Sync downloads and saves all not-yet downloaded emails from the mailbox to the emailDir
func Sync(server, user, password, mailbox, emailDir string) error {
	err := os.MkdirAll(emailDir, 0700)
	if err != nil {
		return fmt.Errorf("Error creating email directory %v: %v", emailDir, err)
	}

	connection, err := connect(server, user, password)
	if err != nil {
		return fmt.Errorf("Error connecting to %v: %v", server, err)
	}

	defer func() {
		err2 := connection.Logout()
		if err2 != nil {
			log.Printf("Error on logout from %v: %v", server, err2)
		}
	}()

	_, err = connection.Select(mailbox, true)
	if err != nil {
		return fmt.Errorf("Error opening mailbox '%v': %v", mailbox, err)
	}

	log.Printf("Listing all messages in %v...", mailbox)
	seqNumMessageIDMap := getMessageIDMap(connection)
	log.Printf("Found %v messages. Looking for existing downloaded messages...", len(seqNumMessageIDMap))
	messagesToFetch, toFetchCount := getMessagesToFetch(emailDir, seqNumMessageIDMap)
	log.Printf("Syncing %v missing messages...", toFetchCount)
	for _, messageSeqNr := range messagesToFetch {
		seqSet := new(imap.SeqSet)
		seqSet.AddNum(messageSeqNr)
		err2 := fetchMessages(connection, emailDir, seqSet)
		if err2 != nil {
			return fmt.Errorf("Error fetching message '%v': %v", messageSeqNr, err2)
		}
		fmt.Println(".")
	}
	log.Printf("Finished syncing.")
	return nil
}

// connect performs an interactive connection to the given IMAP server
func connect(server, username, password string) (*client.Client, error) {
	log.Printf("Connecting to %v...", server)
	c, err := client.DialTLS(server, nil)
	if err != nil {
		return nil, err
	}
	log.Printf("Connected to %v.", server)

	if err := c.Login(username, password); err != nil {
		if err2 := c.Logout(); err2 != nil {
			return nil, fmt.Errorf("Error while logging in to %v: %v\n(followup error: %v)", server, err, err2)
		}
		return nil, fmt.Errorf("Error while logging in to %v: %v", server, err)
	}
	log.Printf("Logged in as user %v on %v.", username, server)
	return c, nil
}

// getMessageIDMap lists all messages in the current mailbox and returns a map of sequence numbers to email MessageID
func getMessageIDMap(c *client.Client) (emails map[uint32]string) {
	emails = make(map[uint32]string)
	// Get all messages
	seqset, err := imap.ParseSeqSet("1:*")
	if err != nil {
		log.Fatal(err)
	}
	messageChan := make(chan *imap.Message)
	go func() {
		if err := c.Fetch(seqset, []string{"ENVELOPE", "UID"}, messageChan); err != nil {
			log.Fatalf("Error fetching list of messages: %v", err)
		}
	}()
	for msg := range messageChan {
		emails[msg.SeqNum] = msg.Envelope.MessageId
	}
	return
}

// getMessagesToFetch determines which messages listed in the map should be downloaded from the server
func getMessagesToFetch(emailDir string, seqNumMessageIDMap map[uint32]string) (messagesToFetch []uint32, toFetchCount uint) {
	for seqNum, messageID := range seqNumMessageIDMap {
		exists, err := fileExists(messageFileName(emailDir, messageID))
		if err != nil {
			log.Fatal(err)
		}
		if !exists {
			messagesToFetch = append(messagesToFetch, seqNum)
			toFetchCount++
		}
	}
	return
}

// fetchMessages downloads the messages specified by the given SeqSet to the emailDir
func fetchMessages(connection *client.Client, emailDir string, messagesToFetch *imap.SeqSet) error {
	messageChan := make(chan *imap.Message)
	err := connection.Fetch(messagesToFetch, []string{"ENVELOPE", "BODY[]"}, messageChan)
	if err != nil {
		return err
	}
	for msg := range messageChan {
		body, err := ioutil.ReadAll(msg.GetBody("BODY[]"))
		if err != nil {
			return err
		}
		err = ioutil.WriteFile(messageFileName(emailDir, msg.Envelope.MessageId), body, 0600)
		if err != nil {
			return err
		}
	}
	return nil
}

// sha512TruncatedHex returns a hex representation of the first 32 bytes of the SHA512 hash of the given string
func sha512TruncatedHex(messageID string) string {
	h := sha512.New()
	if _, err := io.WriteString(h, messageID); err != nil {
		log.Fatal(err)
	}
	b := h.Sum(nil)
	return hex.EncodeToString(b[0:31])
}

// messageFileName returns the target file name of the email with the given messageID
func messageFileName(emailDir, messageID string) string {
	return filepath.Join(emailDir, fmt.Sprintf("%s.eml", sha512TruncatedHex(messageID)))
}

// fileExists checks if the given path exists and can be Stat'd.
func fileExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return true, err
}
<commit_msg>Add Result to Sync call<commit_after>// Package imapsync provides a single function to download an IMAP folder to a local directory, with each email
// in a plain text file. Emails are downloaded only once, even if the function is run repeatedly.
//
// A command line tool is available at https://github.com/JohannesEbke/go-imap-sync/cmd/go-imap-sync
package imapsync

import (
	"crypto/sha512"
	"encoding/hex"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path/filepath"

	"github.com/emersion/go-imap"
	"github.com/emersion/go-imap/client"
)

type Result struct {
	ExistingEmails []string
	NewEmails      []string
}

// Sync downloads and saves all not-yet downloaded emails from the mailbox to the emailDir
func Sync(server, user, password, mailbox, emailDir string) (*Result, error) {
	err := os.MkdirAll(emailDir, 0700)
	if err != nil {
		return nil, fmt.Errorf("Error creating email directory %v: %v", emailDir, err)
	}

	connection, err := connect(server, user, password)
	if err != nil {
		return nil, fmt.Errorf("Error connecting to %v: %v", server, err)
	}

	defer func() {
		err2 := connection.Logout()
		if err2 != nil {
			log.Printf("Error on logout from %v: %v", server, err2)
		}
	}()

	_, err = connection.Select(mailbox, true)
	if err != nil {
		return nil, fmt.Errorf("Error opening mailbox '%v': %v", mailbox, err)
	}

	log.Printf("Listing all messages in %v...", mailbox)
	seqNumMessageIDMap := getMessageIDMap(connection)
	log.Printf("Found %v messages. Looking for existing downloaded messages...", len(seqNumMessageIDMap))
	messagesToFetch, toFetchCount := getMessagesToFetch(emailDir, seqNumMessageIDMap)
	log.Printf("Syncing %v missing messages...", toFetchCount)
	for _, messageSeqNr := range messagesToFetch {
		seqSet := new(imap.SeqSet)
		seqSet.AddNum(messageSeqNr)
		err2 := fetchMessages(connection, emailDir, seqSet)
		if err2 != nil {
			return nil, fmt.Errorf("Error fetching message '%v': %v", messageSeqNr, err2)
		}
		fmt.Println(".")
	}
	log.Printf("Finished syncing.")

	// Calculate Result structure
	isNew := make(map[uint32]bool)
	existingEmails := []string{}
	newEmails := []string{}
	for _, seqNum := range messagesToFetch {
		newEmails = append(newEmails, messageFileName(emailDir, seqNumMessageIDMap[seqNum]))
		isNew[seqNum] = true
	}
	for seqNum, messageId := range seqNumMessageIDMap {
		if !isNew[seqNum] {
			existingEmails = append(existingEmails, messageFileName(emailDir, messageId))
		}
	}
	return &Result{
		ExistingEmails: existingEmails,
		NewEmails:      newEmails,
	}, nil
}

// connect performs an interactive connection to the given IMAP server
func connect(server, username, password string) (*client.Client, error) {
	log.Printf("Connecting to %v...", server)
	c, err := client.DialTLS(server, nil)
	if err != nil {
		return nil, err
	}
	log.Printf("Connected to %v.", server)

	if err := c.Login(username, password); err != nil {
		if err2 := c.Logout(); err2 != nil {
			return nil, fmt.Errorf("Error while logging in to %v: %v\n(followup error: %v)", server, err, err2)
		}
		return nil, fmt.Errorf("Error while logging in to %v: %v", server, err)
	}
	log.Printf("Logged in as user %v on %v.", username, server)
	return c, nil
}

// getMessageIDMap lists all messages in the current mailbox and returns a map of sequence numbers to email MessageID
func getMessageIDMap(c *client.Client) (emails map[uint32]string) {
	emails = make(map[uint32]string)
	// Get all messages
	seqset, err := imap.ParseSeqSet("1:*")
	if err != nil {
		log.Fatal(err)
	}
	messageChan := make(chan *imap.Message)
	go func() {
		if err := c.Fetch(seqset, []string{"ENVELOPE", "UID"}, messageChan); err != nil {
			log.Fatalf("Error fetching list of messages: %v", err)
		}
	}()
	for msg := range messageChan {
		emails[msg.SeqNum] = msg.Envelope.MessageId
	}
	return
}

// getMessagesToFetch determines which messages listed in the map should be downloaded from the server
func getMessagesToFetch(emailDir string, seqNumMessageIDMap map[uint32]string) (messagesToFetch []uint32, toFetchCount uint) {
	for seqNum, messageID := range seqNumMessageIDMap {
		exists, err := fileExists(messageFileName(emailDir, messageID))
		if err != nil {
			log.Fatal(err)
		}
		if !exists {
			messagesToFetch = append(messagesToFetch, seqNum)
			toFetchCount++
		}
	}
	return
}

// fetchMessages downloads the messages specified by the given SeqSet to the emailDir
func fetchMessages(connection *client.Client, emailDir string, messagesToFetch *imap.SeqSet) error {
	messageChan := make(chan *imap.Message)
	err := connection.Fetch(messagesToFetch, []string{"ENVELOPE", "BODY[]"}, messageChan)
	if err != nil {
		return err
	}
	for msg := range messageChan {
		body, err := ioutil.ReadAll(msg.GetBody("BODY[]"))
		if err != nil {
			return err
		}
		err = ioutil.WriteFile(messageFileName(emailDir, msg.Envelope.MessageId), body, 0600)
		if err != nil {
			return err
		}
	}
	return nil
}

// sha512TruncatedHex returns a hex representation of the first 32 bytes of the SHA512 hash of the given string
func sha512TruncatedHex(messageID string) string {
	h := sha512.New()
	if _, err := io.WriteString(h, messageID); err != nil {
		log.Fatal(err)
	}
	b := h.Sum(nil)
	return hex.EncodeToString(b[0:31])
}

// messageFileName returns the target file name of the email with the given messageID
func messageFileName(emailDir, messageID string) string {
	return filepath.Join(emailDir, fmt.Sprintf("%s.eml", sha512TruncatedHex(messageID)))
}

// fileExists checks if the given path exists and can be Stat'd.
func fileExists(path string) (bool, error) {
	_, err := os.Stat(path)
	if err == nil {
		return true, nil
	}
	if os.IsNotExist(err) {
		return false, nil
	}
	return true, err
}
<|endoftext|>


<commit_before>package main

import (
	"flag"
	"fmt"
	"net/http"
	"os"
	"strings"

	svc "github.com/kardianos/service"

	"github.com/Felamande/filesync/syncer"
)

var run *bool = flag.Bool("run", false, "Run in the shell. -svcctl will be disabled.")
var controls *string = flag.String("svcctl", "install,start", "value:[start,stop,restart,install,uninstall], can be multiple values separated by commas")
var help *bool = flag.Bool("help", false, "Get help")

func main() {
	flag.Parse()

	if len(os.Args) == 1 {
		flag.Usage()
		return
	}

	if *help {
		flag.Usage()
		return
	}

	p := &Program{}
	s, err := svc.New(p, &svc.Config{
		Name:        "Filesync",
		DisplayName: "FileSync Service",
		Description: "Filesync is a simple tool to sync files between multiple directory pairs.",
		Arguments:   []string{"-run"},
	})

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

	if *run {
		err := s.Run()
		fmt.Println("-run with error: ", err.Error())
		return
	}

	Actions := strings.Split(*controls, ",")
	for _, action := range Actions {
		err := svc.Control(s, action)
		fmt.Println(err)
	}

}

func HelloNewPair() string {
	return "<!DOCTYPE html>Hello"
}

func NewPair(s *syncer.Syncer, r *http.Request) string {
	err := r.ParseForm()
	if err != nil {
		return err.Error()
	}

	lName := r.FormValue("left")
	rName := r.FormValue("right")
	err = s.NewPair(syncer.SyncConfig{true, true, true}, lName, rName)
	if err != nil {
		return err.Error()
	}
	return lName + " ==> " + rName

}
<commit_msg>Implement system service.<commit_after>package main

import (
	"flag"
	"fmt"
	"net/http"
	"os"
	"strings"

	svc "github.com/kardianos/service"

	"github.com/Felamande/filesync/syncer"
)

var run *bool = flag.Bool("run", false, "Run in the shell. -svcctl will be disabled.")
var controls *string = flag.String("svcctl", "install,start", "value:[start,stop,restart,install,uninstall], can be multiple values separated by commas")
var help *bool = flag.Bool("help", false, "Get help")

func main() {
	flag.Parse()

	if len(os.Args) == 1 {
		flag.Usage()
		return
	}

	if flag.Lookup("run") == nil && flag.Lookup("svcctl") == nil && flag.Lookup("help") == nil {
		flag.Usage()
		return
	}

	if *help {
		flag.Usage()
		return
	}

	p := &Program{}
	s, err := svc.New(p, &svc.Config{
		Name:        "Filesync",
		DisplayName: "FileSync Service",
		Description: "Filesync is a simple tool to sync files between multiple directory pairs.",
		Arguments:   []string{"-run"},
	})

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

	if *run {
		err := s.Run()
		fmt.Println("-run with error: ", err.Error())
		return
	}

	Actions := strings.Split(*controls, ",")
	for _, action := range Actions {
		err := svc.Control(s, action)
		fmt.Println(err)
	}

}

func HelloNewPair() string {
	return "<!DOCTYPE html>Hello"
}

func NewPair(s *syncer.Syncer, r *http.Request) string {
	err := r.ParseForm()
	if err != nil {
		return err.Error()
	}

	lName := r.FormValue("left")
	rName := r.FormValue("right")
	err = s.NewPair(syncer.SyncConfig{true, true, true}, lName, rName)
	if err != nil {
		return err.Error()
	}
	return lName + " ==> " + rName

}
<|endoftext|>


<commit_before>package tail

import (
	"bufio"
	"context"
	"io"
	"os"
	"path/filepath"
	"sync"
	"time"

	fsnotify "gopkg.in/fsnotify.v1"
)

const (
	openRetryInterval = time.Second
	tailOldFileDelay  = 10 * time.Second
)

// Line is a line of the target file.
type Line struct {
	Text string
	Time time.Time
}

// Tail tails a file.
type Tail struct {
	Lines  <-chan *Line
	Errors <-chan error

	lines    chan<- *Line
	errors   chan<- error
	filename string
	wg       sync.WaitGroup
	ctx      context.Context
	cancel   context.CancelFunc
}

type tail struct {
	parent *Tail

	file    *os.File
	reader  *bufio.Reader
	watcher *fsnotify.Watcher
	buf     string
	ctx     context.Context
	cancel  context.CancelFunc
}

// NewTailFile starts tailing a file
func NewTailFile(filename string) (*Tail, error) {
	filename, err := filepath.Abs(filename)
	if err != nil {
		return nil, err
	}

	ctx, cancel := context.WithCancel(context.Background())
	lines := make(chan *Line, 16)
	errs := make(chan error, 1)
	parent := &Tail{
		Lines:    lines,
		Errors:   errs,
		lines:    lines,
		errors:   errs,
		filename: filename,
		ctx:      ctx,
		cancel:   cancel,
	}
	parent.wg.Add(1)
	go parent.runFile(os.SEEK_END)
	return parent, nil
}

// NewTailReader starts tailing io.Reader
func NewTailReader(reader io.Reader) (*Tail, error) {
	ctx, cancel := context.WithCancel(context.Background())
	lines := make(chan *Line, 16)
	errs := make(chan error, 1)
	parent := &Tail{
		Lines:  lines,
		Errors: errs,
		lines:  lines,
		errors: errs,
		ctx:    ctx,
		cancel: cancel,
	}
	t := &tail{
		parent: parent,
		reader: bufio.NewReader(reader),
		ctx:    ctx,
		cancel: cancel,
	}
	parent.wg.Add(1)
	go t.runReader()
	return parent, nil
}

// Close stops tailing the file.
func (t *Tail) Close() error {
	t.cancel()
	t.wg.Wait()
	close(t.errors)
	close(t.lines)
	return nil
}

// open opens the target file.
// If it does not exist, wait for creating new file.
func (t *Tail) open(seek int) (*tail, error) {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}
	for {
		file, err := os.Open(t.filename)
		if err == nil {
			// success, seek and watch the file.
			if _, err := file.Seek(0, seek); err != nil {
				file.Close()
				watcher.Close()
				return nil, err
			}
			if err := watcher.Add(t.filename); err != nil {
				file.Close()
				watcher.Close()
				return nil, err
			}
			ctx, cancel := context.WithCancel(t.ctx)
			return &tail{
				parent:  t,
				file:    file,
				reader:  bufio.NewReader(file),
				watcher: watcher,
				ctx:     ctx,
				cancel:  cancel,
			}, nil
		}

		// fail. retry...
		seek = os.SEEK_SET
		select {
		case <-t.ctx.Done():
			return nil, t.ctx.Err()
		case <-time.After(openRetryInterval):
		}
	}
}

// runFile tails target files
func (t *Tail) runFile(seek int) {
	defer t.wg.Done()
	child, err := t.open(seek)
	if err != nil {
		t.errors <- err
		return
	}
	t.wg.Add(1)
	go child.runFile()
}

// runFile tails a file
func (t *tail) runFile() {
	defer t.parent.wg.Done()
	defer t.watcher.Close()

	cherr := make(chan error)
	ch := make(chan struct{}, 1)
	defer close(ch)

	t.parent.wg.Add(1)
	go func() {
		defer t.parent.wg.Done()
		for {
			if err := t.restict(); err != nil {
				select {
				case cherr <- err:
				case <-t.ctx.Done():
				}
				return
			}
			err := t.tail()
			if err == nil {
				continue
			}

			// wait for writing new lines.
			select {
			case cherr <- err:
			case <-t.ctx.Done():
				return
			}
			select {
			case _, ok := <-ch:
				if !ok {
					return
				}
			case <-t.ctx.Done():
				return
			}
		}
	}()

	var renamed bool
	var waiting bool // waiting for writing new lines?
	for {
		select {
		case event := <-t.watcher.Events:
			if (event.Op & fsnotify.Remove) != 0 {
				// the target file is removed, stop tailing.
				return
			}
			if (event.Op & fsnotify.Rename) != 0 {
				// log rotation is detected.
				if !renamed {
					// start to watch creating new file.
					t.parent.wg.Add(1)
					go t.parent.runFile(os.SEEK_SET)
				}
				name, err := getFileName(t.file)
				if err != nil {
					t.parent.errors <- err
					return
				}
				t.watcher.Add(name)
				renamed = true
			}

			// notify new lines are wrote.
			if waiting {
				ch <- struct{}{}
			}
		case err := <-cherr:
			if err == io.EOF {
				waiting = true
			} else {
				t.parent.errors <- err
				return
			}
		case err := <-t.watcher.Errors:
			t.parent.errors <- err
			return
		case <-t.ctx.Done():
			return
		}
	}
}

// runReader tails io.Reader
func (t *tail) runReader() {
	defer t.parent.wg.Done()
	err := t.tail()
	if err == io.EOF || err == io.ErrClosedPipe {
		return
	}
	if err != nil {
		t.parent.errors <- err
		return
	}
}

// restrict detects a file that is truncated
func (t *tail) restict() error {
	stat, err := t.file.Stat()
	if err != nil {
		return err
	}
	pos, err := t.file.Seek(0, os.SEEK_CUR)
	if err != nil {
		return err
	}
	if stat.Size() < pos {
		// file is trancated. seek to head of file.
		_, err := t.file.Seek(0, os.SEEK_SET)
		if err != nil {
			return err
		}
	}
	return nil
}

// tail reads lines until EOF
func (t *tail) tail() error {
	for {
		line, err := t.reader.ReadString('\n')
		if err != nil {
			t.buf += line
			return err
		}
		t.parent.lines <- &Line{t.buf + line, time.Now()}
		t.buf = ""
	}
}
<commit_msg>wait a little, and stop tailing old file.<commit_after>package tail

import (
	"bufio"
	"context"
	"io"
	"os"
	"path/filepath"
	"sync"
	"time"

	fsnotify "gopkg.in/fsnotify.v1"
)

const (
	openRetryInterval = time.Second
	tailOldFileDelay  = 10 * time.Second
)

// Line is a line of the target file.
type Line struct {
	Text string
	Time time.Time
}

// Tail tails a file.
type Tail struct {
	Lines  <-chan *Line
	Errors <-chan error

	lines    chan<- *Line
	errors   chan<- error
	filename string
	wg       sync.WaitGroup
	ctx      context.Context
	cancel   context.CancelFunc
}

type tail struct {
	parent *Tail

	file    *os.File
	reader  *bufio.Reader
	watcher *fsnotify.Watcher
	buf     string
	ctx     context.Context
	cancel  context.CancelFunc
}

// NewTailFile starts tailing a file
func NewTailFile(filename string) (*Tail, error) {
	filename, err := filepath.Abs(filename)
	if err != nil {
		return nil, err
	}

	ctx, cancel := context.WithCancel(context.Background())
	lines := make(chan *Line, 16)
	errs := make(chan error, 1)
	parent := &Tail{
		Lines:    lines,
		Errors:   errs,
		lines:    lines,
		errors:   errs,
		filename: filename,
		ctx:      ctx,
		cancel:   cancel,
	}
	parent.wg.Add(1)
	go parent.runFile(os.SEEK_END)
	return parent, nil
}

// NewTailReader starts tailing io.Reader
func NewTailReader(reader io.Reader) (*Tail, error) {
	ctx, cancel := context.WithCancel(context.Background())
	lines := make(chan *Line, 16)
	errs := make(chan error, 1)
	parent := &Tail{
		Lines:  lines,
		Errors: errs,
		lines:  lines,
		errors: errs,
		ctx:    ctx,
		cancel: cancel,
	}
	t := &tail{
		parent: parent,
		reader: bufio.NewReader(reader),
		ctx:    ctx,
		cancel: cancel,
	}
	parent.wg.Add(1)
	go t.runReader()
	return parent, nil
}

// Close stops tailing the file.
func (t *Tail) Close() error {
	t.cancel()
	t.wg.Wait()
	close(t.errors)
	close(t.lines)
	return nil
}

// open opens the target file.
// If it does not exist, wait for creating new file.
func (t *Tail) open(seek int) (*tail, error) {
	watcher, err := fsnotify.NewWatcher()
	if err != nil {
		return nil, err
	}
	for {
		file, err := os.Open(t.filename)
		if err == nil {
			// success, seek and watch the file.
			if _, err := file.Seek(0, seek); err != nil {
				file.Close()
				watcher.Close()
				return nil, err
			}
			if err := watcher.Add(t.filename); err != nil {
				file.Close()
				watcher.Close()
				return nil, err
			}
			ctx, cancel := context.WithCancel(t.ctx)
			return &tail{
				parent:  t,
				file:    file,
				reader:  bufio.NewReader(file),
				watcher: watcher,
				ctx:     ctx,
				cancel:  cancel,
			}, nil
		}

		// fail. retry...
		seek = os.SEEK_SET
		select {
		case <-t.ctx.Done():
			return nil, t.ctx.Err()
		case <-time.After(openRetryInterval):
		}
	}
}

// runFile tails target files
func (t *Tail) runFile(seek int) {
	defer t.wg.Done()
	child, err := t.open(seek)
	if err != nil {
		t.errors <- err
		return
	}
	t.wg.Add(1)
	go child.runFile()
}

// runFile tails a file
func (t *tail) runFile() {
	defer t.parent.wg.Done()
	defer t.watcher.Close()
	defer t.cancel()

	cherr := make(chan error)
	ch := make(chan struct{}, 1)
	defer close(ch)

	t.parent.wg.Add(1)
	go func() {
		defer t.parent.wg.Done()
		for {
			if err := t.restict(); err != nil {
				select {
				case cherr <- err:
				case <-t.ctx.Done():
				}
				return
			}
			err := t.tail()
			if err == nil {
				continue
			}

			// wait for writing new lines.
			select {
			case cherr <- err:
			case <-t.ctx.Done():
				return
			}
			select {
			case _, ok := <-ch:
				if !ok {
					return
				}
			case <-t.ctx.Done():
				return
			}
		}
	}()

	var renamed bool
	var waiting bool // waiting for writing new lines?
	for {
		select {
		case event := <-t.watcher.Events:
			if (event.Op & fsnotify.Remove) != 0 {
				// the target file is removed, stop tailing.
				return
			}
			if (event.Op & fsnotify.Rename) != 0 {
				// log rotation is detected.
				if !renamed {
					// start to watch creating new file.
					t.parent.wg.Add(1)
					go t.parent.runFile(os.SEEK_SET)

					// wait a little, and stop tailing old file.
					go func() {
						timer := time.NewTimer(tailOldFileDelay)
						defer timer.Stop()
						select {
						case <-timer.C:
							t.cancel()
						case <-t.ctx.Done():
						}
					}()
				}
				name, err := getFileName(t.file)
				if err != nil {
					t.parent.errors <- err
					return
				}
				t.watcher.Add(name)
				renamed = true
			}

			// notify new lines are wrote.
			if waiting {
				ch <- struct{}{}
			}
		case err := <-cherr:
			if err == io.EOF {
				waiting = true
			} else {
				t.parent.errors <- err
				return
			}
		case err := <-t.watcher.Errors:
			t.parent.errors <- err
			return
		case <-t.ctx.Done():
			return
		}
	}
}

// runReader tails io.Reader
func (t *tail) runReader() {
	defer t.parent.wg.Done()
	defer t.cancel()
	err := t.tail()
	if err == io.EOF || err == io.ErrClosedPipe {
		return
	}
	if err != nil {
		t.parent.errors <- err
		return
	}
}

// restrict detects a file that is truncated
func (t *tail) restict() error {
	stat, err := t.file.Stat()
	if err != nil {
		return err
	}
	pos, err := t.file.Seek(0, os.SEEK_CUR)
	if err != nil {
		return err
	}
	if stat.Size() < pos {
		// file is trancated. seek to head of file.
		_, err := t.file.Seek(0, os.SEEK_SET)
		if err != nil {
			return err
		}
	}
	return nil
}

// tail reads lines until EOF
func (t *tail) tail() error {
	for {
		line, err := t.reader.ReadString('\n')
		if err != nil {
			t.buf += line
			return err
		}
		t.parent.lines <- &Line{t.buf + line, time.Now()}
		t.buf = ""
	}
}
<|endoftext|>


<commit_before>package redkeep

import (
	"errors"
	"log"
	"strings"
	"time"

	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

const requeryDuration = 2 * time.Second

//TailAgent the worker that tails the database
type TailAgent struct {
	isConnected bool
	config      Configuration
	session     *mgo.Session
}

func (t TailAgent) analyzeResult(dataset map[string]interface{}) {
	namespace, ok := dataset["ns"].(string)
	if namespace == "" || !ok {
		return
	}

	p := strings.Index(namespace, ".")
	if p == -1 {
		return
	}

	triggerDB := namespace[:p]
	triggerCollection := namespace[p+1:]

	operationType := dataset["op"]

	if command, ok := dataset["o"].(map[string]interface{}); ok {
		triggerID, _ := command["_id"].(bson.ObjectId)

		switch operationType {
		case "i":
			if namespace == "live.comment" {
				author := GetValue("author", command)

				id, okA := GetValue("$id", author).(bson.ObjectId)
				col, okB := GetValue("$ref", author).(string)
				db, okC := GetValue("$db", author).(string)

				if okA && okB && okC {
					session := t.session.Copy()
					session.SetMode(mgo.Strong, true)

					user := map[string]interface{}{}

					collection := session.DB(db).C(col)
					collection.FindId(id).One(&user)

					username := GetValue("username", user)

					collection = session.DB(triggerDB).C(triggerCollection)
					collection.Update(bson.M{"_id": triggerID}, bson.M{"$set": bson.M{"redkeep.metadata.username": username}})
				}
			}

		case "u":
		case "d":
		case "c":
			//system commands. We do not care.
		default:
			log.Printf("unsupported operation %s.\n", operationType)
			return
		}
	}
}

//GetValue works like this:
//from must be a selector like user.comment.author
//GetValue then looks recursively for that element
//therefore all of the following return values are possible
//map[string]interface{}
//nil
//string
//or basic mongodb types
func GetValue(from string, ds interface{}) interface{} {
	data, ok := ds.(map[string]interface{})
	if !ok {
		return nil
	}

	if index := strings.Index(from, "."); index != -1 {
		return GetValue(from[index+1:], data[from[:index]])
	}

	return data[from]
}

//Tail will start an inifite look that tails the oplog
//as long as the channel does not get any input
func (t TailAgent) Tail(quit chan bool) error {
	session := t.session.Copy()
	defer session.Close()

	oplogCollection := session.DB("local").C("oplog.rs")

	startTime := bson.MongoTimestamp(time.Now().Unix())

	iter := oplogCollection.Find(
		bson.M{"ts": bson.M{"$gt": bson.MongoTimestamp(startTime)}},
	).LogReplay().Sort("$natural").Tail(requeryDuration)

	var lastTimestamp bson.MongoTimestamp
	for {
		select {
		case <-quit:
			log.Println("Agent stopped.")
			return nil
		default:
		}

		var result map[string]interface{}

		for iter.Next(&result) {
			lastTimestamp = result["ts"].(bson.MongoTimestamp)
			t.analyzeResult(result)
		}

		if iter.Err() != nil {
			return iter.Close()
		}

		if iter.Timeout() {
			continue
		}

		query := oplogCollection.Find(bson.M{"ts": bson.M{"$gt": lastTimestamp}})
		iter = query.LogReplay().Sort("$natural").Tail(requeryDuration)
	}

	iter.Close()

	return errors.New("Tailable has no more results")
}

func (t *TailAgent) connect() error {
	if !t.isConnected {
		session, err := mgo.Dial(t.config.Mongo.ConnectionURI)

		if err != nil {
			log.Println(err)
			return err
		}

		session.SetMode(mgo.Monotonic, true)
		t.session = session
	}

	return nil
}

//NewTailAgent will generate a new tail agent
func NewTailAgent(c Configuration) (*TailAgent, error) {
	agent := &TailAgent{config: c}
	err := agent.connect()
	return agent, err
}
<commit_msg>Minor cleanups<commit_after>package redkeep

import (
	"errors"
	"log"
	"strings"
	"time"

	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
)

const requeryDuration = 2 * time.Second

//TailAgent the worker that tails the database
type TailAgent struct {
	config  Configuration
	session *mgo.Session
}

func (t TailAgent) analyzeResult(dataset map[string]interface{}) {
	namespace, ok := dataset["ns"].(string)
	if namespace == "" || !ok {
		return
	}

	p := strings.Index(namespace, ".")
	if p == -1 {
		return
	}

	triggerDB := namespace[:p]
	triggerCollection := namespace[p+1:]
	operationType := dataset["op"]

	if command, ok := dataset["o"].(map[string]interface{}); ok {
		triggerID, _ := command["_id"].(bson.ObjectId)

		switch operationType {
		case "i":
			if namespace == "live.comment" {
				author := GetValue("author", command)

				id, okA := GetValue("$id", author).(bson.ObjectId)
				col, okB := GetValue("$ref", author).(string)
				db, okC := GetValue("$db", author).(string)

				if okA && okB && okC {
					session := t.session.Copy()

					user := map[string]interface{}{}

					collection := session.DB(db).C(col)
					collection.FindId(id).One(&user)

					username := GetValue("username", user)

					collection = session.DB(triggerDB).C(triggerCollection)
					collection.Update(bson.M{"_id": triggerID}, bson.M{"$set": bson.M{"redkeep.metadata.username": username}})
				}
			}

		case "u":
		case "d":
		case "c":
			//system commands. We do not care.
		default:
			log.Printf("unsupported operation %s.\n", operationType)
			return
		}
	}
}

//GetValue works like this:
//from must be a selector like user.comment.author
//GetValue then looks recursively for that element
//therefore all of the following return values are possible
//map[string]interface{}
//nil
//string
//or basic mongodb types
func GetValue(from string, ds interface{}) interface{} {
	data, ok := ds.(map[string]interface{})
	if !ok {
		return nil
	}

	if index := strings.Index(from, "."); index != -1 {
		return GetValue(from[index+1:], data[from[:index]])
	}

	return data[from]
}

//Tail will start an inifite look that tails the oplog
//as long as the channel does not get any input
func (t TailAgent) Tail(quit chan bool) error {
	session := t.session.Copy()
	defer session.Close()

	oplogCollection := session.DB("local").C("oplog.rs")

	startTime := bson.MongoTimestamp(time.Now().Unix())

	iter := oplogCollection.Find(
		bson.M{"ts": bson.M{"$gt": bson.MongoTimestamp(startTime)}},
	).LogReplay().Sort("$natural").Tail(requeryDuration)

	var lastTimestamp bson.MongoTimestamp
	for {
		select {
		case <-quit:
			log.Println("Agent stopped.")
			return nil
		default:
		}

		var result map[string]interface{}

		for iter.Next(&result) {
			lastTimestamp = result["ts"].(bson.MongoTimestamp)
			t.analyzeResult(result)
		}

		if iter.Err() != nil {
			return iter.Close()
		}

		if iter.Timeout() {
			continue
		}

		query := oplogCollection.Find(bson.M{"ts": bson.M{"$gt": lastTimestamp}})
		iter = query.LogReplay().Sort("$natural").Tail(requeryDuration)
	}

	iter.Close()

	return errors.New("Tailable has no more results")
}

func (t *TailAgent) connect() error {
	session, err := mgo.Dial(t.config.Mongo.ConnectionURI)

	if err != nil {
		log.Println(err)
		return err
	}

	session.SetMode(mgo.Strong, true)
	t.session = session

	return nil
}

//NewTailAgent will generate a new tail agent
func NewTailAgent(c Configuration) (*TailAgent, error) {
	agent := &TailAgent{config: c}
	err := agent.connect()
	return agent, err
}
<|endoftext|>


<commit_before>package proxmox

import (
	"encoding/json"
	"errors"
	"fmt"
	"strings"
	"time"

	_ "github.com/davecgh/go-spew/spew"
)

type Task struct {
	UPid       string  `json:"upid"`
	Type       string  `json:"type"`
	Status     string  `json:"status"`
	ExitStatus string  `json:"exitstatus"`
	PID        float64 `json:"pid"`
	PStart     float64 `json:"pstart"`
	StartTime  float64 `json:"starttime"`
	EndTime    float64 `json:"endtime"`
	ID         string  `json:"id"`
	proxmox    ProxMox
}

type TaskList map[string]Task

type TaskResult struct {
	Data Task `json:"data"`
}

func (task Task) GetStatus() (string, string, error) {
	var target string
	var err error
	var raw []byte
	var data TaskResult

	upidParts := strings.Split(task.UPid, ":")
	target = "nodes/" + upidParts[1] + "/tasks/" + task.UPid + "/status"
	//fmt.Println("target  " + target)
	raw, err = task.proxmox.GetBytes(target)
	if err != nil {
		fmt.Println("Error getting Task Status!")
		return "", "", err
	}
	err = json.Unmarshal(raw, &data)
	if err != nil {
		fmt.Println(string(raw))
		return "", "", err
	}
	return data.Data.Status, data.Data.ExitStatus, nil
}

func (task Task) WaitForStatus(status string, timeout int) (string, error) {
	var i int
	var err error
	var actstatus string
	var exitstatus string
	for i = 0; i < timeout; i++ {
		actstatus, exitstatus, err = task.GetStatus()
		fmt.Println(actstatus)
		if err != nil {
			return "", err
		}
		if actstatus == status {
			return exitstatus, nil
		}
		time.Sleep(time.Second * 1)
	}
	return "", errors.New("Timeout reached")
}
<commit_msg>remove unneeded fmt prints<commit_after>package proxmox

import (
	"encoding/json"
	"errors"
	"strings"
	"time"

	_ "github.com/davecgh/go-spew/spew"
)

type Task struct {
	UPid       string  `json:"upid"`
	Type       string  `json:"type"`
	Status     string  `json:"status"`
	ExitStatus string  `json:"exitstatus"`
	PID        float64 `json:"pid"`
	PStart     float64 `json:"pstart"`
	StartTime  float64 `json:"starttime"`
	EndTime    float64 `json:"endtime"`
	ID         string  `json:"id"`
	proxmox    ProxMox
}

type TaskList map[string]Task

type TaskResult struct {
	Data Task `json:"data"`
}

func (task Task) GetStatus() (string, string, error) {
	var target string
	var err error
	var raw []byte
	var data TaskResult

	upidParts := strings.Split(task.UPid, ":")
	target = "nodes/" + upidParts[1] + "/tasks/" + task.UPid + "/status"
	//fmt.Println("target  " + target)
	raw, err = task.proxmox.GetBytes(target)
	if err != nil {
		return "", "", err
	}
	err = json.Unmarshal(raw, &data)
	if err != nil {
		return "", "", err
	}
	return data.Data.Status, data.Data.ExitStatus, nil
}

func (task Task) WaitForStatus(status string, timeout int) (string, error) {
	var i int
	var err error
	var actstatus string
	var exitstatus string
	for i = 0; i < timeout; i++ {
		actstatus, exitstatus, err = task.GetStatus()
		if err != nil {
			return "", err
		}
		if actstatus == status {
			return exitstatus, nil
		}
		time.Sleep(time.Second * 1)
	}
	return "", errors.New("Timeout reached")
}
<|endoftext|>


<commit_before>package tmsh

import (
	"bufio"
	"bytes"
	"fmt"
	"strings"
)

type BigIP struct {
	host    string
	user    string
	sshconn SSH
}

func NewSession(host, port, user, password string) (*BigIP, error) {
	sshconn, err := NewSSHConnection(host+":"+port, user, password)
	if err != nil {
		return nil, err
	}

	ret, _ := sshconn.Recv("# ")
	if !strings.Contains(string(ret), "(tmos)") {
		_, err := sshconn.Send("tmsh")
		if err != nil {
			return nil, err
		}

		ret, err = sshconn.Recv("# ")
		if err != nil {
			return nil, err
		}
	}

	return &BigIP{
		host:    host,
		user:    user,
		sshconn: sshconn,
	}, nil
}

func (bigip *BigIP) ExecuteCommand(cmd string) (string, error) {
	promptSuffix := "# "

	_, err := bigip.sshconn.Send(cmd)
	if err != nil {
		return "", err
	}

	results, err := bigip.sshconn.Recv(promptSuffix)
	if err != nil {
		return "", err
	}

	results = removeCarriageReturn(results)
	reader := bytes.NewReader(results)
	scanner := bufio.NewScanner(reader)

	var lines []string

	for scanner.Scan() {
		text := scanner.Text()
		line := removeSpaceAndBackspace(text)

		if strings.HasPrefix(line, "Last login:") ||
			strings.Contains(line, "(tmos)") ||
			strings.HasPrefix(line, cmd) {
			continue
		}

		lines = append(lines, text)
	}

	return strings.Join(lines, "\n"), nil
}

func (bigip *BigIP) Save() error {
	ret, err := bigip.ExecuteCommand("save /sys config current-partition")
	if err != nil {
		fmt.Errorf(ret)
	}

	if strings.Contains(ret, "Syntax Error: \"current-partition\" unknown property") {
		ret, err = bigip.ExecuteCommand("save /sys config")
		if err != nil {
			fmt.Errorf(ret)
		}
	}

	if strings.Contains(ret, "Error") {
		return fmt.Errorf(ret)
	}

	return nil
}

func (bigip *BigIP) Close() {
	bigip.sshconn.Close()
}
<commit_msg>Suppress pager output<commit_after>package tmsh

import (
	"bufio"
	"bytes"
	"fmt"
	"strings"
)

type BigIP struct {
	host    string
	user    string
	sshconn SSH
}

func NewSession(host, port, user, password string) (*BigIP, error) {
	sshconn, err := NewSSHConnection(host+":"+port, user, password)
	if err != nil {
		return nil, err
	}

	ret, _ := sshconn.Recv("# ")
	if !strings.Contains(string(ret), "(tmos)") {
		_, err := sshconn.Send("tmsh")
		if err != nil {
			return nil, err
		}

		ret, err = sshconn.Recv("# ")
		if err != nil {
			return nil, err
		}
	}

	bigip := &BigIP{
		host:    host,
		user:    user,
		sshconn: sshconn,
	}

	// Suppress pager output
	if _, err := bigip.ExecuteCommand("modify cli preference pager disabled display-threshold 0"); err != nil {
		return nil, err
	}

	return bigip, nil
}

func (bigip *BigIP) ExecuteCommand(cmd string) (string, error) {
	promptSuffix := "# "

	_, err := bigip.sshconn.Send(cmd)
	if err != nil {
		return "", err
	}

	results, err := bigip.sshconn.Recv(promptSuffix)
	if err != nil {
		return "", err
	}

	results = removeCarriageReturn(results)
	reader := bytes.NewReader(results)
	scanner := bufio.NewScanner(reader)

	var lines []string

	for scanner.Scan() {
		text := scanner.Text()
		line := removeSpaceAndBackspace(text)

		if strings.HasPrefix(line, "Last login:") ||
			strings.Contains(line, "(tmos)") ||
			strings.HasPrefix(line, cmd) {
			continue
		}

		lines = append(lines, text)
	}

	return strings.Join(lines, "\n"), nil
}

func (bigip *BigIP) Save() error {
	ret, err := bigip.ExecuteCommand("save /sys config current-partition")
	if err != nil {
		fmt.Errorf(ret)
	}

	if strings.Contains(ret, "Syntax Error: \"current-partition\" unknown property") {
		ret, err = bigip.ExecuteCommand("save /sys config")
		if err != nil {
			fmt.Errorf(ret)
		}
	}

	if strings.Contains(ret, "Error") {
		return fmt.Errorf(ret)
	}

	return nil
}

func (bigip *BigIP) Close() {
	bigip.sshconn.Close()
}
<|endoftext|>


<commit_before>// Copyright (c) 2011 - Gustavo Niemeyer <gustavo@niemeyer.net>
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice,
//       this list of conditions and the following disclaimer in the documentation
//       and/or other materials provided with the distribution.
//     * Neither the name of the copyright holder nor the names of its
//       contributors may be used to endorse or promote products derived from
//       this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// The tomb package helps with clean goroutine termination.
//
// See the Tomb type for details.
package tomb

import (
	"fmt"
	"os"
	"sync"
)

type nothing struct{}

// A Tomb tracks the lifecycle of a goroutine as alive, dying or dead,
// and the reason for its death.
//
// The clean state of a Tomb informs that a goroutine is about to be
// created or already alive. Once Fatal or Fatalf is called with an
// argument that informs the reason for death, the goroutine is in
// a dying state and is expected to terminate soon. Right before the
// goroutine function or method returns, Done must be called to inform
// that the goroutine is indeed dead and about to stop running.
//
// A Tomb exposes Dying and Dead channels. These channels are closed
// when the Tomb state changes in the respective way. They enable
// explicit blocking until the state changes, and also to selectively
// unblock select statements accordingly.
//
type Tomb struct {
	m      sync.Mutex
	Dying  chan nothing
	Dead   chan nothing
	reason os.Error
}

// New creates a new Tomb to track the lifecycle of a goroutine
// that is already alive or about to be created.
func New() *Tomb {
	return &Tomb{Dying: make(chan nothing), Dead: make(chan nothing)}
}

// IsDying returns true if the goroutine is in a dying or already dead state.
func (t *Tomb) IsDying() bool {
	select {
	case <-t.Dying:
		return true
	default:
	}
	return false
}

// IsDead returns true if the goroutine is in a dead state.
func (t *Tomb) IsDead() bool {
	select {
	case <-t.Dead:
		return true
	default:
	}
	return false
}

// Wait blocks until the goroutine is in a dead state and returns the
// reason for its death. The reason may be nil.
func (t *Tomb) Wait() os.Error {
	<-t.Dead
	return t.reason
}

// Done puts the goroutine in a dead state, and should be called a
// single time right before the goroutine function or method returns.
// If the goroutine was not already in a dying state before Done is
// called, it will flagged as dying and dead at once.
func (t *Tomb) Done() {
	t.Fatal(nil)
	close(t.Dead)
}

// Fatal puts the goroutine in a dying state.
// The first non-nil reason parameter to Fatal or the first Fatalf-generated
// error is recorded as the reason for the goroutine death.
// This method may be safely called concurrently, and may be called both from
// within the goroutine and/or from outside to request the goroutine termination.
func (t *Tomb) Fatal(reason os.Error) {
	t.m.Lock()
	if t.reason == nil {
		t.reason = reason
	}
	select {
	case <-t.Dying:
	default:
		close(t.Dying)
	}
	t.m.Unlock()
}

// Fatalf works like Fatal, but builds the reason providing the received
// arguments to fmt.Errorf. The generated error is also returned.
func (t *Tomb) Fatalf(format string, args ...interface{}) os.Error {
	err := fmt.Errorf(format, args...)
	t.Fatal(err)
	return err
}

// Err returns the reason for the goroutine death provided via Fatal or Fatalf.
func (t *Tomb) Err() (reason os.Error) {
	t.m.Lock()
	reason = t.reason
	t.m.Unlock()
	return
}
<commit_msg>Mentioned the blog post in docs.<commit_after>// Copyright (c) 2011 - Gustavo Niemeyer <gustavo@niemeyer.net>
//
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//
//     * Redistributions of source code must retain the above copyright notice,
//       this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright notice,
//       this list of conditions and the following disclaimer in the documentation
//       and/or other materials provided with the distribution.
//     * Neither the name of the copyright holder nor the names of its
//       contributors may be used to endorse or promote products derived from
//       this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

// The tomb package helps with clean goroutine termination.
//
// See the Tomb type for details.
package tomb

import (
	"fmt"
	"os"
	"sync"
)

type nothing struct{}

// A Tomb tracks the lifecycle of a goroutine as alive, dying or dead,
// and the reason for its death.
//
// The clean state of a Tomb informs that a goroutine is about to be
// created or already alive. Once Fatal or Fatalf is called with an
// argument that informs the reason for death, the goroutine is in
// a dying state and is expected to terminate soon. Right before the
// goroutine function or method returns, Done must be called to inform
// that the goroutine is indeed dead and about to stop running.
//
// A Tomb exposes Dying and Dead channels. These channels are closed
// when the Tomb state changes in the respective way. They enable
// explicit blocking until the state changes, and also to selectively
// unblock select statements accordingly.
//
// For background and a detailed example, see the following blog post:
//
//   http://blog.labix.org/2011/10/09/death-of-goroutines-under-control
//
type Tomb struct {
	m      sync.Mutex
	Dying  chan nothing
	Dead   chan nothing
	reason os.Error
}

// New creates a new Tomb to track the lifecycle of a goroutine
// that is already alive or about to be created.
func New() *Tomb {
	return &Tomb{Dying: make(chan nothing), Dead: make(chan nothing)}
}

// IsDying returns true if the goroutine is in a dying or already dead state.
func (t *Tomb) IsDying() bool {
	select {
	case <-t.Dying:
		return true
	default:
	}
	return false
}

// IsDead returns true if the goroutine is in a dead state.
func (t *Tomb) IsDead() bool {
	select {
	case <-t.Dead:
		return true
	default:
	}
	return false
}

// Wait blocks until the goroutine is in a dead state and returns the
// reason for its death. The reason may be nil.
func (t *Tomb) Wait() os.Error {
	<-t.Dead
	return t.reason
}

// Done puts the goroutine in a dead state, and should be called a
// single time right before the goroutine function or method returns.
// If the goroutine was not already in a dying state before Done is
// called, it will flagged as dying and dead at once.
func (t *Tomb) Done() {
	t.Fatal(nil)
	close(t.Dead)
}

// Fatal puts the goroutine in a dying state.
// The first non-nil reason parameter to Fatal or the first Fatalf-generated
// error is recorded as the reason for the goroutine death.
// This method may be safely called concurrently, and may be called both from
// within the goroutine and/or from outside to request the goroutine termination.
func (t *Tomb) Fatal(reason os.Error) {
	t.m.Lock()
	if t.reason == nil {
		t.reason = reason
	}
	select {
	case <-t.Dying:
	default:
		close(t.Dying)
	}
	t.m.Unlock()
}

// Fatalf works like Fatal, but builds the reason providing the received
// arguments to fmt.Errorf. The generated error is also returned.
func (t *Tomb) Fatalf(format string, args ...interface{}) os.Error {
	err := fmt.Errorf(format, args...)
	t.Fatal(err)
	return err
}

// Err returns the reason for the goroutine death provided via Fatal or Fatalf.
func (t *Tomb) Err() (reason os.Error) {
	t.m.Lock()
	reason = t.reason
	t.m.Unlock()
	return
}
<|endoftext|>


<commit_before>package config

import (
	"os"
	"path/filepath"
	"xd/lib/configparser"
	"xd/lib/fs"
	"xd/lib/storage"
)

// EnvRootDir is the name of the environmental variable to set the root storage directory at runtime
const EnvRootDir = "XD_HOME"

type SFTPConfig struct {
	Enabled      bool
	Username     string
	Hostname     string
	Keyfile      string
	RemotePubkey string
	Port         int
}

func (cfg *SFTPConfig) Load(s *configparser.Section) error {
	cfg.Username = s.Get("sftp_user", "")
	cfg.Hostname = s.Get("sftp_host", "")
	cfg.Keyfile = s.Get("sftp_keyfile", "")
	cfg.RemotePubkey = s.Get("sftp_remotekey", "")
	cfg.Port = s.GetInt("sftp_port", 22)
	return nil
}

func (cfg *SFTPConfig) Save(s *configparser.Section) error {
	return nil
}

func (cfg *SFTPConfig) LoadEnv() {

}

func (cfg *SFTPConfig) ToFS() fs.Driver {
	return fs.SFTP(cfg.Username, cfg.Hostname, cfg.Keyfile, cfg.RemotePubkey, cfg.Port)
}

type StorageConfig struct {
	// downloads directory
	Downloads string
	// metadata directory
	Meta string
	// root directory
	Root string
	// sftp config
	SFTP SFTPConfig
}

func (cfg *StorageConfig) Load(s *configparser.Section) error {

	if cfg.Root == "" {
		cfg.Root = "storage"
		if s != nil {
			cfg.Root = s.Get("rootdir", cfg.Root)
		}
	}

	cfg.Meta = filepath.Join(cfg.Root, "metadata")

	cfg.Downloads = filepath.Join(cfg.Root, "downloads")

	if s != nil {
		cfg.Downloads = s.Get("downloads", cfg.Downloads)
	}

	if s != nil {
		cfg.SFTP.Enabled = s.Get("sftp", "0") == "1"
	}
	if cfg.SFTP.Enabled {
		return cfg.SFTP.Load(s)
	}
	return nil
}

func (cfg *StorageConfig) Save(s *configparser.Section) error {

	s.Add("rootdir", cfg.Root)
	s.Add("metadata", cfg.Meta)
	s.Add("downloads", cfg.Downloads)
	return nil
}

func (cfg *StorageConfig) LoadEnv() {
	dir := os.Getenv(EnvRootDir)
	if dir != "" {
		cfg.Root = dir
	}
}

func (cfg *StorageConfig) CreateStorage() storage.Storage {

	st := &storage.FsStorage{
		DataDir: cfg.Downloads,
		MetaDir: cfg.Meta,
		FS:      fs.STD,
	}
	if cfg.SFTP.Enabled {
		st.FS = cfg.SFTP.ToFS()
	}
	return st
}
<commit_msg>set stoage directories correctly when using environmental variables<commit_after>package config

import (
	"os"
	"path/filepath"
	"xd/lib/configparser"
	"xd/lib/fs"
	"xd/lib/storage"
)

// EnvRootDir is the name of the environmental variable to set the root storage directory at runtime
const EnvRootDir = "XD_HOME"

type SFTPConfig struct {
	Enabled      bool
	Username     string
	Hostname     string
	Keyfile      string
	RemotePubkey string
	Port         int
}

func (cfg *SFTPConfig) Load(s *configparser.Section) error {
	cfg.Username = s.Get("sftp_user", "")
	cfg.Hostname = s.Get("sftp_host", "")
	cfg.Keyfile = s.Get("sftp_keyfile", "")
	cfg.RemotePubkey = s.Get("sftp_remotekey", "")
	cfg.Port = s.GetInt("sftp_port", 22)
	return nil
}

func (cfg *SFTPConfig) Save(s *configparser.Section) error {
	return nil
}

func (cfg *SFTPConfig) LoadEnv() {

}

func (cfg *SFTPConfig) ToFS() fs.Driver {
	return fs.SFTP(cfg.Username, cfg.Hostname, cfg.Keyfile, cfg.RemotePubkey, cfg.Port)
}

type StorageConfig struct {
	// downloads directory
	Downloads string
	// metadata directory
	Meta string
	// root directory
	Root string
	// sftp config
	SFTP SFTPConfig
}

func (cfg *StorageConfig) Load(s *configparser.Section) error {

	if cfg.Root == "" {
		cfg.Root = "storage"
		if s != nil {
			cfg.Root = s.Get("rootdir", cfg.Root)
		}
	}

	cfg.setSubpaths(s)

	if s != nil {
		cfg.SFTP.Enabled = s.Get("sftp", "0") == "1"
	}
	if cfg.SFTP.Enabled {
		return cfg.SFTP.Load(s)
	}
	return nil

}

func (cfg *StorageConfig) setSubpaths(s *configparser.Section) {
	cfg.Meta = filepath.Join(cfg.Root, "metadata")

	cfg.Downloads = filepath.Join(cfg.Root, "downloads")

	if s != nil {
		cfg.Downloads = s.Get("downloads", cfg.Downloads)
	}
}

func (cfg *StorageConfig) Save(s *configparser.Section) error {

	s.Add("rootdir", cfg.Root)
	s.Add("metadata", cfg.Meta)
	s.Add("downloads", cfg.Downloads)
	return nil
}

func (cfg *StorageConfig) LoadEnv() {
	dir := os.Getenv(EnvRootDir)
	if dir != "" {
		cfg.Root = dir
		cfg.setSubpaths(nil)
	}
}

func (cfg *StorageConfig) CreateStorage() storage.Storage {

	st := &storage.FsStorage{
		DataDir: cfg.Downloads,
		MetaDir: cfg.Meta,
		FS:      fs.STD,
	}
	if cfg.SFTP.Enabled {
		st.FS = cfg.SFTP.ToFS()
	}
	return st
}
<|endoftext|>


<commit_before>package routes

import (
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/didip/tollbooth"
	"github.com/didip/tollbooth/limiter"
	"github.com/didip/tollbooth_gin"
	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"github.com/gin-gonic/contrib/ginrus"
	"github.com/gin-gonic/gin"
	"github.com/news-maily/app/actions"
	"github.com/news-maily/app/routes/middleware"
	"github.com/sirupsen/logrus"
	"github.com/unrolled/secure"
)

// New creates a new HTTP handler with the specified middleware.
func New() http.Handler {
	lvl, err := logrus.ParseLevel(os.Getenv("LOG_LEVEL"))
	if err != nil {
		lvl = logrus.InfoLevel
	}

	log := logrus.New()
	log.SetLevel(lvl)
	log.SetFormatter(&logrus.JSONFormatter{})
	log.SetOutput(os.Stdout)

	store := cookie.NewStore(
		[]byte(os.Getenv("SESSION_AUTH_KEY")),
		[]byte(os.Getenv("SESSION_ENCRYPT_KEY")),
	)

	handler := gin.New()

	handler.Use(gin.Recovery())
	handler.Use(ginrus.Ginrus(log, time.RFC3339, true))
	handler.Use(sessions.Sessions("mbsess", store))
	handler.Use(middleware.Storage())
	handler.Use(middleware.Producer())
	handler.Use(middleware.SetUser())

	// Security headers
	isDev := os.Getenv("ENVIRONMENT") != "prod"
	secureMiddleware := secure.New(secure.Options{
		FrameDeny:             true,
		ContentTypeNosniff:    true,
		BrowserXssFilter:      true,
		SSLRedirect:           true,
		SSLProxyHeaders:       map[string]string{"X-Forwarded-Proto": "https"},
		STSSeconds:            31536000,
		STSIncludeSubdomains:  true,
		STSPreload:            true,
		ContentSecurityPolicy: "default-src 'self';style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; script-src 'self' 'unsafe-inline'",

		IsDevelopment: isDev,
	})
	secureFunc := func() gin.HandlerFunc {
		return func(c *gin.Context) {
			err := secureMiddleware.Process(c.Writer, c.Request)

			// If there was an error, do not continue.
			if err != nil {
				c.Abort()
				return
			}

			// Avoid header rewrite if response is a redirection.
			if status := c.Writer.Status(); status > 300 && status < 399 {
				c.Abort()
			}
		}
	}()

	handler.Use(secureFunc)

	// Web app
	appDir := os.Getenv("APP_DIR")
	if appDir == "" {
		logrus.Panic("app directory not set")
	}

	handler.NoRoute(func(c *gin.Context) {
		if strings.HasPrefix(c.Request.URL.Path, "/api") {
			c.JSON(http.StatusNotFound, gin.H{
				"message": "Not found",
			})
			return
		}

		c.File(appDir + "/index.html")
	})

	// Assets
	handler.Static("/static", appDir+"/static")

	//rate limiter
	lmt := tollbooth.NewLimiter(1, &limiter.ExpirableOptions{DefaultExpirationTTL: time.Hour})
	lmt.SetMessage(`{"message": "You have reached the maximum request limit."}`)
	lmt.SetMessageContentType("application/json; charset=utf-8")
	// Guest routes
	guest := handler.Group("/api")
	guest.Use(middleware.NoCache())
	guest.Use(tollbooth_gin.LimitHandler(lmt))

	guest.GET("/auth/github/callback", actions.GithubCallback)
	guest.GET("/auth/github", actions.GetGithubAuth)
	guest.GET("/auth/google/callback", actions.GoogleCallback)
	guest.GET("/auth/google", actions.GetGoogleAuth)
	guest.GET("/auth/facebook/callback", actions.FacebookCallback)
	guest.GET("/auth/facebook", actions.GetFacebookAuth)
	guest.POST("/authenticate", actions.PostAuthenticate)
	guest.POST("/forgot-password", actions.PostForgotPassword)
	guest.PUT("/forgot-password/:token", actions.PutForgotPassword)
	guest.PUT("/verify-email/:token", actions.PutVerifyEmail)
	guest.POST("/signup", actions.PostSignup)
	guest.POST("/hooks/:uuid", actions.HandleHook)

	// Authorized routes
	authorized := handler.Group("/api")
	authorized.Use(middleware.NoCache())
	authorized.Use(middleware.Authorized())
	authorized.Use(tollbooth_gin.LimitHandler(lmt))
	{
		users := authorized.Group("/users")
		{
			users.GET("", actions.GetMe)
			users.POST("/password", actions.ChangePassword)
		}

		templates := authorized.Group("/templates")
		{
			templates.GET("", middleware.Paginate(), actions.GetTemplates)
			templates.GET("/:name", actions.GetTemplate)
			templates.POST("", actions.PostTemplate)
			templates.PUT("/:name", actions.PutTemplate)
			templates.DELETE("/:name", actions.DeleteTemplate)
		}

		campaigns := authorized.Group("/campaigns")
		{
			campaigns.GET("", middleware.Paginate(), actions.GetCampaigns)
			campaigns.GET("/:id", actions.GetCampaign)
			campaigns.POST("", actions.PostCampaign)
			campaigns.PUT("/:id", actions.PutCampaign)
			campaigns.DELETE("/:id", actions.DeleteCampaign)
			campaigns.POST("/:id/start", actions.StartCampaign)
		}

		lists := authorized.Group("/lists")
		{
			lists.GET("", middleware.Paginate(), actions.GetLists)
			lists.GET("/:id", actions.GetList)
			lists.POST("", actions.PostList)
			lists.PUT("/:id", actions.PutList)
			lists.DELETE("/:id", actions.DeleteList)
			lists.PUT("/:id/subscribers", actions.PutListSubscribers)
			lists.GET("/:id/subscribers", middleware.Paginate(), actions.GetListSubscribers)
			lists.DELETE("/:id/subscribers", actions.DetachListSubscribers)
		}

		subscribers := authorized.Group("/subscribers")
		{
			subscribers.GET("", middleware.Paginate(), actions.GetSubscribers)
			subscribers.GET("/:id", actions.GetSubscriber)
			subscribers.POST("", actions.PostSubscriber)
			subscribers.PUT("/:id", actions.PutSubscriber)
			subscribers.DELETE("/:id", actions.DeleteSubscriber)
		}

		sesKeys := authorized.Group(("/ses-keys"))
		{
			sesKeys.GET("", actions.GetSESKeys)
			sesKeys.POST("", actions.PostSESKeys)
			sesKeys.DELETE("", actions.DeleteSESKeys)
		}
	}

	return handler
}
<commit_msg>increased api limits<commit_after>package routes

import (
	"net/http"
	"os"
	"strings"
	"time"

	"github.com/didip/tollbooth"
	"github.com/didip/tollbooth/limiter"
	"github.com/didip/tollbooth_gin"
	"github.com/gin-contrib/sessions"
	"github.com/gin-contrib/sessions/cookie"
	"github.com/gin-gonic/contrib/ginrus"
	"github.com/gin-gonic/gin"
	"github.com/news-maily/app/actions"
	"github.com/news-maily/app/routes/middleware"
	"github.com/sirupsen/logrus"
	"github.com/unrolled/secure"
)

// New creates a new HTTP handler with the specified middleware.
func New() http.Handler {
	lvl, err := logrus.ParseLevel(os.Getenv("LOG_LEVEL"))
	if err != nil {
		lvl = logrus.InfoLevel
	}

	log := logrus.New()
	log.SetLevel(lvl)
	log.SetFormatter(&logrus.JSONFormatter{})
	log.SetOutput(os.Stdout)

	store := cookie.NewStore(
		[]byte(os.Getenv("SESSION_AUTH_KEY")),
		[]byte(os.Getenv("SESSION_ENCRYPT_KEY")),
	)

	handler := gin.New()

	handler.Use(gin.Recovery())
	handler.Use(ginrus.Ginrus(log, time.RFC3339, true))
	handler.Use(sessions.Sessions("mbsess", store))
	handler.Use(middleware.Storage())
	handler.Use(middleware.Producer())
	handler.Use(middleware.SetUser())

	// Security headers
	isDev := os.Getenv("ENVIRONMENT") != "prod"
	secureMiddleware := secure.New(secure.Options{
		FrameDeny:             true,
		ContentTypeNosniff:    true,
		BrowserXssFilter:      true,
		SSLRedirect:           true,
		SSLProxyHeaders:       map[string]string{"X-Forwarded-Proto": "https"},
		STSSeconds:            31536000,
		STSIncludeSubdomains:  true,
		STSPreload:            true,
		ContentSecurityPolicy: "default-src 'self';style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; script-src 'self' 'unsafe-inline'",

		IsDevelopment: isDev,
	})
	secureFunc := func() gin.HandlerFunc {
		return func(c *gin.Context) {
			err := secureMiddleware.Process(c.Writer, c.Request)

			// If there was an error, do not continue.
			if err != nil {
				c.Abort()
				return
			}

			// Avoid header rewrite if response is a redirection.
			if status := c.Writer.Status(); status > 300 && status < 399 {
				c.Abort()
			}
		}
	}()

	handler.Use(secureFunc)

	// Web app
	appDir := os.Getenv("APP_DIR")
	if appDir == "" {
		logrus.Panic("app directory not set")
	}

	handler.NoRoute(func(c *gin.Context) {
		if strings.HasPrefix(c.Request.URL.Path, "/api") {
			c.JSON(http.StatusNotFound, gin.H{
				"message": "Not found",
			})
			return
		}

		c.File(appDir + "/index.html")
	})

	// Assets
	handler.Static("/static", appDir+"/static")

	//rate limiter
	lmt := tollbooth.NewLimiter(3, &limiter.ExpirableOptions{DefaultExpirationTTL: time.Hour})
	lmt.SetMessage(`{"message": "You have reached the maximum request limit."}`)
	lmt.SetMessageContentType("application/json; charset=utf-8")
	// Guest routes
	guest := handler.Group("/api")
	guest.Use(middleware.NoCache())
	guest.Use(tollbooth_gin.LimitHandler(lmt))

	guest.GET("/auth/github/callback", actions.GithubCallback)
	guest.GET("/auth/github", actions.GetGithubAuth)
	guest.GET("/auth/google/callback", actions.GoogleCallback)
	guest.GET("/auth/google", actions.GetGoogleAuth)
	guest.GET("/auth/facebook/callback", actions.FacebookCallback)
	guest.GET("/auth/facebook", actions.GetFacebookAuth)
	guest.POST("/authenticate", actions.PostAuthenticate)
	guest.POST("/forgot-password", actions.PostForgotPassword)
	guest.PUT("/forgot-password/:token", actions.PutForgotPassword)
	guest.PUT("/verify-email/:token", actions.PutVerifyEmail)
	guest.POST("/signup", actions.PostSignup)
	guest.POST("/hooks/:uuid", actions.HandleHook)

	// Authorized routes
	authorized := handler.Group("/api")
	authorized.Use(middleware.NoCache())
	authorized.Use(middleware.Authorized())
	authorized.Use(tollbooth_gin.LimitHandler(lmt))
	{
		users := authorized.Group("/users")
		{
			users.GET("", actions.GetMe)
			users.POST("/password", actions.ChangePassword)
		}

		templates := authorized.Group("/templates")
		{
			templates.GET("", middleware.Paginate(), actions.GetTemplates)
			templates.GET("/:name", actions.GetTemplate)
			templates.POST("", actions.PostTemplate)
			templates.PUT("/:name", actions.PutTemplate)
			templates.DELETE("/:name", actions.DeleteTemplate)
		}

		campaigns := authorized.Group("/campaigns")
		{
			campaigns.GET("", middleware.Paginate(), actions.GetCampaigns)
			campaigns.GET("/:id", actions.GetCampaign)
			campaigns.POST("", actions.PostCampaign)
			campaigns.PUT("/:id", actions.PutCampaign)
			campaigns.DELETE("/:id", actions.DeleteCampaign)
			campaigns.POST("/:id/start", actions.StartCampaign)
		}

		lists := authorized.Group("/lists")
		{
			lists.GET("", middleware.Paginate(), actions.GetLists)
			lists.GET("/:id", actions.GetList)
			lists.POST("", actions.PostList)
			lists.PUT("/:id", actions.PutList)
			lists.DELETE("/:id", actions.DeleteList)
			lists.PUT("/:id/subscribers", actions.PutListSubscribers)
			lists.GET("/:id/subscribers", middleware.Paginate(), actions.GetListSubscribers)
			lists.DELETE("/:id/subscribers", actions.DetachListSubscribers)
		}

		subscribers := authorized.Group("/subscribers")
		{
			subscribers.GET("", middleware.Paginate(), actions.GetSubscribers)
			subscribers.GET("/:id", actions.GetSubscriber)
			subscribers.POST("", actions.PostSubscriber)
			subscribers.PUT("/:id", actions.PutSubscriber)
			subscribers.DELETE("/:id", actions.DeleteSubscriber)
		}

		sesKeys := authorized.Group(("/ses-keys"))
		{
			sesKeys.GET("", actions.GetSESKeys)
			sesKeys.POST("", actions.PostSESKeys)
			sesKeys.DELETE("", actions.DeleteSESKeys)
		}
	}

	return handler
}
<|endoftext|>


<commit_before>package core

import (
	abci "github.com/tendermint/tendermint/abci/types"
	ctypes "github.com/tendermint/tendermint/rpc/core/types"
	"github.com/tendermint/tendermint/version"
	cmn "github.com/tendermint/tendermint/libs/common"
)

// Query the application for some information.
//
// ```shell
// curl 'localhost:26657/abci_query?path=""&data="abcd"&trusted=false'
// ```
//
// ```go
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
// result, err := client.ABCIQuery("", "abcd", true)
// ```
//
// > The above command returns JSON structured like this:
//
// ```json
// {
// 	"error": "",
// 	"result": {
// 		"response": {
// 			"log": "exists",
// 			"height": 0,
// 			"proof": "010114FED0DAD959F36091AD761C922ABA3CBF1D8349990101020103011406AA2262E2F448242DF2C2607C3CDC705313EE3B0001149D16177BC71E445476174622EA559715C293740C",
// 			"value": "61626364",
// 			"key": "61626364",
// 			"index": -1,
// 			"code": 0
// 		}
// 	},
// 	"id": "",
// 	"jsonrpc": "2.0"
// }
// ```
//
// ### Query Parameters
//
// | Parameter | Type   | Default | Required | Description                                    |
// |-----------+--------+---------+----------+------------------------------------------------|
// | path      | string | false   | false    | Path to the data ("/a/b/c")                    |
// | data      | []byte | false   | true     | Data                                           |
// | height    | int64 | 0       | false    | Height (0 means latest)                        |
// | trusted   | bool   | false   | false    | Does not include a proof of the data inclusion |
func ABCIQuery(path string, data cmn.HexBytes, height int64, trusted bool) (*ctypes.ResultABCIQuery, error) {
	resQuery, err := proxyAppQuery.QuerySync(abci.RequestQuery{
		Path:   path,
		Data:   data,
		Height: height,
		Prove:  !trusted,
	})
	if err != nil {
		return nil, err
	}
	logger.Info("ABCIQuery", "path", path, "data", data, "result", resQuery)
	return &ctypes.ResultABCIQuery{*resQuery}, nil
}

// Get some info about the application.
//
// ```shell
// curl 'localhost:26657/abci_info'
// ```
//
// ```go
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
// info, err := client.ABCIInfo()
// ```
//
// > The above command returns JSON structured like this:
//
// ```json
// {
// 	"error": "",
// 	"result": {
// 		"response": {
// 			"data": "{\"size\":3}"
// 		}
// 	},
// 	"id": "",
// 	"jsonrpc": "2.0"
// }
// ```
func ABCIInfo() (*ctypes.ResultABCIInfo, error) {
	resInfo, err := proxyAppQuery.InfoSync(abci.RequestInfo{version.Version})
	if err != nil {
		return nil, err
	}
	return &ctypes.ResultABCIInfo{*resInfo}, nil
}
<commit_msg>rpc: validate height in abci_query<commit_after>package core

import (
	"fmt"

	abci "github.com/tendermint/tendermint/abci/types"
	cmn "github.com/tendermint/tendermint/libs/common"
	ctypes "github.com/tendermint/tendermint/rpc/core/types"
	"github.com/tendermint/tendermint/version"
)

// Query the application for some information.
//
// ```shell
// curl 'localhost:26657/abci_query?path=""&data="abcd"&trusted=false'
// ```
//
// ```go
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
// result, err := client.ABCIQuery("", "abcd", true)
// ```
//
// > The above command returns JSON structured like this:
//
// ```json
// {
// 	"error": "",
// 	"result": {
// 		"response": {
// 			"log": "exists",
// 			"height": 0,
// 			"proof": "010114FED0DAD959F36091AD761C922ABA3CBF1D8349990101020103011406AA2262E2F448242DF2C2607C3CDC705313EE3B0001149D16177BC71E445476174622EA559715C293740C",
// 			"value": "61626364",
// 			"key": "61626364",
// 			"index": -1,
// 			"code": 0
// 		}
// 	},
// 	"id": "",
// 	"jsonrpc": "2.0"
// }
// ```
//
// ### Query Parameters
//
// | Parameter | Type   | Default | Required | Description                                    |
// |-----------+--------+---------+----------+------------------------------------------------|
// | path      | string | false   | false    | Path to the data ("/a/b/c")                    |
// | data      | []byte | false   | true     | Data                                           |
// | height    | int64 | 0       | false    | Height (0 means latest)                        |
// | trusted   | bool   | false   | false    | Does not include a proof of the data inclusion |
func ABCIQuery(path string, data cmn.HexBytes, height int64, trusted bool) (*ctypes.ResultABCIQuery, error) {
	if height < 0 {
		return nil, fmt.Errorf("height must be non-negative")
	}

	resQuery, err := proxyAppQuery.QuerySync(abci.RequestQuery{
		Path:   path,
		Data:   data,
		Height: height,
		Prove:  !trusted,
	})
	if err != nil {
		return nil, err
	}
	logger.Info("ABCIQuery", "path", path, "data", data, "result", resQuery)
	return &ctypes.ResultABCIQuery{*resQuery}, nil
}

// Get some info about the application.
//
// ```shell
// curl 'localhost:26657/abci_info'
// ```
//
// ```go
// client := client.NewHTTP("tcp://0.0.0.0:26657", "/websocket")
// info, err := client.ABCIInfo()
// ```
//
// > The above command returns JSON structured like this:
//
// ```json
// {
// 	"error": "",
// 	"result": {
// 		"response": {
// 			"data": "{\"size\":3}"
// 		}
// 	},
// 	"id": "",
// 	"jsonrpc": "2.0"
// }
// ```
func ABCIInfo() (*ctypes.ResultABCIInfo, error) {
	resInfo, err := proxyAppQuery.InfoSync(abci.RequestInfo{version.Version})
	if err != nil {
		return nil, err
	}
	return &ctypes.ResultABCIInfo{*resInfo}, nil
}
<|endoftext|>


<commit_before>package main

import (
  "log"
  "os"
  "github.com/nlopes/slack"
  "github.com/inteliquent/casatunes"
)

func casa_NowPlaying(slack_api *slack.Client, slack_channel_id string) {
  CASA_ENDPOINT := os.Getenv("CASA_ENDPOINT")

  message_parameters := slack.NewPostMessageParameters()
  message_parameters.AsUser = true

  casa_api := casatunes.New(CASA_ENDPOINT)

  nowplaying, err := casa_api.NowPlaying("0")
  if err != nil {
    log.Fatal(err)
  }

  attachment := slack.Attachment{
    Title: "Now Playing",
    TitleLink: CASA_ENDPOINT,
    ThumbURL: nowplaying.CurrSong.ArtworkURI,
    Color: "#aeffa0",
    Fields: []slack.AttachmentField{
      slack.AttachmentField{
        Title: "Title",
        Value: nowplaying.CurrSong.Title,
      },
      slack.AttachmentField{
        Title: "Artist",
        Value: nowplaying.CurrSong.Artists,
        Short: true,
      },
      slack.AttachmentField{
        Title: "Album",
        Value: nowplaying.CurrSong.Album,
        Short: true,
      },
    },
  }

  message_parameters.Attachments = []slack.Attachment{attachment}
  if len(nowplaying.CurrSong.Title) > 0 {
    slack_api.PostMessage(
      slack_channel_id,
      "",
      message_parameters,
    )
  }
}
<commit_msg>nowplaying Now accepts slack.MessageEvent object<commit_after>package main

import (
  "log"
  "os"
  "github.com/nlopes/slack"
  "github.com/inteliquent/casatunes"
)

func casa_NowPlaying(slack_api *slack.Client, ev *slack.MessageEvent) {
  CASA_ENDPOINT := os.Getenv("CASA_ENDPOINT")

  message_parameters := slack.NewPostMessageParameters()
  message_parameters.AsUser = true

  casa_api := casatunes.New(CASA_ENDPOINT)

  nowplaying, err := casa_api.NowPlaying("0")
  if err != nil {
    log.Fatal(err)
  }

  attachment := slack.Attachment{
    Title: "Now Playing",
    TitleLink: CASA_ENDPOINT,
    ThumbURL: nowplaying.CurrSong.ArtworkURI,
    Color: "#aeffa0",
    Fields: []slack.AttachmentField{
      {
        Title: "Title",
        Value: nowplaying.CurrSong.Title,
      },
      {
        Title: "Artist",
        Value: nowplaying.CurrSong.Artists,
        Short: true,
      },
      {
        Title: "Album",
        Value: nowplaying.CurrSong.Album,
        Short: true,
      },
    },
  }

  message_parameters.Attachments = []slack.Attachment{attachment}
  if len(nowplaying.CurrSong.Title) > 0 {
    slack_api.PostMessage(
      ev.Channel,
      "",
      message_parameters,
    )
  }
}
<|endoftext|>