<commit_before>package types

import (
	"github.com/keybase/client/go/protocol/chat1"
	"github.com/keybase/client/go/protocol/gregor1"
	"github.com/keybase/client/go/protocol/keybase1"
	context "golang.org/x/net/context"
)

type Offlinable interface {
	IsOffline() bool
	Connected(ctx context.Context)
	Disconnected(ctx context.Context)
}

type TLFInfoSource interface {
	Lookup(ctx context.Context, tlfName string, vis chat1.TLFVisibility) (*TLFInfo, error)
	CryptKeys(ctx context.Context, tlfName string) (keybase1.GetTLFCryptKeysRes, error)
	PublicCanonicalTLFNameAndID(ctx context.Context, tlfName string) (keybase1.CanonicalTLFNameAndIDWithBreaks, error)
	CompleteAndCanonicalizePrivateTlfName(ctx context.Context, tlfName string) (res keybase1.CanonicalTLFNameAndIDWithBreaks, err error)
}

type ConversationSource interface {
	Offlinable

	Push(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID,
		msg chat1.MessageBoxed) (chat1.MessageUnboxed, bool, error)
	Pull(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID, query *chat1.GetThreadQuery,
		pagination *chat1.Pagination) (chat1.ThreadView, []*chat1.RateLimit, error)
	PullLocalOnly(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID,
		query *chat1.GetThreadQuery, p *chat1.Pagination) (chat1.ThreadView, error)
	GetMessages(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID, msgIDs []chat1.MessageID, finalizeInfo *chat1.ConversationFinalizeInfo) ([]chat1.MessageUnboxed, error)
	Clear(convID chat1.ConversationID, uid gregor1.UID) error
	TransformSupersedes(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID, msgs []chat1.MessageUnboxed, finalizeInfo *chat1.ConversationFinalizeInfo) ([]chat1.MessageUnboxed, error)

	SetRemoteInterface(func() chat1.RemoteInterface)
	SetTLFInfoSource(tlfInfoSource TLFInfoSource)
}

type MessageDeliverer interface {
	Offlinable

	Queue(ctx context.Context, convID chat1.ConversationID, msg chat1.MessagePlaintext,
		identifyBehavior keybase1.TLFIdentifyBehavior) (chat1.OutboxRecord, error)
	Start(ctx context.Context, uid gregor1.UID)
	Stop(ctx context.Context) chan struct{}
	ForceDeliverLoop(ctx context.Context)
}

type ChatLocalizer interface {
	Localize(ctx context.Context, uid gregor1.UID, inbox chat1.Inbox) ([]chat1.ConversationLocal, error)
	Name() string
	SetOffline()
}

type InboxSource interface {
	Offlinable

	Read(ctx context.Context, uid gregor1.UID, localizer ChatLocalizer, useLocalData bool,
		query *chat1.GetInboxLocalQuery, p *chat1.Pagination) (chat1.Inbox, *chat1.RateLimit, error)
	ReadUnverified(ctx context.Context, uid gregor1.UID, useLocalData bool,
		query *chat1.GetInboxQuery, p *chat1.Pagination) (chat1.Inbox, *chat1.RateLimit, error)

	NewConversation(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers,
		conv chat1.Conversation) error
	NewMessage(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, convID chat1.ConversationID,
		msg chat1.MessageBoxed) (*chat1.ConversationLocal, error)
	ReadMessage(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, convID chat1.ConversationID,
		msgID chat1.MessageID) (*chat1.ConversationLocal, error)
	SetStatus(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, convID chat1.ConversationID,
		status chat1.ConversationStatus) (*chat1.ConversationLocal, error)
	TlfFinalize(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers,
		convIDs []chat1.ConversationID, finalizeInfo chat1.ConversationFinalizeInfo) ([]chat1.ConversationLocal, error)

	SetRemoteInterface(func() chat1.RemoteInterface)
	SetTLFInfoSource(tlfInfoSource TLFInfoSource)
}

type Identifier interface {
	Identify(ctx context.Context, assertion string, private bool, idBehavior keybase1.TLFIdentifyBehavior) (keybase1.TLFIdentifyFailure, error)
}
<commit_msg>More cleanup<commit_after>package types

import (
	"github.com/keybase/client/go/protocol/chat1"
	"github.com/keybase/client/go/protocol/gregor1"
	"github.com/keybase/client/go/protocol/keybase1"
	context "golang.org/x/net/context"
)

type Offlinable interface {
	IsOffline() bool
	Connected(ctx context.Context)
	Disconnected(ctx context.Context)
}

type TLFInfoSource interface {
	Lookup(ctx context.Context, tlfName string, vis chat1.TLFVisibility) (*TLFInfo, error)
	CryptKeys(ctx context.Context, tlfName string) (keybase1.GetTLFCryptKeysRes, error)
	PublicCanonicalTLFNameAndID(ctx context.Context, tlfName string) (keybase1.CanonicalTLFNameAndIDWithBreaks, error)
	CompleteAndCanonicalizePrivateTlfName(ctx context.Context, tlfName string) (res keybase1.CanonicalTLFNameAndIDWithBreaks, err error)
}

type ConversationSource interface {
	Offlinable

	Push(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID,
		msg chat1.MessageBoxed) (chat1.MessageUnboxed, bool, error)
	Pull(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID, query *chat1.GetThreadQuery,
		pagination *chat1.Pagination) (chat1.ThreadView, []*chat1.RateLimit, error)
	PullLocalOnly(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID,
		query *chat1.GetThreadQuery, p *chat1.Pagination) (chat1.ThreadView, error)
	GetMessages(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID, msgIDs []chat1.MessageID, finalizeInfo *chat1.ConversationFinalizeInfo) ([]chat1.MessageUnboxed, error)
	Clear(convID chat1.ConversationID, uid gregor1.UID) error
	TransformSupersedes(ctx context.Context, convID chat1.ConversationID, uid gregor1.UID, msgs []chat1.MessageUnboxed, finalizeInfo *chat1.ConversationFinalizeInfo) ([]chat1.MessageUnboxed, error)

	SetRemoteInterface(func() chat1.RemoteInterface)
	SetTLFInfoSource(tlfInfoSource TLFInfoSource)
}

type MessageDeliverer interface {
	Offlinable

	Queue(ctx context.Context, convID chat1.ConversationID, msg chat1.MessagePlaintext,
		identifyBehavior keybase1.TLFIdentifyBehavior) (chat1.OutboxRecord, error)
	Start(ctx context.Context, uid gregor1.UID)
	Stop(ctx context.Context) chan struct{}
	ForceDeliverLoop(ctx context.Context)
}

type ChatLocalizer interface {
	Localize(ctx context.Context, uid gregor1.UID, inbox chat1.Inbox) ([]chat1.ConversationLocal, error)
	Name() string
	SetOffline()
}

type InboxSource interface {
	Offlinable

	Read(ctx context.Context, uid gregor1.UID, localizer ChatLocalizer, useLocalData bool,
		query *chat1.GetInboxLocalQuery, p *chat1.Pagination) (chat1.Inbox, *chat1.RateLimit, error)
	ReadUnverified(ctx context.Context, uid gregor1.UID, useLocalData bool,
		query *chat1.GetInboxQuery, p *chat1.Pagination) (chat1.Inbox, *chat1.RateLimit, error)

	NewConversation(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers,
		conv chat1.Conversation) error
	NewMessage(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, convID chat1.ConversationID,
		msg chat1.MessageBoxed) (*chat1.ConversationLocal, error)
	ReadMessage(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, convID chat1.ConversationID,
		msgID chat1.MessageID) (*chat1.ConversationLocal, error)
	SetStatus(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers, convID chat1.ConversationID,
		status chat1.ConversationStatus) (*chat1.ConversationLocal, error)
	TlfFinalize(ctx context.Context, uid gregor1.UID, vers chat1.InboxVers,
		convIDs []chat1.ConversationID, finalizeInfo chat1.ConversationFinalizeInfo) ([]chat1.ConversationLocal, error)

	SetRemoteInterface(func() chat1.RemoteInterface)
	SetTLFInfoSource(tlfInfoSource TLFInfoSource)
}
<|endoftext|>


<commit_before>// Copyright 2017-present The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package releaser implements a set of utilities and a wrapper around Goreleaser
// to help automate the Hugo release process.
package releaser

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/gohugoio/hugo/common/hugo"
	"github.com/pkg/errors"
)

const commitPrefix = "releaser:"

type releaseNotesState int

const (
	releaseNotesNone = iota
	releaseNotesCreated
	releaseNotesReady
)

// ReleaseHandler provides functionality to release a new version of Hugo.
type ReleaseHandler struct {
	cliVersion string

	skipPublish bool

	// Just simulate, no actual changes.
	try bool

	git func(args ...string) (string, error)
}

func (r ReleaseHandler) calculateVersions() (hugo.Version, hugo.Version) {
	newVersion := hugo.MustParseVersion(r.cliVersion)
	finalVersion := newVersion.Next()
	finalVersion.PatchLevel = 0

	if newVersion.Suffix != "-test" {
		newVersion.Suffix = ""
	}

	finalVersion.Suffix = "-DEV"

	return newVersion, finalVersion
}

// New initialises a ReleaseHandler.
func New(version string, skipPublish, try bool) *ReleaseHandler {
	// When triggered from CI release branch
	version = strings.TrimPrefix(version, "release-")
	version = strings.TrimPrefix(version, "v")
	rh := &ReleaseHandler{cliVersion: version, skipPublish: skipPublish, try: try}

	if try {
		rh.git = func(args ...string) (string, error) {
			fmt.Println("git", strings.Join(args, " "))
			return "", nil
		}
	} else {
		rh.git = git
	}

	return rh
}

// Run creates a new release.
func (r *ReleaseHandler) Run() error {
	if os.Getenv("GITHUB_TOKEN") == "" {
		return errors.New("GITHUB_TOKEN not set, create one here with the repo scope selected: https://github.com/settings/tokens/new")
	}

	newVersion, finalVersion := r.calculateVersions()

	version := newVersion.String()
	tag := "v" + version

	// Exit early if tag already exists
	exists, err := tagExists(tag)
	if err != nil {
		return err
	}

	if exists {
		return fmt.Errorf("tag %q already exists", tag)
	}

	var changeLogFromTag string

	if newVersion.PatchLevel == 0 {
		// There may have been patch releases between, so set the tag explicitly.
		changeLogFromTag = "v" + newVersion.Prev().String()
		exists, _ := tagExists(changeLogFromTag)
		if !exists {
			// fall back to one that exists.
			changeLogFromTag = ""
		}
	}

	var (
		gitCommits     gitInfos
		gitCommitsDocs gitInfos
		relNotesState  releaseNotesState
	)

	relNotesState, err = r.releaseNotesState(version)
	if err != nil {
		return err
	}

	prepareRelaseNotes := relNotesState == releaseNotesNone
	shouldRelease := relNotesState == releaseNotesReady

	defer r.gitPush() // TODO(bep)

	if prepareRelaseNotes || shouldRelease {
		gitCommits, err = getGitInfos(changeLogFromTag, "hugo", "", !r.try)
		if err != nil {
			return err
		}

		// TODO(bep) explicit tag?
		gitCommitsDocs, err = getGitInfos("", "hugoDocs", "../hugoDocs", !r.try)
		if err != nil {
			return err
		}
	}

	if relNotesState == releaseNotesCreated {
		fmt.Println("Release notes created, but not ready. Reneame to *-ready.md to continue ...")
		return nil
	}

	if prepareRelaseNotes {
		releaseNotesFile, err := r.writeReleaseNotesToTemp(version, gitCommits, gitCommitsDocs)
		if err != nil {
			return err
		}

		if _, err := r.git("add", releaseNotesFile); err != nil {
			return err
		}
		if _, err := r.git("commit", "-m", fmt.Sprintf("%s Add release notes draft for %s\n\nRename to *-ready.md to continue. [ci skip]", commitPrefix, newVersion)); err != nil {
			return err
		}
	}

	if !shouldRelease {
		fmt.Printf("Skip release ... ")
		return nil
	}

	// For docs, for now we assume that:
	// The /docs subtree is up to date and ready to go.
	// The hugoDocs/dev and hugoDocs/master must be merged manually after release.
	// TODO(bep) improve this when we see how it works.

	if err := r.bumpVersions(newVersion); err != nil {
		return err
	}

	if _, err := r.git("commit", "-a", "-m", fmt.Sprintf("%s Bump versions for release of %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
		return err
	}

	releaseNotesFile := getReleaseNotesDocsTempFilename(version, true)

	// Write the release notes to the docs site as well.
	docFile, err := r.writeReleaseNotesToDocs(version, releaseNotesFile)
	if err != nil {
		return err
	}

	if _, err := r.git("add", docFile); err != nil {
		return err
	}
	if _, err := r.git("commit", "-m", fmt.Sprintf("%s Add release notes to /docs for release of %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
		return err
	}

	if _, err := r.git("tag", "-a", tag, "-m", fmt.Sprintf("%s %s [ci skip]", commitPrefix, newVersion)); err != nil {
		return err
	}

	if !r.skipPublish {
		if _, err := r.git("push", "origin", tag); err != nil {
			return err
		}
	}

	if err := r.release(releaseNotesFile); err != nil {
		return err
	}

	if err := r.bumpVersions(finalVersion); err != nil {
		return err
	}

	if !r.try {
		// No longer needed.
		if err := os.Remove(releaseNotesFile); err != nil {
			return err
		}
	}

	if _, err := r.git("commit", "-a", "-m", fmt.Sprintf("%s Prepare repository for %s\n\n[ci skip]", commitPrefix, finalVersion)); err != nil {
		return err
	}

	return nil
}

func (r *ReleaseHandler) gitPush() {
	if r.skipPublish {
		return
	}
	if _, err := r.git("push", "origin", "HEAD"); err != nil {
		log.Fatal("push failed:", err)
	}
}

func (r *ReleaseHandler) release(releaseNotesFile string) error {
	if r.try {
		fmt.Println("Skip goreleaser...")
		return nil
	}

	args := []string{"--rm-dist", "--release-notes", releaseNotesFile}
	if r.skipPublish {
		args = append(args, "--skip-publish")
	}

	cmd := exec.Command("goreleaser", args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	if err != nil {
		return errors.Wrap(err, "goreleaser failed")
	}
	return nil
}

func (r *ReleaseHandler) bumpVersions(ver hugo.Version) error {
	toDev := ""

	if ver.Suffix != "" {
		toDev = ver.Suffix
	}

	if err := r.replaceInFile("common/hugo/version_current.go",
		`Number:(\s{4,})(.*),`, fmt.Sprintf(`Number:${1}%.2f,`, ver.Number),
		`PatchLevel:(\s*)(.*),`, fmt.Sprintf(`PatchLevel:${1}%d,`, ver.PatchLevel),
		`Suffix:(\s{4,})".*",`, fmt.Sprintf(`Suffix:${1}"%s",`, toDev)); err != nil {
		return err
	}

	snapcraftGrade := "stable"
	if ver.Suffix != "" {
		snapcraftGrade = "devel"
	}
	if err := r.replaceInFile("snap/snapcraft.yaml",
		`version: "(.*)"`, fmt.Sprintf(`version: "%s"`, ver),
		`grade: (.*) #`, fmt.Sprintf(`grade: %s #`, snapcraftGrade)); err != nil {
		return err
	}

	var minVersion string
	if ver.Suffix != "" {
		// People use the DEV version in daily use, and we cannot create new themes
		// with the next version before it is released.
		minVersion = ver.Prev().String()
	} else {
		minVersion = ver.String()
	}

	if err := r.replaceInFile("commands/new.go",
		`min_version = "(.*)"`, fmt.Sprintf(`min_version = "%s"`, minVersion)); err != nil {
		return err
	}

	// docs/config.toml
	if err := r.replaceInFile("docs/config.toml",
		`release = "(.*)"`, fmt.Sprintf(`release = "%s"`, ver)); err != nil {
		return err
	}

	return nil
}

func (r *ReleaseHandler) replaceInFile(filename string, oldNew ...string) error {
	fullFilename := hugoFilepath(filename)
	fi, err := os.Stat(fullFilename)
	if err != nil {
		return err
	}

	if r.try {
		fmt.Printf("Replace in %q: %q\n", filename, oldNew)
		return nil
	}

	b, err := ioutil.ReadFile(fullFilename)
	if err != nil {
		return err
	}
	newContent := string(b)

	for i := 0; i < len(oldNew); i += 2 {
		re := regexp.MustCompile(oldNew[i])
		newContent = re.ReplaceAllString(newContent, oldNew[i+1])
	}

	return ioutil.WriteFile(fullFilename, []byte(newContent), fi.Mode())
}

func hugoFilepath(filename string) string {
	pwd, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	return filepath.Join(pwd, filename)
}

func isCI() bool {
	return os.Getenv("CI") != ""
}
<commit_msg>releaser: Remove version replacement in docs config<commit_after>// Copyright 2017-present The Hugo Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

// Package releaser implements a set of utilities and a wrapper around Goreleaser
// to help automate the Hugo release process.
package releaser

import (
	"fmt"
	"io/ioutil"
	"log"
	"os"
	"os/exec"
	"path/filepath"
	"regexp"
	"strings"

	"github.com/gohugoio/hugo/common/hugo"
	"github.com/pkg/errors"
)

const commitPrefix = "releaser:"

type releaseNotesState int

const (
	releaseNotesNone = iota
	releaseNotesCreated
	releaseNotesReady
)

// ReleaseHandler provides functionality to release a new version of Hugo.
type ReleaseHandler struct {
	cliVersion string

	skipPublish bool

	// Just simulate, no actual changes.
	try bool

	git func(args ...string) (string, error)
}

func (r ReleaseHandler) calculateVersions() (hugo.Version, hugo.Version) {
	newVersion := hugo.MustParseVersion(r.cliVersion)
	finalVersion := newVersion.Next()
	finalVersion.PatchLevel = 0

	if newVersion.Suffix != "-test" {
		newVersion.Suffix = ""
	}

	finalVersion.Suffix = "-DEV"

	return newVersion, finalVersion
}

// New initialises a ReleaseHandler.
func New(version string, skipPublish, try bool) *ReleaseHandler {
	// When triggered from CI release branch
	version = strings.TrimPrefix(version, "release-")
	version = strings.TrimPrefix(version, "v")
	rh := &ReleaseHandler{cliVersion: version, skipPublish: skipPublish, try: try}

	if try {
		rh.git = func(args ...string) (string, error) {
			fmt.Println("git", strings.Join(args, " "))
			return "", nil
		}
	} else {
		rh.git = git
	}

	return rh
}

// Run creates a new release.
func (r *ReleaseHandler) Run() error {
	if os.Getenv("GITHUB_TOKEN") == "" {
		return errors.New("GITHUB_TOKEN not set, create one here with the repo scope selected: https://github.com/settings/tokens/new")
	}

	newVersion, finalVersion := r.calculateVersions()

	version := newVersion.String()
	tag := "v" + version

	// Exit early if tag already exists
	exists, err := tagExists(tag)
	if err != nil {
		return err
	}

	if exists {
		return fmt.Errorf("tag %q already exists", tag)
	}

	var changeLogFromTag string

	if newVersion.PatchLevel == 0 {
		// There may have been patch releases between, so set the tag explicitly.
		changeLogFromTag = "v" + newVersion.Prev().String()
		exists, _ := tagExists(changeLogFromTag)
		if !exists {
			// fall back to one that exists.
			changeLogFromTag = ""
		}
	}

	var (
		gitCommits     gitInfos
		gitCommitsDocs gitInfos
		relNotesState  releaseNotesState
	)

	relNotesState, err = r.releaseNotesState(version)
	if err != nil {
		return err
	}

	prepareRelaseNotes := relNotesState == releaseNotesNone
	shouldRelease := relNotesState == releaseNotesReady

	defer r.gitPush() // TODO(bep)

	if prepareRelaseNotes || shouldRelease {
		gitCommits, err = getGitInfos(changeLogFromTag, "hugo", "", !r.try)
		if err != nil {
			return err
		}

		// TODO(bep) explicit tag?
		gitCommitsDocs, err = getGitInfos("", "hugoDocs", "../hugoDocs", !r.try)
		if err != nil {
			return err
		}
	}

	if relNotesState == releaseNotesCreated {
		fmt.Println("Release notes created, but not ready. Reneame to *-ready.md to continue ...")
		return nil
	}

	if prepareRelaseNotes {
		releaseNotesFile, err := r.writeReleaseNotesToTemp(version, gitCommits, gitCommitsDocs)
		if err != nil {
			return err
		}

		if _, err := r.git("add", releaseNotesFile); err != nil {
			return err
		}
		if _, err := r.git("commit", "-m", fmt.Sprintf("%s Add release notes draft for %s\n\nRename to *-ready.md to continue. [ci skip]", commitPrefix, newVersion)); err != nil {
			return err
		}
	}

	if !shouldRelease {
		fmt.Printf("Skip release ... ")
		return nil
	}

	// For docs, for now we assume that:
	// The /docs subtree is up to date and ready to go.
	// The hugoDocs/dev and hugoDocs/master must be merged manually after release.
	// TODO(bep) improve this when we see how it works.

	if err := r.bumpVersions(newVersion); err != nil {
		return err
	}

	if _, err := r.git("commit", "-a", "-m", fmt.Sprintf("%s Bump versions for release of %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
		return err
	}

	releaseNotesFile := getReleaseNotesDocsTempFilename(version, true)

	// Write the release notes to the docs site as well.
	docFile, err := r.writeReleaseNotesToDocs(version, releaseNotesFile)
	if err != nil {
		return err
	}

	if _, err := r.git("add", docFile); err != nil {
		return err
	}
	if _, err := r.git("commit", "-m", fmt.Sprintf("%s Add release notes to /docs for release of %s\n\n[ci skip]", commitPrefix, newVersion)); err != nil {
		return err
	}

	if _, err := r.git("tag", "-a", tag, "-m", fmt.Sprintf("%s %s [ci skip]", commitPrefix, newVersion)); err != nil {
		return err
	}

	if !r.skipPublish {
		if _, err := r.git("push", "origin", tag); err != nil {
			return err
		}
	}

	if err := r.release(releaseNotesFile); err != nil {
		return err
	}

	if err := r.bumpVersions(finalVersion); err != nil {
		return err
	}

	if !r.try {
		// No longer needed.
		if err := os.Remove(releaseNotesFile); err != nil {
			return err
		}
	}

	if _, err := r.git("commit", "-a", "-m", fmt.Sprintf("%s Prepare repository for %s\n\n[ci skip]", commitPrefix, finalVersion)); err != nil {
		return err
	}

	return nil
}

func (r *ReleaseHandler) gitPush() {
	if r.skipPublish {
		return
	}
	if _, err := r.git("push", "origin", "HEAD"); err != nil {
		log.Fatal("push failed:", err)
	}
}

func (r *ReleaseHandler) release(releaseNotesFile string) error {
	if r.try {
		fmt.Println("Skip goreleaser...")
		return nil
	}

	args := []string{"--rm-dist", "--release-notes", releaseNotesFile}
	if r.skipPublish {
		args = append(args, "--skip-publish")
	}

	cmd := exec.Command("goreleaser", args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	err := cmd.Run()
	if err != nil {
		return errors.Wrap(err, "goreleaser failed")
	}
	return nil
}

func (r *ReleaseHandler) bumpVersions(ver hugo.Version) error {
	toDev := ""

	if ver.Suffix != "" {
		toDev = ver.Suffix
	}

	if err := r.replaceInFile("common/hugo/version_current.go",
		`Number:(\s{4,})(.*),`, fmt.Sprintf(`Number:${1}%.2f,`, ver.Number),
		`PatchLevel:(\s*)(.*),`, fmt.Sprintf(`PatchLevel:${1}%d,`, ver.PatchLevel),
		`Suffix:(\s{4,})".*",`, fmt.Sprintf(`Suffix:${1}"%s",`, toDev)); err != nil {
		return err
	}

	snapcraftGrade := "stable"
	if ver.Suffix != "" {
		snapcraftGrade = "devel"
	}
	if err := r.replaceInFile("snap/snapcraft.yaml",
		`version: "(.*)"`, fmt.Sprintf(`version: "%s"`, ver),
		`grade: (.*) #`, fmt.Sprintf(`grade: %s #`, snapcraftGrade)); err != nil {
		return err
	}

	var minVersion string
	if ver.Suffix != "" {
		// People use the DEV version in daily use, and we cannot create new themes
		// with the next version before it is released.
		minVersion = ver.Prev().String()
	} else {
		minVersion = ver.String()
	}

	if err := r.replaceInFile("commands/new.go",
		`min_version = "(.*)"`, fmt.Sprintf(`min_version = "%s"`, minVersion)); err != nil {
		return err
	}

	return nil
}

func (r *ReleaseHandler) replaceInFile(filename string, oldNew ...string) error {
	fullFilename := hugoFilepath(filename)
	fi, err := os.Stat(fullFilename)
	if err != nil {
		return err
	}

	if r.try {
		fmt.Printf("Replace in %q: %q\n", filename, oldNew)
		return nil
	}

	b, err := ioutil.ReadFile(fullFilename)
	if err != nil {
		return err
	}
	newContent := string(b)

	for i := 0; i < len(oldNew); i += 2 {
		re := regexp.MustCompile(oldNew[i])
		newContent = re.ReplaceAllString(newContent, oldNew[i+1])
	}

	return ioutil.WriteFile(fullFilename, []byte(newContent), fi.Mode())
}

func hugoFilepath(filename string) string {
	pwd, err := os.Getwd()
	if err != nil {
		log.Fatal(err)
	}
	return filepath.Join(pwd, filename)
}

func isCI() bool {
	return os.Getenv("CI") != ""
}
<|endoftext|>


<commit_before>// Package clause.
package gigasecond

// Constant declaration.
const testVersion = ? // find the value in gigasecond_test.go

// API function.  It uses a type from the Go standard library.
func AddGigasecond(time.Time) time.Time

// Reviewers don't think much of stub comments.  Replace or remove.
<commit_msg>Golang Gigasecond complete.<commit_after>package gigasecond

import (
	"math"
	"time"
)

const testVersion = 4 // find the value in gigasecond_test.go

var gigasecond = int64(math.Pow(10, 9))

func AddGigasecond(t time.Time) time.Time {
	return time.Unix(t.Unix()+gigasecond, 0).UTC()
}
<|endoftext|>


<commit_before>// -*- tab-width: 4 -*-

// CouchDB API
package couch

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"
)

var def_hdrs = map[string][]string{}

type HttpError struct {
	Status int
	Msg    string
}

type buffer struct {
	b *bytes.Buffer
}

func (b *buffer) Read(out []byte) (int, error) {
	return b.b.Read(out)
}

func (b *buffer) Close() error { return nil }

func (e *HttpError) Error() string {
	return e.Msg
}

func unmarshal_url(u string, results interface{}) error {
	req, err := http.NewRequest("GET", u, nil)
	if err != nil {
		return err
	}
	urlob, err := url.Parse(u)
	if err != nil {
		return err
	}

	if urlob.User != nil {
		if p, hasp := urlob.User.Password(); hasp {
			req.SetBasicAuth(urlob.User.Username(), p)
		}
	}

	if r, err := http.DefaultClient.Do(req); err == nil {
		defer r.Body.Close()

		d := json.NewDecoder(r.Body)
		if err := d.Decode(results); err != nil {
			return err
		}
	} else {
		return err
	}
	return nil
}

type IdAndRev struct {
	Id  string `json:"_id"`
	Rev string `json:"_rev"`
}

// Sends a query to CouchDB and parses the response back.
// method: the name of the HTTP method (POST, PUT,...)
// url: the URL to interact with
// headers: additional headers to pass to the request
// in: body of the request
// out: a structure to fill in with the returned JSON document
func (p Database) interact(method, u string, headers map[string][]string, in []byte, out interface{}) (int, error) {
	fullHeaders := map[string][]string{}
	for k, v := range headers {
		fullHeaders[k] = v
	}
	bodyLength := 0
	if in != nil {
		bodyLength = len(in)
		fullHeaders["Content-Type"] = []string{"application/json"}
	}
	req := http.Request{
		Method:        method,
		ProtoMajor:    1,
		ProtoMinor:    1,
		Close:         true,
		ContentLength: int64(bodyLength),
		Header:        fullHeaders,
	}
	req.TransferEncoding = []string{"chunked"}
	req.URL, _ = url.Parse(u)
	if in != nil {
		req.Body = &buffer{bytes.NewBuffer(in)}
	}
	if req.URL.User != nil {
		if p, hasp := req.URL.User.Password(); hasp {
			req.SetBasicAuth(req.URL.User.Username(), p)
		}
	}
	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%s", p.Host, p.Port))
	if err != nil {
		return 0, err
	}
	http_conn := httputil.NewClientConn(conn, nil)
	defer http_conn.Close()
	if err := http_conn.Write(&req); err != nil {
		return 0, err
	}
	r, err := http_conn.Read(&req)
	if err != nil && err != httputil.ErrPersistEOF {
		return 0, err
	}
	if r.StatusCode < 200 || r.StatusCode >= 300 {
		b := []byte{}
		r.Body.Read(b)
		return r.StatusCode, &HttpError{r.StatusCode, r.Status}
	}
	decoder := json.NewDecoder(r.Body)
	if err = decoder.Decode(out); err != nil && err != httputil.ErrPersistEOF {
		return 0, err
	}
	r.Body.Close()
	return r.StatusCode, nil
}

type Database struct {
	Host     string
	Port     string
	Name     string
	authinfo *url.Userinfo
}

func (p Database) BaseURL() string {
	if p.authinfo == nil {
		return fmt.Sprintf("http://%s:%s", p.Host, p.Port)
	}
	return fmt.Sprintf("http://%s@%s:%s", p.authinfo.String(), p.Host, p.Port)
}

func (p Database) DBURL() string {
	return fmt.Sprintf("%s/%s", p.BaseURL(), p.Name)
}

// Test whether CouchDB is running (ignores Database.Name)
func (p Database) Running() bool {
	dbs := []string{}
	u := fmt.Sprintf("%s/%s", p.BaseURL(), "_all_dbs")
	if err := unmarshal_url(u, &dbs); err != nil {
		return false
	}
	if len(dbs) > 0 {
		return true
	}
	return false
}

type database_info struct {
	Db_name string `json:"db_name"`
	// other stuff too, ignore for now
}

// Test whether specified database exists in specified CouchDB instance
func (p Database) Exists() bool {
	di := new(database_info)
	if err := unmarshal_url(p.DBURL(), &di); err != nil {
		return false
	}
	if di.Db_name != p.Name {
		return false
	}
	return true
}

func (p Database) create_database() error {
	ir := response{}
	if _, err := p.interact("PUT", p.DBURL(), def_hdrs, nil, &ir); err != nil {
		return err
	}
	if !ir.Ok {
		return errors.New("Create database operation returned not-OK")
	}
	return nil
}

// Deletes the given database and all documents
func (p Database) DeleteDatabase() error {
	ir := response{}
	if _, err := p.interact("DELETE", p.DBURL(), def_hdrs, nil, &ir); err != nil {
		return err
	}
	if !ir.Ok {
		return errors.New("Delete database operation returned not-OK")
	}
	return nil
}

// Connect to the database at the given URL.
// example:   couch.Connect("http://localhost:5984/testdb/")
func Connect(dburl string) (Database, error) {
	u, err := url.Parse(dburl)
	if err != nil {
		return Database{}, err
	}

	host := u.Host
	port := "80"
	if hp := strings.Split(u.Host, ":"); len(hp) > 1 {
		host = hp[0]
		port = hp[1]
	}

	db := Database{host, port, u.Path[1:], u.User}
	if !db.Running() {
		return Database{}, errors.New("CouchDB not running")
	}
	if !db.Exists() {
		return Database{}, errors.New("Database does not exist.")
	}

	return db, nil
}

func NewDatabase(host, port, name string) (Database, error) {
	db := Database{host, port, name, nil}
	if !db.Running() {
		return db, errors.New("CouchDB not running")
	}
	if !db.Exists() {
		if err := db.create_database(); err != nil {
			return db, err
		}
	}
	return db, nil
}

// Strip _id and _rev from d, returning them separately if they exist
func clean_JSON(d interface{}) (json_buf []byte, id, rev string, err error) {
	json_buf, err = json.Marshal(d)
	if err != nil {
		return
	}
	m := map[string]interface{}{}
	err = json.Unmarshal(json_buf, &m)
	if err != nil {
		return
	}
	id_rev := new(IdAndRev)
	err = json.Unmarshal(json_buf, &id_rev)
	if err != nil {
		return
	}
	if _, ok := m["_id"]; ok {
		id = id_rev.Id
		delete(m, "_id")
	}
	if _, ok := m["_rev"]; ok {
		rev = id_rev.Rev
		delete(m, "_rev")
	}
	json_buf, err = json.Marshal(m)
	return
}

type response struct {
	Ok     bool
	Id     string
	Rev    string
	Error  string
	Reason string
}

// Inserts document to CouchDB, returning id and rev on success.
// Document may specify both "_id" and "_rev" fields (will overwrite existing)
//	or just "_id" (will use that id, but not overwrite existing)
//	or neither (will use autogenerated id)
func (p Database) Insert(d interface{}) (string, string, error) {
	json_buf, id, rev, err := clean_JSON(d)
	if err != nil {
		return "", "", err
	}
	if id != "" && rev != "" {
		new_rev, err2 := p.Edit(d)
		return id, new_rev, err2
	} else if id != "" {
		return p.insert_with(json_buf, id)
	} else if id == "" {
		return p.insert(json_buf)
	}
	return "", "", errors.New("invalid Document")
}

// Private implementation of simple autogenerated-id insert
func (p Database) insert(json_buf []byte) (string, string, error) {
	ir := response{}
	if _, err := p.interact("POST", p.DBURL(), def_hdrs, json_buf, &ir); err != nil {
		return "", "", err
	}
	if !ir.Ok {
		return "", "", errors.New(fmt.Sprintf("%s: %s", ir.Error, ir.Reason))
	}
	return ir.Id, ir.Rev, nil
}

// Inserts the given document (shouldn't contain "_id" or "_rev" tagged fields)
// using the passed 'id' as the _id. Will fail if the id already exists.
func (p Database) InsertWith(d interface{}, id string) (string, string, error) {
	json_buf, err := json.Marshal(d)
	if err != nil {
		return "", "", err
	}
	return p.insert_with(json_buf, id)
}

// Private implementation of insert with given id
func (p Database) insert_with(json_buf []byte, id string) (string, string, error) {
	u := fmt.Sprintf("%s/%s", p.DBURL(), url.QueryEscape(id))
	ir := response{}
	if _, err := p.interact("PUT", u, def_hdrs, json_buf, &ir); err != nil {
		return "", "", err
	}
	if !ir.Ok {
		return "", "", errors.New(fmt.Sprintf("%s: %s", ir.Error, ir.Reason))
	}
	return ir.Id, ir.Rev, nil
}

// Edits the given document, returning the new revision.
// d must contain "_id" and "_rev" tagged fields.
func (p Database) Edit(d interface{}) (string, error) {
	json_buf, err := json.Marshal(d)
	if err != nil {
		return "", err
	}
	id_rev := new(IdAndRev)
	err = json.Unmarshal(json_buf, id_rev)
	if err != nil {
		return "", err
	}
	if id_rev.Id == "" {
		return "", errors.New("Id not specified in interface")
	}
	if id_rev.Rev == "" {
		return "", errors.New("Rev not specified in interface (try InsertWith)")
	}
	u := fmt.Sprintf("%s/%s", p.DBURL(), url.QueryEscape(id_rev.Id))
	ir := response{}
	if _, err = p.interact("PUT", u, def_hdrs, json_buf, &ir); err != nil {
		return "", err
	}
	return ir.Rev, nil
}

// Edits the given document, returning the new revision.
// d should not contain "_id" or "_rev" tagged fields. If it does, they will
// be overwritten with the passed values.
func (p Database) EditWith(d interface{}, id, rev string) (string, error) {
	if id == "" || rev == "" {
		return "", errors.New("EditWith: must specify both id and rev")
	}
	json_buf, err := json.Marshal(d)
	if err != nil {
		return "", err
	}
	m := map[string]interface{}{}
	err = json.Unmarshal(json_buf, &m)
	if err != nil {
		return "", err
	}
	m["_id"] = id
	m["_rev"] = rev
	return p.Edit(m)
}

// Unmarshals the document matching id to the given interface
func (p Database) Retrieve(id string, d interface{}) error {
	if id == "" {
		return errors.New("no id specified")
	}

	return unmarshal_url(fmt.Sprintf("%s/%s", p.DBURL(), id), d)
}

// Deletes document given by id and rev.
func (p Database) Delete(id, rev string) error {
	headers := map[string][]string{
		"If-Match": []string{rev},
	}
	u := fmt.Sprintf("%s/%s", p.DBURL(), id)
	ir := response{}
	if _, err := p.interact("DELETE", u, headers, nil, &ir); err != nil {
		return err
	}
	if !ir.Ok {
		return errors.New(fmt.Sprintf("%s: %s", ir.Error, ir.Reason))
	}
	return nil
}

type Row struct {
	Id  *string
	Key *string
}

type keyed_view_response struct {
	Total_rows uint64
	Offset     uint64
	Rows       []Row
}

// Return array of document ids as returned by the given view/options combo.
// view should be eg. "_design/my_foo/_view/my_bar"
// options should be eg. { "limit": 10, "key": "baz" }
func (p Database) QueryIds(view string, options map[string]interface{}) ([]string, error) {
	kvr := new(keyed_view_response)

	if err := p.Query(view, options, kvr); err != nil {
		return make([]string, 0), err
	}

	ids := make([]string, len(kvr.Rows))
	i := 0
	for _, row := range kvr.Rows {
		if row.Id != nil {
			ids[i] = *row.Id
			i++
		}
	}
	return ids[:i], nil
}

func (p Database) Query(view string, options map[string]interface{}, results interface{}) error {
	if view == "" {
		return errors.New("empty view")
	}
	parameters := ""
	for k, v := range options {
		switch t := v.(type) {
		case string:
			parameters += fmt.Sprintf(`%s="%s"&`, k, url.QueryEscape(t))
		case int:
			parameters += fmt.Sprintf(`%s=%d&`, k, t)
		case bool:
			parameters += fmt.Sprintf(`%s=%v&`, k, t)
		default:
			b, err := json.Marshal(v)
			if err != nil {
				panic(fmt.Sprintf("unsupported value-type %T in Query, json encoder said %v", t, err))
			}
			parameters += fmt.Sprintf(`%s=%v&`, k, string(b))
		}
	}
	full_url := fmt.Sprintf("%s/%s?%s", p.DBURL(), view, parameters)

	return unmarshal_url(full_url, results)
}
<commit_msg>http.Client.Do does not return errors on undesirable http statuses<commit_after>// -*- tab-width: 4 -*-

// CouchDB API
package couch

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"net/http"
	"net/http/httputil"
	"net/url"
	"strings"
)

var def_hdrs = map[string][]string{}

type HttpError struct {
	Status int
	Msg    string
}

type buffer struct {
	b *bytes.Buffer
}

func (b *buffer) Read(out []byte) (int, error) {
	return b.b.Read(out)
}

func (b *buffer) Close() error { return nil }

func (e *HttpError) Error() string {
	return e.Msg
}

func unmarshal_url(u string, results interface{}) error {
	req, err := http.NewRequest("GET", u, nil)
	if err != nil {
		return err
	}
	urlob, err := url.Parse(u)
	if err != nil {
		return err
	}

	if urlob.User != nil {
		if p, hasp := urlob.User.Password(); hasp {
			req.SetBasicAuth(urlob.User.Username(), p)
		}
	}

	r, err := http.DefaultClient.Do(req)

	if err == nil {
		defer r.Body.Close()
		if r.StatusCode == 200 {
			d := json.NewDecoder(r.Body)
			if err := d.Decode(results); err != nil {
				return err
			}
		} else {
			return &HttpError{r.StatusCode, r.Status}
		}
	} else {
		return err
	}
	return nil
}

type IdAndRev struct {
	Id  string `json:"_id"`
	Rev string `json:"_rev"`
}

// Sends a query to CouchDB and parses the response back.
// method: the name of the HTTP method (POST, PUT,...)
// url: the URL to interact with
// headers: additional headers to pass to the request
// in: body of the request
// out: a structure to fill in with the returned JSON document
func (p Database) interact(method, u string, headers map[string][]string, in []byte, out interface{}) (int, error) {
	fullHeaders := map[string][]string{}
	for k, v := range headers {
		fullHeaders[k] = v
	}
	bodyLength := 0
	if in != nil {
		bodyLength = len(in)
		fullHeaders["Content-Type"] = []string{"application/json"}
	}
	req := http.Request{
		Method:        method,
		ProtoMajor:    1,
		ProtoMinor:    1,
		Close:         true,
		ContentLength: int64(bodyLength),
		Header:        fullHeaders,
	}
	req.TransferEncoding = []string{"chunked"}
	req.URL, _ = url.Parse(u)
	if in != nil {
		req.Body = &buffer{bytes.NewBuffer(in)}
	}
	if req.URL.User != nil {
		if p, hasp := req.URL.User.Password(); hasp {
			req.SetBasicAuth(req.URL.User.Username(), p)
		}
	}
	conn, err := net.Dial("tcp", fmt.Sprintf("%s:%s", p.Host, p.Port))
	if err != nil {
		return 0, err
	}
	http_conn := httputil.NewClientConn(conn, nil)
	defer http_conn.Close()
	if err := http_conn.Write(&req); err != nil {
		return 0, err
	}
	r, err := http_conn.Read(&req)
	if err != nil && err != httputil.ErrPersistEOF {
		return 0, err
	}
	if r.StatusCode < 200 || r.StatusCode >= 300 {
		b := []byte{}
		r.Body.Read(b)
		return r.StatusCode, &HttpError{r.StatusCode, r.Status}
	}
	decoder := json.NewDecoder(r.Body)
	if err = decoder.Decode(out); err != nil && err != httputil.ErrPersistEOF {
		return 0, err
	}
	r.Body.Close()
	return r.StatusCode, nil
}

type Database struct {
	Host     string
	Port     string
	Name     string
	authinfo *url.Userinfo
}

func (p Database) BaseURL() string {
	if p.authinfo == nil {
		return fmt.Sprintf("http://%s:%s", p.Host, p.Port)
	}
	return fmt.Sprintf("http://%s@%s:%s", p.authinfo.String(), p.Host, p.Port)
}

func (p Database) DBURL() string {
	return fmt.Sprintf("%s/%s", p.BaseURL(), p.Name)
}

// Test whether CouchDB is running (ignores Database.Name)
func (p Database) Running() bool {
	dbs := []string{}
	u := fmt.Sprintf("%s/%s", p.BaseURL(), "_all_dbs")
	if err := unmarshal_url(u, &dbs); err != nil {
		return false
	}
	if len(dbs) > 0 {
		return true
	}
	return false
}

type database_info struct {
	Db_name string `json:"db_name"`
	// other stuff too, ignore for now
}

// Test whether specified database exists in specified CouchDB instance
func (p Database) Exists() bool {
	di := new(database_info)
	if err := unmarshal_url(p.DBURL(), &di); err != nil {
		return false
	}
	if di.Db_name != p.Name {
		return false
	}
	return true
}

func (p Database) create_database() error {
	ir := response{}
	if _, err := p.interact("PUT", p.DBURL(), def_hdrs, nil, &ir); err != nil {
		return err
	}
	if !ir.Ok {
		return errors.New("Create database operation returned not-OK")
	}
	return nil
}

// Deletes the given database and all documents
func (p Database) DeleteDatabase() error {
	ir := response{}
	if _, err := p.interact("DELETE", p.DBURL(), def_hdrs, nil, &ir); err != nil {
		return err
	}
	if !ir.Ok {
		return errors.New("Delete database operation returned not-OK")
	}
	return nil
}

// Connect to the database at the given URL.
// example:   couch.Connect("http://localhost:5984/testdb/")
func Connect(dburl string) (Database, error) {
	u, err := url.Parse(dburl)
	if err != nil {
		return Database{}, err
	}

	host := u.Host
	port := "80"
	if hp := strings.Split(u.Host, ":"); len(hp) > 1 {
		host = hp[0]
		port = hp[1]
	}

	db := Database{host, port, u.Path[1:], u.User}
	if !db.Running() {
		return Database{}, errors.New("CouchDB not running")
	}
	if !db.Exists() {
		return Database{}, errors.New("Database does not exist.")
	}

	return db, nil
}

func NewDatabase(host, port, name string) (Database, error) {
	db := Database{host, port, name, nil}
	if !db.Running() {
		return db, errors.New("CouchDB not running")
	}
	if !db.Exists() {
		if err := db.create_database(); err != nil {
			return db, err
		}
	}
	return db, nil
}

// Strip _id and _rev from d, returning them separately if they exist
func clean_JSON(d interface{}) (json_buf []byte, id, rev string, err error) {
	json_buf, err = json.Marshal(d)
	if err != nil {
		return
	}
	m := map[string]interface{}{}
	err = json.Unmarshal(json_buf, &m)
	if err != nil {
		return
	}
	id_rev := new(IdAndRev)
	err = json.Unmarshal(json_buf, &id_rev)
	if err != nil {
		return
	}
	if _, ok := m["_id"]; ok {
		id = id_rev.Id
		delete(m, "_id")
	}
	if _, ok := m["_rev"]; ok {
		rev = id_rev.Rev
		delete(m, "_rev")
	}
	json_buf, err = json.Marshal(m)
	return
}

type response struct {
	Ok     bool
	Id     string
	Rev    string
	Error  string
	Reason string
}

// Inserts document to CouchDB, returning id and rev on success.
// Document may specify both "_id" and "_rev" fields (will overwrite existing)
//	or just "_id" (will use that id, but not overwrite existing)
//	or neither (will use autogenerated id)
func (p Database) Insert(d interface{}) (string, string, error) {
	json_buf, id, rev, err := clean_JSON(d)
	if err != nil {
		return "", "", err
	}
	if id != "" && rev != "" {
		new_rev, err2 := p.Edit(d)
		return id, new_rev, err2
	} else if id != "" {
		return p.insert_with(json_buf, id)
	} else if id == "" {
		return p.insert(json_buf)
	}
	return "", "", errors.New("invalid Document")
}

// Private implementation of simple autogenerated-id insert
func (p Database) insert(json_buf []byte) (string, string, error) {
	ir := response{}
	if _, err := p.interact("POST", p.DBURL(), def_hdrs, json_buf, &ir); err != nil {
		return "", "", err
	}
	if !ir.Ok {
		return "", "", errors.New(fmt.Sprintf("%s: %s", ir.Error, ir.Reason))
	}
	return ir.Id, ir.Rev, nil
}

// Inserts the given document (shouldn't contain "_id" or "_rev" tagged fields)
// using the passed 'id' as the _id. Will fail if the id already exists.
func (p Database) InsertWith(d interface{}, id string) (string, string, error) {
	json_buf, err := json.Marshal(d)
	if err != nil {
		return "", "", err
	}
	return p.insert_with(json_buf, id)
}

// Private implementation of insert with given id
func (p Database) insert_with(json_buf []byte, id string) (string, string, error) {
	u := fmt.Sprintf("%s/%s", p.DBURL(), url.QueryEscape(id))
	ir := response{}
	if _, err := p.interact("PUT", u, def_hdrs, json_buf, &ir); err != nil {
		return "", "", err
	}
	if !ir.Ok {
		return "", "", errors.New(fmt.Sprintf("%s: %s", ir.Error, ir.Reason))
	}
	return ir.Id, ir.Rev, nil
}

// Edits the given document, returning the new revision.
// d must contain "_id" and "_rev" tagged fields.
func (p Database) Edit(d interface{}) (string, error) {
	json_buf, err := json.Marshal(d)
	if err != nil {
		return "", err
	}
	id_rev := new(IdAndRev)
	err = json.Unmarshal(json_buf, id_rev)
	if err != nil {
		return "", err
	}
	if id_rev.Id == "" {
		return "", errors.New("Id not specified in interface")
	}
	if id_rev.Rev == "" {
		return "", errors.New("Rev not specified in interface (try InsertWith)")
	}
	u := fmt.Sprintf("%s/%s", p.DBURL(), url.QueryEscape(id_rev.Id))
	ir := response{}
	if _, err = p.interact("PUT", u, def_hdrs, json_buf, &ir); err != nil {
		return "", err
	}
	return ir.Rev, nil
}

// Edits the given document, returning the new revision.
// d should not contain "_id" or "_rev" tagged fields. If it does, they will
// be overwritten with the passed values.
func (p Database) EditWith(d interface{}, id, rev string) (string, error) {
	if id == "" || rev == "" {
		return "", errors.New("EditWith: must specify both id and rev")
	}
	json_buf, err := json.Marshal(d)
	if err != nil {
		return "", err
	}
	m := map[string]interface{}{}
	err = json.Unmarshal(json_buf, &m)
	if err != nil {
		return "", err
	}
	m["_id"] = id
	m["_rev"] = rev
	return p.Edit(m)
}

// Unmarshals the document matching id to the given interface
func (p Database) Retrieve(id string, d interface{}) error {
	if id == "" {
		return errors.New("no id specified")
	}

	return unmarshal_url(fmt.Sprintf("%s/%s", p.DBURL(), id), d)
}

// Deletes document given by id and rev.
func (p Database) Delete(id, rev string) error {
	headers := map[string][]string{
		"If-Match": []string{rev},
	}
	u := fmt.Sprintf("%s/%s", p.DBURL(), id)
	ir := response{}
	if _, err := p.interact("DELETE", u, headers, nil, &ir); err != nil {
		return err
	}
	if !ir.Ok {
		return errors.New(fmt.Sprintf("%s: %s", ir.Error, ir.Reason))
	}
	return nil
}

type Row struct {
	Id  *string
	Key *string
}

type keyed_view_response struct {
	Total_rows uint64
	Offset     uint64
	Rows       []Row
}

// Return array of document ids as returned by the given view/options combo.
// view should be eg. "_design/my_foo/_view/my_bar"
// options should be eg. { "limit": 10, "key": "baz" }
func (p Database) QueryIds(view string, options map[string]interface{}) ([]string, error) {
	kvr := new(keyed_view_response)

	if err := p.Query(view, options, kvr); err != nil {
		return make([]string, 0), err
	}

	ids := make([]string, len(kvr.Rows))
	i := 0
	for _, row := range kvr.Rows {
		if row.Id != nil {
			ids[i] = *row.Id
			i++
		}
	}
	return ids[:i], nil
}

func (p Database) Query(view string, options map[string]interface{}, results interface{}) error {
	if view == "" {
		return errors.New("empty view")
	}
	parameters := ""
	for k, v := range options {
		switch t := v.(type) {
		case string:
			parameters += fmt.Sprintf(`%s="%s"&`, k, url.QueryEscape(t))
		case int:
			parameters += fmt.Sprintf(`%s=%d&`, k, t)
		case bool:
			parameters += fmt.Sprintf(`%s=%v&`, k, t)
		default:
			b, err := json.Marshal(v)
			if err != nil {
				panic(fmt.Sprintf("unsupported value-type %T in Query, json encoder said %v", t, err))
			}
			parameters += fmt.Sprintf(`%s=%v&`, k, string(b))
		}
	}
	full_url := fmt.Sprintf("%s/%s?%s", p.DBURL(), view, parameters)

	return unmarshal_url(full_url, results)
}
<|endoftext|>


<commit_before>package crawl

import (
       "errors"
	"fmt"
	walk "github.com/whosonfirst/walk"
	"os"
)

type CrawlFunc func(path string, info os.FileInfo) error

type Crawler struct {
	Root string
	NFS bool
}

func NewCrawler(path string) *Crawler {
	return &Crawler{
		Root: path,
		NFS: false,
	}
}

func (c Crawler) Crawl(cb CrawlFunc) error {

	walker := func(path string, info os.FileInfo, err error) error {

		if err != nil {
			return err
		}

		if info.IsDir() {
			return nil
		}

		_ = cb(path, info)
		return nil
	}

	var err errors.Error

	if c.NFS {
		err = walk.WalkNFS(c.Root, walker)
	} else {
		err = walk.Walk(c.Root, walker)
	}

	if err != nil {
		fmt.Printf("error: %s\n", err)
	}

	return nil
}
<commit_msg>hooks to account for NFS stuff<commit_after>package crawl

import (
	"fmt"
	walk "github.com/whosonfirst/walk"
	"os"
)

type CrawlFunc func(path string, info os.FileInfo) error

type Crawler struct {
	Root string
	NFS  bool
}

func NewCrawler(path string) *Crawler {
	return &Crawler{
		Root: path,
		NFS:  false,
	}
}

func (c Crawler) Crawl(cb CrawlFunc) error {

	walker := func(path string, info os.FileInfo, err error) error {

		if err != nil {
			return err
		}

		if info.IsDir() {
			return nil
		}

		_ = cb(path, info)
		return nil
	}

	var err error

	if c.NFS {
		err = walk.WalkNFS(c.Root, walker)
	} else {
		err = walk.Walk(c.Root, walker)
	}

	if err != nil {
		fmt.Printf("error: %s\n", err)
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strings"
	"time"
)

func doHTTP(url string, input io.Reader) (io.ReadCloser, error) {
	method := "GET"
	if input != nil {
		method = "POST"
	}
	rq, err := http.NewRequest(method, url, input)
	if err != nil {
		return nil, err
	}
	rq.Header.Set("User-Agent", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)")
	if input != nil {
		rq.Header.Set("Content-Type", "application/json")
	}
	rsp, err := http.DefaultClient.Do(rq)
	if err != nil {
		return nil, err
	}
	if rsp.StatusCode != http.StatusOK {
		rsp.Body.Close()
		return nil, fmt.Errorf("got %s fetching %s", rsp.Status, url)
	}
	return rsp.Body, nil
}

func doJson(url string, input interface{}, output interface{}) error {
	var post io.Reader
	if input != nil {
		body := &bytes.Buffer{}
		err := json.NewEncoder(body).Encode(input)
		if err != nil {
			return err
		}
		post = body
	}
	result, err := doHTTP(url, post)
	if err != nil {
		return err
	}
	defer result.Close()
	return json.NewDecoder(result).Decode(output)
}

type SearchPaging struct {
	Range      int `json:"range"`
	StartIndex int `json:"startIndex"`
}

type SearchSorts struct {
	Direction string `json:"direction"`
	Type      string `json:"type"`
}

type SearchFilters struct {
	EnableFilter    bool          `json:"activeFiltre"`
	Functions       []int         `json:"fonctions"`
	Places          []int         `json:"lieux"`
	Keywords        string        `json:"motsCles"`
	Experience      []int         `json:"niveauxExperience"`
	Paging          SearchPaging  `json:"pagination"`
	MinSalary       int           `json:"salaireMinimum"`
	MaxSalary       int           `json:"salaireMaximum"`
	Sectors         []int         `json:"secteursActivite"`
	Sorts           []SearchSorts `json:"sorts"`
	ClientType      string        `json:"typeClient"`
	ContractTypes   []int         `json:"typesContrat"`
	ConventionTypes []int         `json:"typesConvention"`
}

func searchOffers(start, count, minSalary int, locations []int) ([]string, error) {
	filter := &SearchFilters{
		EnableFilter: true,
		Functions:    []int{},
		Places:       locations,
		Experience:   []int{},
		Paging: SearchPaging{
			Range:      count,
			StartIndex: start,
		},
		MinSalary: minSalary,
		MaxSalary: 1000,
		Sectors:   []int{},
		Sorts: []SearchSorts{
			{
				Direction: "DESCENDING",
				Type:      "DATE",
			},
		},
		ClientType:      "CADRE",
		ContractTypes:   []int{},
		ConventionTypes: []int{},
	}
	results := &struct {
		Results []struct {
			URI string `json:"@uriOffre"`
		} `json:"resultats"`
	}{}
	url := "https://cadres.apec.fr/cms/webservices/rechercheOffre/ids"
	err := doJson(url, filter, results)
	if err != nil {
		return nil, err
	}
	ids := []string{}
	for _, uri := range results.Results {
		parts := strings.Split(uri.URI, "numeroOffre=")
		if len(parts) != 2 {
			return nil, fmt.Errorf("invalid offer identifier: %s", uri.URI)
		}
		ids = append(ids, parts[1])
	}
	return ids, nil
}

func getOffer(id string) ([]byte, error) {
	u := "https://cadres.apec.fr/cms/webservices/offre/public?numeroOffre=" + id
	output, err := doHTTP(u, nil)
	if err != nil {
		return nil, err
	}
	defer output.Close()
	return ioutil.ReadAll(output)
}

func enumerateOffers(minSalary int, locations []int, callback func([]string) error) error {
	start := 0
	count := 250
	baseDelay := 5 * time.Second
	maxDelay := 5 * time.Minute
	delay := baseDelay
	for ; ; time.Sleep(delay) {
		fmt.Printf("fetching from %d to %d\n", start, start+count)
		ids, err := searchOffers(start, count, minSalary, locations)
		if err != nil {
			fmt.Printf("fetching failed with: %s\n", err)
			delay *= 2
			if delay > maxDelay {
				return err
			}
			continue
		}
		delay = baseDelay
		start += count
		err = callback(ids)
		if err != nil {
			return err
		}
		if len(ids) < count {
			break
		}
	}
	return nil
}

var (
	crawlCmd       = app.Command("crawl", "crawl APEC offers")
	crawlStoreDir  = crawlCmd.Arg("store", "data store directory").Required().String()
	crawlMinSalary = crawlCmd.Arg("min-salary", "minimum salary in kEUR").Default("50").Int()
	crawlLocations = crawlCmd.Flag("location", "offer location code").Ints()
)

func crawlOffers() error {
	store, err := CreateStore(*crawlStoreDir)
	if err != nil {
		return err
	}
	added, deleted := 0, 0
	seen := map[string]bool{}
	err = enumerateOffers(*crawlMinSalary, *crawlLocations, func(ids []string) error {
		for _, id := range ids {
			seen[id] = true
			if store.Has(id) {
				continue
			}
			fmt.Printf("fetching %s\n", id)
			data, err := getOffer(id)
			if err != nil {
				return err
			}
			time.Sleep(time.Second)
			written, err := store.Write(id, data)
			if err != nil {
				return err
			}
			if !written {
				fmt.Printf("racing %s\n", id)
				continue
			}
			added += 1
		}
		return nil
	})
	if err != nil {
		return err
	}
	for _, id := range store.List() {
		if !seen[id] {
			fmt.Printf("deleting %s\n", id)
			store.Delete(id)
			deleted += 1
		}
	}
	fmt.Printf("%d added, %d deleted, %d total\n", added, deleted, store.Size())
	return nil
}
<commit_msg>crawl: retry failed offer retrievals<commit_after>package main

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"strings"
	"time"
)

func doHTTP(url string, input io.Reader) (io.ReadCloser, error) {
	method := "GET"
	if input != nil {
		method = "POST"
	}
	rq, err := http.NewRequest(method, url, input)
	if err != nil {
		return nil, err
	}
	rq.Header.Set("User-Agent", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)")
	if input != nil {
		rq.Header.Set("Content-Type", "application/json")
	}
	rsp, err := http.DefaultClient.Do(rq)
	if err != nil {
		return nil, err
	}
	if rsp.StatusCode != http.StatusOK {
		rsp.Body.Close()
		return nil, fmt.Errorf("got %s fetching %s", rsp.Status, url)
	}
	return rsp.Body, nil
}

func tryHTTP(url string, baseDelay time.Duration, loops int,
	input io.Reader) (io.ReadCloser, error) {

	delay := baseDelay
	for {
		output, err := doHTTP(url, input)
		if err == nil {
			return output, nil
		}
		fmt.Printf("fetching failed with: %s\n", err)
		loops -= 1
		if loops <= 0 {
			return nil, err
		}
		time.Sleep(delay)
		delay *= 2
	}
}

func doJson(url string, baseDelay time.Duration, loops int, input interface{},
	output interface{}) error {

	var post io.Reader
	if input != nil {
		body := &bytes.Buffer{}
		err := json.NewEncoder(body).Encode(input)
		if err != nil {
			return err
		}
		post = body
	}
	result, err := tryHTTP(url, baseDelay, loops, post)
	if err != nil {
		return err
	}
	defer result.Close()
	return json.NewDecoder(result).Decode(output)
}

type SearchPaging struct {
	Range      int `json:"range"`
	StartIndex int `json:"startIndex"`
}

type SearchSorts struct {
	Direction string `json:"direction"`
	Type      string `json:"type"`
}

type SearchFilters struct {
	EnableFilter    bool          `json:"activeFiltre"`
	Functions       []int         `json:"fonctions"`
	Places          []int         `json:"lieux"`
	Keywords        string        `json:"motsCles"`
	Experience      []int         `json:"niveauxExperience"`
	Paging          SearchPaging  `json:"pagination"`
	MinSalary       int           `json:"salaireMinimum"`
	MaxSalary       int           `json:"salaireMaximum"`
	Sectors         []int         `json:"secteursActivite"`
	Sorts           []SearchSorts `json:"sorts"`
	ClientType      string        `json:"typeClient"`
	ContractTypes   []int         `json:"typesContrat"`
	ConventionTypes []int         `json:"typesConvention"`
}

func searchOffers(start, count, minSalary int, locations []int) ([]string, error) {
	filter := &SearchFilters{
		EnableFilter: true,
		Functions:    []int{},
		Places:       locations,
		Experience:   []int{},
		Paging: SearchPaging{
			Range:      count,
			StartIndex: start,
		},
		MinSalary: minSalary,
		MaxSalary: 1000,
		Sectors:   []int{},
		Sorts: []SearchSorts{
			{
				Direction: "DESCENDING",
				Type:      "DATE",
			},
		},
		ClientType:      "CADRE",
		ContractTypes:   []int{},
		ConventionTypes: []int{},
	}
	results := &struct {
		Results []struct {
			URI string `json:"@uriOffre"`
		} `json:"resultats"`
	}{}
	url := "https://cadres.apec.fr/cms/webservices/rechercheOffre/ids"
	err := doJson(url, 5*time.Second, 5, filter, results)
	if err != nil {
		return nil, err
	}
	ids := []string{}
	for _, uri := range results.Results {
		parts := strings.Split(uri.URI, "numeroOffre=")
		if len(parts) != 2 {
			return nil, fmt.Errorf("invalid offer identifier: %s", uri.URI)
		}
		ids = append(ids, parts[1])
	}
	return ids, nil
}

func getOffer(id string) ([]byte, error) {
	u := "https://cadres.apec.fr/cms/webservices/offre/public?numeroOffre=" + id
	output, err := tryHTTP(u, time.Second, 5, nil)
	if err != nil {
		return nil, err
	}
	defer output.Close()
	return ioutil.ReadAll(output)
}

func enumerateOffers(minSalary int, locations []int, callback func([]string) error) error {
	start := 0
	count := 250
	delay := 5 * time.Second
	for ; ; time.Sleep(delay) {
		fmt.Printf("fetching from %d to %d\n", start, start+count)
		ids, err := searchOffers(start, count, minSalary, locations)
		if err != nil {
			return err
		}
		start += count
		err = callback(ids)
		if err != nil {
			return err
		}
		if len(ids) < count {
			break
		}
	}
	return nil
}

var (
	crawlCmd       = app.Command("crawl", "crawl APEC offers")
	crawlStoreDir  = crawlCmd.Arg("store", "data store directory").Required().String()
	crawlMinSalary = crawlCmd.Arg("min-salary", "minimum salary in kEUR").Default("50").Int()
	crawlLocations = crawlCmd.Flag("location", "offer location code").Ints()
)

func crawlOffers() error {
	store, err := CreateStore(*crawlStoreDir)
	if err != nil {
		return err
	}
	added, deleted := 0, 0
	seen := map[string]bool{}
	err = enumerateOffers(*crawlMinSalary, *crawlLocations, func(ids []string) error {
		for _, id := range ids {
			seen[id] = true
			if store.Has(id) {
				continue
			}
			fmt.Printf("fetching %s\n", id)
			data, err := getOffer(id)
			if err != nil {
				return err
			}
			time.Sleep(time.Second)
			written, err := store.Write(id, data)
			if err != nil {
				return err
			}
			if !written {
				fmt.Printf("racing %s\n", id)
				continue
			}
			added += 1
		}
		return nil
	})
	if err != nil {
		return err
	}
	for _, id := range store.List() {
		if !seen[id] {
			fmt.Printf("deleting %s\n", id)
			store.Delete(id)
			deleted += 1
		}
	}
	fmt.Printf("%d added, %d deleted, %d total\n", added, deleted, store.Size())
	return nil
}
<|endoftext|>


<commit_before>package auth

import (
	"bytes"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"time"

	"appengine"
	"appengine/datastore"
	"appengine/delay"
	"appengine/mail"
	"appengine/taskqueue"
	"appengine/user"
)

// Errors returned from login-related functions.
var (
	ErrUserNotFound          = fmt.Errorf("User not found")
	ErrAlreadyConfirmed      = fmt.Errorf("User is already confirmed")
	ErrWrongConfirmationCode = fmt.Errorf("Wrong confirmation code")
	ErrEmailAlreadyClaimed   = fmt.Errorf("The email is already claimed")
)

var (
	noReply = "no-reply@innerhearthyoga.appspotmail.com"
)

var (
	delayedConfirmAccount = delay.Func("confirmAccount", func(c appengine.Context, user UserAccount) error {
		buf := &bytes.Buffer{}
		if err := accountConfirmationEmail.Execute(buf, user); err != nil {
			c.Criticalf("Couldn't execute account confirm email: %s", err)
			return nil
		}
		msg := &mail.Message{
			Sender:  fmt.Sprintf("no-reply@%s.appspotmail.com", appengine.AppID(c)),
			To:      []string{user.Email},
			Subject: "Confirm your account registration with Inner Hearth Yoga",
			Body:    buf.String(),
		}
		if err := mail.Send(c, msg); err != nil {
			c.Criticalf("Couldn't send email to %q: %s", user.Email, err)
			return fmt.Errorf("failed to send email")
		}
		return nil
	})
)

var (
	// OpenIDProviders is a list of the OpenID providers we support.
	OpenIDProviders = []Provider{
		{"Google", "https://www.google.com/accounts/o8/id"},
		{"Yahoo", "yahoo.com"},
		{"AOL", "aol.com"},
	}
)

// Provider represents an OpenID provider which we use for login.
type Provider struct {
	// The display name of the provider.
	Name string

	// The OpenID identifier given internally to AppEngine to create a login link.
	Identifier string
}

// AsLink returns a LoginLink to allow the user to login with the provider.
func (p Provider) AsLink(c appengine.Context, continueURL string) (LoginLink, error) {
	url, err := user.LoginURLFederated(c, continueURL, p.Identifier)
	if err != nil {
		return LoginLink{}, fmt.Errorf("failed to create login link for %q: %s", p.Name, err)
	}
	return LoginLink{p.Name, url}, nil
}

// MakeLinkList converts a list of Provider structs to a list of login
// links for display to a user.
func MakeLinkList(c appengine.Context, providers []Provider, continueURL string) ([]LoginLink, error) {
	links := make([]LoginLink, len(providers))
	for i, provider := range providers {
		link, err := provider.AsLink(c, continueURL)
		if err != nil {
			c.Errorf("Couldn't create login link for %q: %s", provider.Identifier, err)
			return nil, fmt.Errorf("invalid provider ID %q", provider.Identifier)
		}
		links[i] = link
	}
	return links, nil
}

// A LoginLink is a login redirect URL associated with the name of the
// OpenID provider to which it redirects.
type LoginLink struct {
	ProviderName string
	URL          string
}

// UserInfo stores basic user contact & identification information.
type UserInfo struct {
	FirstName string `datastore: ",noindex"`
	LastName  string
	Email     string
	Phone     string `datastore: ",noindex"`
}

// A UserAccount stores data about a registered user of the site.
type UserAccount struct {
	AccountID string `datastore: "-"`

	UserInfo

	Confirmed        time.Time `datastore: ",noindex"`
	ConfirmationCode string    `datastore: ",noindex"`
}

func newConfirmationCode() (string, error) {
	b := make([]byte, 32)
	if _, err := rand.Read(b); err != nil {
		return "", err
	}
	return base64.URLEncoding.EncodeToString(b), nil
}

// NewUserAccount creates a new UserAccount for the given user.
func NewUserAccount(u *user.User, info UserInfo) (*UserAccount, error) {
	confirmCode, err := newConfirmationCode()
	if err != nil {
		return nil, fmt.Errorf("couldnt' create confirmation code: %s", err)
	}
	return &UserAccount{
		AccountID:        SaltAndHashString(u.FederatedIdentity),
		UserInfo:         info,
		ConfirmationCode: confirmCode,
	}, nil
}

func userKeyFromID(c appengine.Context, id string) *datastore.Key {
	return datastore.NewKey(c, "UserAccount", id, 0, nil)
}

func userKeyFromFederatedIdentity(c appengine.Context, u *user.User) *datastore.Key {
	return userKeyFromID(c, SaltAndHashString(u.FederatedIdentity))
}

func (u *UserAccount) key(c appengine.Context) *datastore.Key {
	return userKeyFromID(c, u.AccountID)
}

func isFieldMismatch(err error) bool {
	_, ok := err.(*datastore.ErrFieldMismatch)
	return ok
}

func lookupUserByKey(c appengine.Context, key *datastore.Key) (*UserAccount, error) {
	ihu := &UserAccount{}
	if err := datastore.Get(c, key, ihu); err != nil {
		switch {
		case err == datastore.ErrNoSuchEntity:
			return nil, ErrUserNotFound
		case isFieldMismatch(err):
			c.Warningf("Type mismatch on user %q: %+v", key.StringID(), err)
			return ihu, nil
		default:
			c.Errorf("Failed looking up user %q: %s", key.StringID(), err)
			return nil, ErrUserNotFound
		}
	}
	ihu.AccountID = key.StringID()
	return ihu, nil
}

// LookupUser returns the InnerHearthUser for the given AppEngine
// user's federated identity, if any.
func LookupUser(c appengine.Context, u *user.User) (*UserAccount, error) {
	return lookupUserByKey(c, userKeyFromFederatedIdentity(c, u))
}

// LookupUserByID returns the InnerHearthUser with the given ID, if any.
func LookupUserByID(c appengine.Context, id string) (*UserAccount, error) {
	return lookupUserByKey(c, userKeyFromID(c, id))
}

// LookupUserByEmail returns the InnerHearthUser with the given email, if any.
func LookupUserByEmail(c appengine.Context, email string) (*UserAccount, error) {
	q := datastore.NewQuery("UserAccount").
		KeysOnly().
		Filter("Email =", email).
		Limit(1)
	keys, err := q.GetAll(c, nil)
	if err != nil {
		c.Errorf("Failure looking for user %q: %s", email, err)
		return nil, ErrUserNotFound
	}
	if len(keys) == 0 {
		return nil, ErrUserNotFound
	}
	return lookupUserByKey(c, keys[0])
}

// Store persists the InnerHearthUser to the datastore.
func (u *UserAccount) Store(c appengine.Context) error {
	key := u.key(c)
	if _, err := datastore.Put(c, key, u); err != nil {
		return fmt.Errorf("failed to store user %q: %s", u.AccountID, err)
	}
	return nil
}

// SendConfirmation schedules a task to email a confirmation request
// to a new user.
func (u *UserAccount) SendConfirmation(c appengine.Context) error {
	t, err := delayedConfirmAccount.Task(*u)
	if err != nil {
		return fmt.Errorf("error getting function task: %s", err)
	}
	t.RetryOptions = &taskqueue.RetryOptions{
		RetryLimit: 3,
	}
	if _, err := taskqueue.Add(c, t, ""); err != nil {
		return fmt.Errorf("error adding confirmation to taskqueue: %s", err)
	}
	return nil
}

// Confirm marks the user as having confirmed their registration and
// stores the confirmation time back to the datastore.
func (u *UserAccount) Confirm(c appengine.Context, code string, now time.Time) error {
	if u.ConfirmationCode == "" {
		return ErrAlreadyConfirmed
	}
	if code != u.ConfirmationCode {
		return ErrWrongConfirmationCode
	}
	u.Confirmed = now.In(time.UTC)
	u.ConfirmationCode = ""
	return u.Store(c)
}

// A UserEmail associates a user ID with an email address, enforcing uniqueness among email addresses.
type ClaimedEmail struct {
	ClaimedBy *datastore.Key
	Email     string
}

// Creates a new ClaimedEmail struct associating the user with their email.
func NewClaimedEmail(c appengine.Context, u *user.User, email string) *ClaimedEmail {
	return &ClaimedEmail{
		ClaimedBy: userKeyFromFederatedIdentity(c, u),
		Email:     email,
	}
}

func (e *ClaimedEmail) key(c appengine.Context) *datastore.Key {
	return datastore.NewKey(c, "ClaimedEmail", e.Email, 0, nil)
}

// Claim attempts to uniquely associate the user and email.
func (e *ClaimedEmail) Claim(c appengine.Context) error {
	err := datastore.RunInTransaction(c, func(c appengine.Context) error {
		key := e.key(c)
		old := &ClaimedEmail{}
		lookupErr := datastore.Get(c, key, old)
		switch {
		case lookupErr == nil:
			return ErrEmailAlreadyClaimed
		case lookupErr == datastore.ErrNoSuchEntity:
			// Didn't find old claim: all is well.
			break
		default:
			return fmt.Errorf("failed to look up old UserEmail: %s", lookupErr)
		}

		if _, storeErr := datastore.Put(c, key, e); storeErr != nil {
			return fmt.Errorf("failed to store UserEmail: %s", storeErr)
		}
		return nil
	}, nil)
	if err != nil {
		return err
	}
	return nil
}
<commit_msg>Removed unused var.<commit_after>package auth

import (
	"bytes"
	"crypto/rand"
	"encoding/base64"
	"fmt"
	"time"

	"appengine"
	"appengine/datastore"
	"appengine/delay"
	"appengine/mail"
	"appengine/taskqueue"
	"appengine/user"
)

// Errors returned from login-related functions.
var (
	ErrUserNotFound          = fmt.Errorf("User not found")
	ErrAlreadyConfirmed      = fmt.Errorf("User is already confirmed")
	ErrWrongConfirmationCode = fmt.Errorf("Wrong confirmation code")
	ErrEmailAlreadyClaimed   = fmt.Errorf("The email is already claimed")
)

var (
	delayedConfirmAccount = delay.Func("confirmAccount", func(c appengine.Context, user UserAccount) error {
		buf := &bytes.Buffer{}
		if err := accountConfirmationEmail.Execute(buf, user); err != nil {
			c.Criticalf("Couldn't execute account confirm email: %s", err)
			return nil
		}
		msg := &mail.Message{
			Sender:  fmt.Sprintf("no-reply@%s.appspotmail.com", appengine.AppID(c)),
			To:      []string{user.Email},
			Subject: "Confirm your account registration with Inner Hearth Yoga",
			Body:    buf.String(),
		}
		if err := mail.Send(c, msg); err != nil {
			c.Criticalf("Couldn't send email to %q: %s", user.Email, err)
			return fmt.Errorf("failed to send email")
		}
		return nil
	})
)

var (
	// OpenIDProviders is a list of the OpenID providers we support.
	OpenIDProviders = []Provider{
		{"Google", "https://www.google.com/accounts/o8/id"},
		{"Yahoo", "yahoo.com"},
		{"AOL", "aol.com"},
	}
)

// Provider represents an OpenID provider which we use for login.
type Provider struct {
	// The display name of the provider.
	Name string

	// The OpenID identifier given internally to AppEngine to create a login link.
	Identifier string
}

// AsLink returns a LoginLink to allow the user to login with the provider.
func (p Provider) AsLink(c appengine.Context, continueURL string) (LoginLink, error) {
	url, err := user.LoginURLFederated(c, continueURL, p.Identifier)
	if err != nil {
		return LoginLink{}, fmt.Errorf("failed to create login link for %q: %s", p.Name, err)
	}
	return LoginLink{p.Name, url}, nil
}

// MakeLinkList converts a list of Provider structs to a list of login
// links for display to a user.
func MakeLinkList(c appengine.Context, providers []Provider, continueURL string) ([]LoginLink, error) {
	links := make([]LoginLink, len(providers))
	for i, provider := range providers {
		link, err := provider.AsLink(c, continueURL)
		if err != nil {
			c.Errorf("Couldn't create login link for %q: %s", provider.Identifier, err)
			return nil, fmt.Errorf("invalid provider ID %q", provider.Identifier)
		}
		links[i] = link
	}
	return links, nil
}

// A LoginLink is a login redirect URL associated with the name of the
// OpenID provider to which it redirects.
type LoginLink struct {
	ProviderName string
	URL          string
}

// UserInfo stores basic user contact & identification information.
type UserInfo struct {
	FirstName string `datastore: ",noindex"`
	LastName  string
	Email     string
	Phone     string `datastore: ",noindex"`
}

// A UserAccount stores data about a registered user of the site.
type UserAccount struct {
	AccountID string `datastore: "-"`

	UserInfo

	Confirmed        time.Time `datastore: ",noindex"`
	ConfirmationCode string    `datastore: ",noindex"`
}

func newConfirmationCode() (string, error) {
	b := make([]byte, 32)
	if _, err := rand.Read(b); err != nil {
		return "", err
	}
	return base64.URLEncoding.EncodeToString(b), nil
}

// NewUserAccount creates a new UserAccount for the given user.
func NewUserAccount(u *user.User, info UserInfo) (*UserAccount, error) {
	confirmCode, err := newConfirmationCode()
	if err != nil {
		return nil, fmt.Errorf("couldnt' create confirmation code: %s", err)
	}
	return &UserAccount{
		AccountID:        SaltAndHashString(u.FederatedIdentity),
		UserInfo:         info,
		ConfirmationCode: confirmCode,
	}, nil
}

func userKeyFromID(c appengine.Context, id string) *datastore.Key {
	return datastore.NewKey(c, "UserAccount", id, 0, nil)
}

func userKeyFromFederatedIdentity(c appengine.Context, u *user.User) *datastore.Key {
	return userKeyFromID(c, SaltAndHashString(u.FederatedIdentity))
}

func (u *UserAccount) key(c appengine.Context) *datastore.Key {
	return userKeyFromID(c, u.AccountID)
}

func isFieldMismatch(err error) bool {
	_, ok := err.(*datastore.ErrFieldMismatch)
	return ok
}

func lookupUserByKey(c appengine.Context, key *datastore.Key) (*UserAccount, error) {
	ihu := &UserAccount{}
	if err := datastore.Get(c, key, ihu); err != nil {
		switch {
		case err == datastore.ErrNoSuchEntity:
			return nil, ErrUserNotFound
		case isFieldMismatch(err):
			c.Warningf("Type mismatch on user %q: %+v", key.StringID(), err)
			return ihu, nil
		default:
			c.Errorf("Failed looking up user %q: %s", key.StringID(), err)
			return nil, ErrUserNotFound
		}
	}
	ihu.AccountID = key.StringID()
	return ihu, nil
}

// LookupUser returns the InnerHearthUser for the given AppEngine
// user's federated identity, if any.
func LookupUser(c appengine.Context, u *user.User) (*UserAccount, error) {
	return lookupUserByKey(c, userKeyFromFederatedIdentity(c, u))
}

// LookupUserByID returns the InnerHearthUser with the given ID, if any.
func LookupUserByID(c appengine.Context, id string) (*UserAccount, error) {
	return lookupUserByKey(c, userKeyFromID(c, id))
}

// LookupUserByEmail returns the InnerHearthUser with the given email, if any.
func LookupUserByEmail(c appengine.Context, email string) (*UserAccount, error) {
	q := datastore.NewQuery("UserAccount").
		KeysOnly().
		Filter("Email =", email).
		Limit(1)
	keys, err := q.GetAll(c, nil)
	if err != nil {
		c.Errorf("Failure looking for user %q: %s", email, err)
		return nil, ErrUserNotFound
	}
	if len(keys) == 0 {
		return nil, ErrUserNotFound
	}
	return lookupUserByKey(c, keys[0])
}

// Store persists the InnerHearthUser to the datastore.
func (u *UserAccount) Store(c appengine.Context) error {
	key := u.key(c)
	if _, err := datastore.Put(c, key, u); err != nil {
		return fmt.Errorf("failed to store user %q: %s", u.AccountID, err)
	}
	return nil
}

// SendConfirmation schedules a task to email a confirmation request
// to a new user.
func (u *UserAccount) SendConfirmation(c appengine.Context) error {
	t, err := delayedConfirmAccount.Task(*u)
	if err != nil {
		return fmt.Errorf("error getting function task: %s", err)
	}
	t.RetryOptions = &taskqueue.RetryOptions{
		RetryLimit: 3,
	}
	if _, err := taskqueue.Add(c, t, ""); err != nil {
		return fmt.Errorf("error adding confirmation to taskqueue: %s", err)
	}
	return nil
}

// Confirm marks the user as having confirmed their registration and
// stores the confirmation time back to the datastore.
func (u *UserAccount) Confirm(c appengine.Context, code string, now time.Time) error {
	if u.ConfirmationCode == "" {
		return ErrAlreadyConfirmed
	}
	if code != u.ConfirmationCode {
		return ErrWrongConfirmationCode
	}
	u.Confirmed = now.In(time.UTC)
	u.ConfirmationCode = ""
	return u.Store(c)
}

// A UserEmail associates a user ID with an email address, enforcing uniqueness among email addresses.
type ClaimedEmail struct {
	ClaimedBy *datastore.Key
	Email     string
}

// Creates a new ClaimedEmail struct associating the user with their email.
func NewClaimedEmail(c appengine.Context, u *user.User, email string) *ClaimedEmail {
	return &ClaimedEmail{
		ClaimedBy: userKeyFromFederatedIdentity(c, u),
		Email:     email,
	}
}

func (e *ClaimedEmail) key(c appengine.Context) *datastore.Key {
	return datastore.NewKey(c, "ClaimedEmail", e.Email, 0, nil)
}

// Claim attempts to uniquely associate the user and email.
func (e *ClaimedEmail) Claim(c appengine.Context) error {
	err := datastore.RunInTransaction(c, func(c appengine.Context) error {
		key := e.key(c)
		old := &ClaimedEmail{}
		lookupErr := datastore.Get(c, key, old)
		switch {
		case lookupErr == nil:
			return ErrEmailAlreadyClaimed
		case lookupErr == datastore.ErrNoSuchEntity:
			// Didn't find old claim: all is well.
			break
		default:
			return fmt.Errorf("failed to look up old UserEmail: %s", lookupErr)
		}

		if _, storeErr := datastore.Put(c, key, e); storeErr != nil {
			return fmt.Errorf("failed to store UserEmail: %s", storeErr)
		}
		return nil
	}, nil)
	if err != nil {
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/gernest/authboss"
	"log"
)

type AccountAuth struct {
	Store         Storage
	AccountBucket string
	TokensBucket  string
}

func (a AccountAuth) Create(key string, attr authboss.Attributes) error {
	user := new(Account)
	if err := attr.Bind(user, true); err != nil {
		log.Panicln(err)
		return err
	}
	user.Store = a.Store
	user.Bucket = a.AccountBucket
	return user.Save()
}

func (a AccountAuth) Put(key string, attr authboss.Attributes) error {
	return a.Create(key, attr)
}

func (a AccountAuth) Get(key string) (result interface{}, err error) {
	user := NewAccount(a.AccountBucket, a.Store)
	user.UserName=key
	err=user.Get()
	if err != nil {
		return nil, authboss.ErrUserNotFound
	}
	return user, nil
}

func (a AccountAuth) PutOAuth(uid, provider string, attr authboss.Attributes) error {
	var user = NewAccount(a.AccountBucket, a.Store)
	if err := attr.Bind(user, true); err != nil {
		return err
	}
	return user.CreateOauth(uid, provider)
}

func (a AccountAuth) GetOAuth(uid, provider string) (result interface{}, err error) {
	var acc = NewAccount(a.AccountBucket, a.Store)
	user, err := acc.GetOauth(uid, provider)
	if err != nil {
		return nil, authboss.ErrUserNotFound
	}
	return user, nil
}

func (a AccountAuth) AddToken(key, token string) error {
	tk := a.Store.CreateDataRecord(a.TokensBucket, key, []byte(token))
	return tk.Error
}

func (a AccountAuth) DelTokens(key string) error {
	tk := a.Store.RemoveDataRecord(a.TokensBucket, key)
	return tk.Error
}

func (a AccountAuth) UseToken(givenKey, token string) error {
	tk := a.Store.GetDataRecord(a.TokensBucket, givenKey)
	if tk.Error != nil {
		return authboss.ErrTokenNotFound
	} else if tk.Data == nil {
		return authboss.ErrTokenNotFound
	}
	if string(a.Store.Data) == token {
		tk.RemoveDataRecord(a.TokensBucket, givenKey)
		return nil
	}
	return authboss.ErrTokenNotFound
}

func (a AccountAuth) ConfirmUser(tok string) (result interface{}, err error) {
	return nil, authboss.ErrUserNotFound
}

func (a AccountAuth) RecoverUser(rec string) (result interface{}, err error) {
	return nil, authboss.ErrUserNotFound
}
<commit_msg>removed Store and replace it with Storage, migrated all tests to goconvey<commit_after>package main

import (
	"github.com/gernest/authboss"
	"log"
)

type AccountAuth struct {
	Store         Storage
	AccountBucket string
	TokensBucket  string
}

func (a AccountAuth) Create(key string, attr authboss.Attributes) error {
	user := new(Account)
	if err := attr.Bind(user, true); err != nil {
		log.Panicln(err)
		return err
	}
	user.Store = a.Store
	user.Bucket = a.AccountBucket
	return user.Save()
}

func (a AccountAuth) Put(key string, attr authboss.Attributes) error {
	return a.Create(key, attr)
}

func (a AccountAuth) Get(key string) (result interface{}, err error) {
	user := NewAccount(a.AccountBucket, a.Store)
	user.UserName = key
	err = user.Get()
	if err != nil {
		return nil, authboss.ErrUserNotFound
	}
	return user, nil
}

func (a AccountAuth) PutOAuth(uid, provider string, attr authboss.Attributes) error {
	var user = NewAccount(a.AccountBucket, a.Store)
	if err := attr.Bind(user, true); err != nil {
		return err
	}
	return user.CreateOauth(uid, provider)
}

func (a AccountAuth) GetOAuth(uid, provider string) (result interface{}, err error) {
	var acc = NewAccount(a.AccountBucket, a.Store)
	user, err := acc.GetOauth(uid, provider)
	if err != nil {
		return nil, authboss.ErrUserNotFound
	}
	return user, nil
}

func (a AccountAuth) AddToken(key, token string) error {
	tk := a.Store.CreateDataRecord(a.TokensBucket, key, []byte(token))
	return tk.Error
}

func (a AccountAuth) DelTokens(key string) error {
	tk := a.Store.RemoveDataRecord(a.TokensBucket, key)
	return tk.Error
}

func (a AccountAuth) UseToken(givenKey, token string) error {
	tk := a.Store.GetDataRecord(a.TokensBucket, givenKey)
	if tk.Error != nil {
		return authboss.ErrTokenNotFound
	} else if tk.Data == nil {
		return authboss.ErrTokenNotFound
	}
	if string(a.Store.Data) == token {
		tk.RemoveDataRecord(a.TokensBucket, givenKey)
		return nil
	}
	return authboss.ErrTokenNotFound
}

func (a AccountAuth) ConfirmUser(tok string) (result interface{}, err error) {
	return nil, authboss.ErrUserNotFound
}

func (a AccountAuth) RecoverUser(rec string) (result interface{}, err error) {
	return nil, authboss.ErrUserNotFound
}
<|endoftext|>