<commit_before>//  Copyright (c) 2014 Couchbase, Inc.
//  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 main

import (
	"fmt"
	"sync"

	"github.com/couchbase/gomemcached"
	log "github.com/couchbaselabs/clog"

	"github.com/steveyen/cbdatasource"
)

// Implements both Feed and cbdatasource.Receiver interfaces.
type DCPFeed struct {
	name       string
	url        string
	poolName   string
	bucketName string
	bucketUUID string
	pf         DestPartitionFunc
	dests      map[string]Dest
	bds        cbdatasource.BucketDataSource

	m       sync.Mutex
	closed  bool
	lastErr error

	numError         uint64
	numUpdate        uint64
	numDelete        uint64
	numSnapshotStart uint64
	numSetMetaData   uint64
	numGetMetaData   uint64
	numRollback      uint64
}

func NewDCPFeed(name, url, poolName, bucketName, bucketUUID string,
	pf DestPartitionFunc, dests map[string]Dest) (*DCPFeed, error) {
	vbucketIds, err := ParsePartitionsToVBucketIds(dests)
	if err != nil {
		return nil, err
	}
	if len(vbucketIds) <= 0 {
		vbucketIds = nil
	}

	var authFunc cbdatasource.AuthFunc                // TODO: AUTH.
	var options *cbdatasource.BucketDataSourceOptions // TODO: options.

	feed := &DCPFeed{
		name:       name, // TODO: unique name.
		url:        url,
		poolName:   poolName,
		bucketName: bucketName,
		bucketUUID: bucketUUID,
		pf:         pf,
		dests:      dests,
	}

	feed.bds, err = cbdatasource.NewBucketDataSource(
		[]string{url}, // TODO: more than 1 seed URL.
		poolName, bucketName, bucketUUID,
		vbucketIds, authFunc, feed, options)
	if err != nil {
		return nil, err
	}

	return feed, nil
}

func (t *DCPFeed) Name() string {
	return t.name
}

func (t *DCPFeed) Start() error {
	log.Printf("DCPFeed.Start, name: %s", t.Name())
	return t.bds.Start()
}

func (t *DCPFeed) Close() error {
	t.m.Lock()
	if t.closed {
		t.m.Unlock()
		return fmt.Errorf("already closed")
	}
	t.closed = true
	t.m.Unlock()

	log.Printf("DCPFeed.Close, name: %s", t.Name())
	return t.bds.Close()
}

func (t *DCPFeed) Dests() map[string]Dest {
	return t.dests
}

// --------------------------------------------------------

func (r *DCPFeed) OnError(err error) {
	log.Printf("DCPFeed.OnError: %s: %v\n", r.name, err)

	r.m.Lock()
	r.numError += 1
	r.lastErr = err
	r.m.Unlock()
}

func (r *DCPFeed) DataUpdate(vbucketId uint16, key []byte, seq uint64,
	req *gomemcached.MCRequest) error {
	// log.Printf("DCPFeed.DataUpdate: %s: vbucketId: %d, key: %s, seq: %d, req: %v\n",
	// r.name, vbucketId, key, seq, req)

	partition, dest, err :=
		VBucketIdToPartitionDest(r.pf, r.dests, vbucketId, key)
	if err != nil {
		return err
	}

	r.m.Lock()
	r.numUpdate += 1
	r.m.Unlock()

	return dest.OnDataUpdate(partition, key, seq, req.Body)
}

func (r *DCPFeed) DataDelete(vbucketId uint16, key []byte, seq uint64,
	req *gomemcached.MCRequest) error {
	// log.Printf("DCPFeed.DataDelete: %s: vbucketId: %d, key: %s, seq: %d, req: %#v",
	// r.name, vbucketId, key, seq, req)

	partition, dest, err :=
		VBucketIdToPartitionDest(r.pf, r.dests, vbucketId, key)
	if err != nil {
		return err
	}

	r.m.Lock()
	r.numDelete += 1
	r.m.Unlock()

	return dest.OnDataDelete(partition, key, seq)
}

func (r *DCPFeed) SnapshotStart(vbucketId uint16,
	snapStart, snapEnd uint64, snapType uint32) error {
	log.Printf("DCPFeed.SnapshotStart: %s: vbucketId: %d,"+
		" snapStart: %d, snapEnd: %d, snapType: %d",
		r.name, vbucketId, snapStart, snapEnd, snapType)

	partition, dest, err :=
		VBucketIdToPartitionDest(r.pf, r.dests, vbucketId, nil)
	if err != nil {
		return err
	}

	r.m.Lock()
	r.numSnapshotStart += 1
	r.m.Unlock()

	return dest.OnSnapshotStart(partition, snapStart, snapEnd)
}

func (r *DCPFeed) SetMetaData(vbucketId uint16, value []byte) error {
	log.Printf("DCPFeed.SetMetaData: %s: vbucketId: %d,"+
		" value: %s", r.name, vbucketId, value)

	partition, dest, err :=
		VBucketIdToPartitionDest(r.pf, r.dests, vbucketId, nil)
	if err != nil {
		return err
	}

	r.m.Lock()
	r.numSetMetaData += 1
	r.m.Unlock()

	return dest.SetOpaque(partition, value)
}

func (r *DCPFeed) GetMetaData(vbucketId uint16) (value []byte, lastSeq uint64, err error) {
	log.Printf("DCPFeed.GetMetaData: %s: vbucketId: %d", r.name, vbucketId)

	partition, dest, err :=
		VBucketIdToPartitionDest(r.pf, r.dests, vbucketId, nil)
	if err != nil {
		return nil, 0, err
	}

	r.m.Lock()
	r.numGetMetaData += 1
	r.m.Unlock()

	return dest.GetOpaque(partition)
}

func (r *DCPFeed) Rollback(vbucketId uint16, rollbackSeq uint64) error {
	log.Printf("DCPFeed.Rollback: %s: vbucketId: %d,"+
		" rollbackSeq: %d", r.name, vbucketId, rollbackSeq)

	partition, dest, err :=
		VBucketIdToPartitionDest(r.pf, r.dests, vbucketId, nil)
	if err != nil {
		return err
	}

	r.m.Lock()
	r.numRollback += 1
	r.m.Unlock()

	return dest.Rollback(partition, rollbackSeq)
}
<commit_msg>re-closing a DCP feed returns nil error<commit_after>//  Copyright (c) 2014 Couchbase, Inc.
//  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 main

import (
	"sync"

	"github.com/couchbase/gomemcached"
	log "github.com/couchbaselabs/clog"

	"github.com/steveyen/cbdatasource"
)

// Implements both Feed and cbdatasource.Receiver interfaces.
type DCPFeed struct {
	name       string
	url        string
	poolName   string
	bucketName string
	bucketUUID string
	pf         DestPartitionFunc
	dests      map[string]Dest
	bds        cbdatasource.BucketDataSource

	m       sync.Mutex
	closed  bool
	lastErr error

	numError         uint64
	numUpdate        uint64
	numDelete        uint64
	numSnapshotStart uint64
	numSetMetaData   uint64
	numGetMetaData   uint64
	numRollback      uint64
}

func NewDCPFeed(name, url, poolName, bucketName, bucketUUID string,
	pf DestPartitionFunc, dests map[string]Dest) (*DCPFeed, error) {
	vbucketIds, err := ParsePartitionsToVBucketIds(dests)
	if err != nil {
		return nil, err
	}
	if len(vbucketIds) <= 0 {
		vbucketIds = nil
	}

	var authFunc cbdatasource.AuthFunc                // TODO: AUTH.
	var options *cbdatasource.BucketDataSourceOptions // TODO: options.

	feed := &DCPFeed{
		name:       name, // TODO: unique name.
		url:        url,
		poolName:   poolName,
		bucketName: bucketName,
		bucketUUID: bucketUUID,
		pf:         pf,
		dests:      dests,
	}

	feed.bds, err = cbdatasource.NewBucketDataSource(
		[]string{url}, // TODO: more than 1 seed URL.
		poolName, bucketName, bucketUUID,
		vbucketIds, authFunc, feed, options)
	if err != nil {
		return nil, err
	}

	return feed, nil
}

func (t *DCPFeed) Name() string {
	return t.name
}

func (t *DCPFeed) Start() error {
	log.Printf("DCPFeed.Start, name: %s", t.Name())
	return t.bds.Start()
}

func (t *DCPFeed) Close() error {
	t.m.Lock()
	if t.closed {
		t.m.Unlock()
		return nil
	}
	t.closed = true
	t.m.Unlock()

	log.Printf("DCPFeed.Close, name: %s", t.Name())
	return t.bds.Close()
}

func (t *DCPFeed) Dests() map[string]Dest {
	return t.dests
}

// --------------------------------------------------------

func (r *DCPFeed) OnError(err error) {
	log.Printf("DCPFeed.OnError: %s: %v\n", r.name, err)

	r.m.Lock()
	r.numError += 1
	r.lastErr = err
	r.m.Unlock()
}

func (r *DCPFeed) DataUpdate(vbucketId uint16, key []byte, seq uint64,
	req *gomemcached.MCRequest) error {
	// log.Printf("DCPFeed.DataUpdate: %s: vbucketId: %d, key: %s, seq: %d, req: %v\n",
	// r.name, vbucketId, key, seq, req)

	partition, dest, err :=
		VBucketIdToPartitionDest(r.pf, r.dests, vbucketId, key)
	if err != nil {
		return err
	}

	r.m.Lock()
	r.numUpdate += 1
	r.m.Unlock()

	return dest.OnDataUpdate(partition, key, seq, req.Body)
}

func (r *DCPFeed) DataDelete(vbucketId uint16, key []byte, seq uint64,
	req *gomemcached.MCRequest) error {
	// log.Printf("DCPFeed.DataDelete: %s: vbucketId: %d, key: %s, seq: %d, req: %#v",
	// r.name, vbucketId, key, seq, req)

	partition, dest, err :=
		VBucketIdToPartitionDest(r.pf, r.dests, vbucketId, key)
	if err != nil {
		return err
	}

	r.m.Lock()
	r.numDelete += 1
	r.m.Unlock()

	return dest.OnDataDelete(partition, key, seq)
}

func (r *DCPFeed) SnapshotStart(vbucketId uint16,
	snapStart, snapEnd uint64, snapType uint32) error {
	log.Printf("DCPFeed.SnapshotStart: %s: vbucketId: %d,"+
		" snapStart: %d, snapEnd: %d, snapType: %d",
		r.name, vbucketId, snapStart, snapEnd, snapType)

	partition, dest, err :=
		VBucketIdToPartitionDest(r.pf, r.dests, vbucketId, nil)
	if err != nil {
		return err
	}

	r.m.Lock()
	r.numSnapshotStart += 1
	r.m.Unlock()

	return dest.OnSnapshotStart(partition, snapStart, snapEnd)
}

func (r *DCPFeed) SetMetaData(vbucketId uint16, value []byte) error {
	log.Printf("DCPFeed.SetMetaData: %s: vbucketId: %d,"+
		" value: %s", r.name, vbucketId, value)

	partition, dest, err :=
		VBucketIdToPartitionDest(r.pf, r.dests, vbucketId, nil)
	if err != nil {
		return err
	}

	r.m.Lock()
	r.numSetMetaData += 1
	r.m.Unlock()

	return dest.SetOpaque(partition, value)
}

func (r *DCPFeed) GetMetaData(vbucketId uint16) (value []byte, lastSeq uint64, err error) {
	log.Printf("DCPFeed.GetMetaData: %s: vbucketId: %d", r.name, vbucketId)

	partition, dest, err :=
		VBucketIdToPartitionDest(r.pf, r.dests, vbucketId, nil)
	if err != nil {
		return nil, 0, err
	}

	r.m.Lock()
	r.numGetMetaData += 1
	r.m.Unlock()

	return dest.GetOpaque(partition)
}

func (r *DCPFeed) Rollback(vbucketId uint16, rollbackSeq uint64) error {
	log.Printf("DCPFeed.Rollback: %s: vbucketId: %d,"+
		" rollbackSeq: %d", r.name, vbucketId, rollbackSeq)

	partition, dest, err :=
		VBucketIdToPartitionDest(r.pf, r.dests, vbucketId, nil)
	if err != nil {
		return err
	}

	r.m.Lock()
	r.numRollback += 1
	r.m.Unlock()

	return dest.Rollback(partition, rollbackSeq)
}
<|endoftext|>


<commit_before>package datastore

import (
	"context"
	"log"

	"google.golang.org/appengine/v2/memcache"
)

type Memcache struct {
	Inner DataStore
}

func (s Memcache) Get(ctx context.Context, name string) ([]byte, error) {
	item, err := memcache.Get(ctx, name)
	if err != nil {
		log.Printf("error getting %s from memcache: %v", name, err)
		b, err := s.Inner.Get(ctx, name)
		if err != nil {
			go s.TrySet(ctx, name, b)
		}
		return b, err
	}
	log.Printf("got %s from memcache", name)
	return item.Value, nil
}

func (s Memcache) Put(ctx context.Context, name string, value []byte) error {
	err := s.Inner.Put(ctx, name, value)
	if err != nil {
		go s.TrySet(ctx, name, value)
	}
	return err
}

func (s Memcache) Has(ctx context.Context, name string) (bool, error) {
	return s.Inner.Has(ctx, name)
}

func (s Memcache) TrySet(ctx context.Context, name string, value []byte) {
	err := memcache.Set(ctx, &memcache.Item{
		Key:   name,
		Value: value,
	})
	if err != nil {
		log.Printf("error adding %s to memcache: %v", name, err)
	} else {
		log.Printf("added %s to memcache", name)
	}
}
<commit_msg>Fix error handling<commit_after>package datastore

import (
	"context"
	"log"

	"google.golang.org/appengine/v2/memcache"
)

type Memcache struct {
	Inner DataStore
}

func (s Memcache) Get(ctx context.Context, name string) ([]byte, error) {
	item, err := memcache.Get(ctx, name)
	if err != nil {
		log.Printf("error getting %s from memcache: %v", name, err)
		b, err := s.Inner.Get(ctx, name)
		if err != nil {
			return nil, err
		}
		go s.TrySet(ctx, name, b)
		return b, nil
	}
	log.Printf("got %s from memcache", name)
	return item.Value, nil
}

func (s Memcache) Put(ctx context.Context, name string, value []byte) error {
	err := s.Inner.Put(ctx, name, value)
	if err != nil {
		return err
	}
	go s.TrySet(ctx, name, value)
	return nil
}

func (s Memcache) Has(ctx context.Context, name string) (bool, error) {
	return s.Inner.Has(ctx, name)
}

func (s Memcache) TrySet(ctx context.Context, name string, value []byte) {
	err := memcache.Set(ctx, &memcache.Item{
		Key:   name,
		Value: value,
	})
	if err != nil {
		log.Printf("error adding %s to memcache: %v", name, err)
	} else {
		log.Printf("added %s to memcache", name)
	}
}
<|endoftext|>


<commit_before>/*
Package evidence handles all evidence storage and gossiping from detection to block proposal.
For the different types of evidence refer to the `evidence.go` file in the types package.

## Gossiping

The core functionality begins with the evidence reactor (see reactor.
go) which operates both the sending and receiving of evidence.

The `Receive` function takes a list of evidence and does the following:
 1. breaks it down into individual evidence if it is `Composite Evidence` (see types/evidence.
go#ConflictingHeadersEvidence)
 2. checks that it does not already have the evidence stored
 3. verifies the evidence against the nodes state (see state/validation.go#VerifyEvidence)
 4. stores the evidence to a db and a concurrent list

The gossiping of evidence is initiated when a peer is added which starts a go routine to broadcast currently
uncommitted evidence at intervals of 60 seconds (set by the by broadcastEvidenceIntervalS).
It uses a concurrent list to store the evidence and before sending verifies that each evidence is still valid in the
sense that it has not exceeded the max evidence age and height which should be set to be equal to the "trusting
period" (see types/params.go#EvidenceParams).

## Proposing

When a new block is being proposed (in state/execution.go#CreateProposalBlock),
`PendingEvidence(maxNum)` is called to send up to the maxNum number of uncommitted evidence, from the evidence store,
based on a priority that is a product of the age of the evidence and the voting power of the malicious validator.

Once the proposed evidence is submitted,
the evidence is marked as committed and is moved from the broadcasted set to the committed set (
the committed set is used to verify whether new evidence has actually already been submitted).
As a result it is also removed from the concurrent list so that it is no longer gossiped.

Last Update: 14/04/20
*/
package evidence
<commit_msg>clean up doc<commit_after>/*
Package evidence handles all evidence storage and gossiping from detection to block proposal.
For the different types of evidence refer to the `evidence.go` file in the types package
or https://github.com/tendermint/spec/blob/master/spec/consensus/light-client/accountability.md.

## Gossiping

The core functionality begins with the evidence reactor (see reactor.
go) which operates both the sending and receiving of evidence.

The `Receive` function takes a list of evidence and does the following:
 1. breaks it down into individual evidence if it is `Composite Evidence` (see types/evidence.
go#ConflictingHeadersEvidence)
 2. checks that it does not already have the evidence stored
 3. verifies the evidence against the nodes state (see state/validation.go#VerifyEvidence)
 4. stores the evidence to a db and a concurrent list

The gossiping of evidence is initiated when a peer is added which starts a go routine to broadcast currently
uncommitted evidence at intervals of 60 seconds (set by the by broadcastEvidenceIntervalS).
It uses a concurrent list to store the evidence and before sending verifies that each evidence is still valid in the
sense that it has not exceeded the max evidence age and height. This should be set to be equal to the "trusting
period" (see types/params.go#EvidenceParams).

## Proposing

When a new block is being proposed (in state/execution.go#CreateProposalBlock),
`PendingEvidence(maxNum)` is called to send up to the maxNum number of uncommitted evidence, from the evidence store,
based on a priority that is a product of the age of the evidence and the voting power of the malicious validator.

Once the proposed evidence is submitted,
the evidence is marked as committed and is moved from the broadcasted set to the committed set (
the committed set is used to verify whether new evidence has actually already been submitted).
As a result it is also removed from the concurrent list so that it is no longer gossiped.

Last Update: 14/04/20
*/
package evidence
<|endoftext|>


<commit_before>// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.

package main

import (
	"context"
	"log"

	"google.golang.org/cloud/datastore"
	"google.golang.org/cloud/pubsub"
)

func main() {
	ctx := context.Background()

	pubsubClient, err := pubsub.NewClient(ctx, "project")
	if err != nil {
		log.Fatalf("pubsub.NewClient: %v", err)
	}
	topic, err := pubsubClient.NewTopic(ctx, "topic")
	if err != nil {
		log.Fatalf("NewTopic: %v", err)
	}
	sub, err := pubsubClient.NewSubscription(ctx, "sub", topic, 0, nil)
	if err != nil {
		log.Fatalf("NewSubscription: %v", err)
	}
	if _, err := topic.Publish(ctx, &pubsub.Message{
		Data: []byte("hello"),
	}); err != nil {
		log.Fatalf("Publish: %v", err)
	}
	it, err := sub.Pull(ctx)
	if err != nil {
		log.Fatalf("Pull: %v", err)
	}
	msg, err := it.Next()
	if err != nil {
		log.Fatalf("Next: %v", err)
	}
	log.Printf("pubsub message: %s", msg.Data)
	msg.Done(true)
	it.Stop()

	datastoreClient, err := datastore.NewClient(ctx, "project")
	if err != nil {
		log.Fatalf("datastore.NewClient: %v", err)
	}
	k := datastore.NewIncompleteKey(ctx, "Foo", nil)
	k, err = datastoreClient.Put(ctx, k, &Foo{F: "foo!"})
	if err != nil {
		log.Fatalf("Put: %v", err)
	}

	var f Foo
	if err := datastoreClient.Get(ctx, k, &f); err != nil {
		log.Fatalf("Get: %v", err)
	}
	log.Printf("datastore got %v", f)
}

type Foo struct {
	F string
}
<commit_msg>example: update to new cloud.google.com package path<commit_after>// Copyright 2016 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.

package main

import (
	"context"
	"log"

	"cloud.google.com/go/datastore"
	"cloud.google.com/go/pubsub"
)

func main() {
	ctx := context.Background()

	pubsubClient, err := pubsub.NewClient(ctx, "project")
	if err != nil {
		log.Fatalf("pubsub.NewClient: %v", err)
	}
	topic, err := pubsubClient.NewTopic(ctx, "topic")
	if err != nil {
		log.Fatalf("NewTopic: %v", err)
	}
	sub, err := pubsubClient.NewSubscription(ctx, "sub", topic, 0, nil)
	if err != nil {
		log.Fatalf("NewSubscription: %v", err)
	}
	if _, err := topic.Publish(ctx, &pubsub.Message{
		Data: []byte("hello"),
	}); err != nil {
		log.Fatalf("Publish: %v", err)
	}
	it, err := sub.Pull(ctx)
	if err != nil {
		log.Fatalf("Pull: %v", err)
	}
	msg, err := it.Next()
	if err != nil {
		log.Fatalf("Next: %v", err)
	}
	log.Printf("pubsub message: %s", msg.Data)
	msg.Done(true)
	it.Stop()

	datastoreClient, err := datastore.NewClient(ctx, "project")
	if err != nil {
		log.Fatalf("datastore.NewClient: %v", err)
	}
	k := datastore.NewIncompleteKey(ctx, "Foo", nil)
	k, err = datastoreClient.Put(ctx, k, &Foo{F: "foo!"})
	if err != nil {
		log.Fatalf("Put: %v", err)
	}

	var f Foo
	if err := datastoreClient.Get(ctx, k, &f); err != nil {
		log.Fatalf("Get: %v", err)
	}
	log.Printf("datastore got %v", f)
}

type Foo struct {
	F string
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/4ydx/gltext"
	"github.com/4ydx/gltext/v4.1"
	"github.com/go-gl/gl/v4.1-core/gl"
	"github.com/go-gl/glfw/v3.2/glfw"
	"github.com/go-gl/mathgl/mgl32"
	"golang.org/x/image/math/fixed"
	"math"
	"os"
	"runtime"
	"time"
)

var useStrictCoreProfile = (runtime.GOOS == "darwin")

func main() {
	runtime.LockOSThread()

	err := glfw.Init()
	if err != nil {
		panic("glfw error")
	}
	defer glfw.Terminate()

	glfw.WindowHint(glfw.Resizable, glfw.False)
	glfw.WindowHint(glfw.ContextVersionMajor, 3)
	glfw.WindowHint(glfw.ContextVersionMinor, 3)
	if useStrictCoreProfile {
		glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)
		glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
	}
	glfw.WindowHint(glfw.OpenGLDebugContext, glfw.True)

	window, err := glfw.CreateWindow(640, 480, "Testing", nil, nil)
	if err != nil {
		panic(err)
	}
	window.MakeContextCurrent()
	if err := gl.Init(); err != nil {
		panic(err)
	}
	version := gl.GoStr(gl.GetString(gl.VERSION))
	fmt.Println("Opengl version", version)

	// code from here
	gltext.IsDebug = true

	var font *v41.Font
	config, err := gltext.LoadTruetypeFontConfig("fontconfigs", "font_1_honokamin")
	if err == nil {
		font, err = v41.NewFont(config)
		if err != nil {
			panic(err)
		}
		fmt.Println("Font loaded from disk...")
	} else {
		fd, err := os.Open("font/font_1_honokamin.ttf")
		if err != nil {
			panic(err)
		}
		defer fd.Close()

		// Japanese character ranges
		// http://www.rikai.com/library/kanjitables/kanji_codes.unicode.shtml
		runeRanges := make(gltext.RuneRanges, 0)
		runeRanges = append(runeRanges, gltext.RuneRange{Low: 32, High: 128})
		runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x3000, High: 0x3030})
		runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x3040, High: 0x309f})
		runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x30a0, High: 0x30ff})
		runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x4e00, High: 0x9faf})
		runeRanges = append(runeRanges, gltext.RuneRange{Low: 0xff00, High: 0xffef})

		scale := fixed.Int26_6(24)
		runesPerRow := fixed.Int26_6(128)
		config, err = gltext.NewTruetypeFontConfig(fd, scale, runeRanges, runesPerRow)
		if err != nil {
			panic(err)
		}
		err = config.Save("fontconfigs", "font_1_honokamin")
		if err != nil {
			panic(err)
		}
		font, err = v41.NewFont(config)
		if err != nil {
			panic(err)
		}
	}

	width, height := window.GetSize()
	font.ResizeWindow(float32(width), float32(height))

	scaleMin, scaleMax := float32(1.0), float32(1.1)
	text := v41.NewText(font, scaleMin, scaleMax)
	str := "大好き"
	for _, s := range str {
		fmt.Printf("%c: %d\n", s, rune(s))
	}
	text.SetString(str)
	text.SetColor(mgl32.Vec3{1, 1, 1})
	text.FadeOutPerFrame = 0.01

	i := 0
	start := time.Now()

	gl.ClearColor(0.4, 0.4, 0.4, 0.0)
	for !window.ShouldClose() {
		gl.Clear(gl.COLOR_BUFFER_BIT)

		// Position can be set freely
		text.SetPosition(mgl32.Vec2{0, float32(i)})
		i++
		if i > 200 {
			i = -200
		}
		text.Draw()

		// just for illustrative purposes
		// i imagine that user interaction of some sort will trigger these rather than a moment in time

		// fade out
		if math.Floor(time.Now().Sub(start).Seconds()) == 5 {
			text.BeginFadeOut()
		}

		// show text
		if math.Floor(time.Now().Sub(start).Seconds()) == 10 {
			text.Show()
		}

		// hide
		if math.Floor(time.Now().Sub(start).Seconds()) == 15 {
			text.Hide()
		}

		window.SwapBuffers()
		glfw.PollEvents()
	}
	text.Release()
	font.Release()
}
<commit_msg>Updated the example to reflect the api changes.<commit_after>package main

import (
	"fmt"
	"github.com/4ydx/gltext"
	"github.com/4ydx/gltext/v4.1"
	"github.com/go-gl/gl/v4.1-core/gl"
	"github.com/go-gl/glfw/v3.2/glfw"
	"github.com/go-gl/mathgl/mgl32"
	"golang.org/x/image/math/fixed"
	"math"
	"os"
	"runtime"
	"time"
)

var useStrictCoreProfile = (runtime.GOOS == "darwin")

func main() {
	runtime.LockOSThread()

	err := glfw.Init()
	if err != nil {
		panic("glfw error")
	}
	defer glfw.Terminate()

	glfw.WindowHint(glfw.Resizable, glfw.False)
	glfw.WindowHint(glfw.ContextVersionMajor, 3)
	glfw.WindowHint(glfw.ContextVersionMinor, 3)
	if useStrictCoreProfile {
		glfw.WindowHint(glfw.OpenGLForwardCompatible, glfw.True)
		glfw.WindowHint(glfw.OpenGLProfile, glfw.OpenGLCoreProfile)
	}
	glfw.WindowHint(glfw.OpenGLDebugContext, glfw.True)

	window, err := glfw.CreateWindow(1280, 960, "Testing", nil, nil)
	if err != nil {
		panic(err)
	}
	window.MakeContextCurrent()
	if err := gl.Init(); err != nil {
		panic(err)
	}
	version := gl.GoStr(gl.GetString(gl.VERSION))
	fmt.Println("Opengl version", version)

	// code from here
	gltext.IsDebug = true

	var font *v41.Font
	config, err := gltext.LoadTruetypeFontConfig("fontconfigs", "font_1_honokamin")
	if err == nil {
		font, err = v41.NewFont(config)
		if err != nil {
			panic(err)
		}
		fmt.Println("Font loaded from disk...")
	} else {
		fd, err := os.Open("font/font_1_honokamin.ttf")
		if err != nil {
			panic(err)
		}
		defer fd.Close()

		// Japanese character ranges
		// http://www.rikai.com/library/kanjitables/kanji_codes.unicode.shtml
		runeRanges := make(gltext.RuneRanges, 0)
		runeRanges = append(runeRanges, gltext.RuneRange{Low: 32, High: 128})
		runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x3000, High: 0x3030})
		runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x3040, High: 0x309f})
		runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x30a0, High: 0x30ff})
		runeRanges = append(runeRanges, gltext.RuneRange{Low: 0x4e00, High: 0x9faf})
		runeRanges = append(runeRanges, gltext.RuneRange{Low: 0xff00, High: 0xffef})

		scale := fixed.Int26_6(32)
		runesPerRow := fixed.Int26_6(128)
		config, err = gltext.NewTruetypeFontConfig(fd, scale, runeRanges, runesPerRow, 5)
		if err != nil {
			panic(err)
		}
		err = config.Save("fontconfigs", "font_1_honokamin")
		if err != nil {
			panic(err)
		}
		font, err = v41.NewFont(config)
		if err != nil {
			panic(err)
		}
	}

	width, height := window.GetSize()
	font.ResizeWindow(float32(width), float32(height))

	str0 := "! \" # $ ' ( ) + , - . / 0123456789 : "
	str1 := "; <=> ? @ ABCDEFGHIJKLMNOPQRSTUVWXYZ"
	str2 := "[^_`] abcdefghijklmnopqrstuvwxyz {|}"
	str3 := "大好きどなにｂｃｄｆｇｈｊｋｌｍｎｐｑ"

	scaleMin, scaleMax := float32(1.0), float32(1.1)
	strs := []string{str0, str1, str2, str3}
	txts := []*v41.Text{}
	for _, str := range strs {
		text := v41.NewText(font, scaleMin, scaleMax)
		text.SetString(str)
		text.SetColor(mgl32.Vec3{1, 1, 1})
		text.FadeOutPerFrame = 0.01
		if gltext.IsDebug {
			for _, s := range str {
				fmt.Printf("%c: %d\n", s, rune(s))
			}
		}
		txts = append(txts, text)
	}

	start := time.Now()

	gl.ClearColor(0.4, 0.4, 0.4, 0.0)
	for !window.ShouldClose() {
		gl.Clear(gl.COLOR_BUFFER_BIT)

		// Position can be set freely
		for index, text := range txts {
			text.SetPosition(mgl32.Vec2{0, float32(index * 50)})
			text.Draw()

			// just for illustrative purposes
			// i imagine that user interaction of some sort will trigger these rather than a moment in time

			// fade out
			if math.Floor(time.Now().Sub(start).Seconds()) == 5 {
				text.BeginFadeOut()
			}

			// show text
			if math.Floor(time.Now().Sub(start).Seconds()) == 10 {
				text.Show()
			}

			// hide
			if math.Floor(time.Now().Sub(start).Seconds()) == 15 {
				text.Hide()
			}
		}

		window.SwapBuffers()
		glfw.PollEvents()
	}
	for _, text := range txts {
		text.Release()
	}
	font.Release()
}
<|endoftext|>


<commit_before>package main

import (
	"runtime"

	"github.com/smartystreets/go-disruptor"
)

const MaxConsumers = 1

func main() {
	runtime.GOMAXPROCS(MaxConsumers + 3)

	writerCursor := disruptor.NewCursor()
	writerBarrier := disruptor.NewBarrier(writerCursor)

	readerCursors1 := startReaders(writerBarrier, writerCursor)
	readerBarrier1 := disruptor.NewBarrier(readerCursors1...)

	readerCursors2 := startReaders(readerBarrier1, writerCursor)
	readerBarrier2 := disruptor.NewBarrier(readerCursors2...)

	readerCursors3 := startReaders(readerBarrier2, writerCursor)
	readerBarrier3 := disruptor.NewBarrier(readerCursors3...)

	writer := disruptor.NewWriter(writerCursor, RingSize, readerBarrier3)
	publish(writer)
}
func startReaders(upstreamBarrier disruptor.Barrier, writerCursor *disruptor.Cursor) (readerCursors []*disruptor.Cursor) {
	for i := 0; i < MaxConsumers; i++ {
		readerCursor := disruptor.NewCursor()
		readerCursors = append(readerCursors, readerCursor)
		reader := disruptor.NewReader(upstreamBarrier, writerCursor, readerCursor)

		// wildly sporadic latency for single-item publish, e.g. 2 seconds, 65 ms, etc.
		// faster for 2-3+ items per publish
		go consume(reader)

		// constant time regardless of the number of items
		// go easyConsume(disruptor.NewEasyReader(reader, NewExampleConsumerHandler()))
	}

	return readerCursors
}
<commit_msg>Updated GOMAXPROCS formula.<commit_after>package main

import (
	"runtime"

	"github.com/smartystreets/go-disruptor"
)

const MaxConsumers = 2
const MaxConsumerLayers = 3

func main() {
	runtime.GOMAXPROCS(MaxConsumers*MaxConsumerLayers + 1)

	writerCursor := disruptor.NewCursor()
	writerBarrier := disruptor.NewBarrier(writerCursor)

	readerCursors1 := startReaders(writerBarrier, writerCursor)
	readerBarrier1 := disruptor.NewBarrier(readerCursors1...)

	readerCursors2 := startReaders(readerBarrier1, writerCursor)
	readerBarrier2 := disruptor.NewBarrier(readerCursors2...)

	readerCursors3 := startReaders(readerBarrier2, writerCursor)
	readerBarrier3 := disruptor.NewBarrier(readerCursors3...)

	writer := disruptor.NewWriter(writerCursor, RingSize, readerBarrier3)
	publish(writer)
}
func startReaders(upstreamBarrier disruptor.Barrier, writerCursor *disruptor.Cursor) (readerCursors []*disruptor.Cursor) {
	for i := 0; i < MaxConsumers; i++ {
		readerCursor := disruptor.NewCursor()
		readerCursors = append(readerCursors, readerCursor)
		reader := disruptor.NewReader(upstreamBarrier, writerCursor, readerCursor)

		// wildly sporadic latency for single-item publish, e.g. 2 seconds, 65 ms, etc.
		// faster for 2-3+ items per publish
		go consume(reader)

		// constant time regardless of the number of items
		// go easyConsume(disruptor.NewEasyReader(reader, NewExampleConsumerHandler()))
	}

	return readerCursors
}
<|endoftext|>


<commit_before>package webhookserver

import (
	"bytes"
	"fmt"
	"net"
	"net/http"
	"net/http/httptest"
	"net/url"
	"sync"
	"testing"
	"time"
)

func assert(condition bool, a ...interface{}) {
	if !condition {
		panic(fmt.Sprintln(a...))
	}
}

func nilOrPanic(err error, a ...interface{}) {
	assert(err == nil, append(a, err))
}

func TestLocalServer(*testing.T) {
	s, err := NewLocalServer(net.TCPAddr{
		IP:   []byte{127, 0, 0, 1},
		Port: 80,
	}, "example.com", "no-secret", "", "", 10*time.Minute)
	nilOrPanic(err)

	path := ""
	url, _ := s.AttachHook(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(200)
		w.Write([]byte("Hello World"))
		path = r.URL.Path
	}))

	// Test that we can do GET requests
	r, err := http.NewRequest("GET", url, new(bytes.Buffer))
	nilOrPanic(err)
	w := httptest.NewRecorder()
	s.server.Handler.ServeHTTP(w, r)
	assert(w.Code == 200, "Wrong response")
	assert(path == "/", "Wrong path")

	// Test with different suffix
	r, err = http.NewRequest("GET", url+"myfile", new(bytes.Buffer))
	nilOrPanic(err)
	w = httptest.NewRecorder()
	s.server.Handler.ServeHTTP(w, r)
	assert(w.Code == 200, "Wrong response")
	assert(path == "/myfile", "Wrong path")

	url, detach := s.AttachHook(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(200)
		w.Write([]byte("Hello World"))
	}))

	// Call in parallel for race detector
	wg := sync.WaitGroup{}
	wg.Add(2)
	go func() {
		req, rerr := http.NewRequest("GET", url, new(bytes.Buffer))
		nilOrPanic(rerr)
		res := httptest.NewRecorder()
		s.server.Handler.ServeHTTP(res, req)
		assert(res.Code == 200, "Wrong response")
		wg.Done()
	}()
	go func() {
		req, rerr := http.NewRequest("POST", url, new(bytes.Buffer))
		nilOrPanic(rerr)
		res := httptest.NewRecorder()
		s.server.Handler.ServeHTTP(res, req)
		assert(res.Code == 200, "Wrong response")
		wg.Done()
	}()
	wg.Wait()

	// Test wrong id
	badurl := url[:len(url)-4] + "333/"
	r, err = http.NewRequest("GET", badurl, new(bytes.Buffer))
	nilOrPanic(err)
	w = httptest.NewRecorder()
	s.server.Handler.ServeHTTP(w, r)
	assert(w.Code != 200, "Wrong response")

	// Test id too short
	shorturl := url[:len(url)-4] + "/"
	r, err = http.NewRequest("GET", shorturl, new(bytes.Buffer))
	nilOrPanic(err)
	w = httptest.NewRecorder()
	s.server.Handler.ServeHTTP(w, r)
	assert(w.Code != 200, "Wrong response")

	detach()
	r, err = http.NewRequest("GET", url, new(bytes.Buffer))
	nilOrPanic(err)
	w = httptest.NewRecorder()
	s.server.Handler.ServeHTTP(w, r)
	assert(w.Code == 404, "Expected 404")
}

func TestLocalServerStop(*testing.T) {
	s, err := NewLocalServer(net.TCPAddr{
		IP:   []byte{127, 0, 0, 1},
		Port: 60172, // random port...
	}, "example.com", "no-secret", "", "", 10*time.Minute)
	nilOrPanic(err)

	done := make(chan struct{})
	go func() {
		s.ListenAndServe()
		close(done)
	}()

	link, detach := s.AttachHook(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(200)
		w.Write([]byte("Hello World"))
	}))

	u, err := url.Parse(link)
	nilOrPanic(err)

	// Try a request
	r, err := http.NewRequest("GET", "http://127.0.0.1:60172"+u.Path, nil)
	nilOrPanic(err)

	res, err := http.DefaultClient.Do(r)
	nilOrPanic(err)
	assert(res.StatusCode == 200, "Wrong status")

	// Try again after detaching
	detach()
	r, err = http.NewRequest("GET", "http://127.0.0.1:60172"+u.Path, nil)
	nilOrPanic(err)

	res, err = http.DefaultClient.Do(r)
	nilOrPanic(err)
	assert(res.StatusCode == 404, "Wrong status")

	// Stop server, and wait for it to be done
	s.Stop()
	<-done
}
<commit_msg>Avoid hardcoded ports that makes test fragile<commit_after>package webhookserver

import (
	"bytes"
	"fmt"
	"net"
	"net/http"
	"net/http/httptest"
	"sync"
	"testing"
	"time"
)

func assert(condition bool, a ...interface{}) {
	if !condition {
		panic(fmt.Sprintln(a...))
	}
}

func nilOrPanic(err error, a ...interface{}) {
	assert(err == nil, append(a, err))
}

func TestLocalServer(*testing.T) {
	s, err := NewLocalServer(net.TCPAddr{
		IP:   []byte{127, 0, 0, 1},
		Port: 80,
	}, "example.com", "no-secret", "", "", 10*time.Minute)
	nilOrPanic(err)

	path := ""
	url, _ := s.AttachHook(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(200)
		w.Write([]byte("Hello World"))
		path = r.URL.Path
	}))

	// Test that we can do GET requests
	r, err := http.NewRequest("GET", url, new(bytes.Buffer))
	nilOrPanic(err)
	w := httptest.NewRecorder()
	s.server.Handler.ServeHTTP(w, r)
	assert(w.Code == 200, "Wrong response")
	assert(path == "/", "Wrong path")

	// Test with different suffix
	r, err = http.NewRequest("GET", url+"myfile", new(bytes.Buffer))
	nilOrPanic(err)
	w = httptest.NewRecorder()
	s.server.Handler.ServeHTTP(w, r)
	assert(w.Code == 200, "Wrong response")
	assert(path == "/myfile", "Wrong path")

	url, detach := s.AttachHook(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(200)
		w.Write([]byte("Hello World"))
	}))

	// Call in parallel for race detector
	wg := sync.WaitGroup{}
	wg.Add(2)
	go func() {
		req, rerr := http.NewRequest("GET", url, new(bytes.Buffer))
		nilOrPanic(rerr)
		res := httptest.NewRecorder()
		s.server.Handler.ServeHTTP(res, req)
		assert(res.Code == 200, "Wrong response")
		wg.Done()
	}()
	go func() {
		req, rerr := http.NewRequest("POST", url, new(bytes.Buffer))
		nilOrPanic(rerr)
		res := httptest.NewRecorder()
		s.server.Handler.ServeHTTP(res, req)
		assert(res.Code == 200, "Wrong response")
		wg.Done()
	}()
	wg.Wait()

	// Test wrong id
	badurl := url[:len(url)-4] + "333/"
	r, err = http.NewRequest("GET", badurl, new(bytes.Buffer))
	nilOrPanic(err)
	w = httptest.NewRecorder()
	s.server.Handler.ServeHTTP(w, r)
	assert(w.Code != 200, "Wrong response")

	// Test id too short
	shorturl := url[:len(url)-4] + "/"
	r, err = http.NewRequest("GET", shorturl, new(bytes.Buffer))
	nilOrPanic(err)
	w = httptest.NewRecorder()
	s.server.Handler.ServeHTTP(w, r)
	assert(w.Code != 200, "Wrong response")

	detach()
	r, err = http.NewRequest("GET", url, new(bytes.Buffer))
	nilOrPanic(err)
	w = httptest.NewRecorder()
	s.server.Handler.ServeHTTP(w, r)
	assert(w.Code == 404, "Expected 404")
}

func TestLocalServerStop(*testing.T) {
	s, err := NewLocalServer(net.TCPAddr{
		IP:   []byte{127, 0, 0, 1},
		Port: 0, // random port...
	}, "example.com", "no-secret", "", "", 10*time.Minute)
	nilOrPanic(err)

	done := make(chan struct{})
	go func() {
		s.ListenAndServe()
		close(done)
	}()

	// Stop server, and wait for it to be done
	s.Stop()
	<-done
}
<|endoftext|>


<commit_before>// Copyright 2017 Google Inc. 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 firebase is the entry point to the Firebase Admin SDK. It provides functionality for initializing App
// instances, which serve as the central entities that provide access to various other Firebase services exposed
// from the SDK.
package firebase // import "firebase.google.com/go"

import (
	"context"
	"encoding/json"
	"errors"
	"io/ioutil"
	"os"

	"cloud.google.com/go/firestore"
	"firebase.google.com/go/auth"
	"firebase.google.com/go/db"
	"firebase.google.com/go/iid"
	"firebase.google.com/go/internal"
	"firebase.google.com/go/messaging"
	"firebase.google.com/go/storage"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/option"
	"google.golang.org/api/transport"
)

var defaultAuthOverrides = make(map[string]interface{})

// Version of the Firebase Go Admin SDK.
const Version = "3.11.0"

// firebaseEnvName is the name of the environment variable with the Config.
const firebaseEnvName = "FIREBASE_CONFIG"

// An App holds configuration and state common to all Firebase services that are exposed from the SDK.
type App struct {
	authOverride     map[string]interface{}
	creds            *google.DefaultCredentials
	dbURL            string
	projectID        string
	serviceAccountID string
	storageBucket    string
	opts             []option.ClientOption
}

// Config represents the configuration used to initialize an App.
type Config struct {
	AuthOverride     *map[string]interface{} `json:"databaseAuthVariableOverride"`
	DatabaseURL      string                  `json:"databaseURL"`
	ProjectID        string                  `json:"projectId"`
	ServiceAccountID string                  `json:"serviceAccountId"`
	StorageBucket    string                  `json:"storageBucket"`
}

// Auth returns an instance of auth.Client.
func (a *App) Auth(ctx context.Context) (*auth.Client, error) {
	conf := &internal.AuthConfig{
		Creds:            a.creds,
		ProjectID:        a.projectID,
		Opts:             a.opts,
		ServiceAccountID: a.serviceAccountID,
		Version:          Version,
	}
	return auth.NewClient(ctx, conf)
}

// Database returns an instance of db.Client to interact with the default Firebase Database
// configured via Config.DatabaseURL.
func (a *App) Database(ctx context.Context) (*db.Client, error) {
	return a.DatabaseWithURL(ctx, a.dbURL)
}

// DatabaseWithURL returns an instance of db.Client to interact with the Firebase Database
// identified by the given URL.
func (a *App) DatabaseWithURL(ctx context.Context, url string) (*db.Client, error) {
	conf := &internal.DatabaseConfig{
		AuthOverride: a.authOverride,
		URL:          url,
		Opts:         a.opts,
		Version:      Version,
	}
	return db.NewClient(ctx, conf)
}

// Storage returns a new instance of storage.Client.
func (a *App) Storage(ctx context.Context) (*storage.Client, error) {
	conf := &internal.StorageConfig{
		Opts:   a.opts,
		Bucket: a.storageBucket,
	}
	return storage.NewClient(ctx, conf)
}

// Firestore returns a new firestore.Client instance from the https://godoc.org/cloud.google.com/go/firestore
// package.
func (a *App) Firestore(ctx context.Context) (*firestore.Client, error) {
	if a.projectID == "" {
		return nil, errors.New("project id is required to access Firestore")
	}
	return firestore.NewClient(ctx, a.projectID, a.opts...)
}

// InstanceID returns an instance of iid.Client.
func (a *App) InstanceID(ctx context.Context) (*iid.Client, error) {
	conf := &internal.InstanceIDConfig{
		ProjectID: a.projectID,
		Opts:      a.opts,
	}
	return iid.NewClient(ctx, conf)
}

// Messaging returns an instance of messaging.Client.
func (a *App) Messaging(ctx context.Context) (*messaging.Client, error) {
	conf := &internal.MessagingConfig{
		ProjectID: a.projectID,
		Opts:      a.opts,
		Version:   Version,
	}
	return messaging.NewClient(ctx, conf)
}

// NewApp creates a new App from the provided config and client options.
//
// If the client options contain a valid credential (a service account file, a refresh token
// file or an oauth2.TokenSource) the App will be authenticated using that credential. Otherwise,
// NewApp attempts to authenticate the App with Google application default credentials.
// If `config` is nil, the SDK will attempt to load the config options from the
// `FIREBASE_CONFIG` environment variable. If the value in it starts with a `{` it is parsed as a
// JSON object, otherwise it is assumed to be the name of the JSON file containing the options.
func NewApp(ctx context.Context, config *Config, opts ...option.ClientOption) (*App, error) {
	o := []option.ClientOption{option.WithScopes(internal.FirebaseScopes...)}
	o = append(o, opts...)
	creds, err := transport.Creds(ctx, o...)
	if err != nil {
		return nil, err
	}
	if config == nil {
		if config, err = getConfigDefaults(); err != nil {
			return nil, err
		}
	}

	var pid string
	if config.ProjectID != "" {
		pid = config.ProjectID
	} else if creds.ProjectID != "" {
		pid = creds.ProjectID
	} else {
		pid = os.Getenv("GOOGLE_CLOUD_PROJECT")
		if pid == "" {
			pid = os.Getenv("GCLOUD_PROJECT")
		}
	}

	ao := defaultAuthOverrides
	if config.AuthOverride != nil {
		ao = *config.AuthOverride
	}

	return &App{
		authOverride:     ao,
		creds:            creds,
		dbURL:            config.DatabaseURL,
		projectID:        pid,
		serviceAccountID: config.ServiceAccountID,
		storageBucket:    config.StorageBucket,
		opts:             o,
	}, nil
}

// getConfigDefaults reads the default config file, defined by the FIREBASE_CONFIG
// env variable, used only when options are nil.
func getConfigDefaults() (*Config, error) {
	fbc := &Config{}
	confFileName := os.Getenv(firebaseEnvName)
	if confFileName == "" {
		return fbc, nil
	}
	var dat []byte
	if confFileName[0] == byte('{') {
		dat = []byte(confFileName)
	} else {
		var err error
		if dat, err = ioutil.ReadFile(confFileName); err != nil {
			return nil, err
		}
	}
	if err := json.Unmarshal(dat, fbc); err != nil {
		return nil, err
	}

	// Some special handling necessary for db auth overrides
	var m map[string]interface{}
	if err := json.Unmarshal(dat, &m); err != nil {
		return nil, err
	}
	if ao, ok := m["databaseAuthVariableOverride"]; ok && ao == nil {
		// Auth overrides are explicitly set to null
		var nullMap map[string]interface{}
		fbc.AuthOverride = &nullMap
	}
	return fbc, nil
}
<commit_msg>Bumped version to 3.11.1 (#319)<commit_after>// Copyright 2017 Google Inc. 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 firebase is the entry point to the Firebase Admin SDK. It provides functionality for initializing App
// instances, which serve as the central entities that provide access to various other Firebase services exposed
// from the SDK.
package firebase // import "firebase.google.com/go"

import (
	"context"
	"encoding/json"
	"errors"
	"io/ioutil"
	"os"

	"cloud.google.com/go/firestore"
	"firebase.google.com/go/auth"
	"firebase.google.com/go/db"
	"firebase.google.com/go/iid"
	"firebase.google.com/go/internal"
	"firebase.google.com/go/messaging"
	"firebase.google.com/go/storage"
	"golang.org/x/oauth2/google"
	"google.golang.org/api/option"
	"google.golang.org/api/transport"
)

var defaultAuthOverrides = make(map[string]interface{})

// Version of the Firebase Go Admin SDK.
const Version = "3.11.1"

// firebaseEnvName is the name of the environment variable with the Config.
const firebaseEnvName = "FIREBASE_CONFIG"

// An App holds configuration and state common to all Firebase services that are exposed from the SDK.
type App struct {
	authOverride     map[string]interface{}
	creds            *google.DefaultCredentials
	dbURL            string
	projectID        string
	serviceAccountID string
	storageBucket    string
	opts             []option.ClientOption
}

// Config represents the configuration used to initialize an App.
type Config struct {
	AuthOverride     *map[string]interface{} `json:"databaseAuthVariableOverride"`
	DatabaseURL      string                  `json:"databaseURL"`
	ProjectID        string                  `json:"projectId"`
	ServiceAccountID string                  `json:"serviceAccountId"`
	StorageBucket    string                  `json:"storageBucket"`
}

// Auth returns an instance of auth.Client.
func (a *App) Auth(ctx context.Context) (*auth.Client, error) {
	conf := &internal.AuthConfig{
		Creds:            a.creds,
		ProjectID:        a.projectID,
		Opts:             a.opts,
		ServiceAccountID: a.serviceAccountID,
		Version:          Version,
	}
	return auth.NewClient(ctx, conf)
}

// Database returns an instance of db.Client to interact with the default Firebase Database
// configured via Config.DatabaseURL.
func (a *App) Database(ctx context.Context) (*db.Client, error) {
	return a.DatabaseWithURL(ctx, a.dbURL)
}

// DatabaseWithURL returns an instance of db.Client to interact with the Firebase Database
// identified by the given URL.
func (a *App) DatabaseWithURL(ctx context.Context, url string) (*db.Client, error) {
	conf := &internal.DatabaseConfig{
		AuthOverride: a.authOverride,
		URL:          url,
		Opts:         a.opts,
		Version:      Version,
	}
	return db.NewClient(ctx, conf)
}

// Storage returns a new instance of storage.Client.
func (a *App) Storage(ctx context.Context) (*storage.Client, error) {
	conf := &internal.StorageConfig{
		Opts:   a.opts,
		Bucket: a.storageBucket,
	}
	return storage.NewClient(ctx, conf)
}

// Firestore returns a new firestore.Client instance from the https://godoc.org/cloud.google.com/go/firestore
// package.
func (a *App) Firestore(ctx context.Context) (*firestore.Client, error) {
	if a.projectID == "" {
		return nil, errors.New("project id is required to access Firestore")
	}
	return firestore.NewClient(ctx, a.projectID, a.opts...)
}

// InstanceID returns an instance of iid.Client.
func (a *App) InstanceID(ctx context.Context) (*iid.Client, error) {
	conf := &internal.InstanceIDConfig{
		ProjectID: a.projectID,
		Opts:      a.opts,
	}
	return iid.NewClient(ctx, conf)
}

// Messaging returns an instance of messaging.Client.
func (a *App) Messaging(ctx context.Context) (*messaging.Client, error) {
	conf := &internal.MessagingConfig{
		ProjectID: a.projectID,
		Opts:      a.opts,
		Version:   Version,
	}
	return messaging.NewClient(ctx, conf)
}

// NewApp creates a new App from the provided config and client options.
//
// If the client options contain a valid credential (a service account file, a refresh token
// file or an oauth2.TokenSource) the App will be authenticated using that credential. Otherwise,
// NewApp attempts to authenticate the App with Google application default credentials.
// If `config` is nil, the SDK will attempt to load the config options from the
// `FIREBASE_CONFIG` environment variable. If the value in it starts with a `{` it is parsed as a
// JSON object, otherwise it is assumed to be the name of the JSON file containing the options.
func NewApp(ctx context.Context, config *Config, opts ...option.ClientOption) (*App, error) {
	o := []option.ClientOption{option.WithScopes(internal.FirebaseScopes...)}
	o = append(o, opts...)
	creds, err := transport.Creds(ctx, o...)
	if err != nil {
		return nil, err
	}
	if config == nil {
		if config, err = getConfigDefaults(); err != nil {
			return nil, err
		}
	}

	var pid string
	if config.ProjectID != "" {
		pid = config.ProjectID
	} else if creds.ProjectID != "" {
		pid = creds.ProjectID
	} else {
		pid = os.Getenv("GOOGLE_CLOUD_PROJECT")
		if pid == "" {
			pid = os.Getenv("GCLOUD_PROJECT")
		}
	}

	ao := defaultAuthOverrides
	if config.AuthOverride != nil {
		ao = *config.AuthOverride
	}

	return &App{
		authOverride:     ao,
		creds:            creds,
		dbURL:            config.DatabaseURL,
		projectID:        pid,
		serviceAccountID: config.ServiceAccountID,
		storageBucket:    config.StorageBucket,
		opts:             o,
	}, nil
}

// getConfigDefaults reads the default config file, defined by the FIREBASE_CONFIG
// env variable, used only when options are nil.
func getConfigDefaults() (*Config, error) {
	fbc := &Config{}
	confFileName := os.Getenv(firebaseEnvName)
	if confFileName == "" {
		return fbc, nil
	}
	var dat []byte
	if confFileName[0] == byte('{') {
		dat = []byte(confFileName)
	} else {
		var err error
		if dat, err = ioutil.ReadFile(confFileName); err != nil {
			return nil, err
		}
	}
	if err := json.Unmarshal(dat, fbc); err != nil {
		return nil, err
	}

	// Some special handling necessary for db auth overrides
	var m map[string]interface{}
	if err := json.Unmarshal(dat, &m); err != nil {
		return nil, err
	}
	if ao, ok := m["databaseAuthVariableOverride"]; ok && ao == nil {
		// Auth overrides are explicitly set to null
		var nullMap map[string]interface{}
		fbc.AuthOverride = &nullMap
	}
	return fbc, nil
}
<|endoftext|>


<commit_before>package auth

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/docker/distribution/registry/client"
	"github.com/docker/distribution/registry/client/transport"
)

// ErrNoBasicAuthCredentials is returned if a request can't be authorized with
// basic auth due to lack of credentials.
var ErrNoBasicAuthCredentials = errors.New("no basic auth credentials")

const defaultClientID = "registry-client"

// AuthenticationHandler is an interface for authorizing a request from
// params from a "WWW-Authenicate" header for a single scheme.
type AuthenticationHandler interface {
	// Scheme returns the scheme as expected from the "WWW-Authenicate" header.
	Scheme() string

	// AuthorizeRequest adds the authorization header to a request (if needed)
	// using the parameters from "WWW-Authenticate" method. The parameters
	// values depend on the scheme.
	AuthorizeRequest(req *http.Request, params map[string]string) error
}

// CredentialStore is an interface for getting credentials for
// a given URL
type CredentialStore interface {
	// Basic returns basic auth for the given URL
	Basic(*url.URL) (string, string)

	// RefreshToken returns a refresh token for the
	// given URL and service
	RefreshToken(*url.URL, string) string

	// SetRefreshToken sets the refresh token if none
	// is provided for the given url and service
	SetRefreshToken(realm *url.URL, service, token string)
}

// NewAuthorizer creates an authorizer which can handle multiple authentication
// schemes. The handlers are tried in order, the higher priority authentication
// methods should be first. The challengeMap holds a list of challenges for
// a given root API endpoint (for example "https://registry-1.docker.io/v2/").
func NewAuthorizer(manager ChallengeManager, handlers ...AuthenticationHandler) transport.RequestModifier {
	return &endpointAuthorizer{
		challenges: manager,
		handlers:   handlers,
	}
}

type endpointAuthorizer struct {
	challenges ChallengeManager
	handlers   []AuthenticationHandler
	transport  http.RoundTripper
}

func (ea *endpointAuthorizer) ModifyRequest(req *http.Request) error {
	v2Root := strings.Index(req.URL.Path, "/v2/")
	if v2Root == -1 {
		return nil
	}

	ping := url.URL{
		Host:   req.URL.Host,
		Scheme: req.URL.Scheme,
		Path:   req.URL.Path[:v2Root+4],
	}

	challenges, err := ea.challenges.GetChallenges(ping)
	if err != nil {
		return err
	}

	if len(challenges) > 0 {
		for _, handler := range ea.handlers {
			for _, challenge := range challenges {
				if challenge.Scheme != handler.Scheme() {
					continue
				}
				if err := handler.AuthorizeRequest(req, challenge.Parameters); err != nil {
					return err
				}
			}
		}
	}

	return nil
}

// This is the minimum duration a token can last (in seconds).
// A token must not live less than 60 seconds because older versions
// of the Docker client didn't read their expiration from the token
// response and assumed 60 seconds.  So to remain compatible with
// those implementations, a token must live at least this long.
const minimumTokenLifetimeSeconds = 60

// Private interface for time used by this package to enable tests to provide their own implementation.
type clock interface {
	Now() time.Time
}

type tokenHandler struct {
	header    http.Header
	creds     CredentialStore
	transport http.RoundTripper
	clock     clock

	offlineAccess bool
	forceOAuth    bool
	clientID      string
	scopes        []Scope

	tokenLock       sync.Mutex
	tokenCache      string
	tokenExpiration time.Time
}

// Scope is a type which is serializable to a string
// using the allow scope grammar.
type Scope interface {
	String() string
}

// RepositoryScope represents a token scope for access
// to a repository.
type RepositoryScope struct {
	Repository string
	Actions    []string
}

// String returns the string representation of the repository
// using the scope grammar
func (rs RepositoryScope) String() string {
	return fmt.Sprintf("repository:%s:%s", rs.Repository, strings.Join(rs.Actions, ","))
}

// TokenHandlerOptions is used to configure a new token handler
type TokenHandlerOptions struct {
	Transport   http.RoundTripper
	Credentials CredentialStore

	OfflineAccess bool
	ForceOAuth    bool
	ClientID      string
	Scopes        []Scope
}

// An implementation of clock for providing real time data.
type realClock struct{}

// Now implements clock
func (realClock) Now() time.Time { return time.Now() }

// NewTokenHandler creates a new AuthenicationHandler which supports
// fetching tokens from a remote token server.
func NewTokenHandler(transport http.RoundTripper, creds CredentialStore, scope string, actions ...string) AuthenticationHandler {
	// Create options...
	return NewTokenHandlerWithOptions(TokenHandlerOptions{
		Transport:   transport,
		Credentials: creds,
		Scopes: []Scope{
			RepositoryScope{
				Repository: scope,
				Actions:    actions,
			},
		},
	})
}

// NewTokenHandlerWithOptions creates a new token handler using the provided
// options structure.
func NewTokenHandlerWithOptions(options TokenHandlerOptions) AuthenticationHandler {
	handler := &tokenHandler{
		transport:     options.Transport,
		creds:         options.Credentials,
		offlineAccess: options.OfflineAccess,
		forceOAuth:    options.ForceOAuth,
		clientID:      options.ClientID,
		scopes:        options.Scopes,
		clock:         realClock{},
	}

	return handler
}

func (th *tokenHandler) client() *http.Client {
	return &http.Client{
		Transport: th.transport,
		Timeout:   15 * time.Second,
	}
}

func (th *tokenHandler) Scheme() string {
	return "bearer"
}

func (th *tokenHandler) AuthorizeRequest(req *http.Request, params map[string]string) error {
	var additionalScopes []string
	if fromParam := req.URL.Query().Get("from"); fromParam != "" {
		additionalScopes = append(additionalScopes, RepositoryScope{
			Repository: fromParam,
			Actions:    []string{"pull"},
		}.String())
	}

	token, err := th.getToken(params, additionalScopes...)
	if err != nil {
		return err
	}

	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))

	return nil
}

func (th *tokenHandler) getToken(params map[string]string, additionalScopes ...string) (string, error) {
	th.tokenLock.Lock()
	defer th.tokenLock.Unlock()
	scopes := make([]string, 0, len(th.scopes)+len(additionalScopes))
	for _, scope := range th.scopes {
		scopes = append(scopes, scope.String())
	}
	var addedScopes bool
	for _, scope := range additionalScopes {
		scopes = append(scopes, scope)
		addedScopes = true
	}

	now := th.clock.Now()
	if now.After(th.tokenExpiration) || addedScopes {
		token, expiration, err := th.fetchToken(params, scopes)
		if err != nil {
			return "", err
		}

		// do not update cache for added scope tokens
		if !addedScopes {
			th.tokenCache = token
			th.tokenExpiration = expiration
		}

		return token, nil
	}

	return th.tokenCache, nil
}

type postTokenResponse struct {
	AccessToken  string    `json:"access_token"`
	RefreshToken string    `json:"refresh_token"`
	ExpiresIn    int       `json:"expires_in"`
	IssuedAt     time.Time `json:"issued_at"`
	Scope        string    `json:"scope"`
}

func (th *tokenHandler) fetchTokenWithOAuth(realm *url.URL, refreshToken, service string, scopes []string) (token string, expiration time.Time, err error) {
	form := url.Values{}
	form.Set("scope", strings.Join(scopes, " "))
	form.Set("service", service)

	clientID := th.clientID
	if clientID == "" {
		// Use default client, this is a required field
		clientID = defaultClientID
	}
	form.Set("client_id", clientID)

	if refreshToken != "" {
		form.Set("grant_type", "refresh_token")
		form.Set("refresh_token", refreshToken)
	} else if th.creds != nil {
		form.Set("grant_type", "password")
		username, password := th.creds.Basic(realm)
		form.Set("username", username)
		form.Set("password", password)

		// attempt to get a refresh token
		form.Set("access_type", "offline")
	} else {
		// refuse to do oauth without a grant type
		return "", time.Time{}, fmt.Errorf("no supported grant type")
	}

	resp, err := th.client().PostForm(realm.String(), form)
	if err != nil {
		return "", time.Time{}, err
	}
	defer resp.Body.Close()

	if !client.SuccessStatus(resp.StatusCode) {
		err := client.HandleErrorResponse(resp)
		return "", time.Time{}, err
	}

	decoder := json.NewDecoder(resp.Body)

	var tr postTokenResponse
	if err = decoder.Decode(&tr); err != nil {
		return "", time.Time{}, fmt.Errorf("unable to decode token response: %s", err)
	}

	if tr.RefreshToken != "" && tr.RefreshToken != refreshToken {
		th.creds.SetRefreshToken(realm, service, tr.RefreshToken)
	}

	if tr.ExpiresIn < minimumTokenLifetimeSeconds {
		// The default/minimum lifetime.
		tr.ExpiresIn = minimumTokenLifetimeSeconds
		logrus.Debugf("Increasing token expiration to: %d seconds", tr.ExpiresIn)
	}

	if tr.IssuedAt.IsZero() {
		// issued_at is optional in the token response.
		tr.IssuedAt = th.clock.Now().UTC()
	}

	return tr.AccessToken, tr.IssuedAt.Add(time.Duration(tr.ExpiresIn) * time.Second), nil
}

type getTokenResponse struct {
	Token        string    `json:"token"`
	AccessToken  string    `json:"access_token"`
	ExpiresIn    int       `json:"expires_in"`
	IssuedAt     time.Time `json:"issued_at"`
	RefreshToken string    `json:"refresh_token"`
}

func (th *tokenHandler) fetchTokenWithBasicAuth(realm *url.URL, service string, scopes []string) (token string, expiration time.Time, err error) {

	req, err := http.NewRequest("GET", realm.String(), nil)
	if err != nil {
		return "", time.Time{}, err
	}

	reqParams := req.URL.Query()

	if service != "" {
		reqParams.Add("service", service)
	}

	for _, scope := range scopes {
		reqParams.Add("scope", scope)
	}

	if th.offlineAccess {
		reqParams.Add("offline_token", "true")
		clientID := th.clientID
		if clientID == "" {
			clientID = defaultClientID
		}
		reqParams.Add("client_id", clientID)
	}

	if th.creds != nil {
		username, password := th.creds.Basic(realm)
		if username != "" && password != "" {
			reqParams.Add("account", username)
			req.SetBasicAuth(username, password)
		}
	}

	req.URL.RawQuery = reqParams.Encode()

	resp, err := th.client().Do(req)
	if err != nil {
		return "", time.Time{}, err
	}
	defer resp.Body.Close()

	if !client.SuccessStatus(resp.StatusCode) {
		err := client.HandleErrorResponse(resp)
		return "", time.Time{}, err
	}

	decoder := json.NewDecoder(resp.Body)

	var tr getTokenResponse
	if err = decoder.Decode(&tr); err != nil {
		return "", time.Time{}, fmt.Errorf("unable to decode token response: %s", err)
	}

	if tr.RefreshToken != "" && th.creds != nil {
		th.creds.SetRefreshToken(realm, service, tr.RefreshToken)
	}

	// `access_token` is equivalent to `token` and if both are specified
	// the choice is undefined.  Canonicalize `access_token` by sticking
	// things in `token`.
	if tr.AccessToken != "" {
		tr.Token = tr.AccessToken
	}

	if tr.Token == "" {
		return "", time.Time{}, errors.New("authorization server did not include a token in the response")
	}

	if tr.ExpiresIn < minimumTokenLifetimeSeconds {
		// The default/minimum lifetime.
		tr.ExpiresIn = minimumTokenLifetimeSeconds
		logrus.Debugf("Increasing token expiration to: %d seconds", tr.ExpiresIn)
	}

	if tr.IssuedAt.IsZero() {
		// issued_at is optional in the token response.
		tr.IssuedAt = th.clock.Now().UTC()
	}

	return tr.Token, tr.IssuedAt.Add(time.Duration(tr.ExpiresIn) * time.Second), nil
}

func (th *tokenHandler) fetchToken(params map[string]string, scopes []string) (token string, expiration time.Time, err error) {
	realm, ok := params["realm"]
	if !ok {
		return "", time.Time{}, errors.New("no realm specified for token auth challenge")
	}

	// TODO(dmcgowan): Handle empty scheme and relative realm
	realmURL, err := url.Parse(realm)
	if err != nil {
		return "", time.Time{}, fmt.Errorf("invalid token auth challenge realm: %s", err)
	}

	service := params["service"]

	var refreshToken string

	if th.creds != nil {
		refreshToken = th.creds.RefreshToken(realmURL, service)
	}

	if refreshToken != "" || th.forceOAuth {
		return th.fetchTokenWithOAuth(realmURL, refreshToken, service, scopes)
	}

	return th.fetchTokenWithBasicAuth(realmURL, service, scopes)
}

type basicHandler struct {
	creds CredentialStore
}

// NewBasicHandler creaters a new authentiation handler which adds
// basic authentication credentials to a request.
func NewBasicHandler(creds CredentialStore) AuthenticationHandler {
	return &basicHandler{
		creds: creds,
	}
}

func (*basicHandler) Scheme() string {
	return "basic"
}

func (bh *basicHandler) AuthorizeRequest(req *http.Request, params map[string]string) error {
	if bh.creds != nil {
		username, password := bh.creds.Basic(req.URL)
		if username != "" && password != "" {
			req.SetBasicAuth(username, password)
			return nil
		}
	}
	return ErrNoBasicAuthCredentials
}
<commit_msg>registry: client: auth: type errors<commit_after>package auth

import (
	"encoding/json"
	"errors"
	"fmt"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"

	"github.com/Sirupsen/logrus"
	"github.com/docker/distribution/registry/client"
	"github.com/docker/distribution/registry/client/transport"
)

var (
	// ErrNoBasicAuthCredentials is returned if a request can't be authorized with
	// basic auth due to lack of credentials.
	ErrNoBasicAuthCredentials = errors.New("no basic auth credentials")

	// ErrNoToken is returned if a request is successful but the body does not
	// contain an authorization token.
	ErrNoToken = errors.New("authorization server did not include a token in the response")
)

const defaultClientID = "registry-client"

// AuthenticationHandler is an interface for authorizing a request from
// params from a "WWW-Authenicate" header for a single scheme.
type AuthenticationHandler interface {
	// Scheme returns the scheme as expected from the "WWW-Authenicate" header.
	Scheme() string

	// AuthorizeRequest adds the authorization header to a request (if needed)
	// using the parameters from "WWW-Authenticate" method. The parameters
	// values depend on the scheme.
	AuthorizeRequest(req *http.Request, params map[string]string) error
}

// CredentialStore is an interface for getting credentials for
// a given URL
type CredentialStore interface {
	// Basic returns basic auth for the given URL
	Basic(*url.URL) (string, string)

	// RefreshToken returns a refresh token for the
	// given URL and service
	RefreshToken(*url.URL, string) string

	// SetRefreshToken sets the refresh token if none
	// is provided for the given url and service
	SetRefreshToken(realm *url.URL, service, token string)
}

// NewAuthorizer creates an authorizer which can handle multiple authentication
// schemes. The handlers are tried in order, the higher priority authentication
// methods should be first. The challengeMap holds a list of challenges for
// a given root API endpoint (for example "https://registry-1.docker.io/v2/").
func NewAuthorizer(manager ChallengeManager, handlers ...AuthenticationHandler) transport.RequestModifier {
	return &endpointAuthorizer{
		challenges: manager,
		handlers:   handlers,
	}
}

type endpointAuthorizer struct {
	challenges ChallengeManager
	handlers   []AuthenticationHandler
	transport  http.RoundTripper
}

func (ea *endpointAuthorizer) ModifyRequest(req *http.Request) error {
	v2Root := strings.Index(req.URL.Path, "/v2/")
	if v2Root == -1 {
		return nil
	}

	ping := url.URL{
		Host:   req.URL.Host,
		Scheme: req.URL.Scheme,
		Path:   req.URL.Path[:v2Root+4],
	}

	challenges, err := ea.challenges.GetChallenges(ping)
	if err != nil {
		return err
	}

	if len(challenges) > 0 {
		for _, handler := range ea.handlers {
			for _, challenge := range challenges {
				if challenge.Scheme != handler.Scheme() {
					continue
				}
				if err := handler.AuthorizeRequest(req, challenge.Parameters); err != nil {
					return err
				}
			}
		}
	}

	return nil
}

// This is the minimum duration a token can last (in seconds).
// A token must not live less than 60 seconds because older versions
// of the Docker client didn't read their expiration from the token
// response and assumed 60 seconds.  So to remain compatible with
// those implementations, a token must live at least this long.
const minimumTokenLifetimeSeconds = 60

// Private interface for time used by this package to enable tests to provide their own implementation.
type clock interface {
	Now() time.Time
}

type tokenHandler struct {
	header    http.Header
	creds     CredentialStore
	transport http.RoundTripper
	clock     clock

	offlineAccess bool
	forceOAuth    bool
	clientID      string
	scopes        []Scope

	tokenLock       sync.Mutex
	tokenCache      string
	tokenExpiration time.Time
}

// Scope is a type which is serializable to a string
// using the allow scope grammar.
type Scope interface {
	String() string
}

// RepositoryScope represents a token scope for access
// to a repository.
type RepositoryScope struct {
	Repository string
	Actions    []string
}

// String returns the string representation of the repository
// using the scope grammar
func (rs RepositoryScope) String() string {
	return fmt.Sprintf("repository:%s:%s", rs.Repository, strings.Join(rs.Actions, ","))
}

// TokenHandlerOptions is used to configure a new token handler
type TokenHandlerOptions struct {
	Transport   http.RoundTripper
	Credentials CredentialStore

	OfflineAccess bool
	ForceOAuth    bool
	ClientID      string
	Scopes        []Scope
}

// An implementation of clock for providing real time data.
type realClock struct{}

// Now implements clock
func (realClock) Now() time.Time { return time.Now() }

// NewTokenHandler creates a new AuthenicationHandler which supports
// fetching tokens from a remote token server.
func NewTokenHandler(transport http.RoundTripper, creds CredentialStore, scope string, actions ...string) AuthenticationHandler {
	// Create options...
	return NewTokenHandlerWithOptions(TokenHandlerOptions{
		Transport:   transport,
		Credentials: creds,
		Scopes: []Scope{
			RepositoryScope{
				Repository: scope,
				Actions:    actions,
			},
		},
	})
}

// NewTokenHandlerWithOptions creates a new token handler using the provided
// options structure.
func NewTokenHandlerWithOptions(options TokenHandlerOptions) AuthenticationHandler {
	handler := &tokenHandler{
		transport:     options.Transport,
		creds:         options.Credentials,
		offlineAccess: options.OfflineAccess,
		forceOAuth:    options.ForceOAuth,
		clientID:      options.ClientID,
		scopes:        options.Scopes,
		clock:         realClock{},
	}

	return handler
}

func (th *tokenHandler) client() *http.Client {
	return &http.Client{
		Transport: th.transport,
		Timeout:   15 * time.Second,
	}
}

func (th *tokenHandler) Scheme() string {
	return "bearer"
}

func (th *tokenHandler) AuthorizeRequest(req *http.Request, params map[string]string) error {
	var additionalScopes []string
	if fromParam := req.URL.Query().Get("from"); fromParam != "" {
		additionalScopes = append(additionalScopes, RepositoryScope{
			Repository: fromParam,
			Actions:    []string{"pull"},
		}.String())
	}

	token, err := th.getToken(params, additionalScopes...)
	if err != nil {
		return err
	}

	req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", token))

	return nil
}

func (th *tokenHandler) getToken(params map[string]string, additionalScopes ...string) (string, error) {
	th.tokenLock.Lock()
	defer th.tokenLock.Unlock()
	scopes := make([]string, 0, len(th.scopes)+len(additionalScopes))
	for _, scope := range th.scopes {
		scopes = append(scopes, scope.String())
	}
	var addedScopes bool
	for _, scope := range additionalScopes {
		scopes = append(scopes, scope)
		addedScopes = true
	}

	now := th.clock.Now()
	if now.After(th.tokenExpiration) || addedScopes {
		token, expiration, err := th.fetchToken(params, scopes)
		if err != nil {
			return "", err
		}

		// do not update cache for added scope tokens
		if !addedScopes {
			th.tokenCache = token
			th.tokenExpiration = expiration
		}

		return token, nil
	}

	return th.tokenCache, nil
}

type postTokenResponse struct {
	AccessToken  string    `json:"access_token"`
	RefreshToken string    `json:"refresh_token"`
	ExpiresIn    int       `json:"expires_in"`
	IssuedAt     time.Time `json:"issued_at"`
	Scope        string    `json:"scope"`
}

func (th *tokenHandler) fetchTokenWithOAuth(realm *url.URL, refreshToken, service string, scopes []string) (token string, expiration time.Time, err error) {
	form := url.Values{}
	form.Set("scope", strings.Join(scopes, " "))
	form.Set("service", service)

	clientID := th.clientID
	if clientID == "" {
		// Use default client, this is a required field
		clientID = defaultClientID
	}
	form.Set("client_id", clientID)

	if refreshToken != "" {
		form.Set("grant_type", "refresh_token")
		form.Set("refresh_token", refreshToken)
	} else if th.creds != nil {
		form.Set("grant_type", "password")
		username, password := th.creds.Basic(realm)
		form.Set("username", username)
		form.Set("password", password)

		// attempt to get a refresh token
		form.Set("access_type", "offline")
	} else {
		// refuse to do oauth without a grant type
		return "", time.Time{}, fmt.Errorf("no supported grant type")
	}

	resp, err := th.client().PostForm(realm.String(), form)
	if err != nil {
		return "", time.Time{}, err
	}
	defer resp.Body.Close()

	if !client.SuccessStatus(resp.StatusCode) {
		err := client.HandleErrorResponse(resp)
		return "", time.Time{}, err
	}

	decoder := json.NewDecoder(resp.Body)

	var tr postTokenResponse
	if err = decoder.Decode(&tr); err != nil {
		return "", time.Time{}, fmt.Errorf("unable to decode token response: %s", err)
	}

	if tr.RefreshToken != "" && tr.RefreshToken != refreshToken {
		th.creds.SetRefreshToken(realm, service, tr.RefreshToken)
	}

	if tr.ExpiresIn < minimumTokenLifetimeSeconds {
		// The default/minimum lifetime.
		tr.ExpiresIn = minimumTokenLifetimeSeconds
		logrus.Debugf("Increasing token expiration to: %d seconds", tr.ExpiresIn)
	}

	if tr.IssuedAt.IsZero() {
		// issued_at is optional in the token response.
		tr.IssuedAt = th.clock.Now().UTC()
	}

	return tr.AccessToken, tr.IssuedAt.Add(time.Duration(tr.ExpiresIn) * time.Second), nil
}

type getTokenResponse struct {
	Token        string    `json:"token"`
	AccessToken  string    `json:"access_token"`
	ExpiresIn    int       `json:"expires_in"`
	IssuedAt     time.Time `json:"issued_at"`
	RefreshToken string    `json:"refresh_token"`
}

func (th *tokenHandler) fetchTokenWithBasicAuth(realm *url.URL, service string, scopes []string) (token string, expiration time.Time, err error) {

	req, err := http.NewRequest("GET", realm.String(), nil)
	if err != nil {
		return "", time.Time{}, err
	}

	reqParams := req.URL.Query()

	if service != "" {
		reqParams.Add("service", service)
	}

	for _, scope := range scopes {
		reqParams.Add("scope", scope)
	}

	if th.offlineAccess {
		reqParams.Add("offline_token", "true")
		clientID := th.clientID
		if clientID == "" {
			clientID = defaultClientID
		}
		reqParams.Add("client_id", clientID)
	}

	if th.creds != nil {
		username, password := th.creds.Basic(realm)
		if username != "" && password != "" {
			reqParams.Add("account", username)
			req.SetBasicAuth(username, password)
		}
	}

	req.URL.RawQuery = reqParams.Encode()

	resp, err := th.client().Do(req)
	if err != nil {
		return "", time.Time{}, err
	}
	defer resp.Body.Close()

	if !client.SuccessStatus(resp.StatusCode) {
		err := client.HandleErrorResponse(resp)
		return "", time.Time{}, err
	}

	decoder := json.NewDecoder(resp.Body)

	var tr getTokenResponse
	if err = decoder.Decode(&tr); err != nil {
		return "", time.Time{}, fmt.Errorf("unable to decode token response: %s", err)
	}

	if tr.RefreshToken != "" && th.creds != nil {
		th.creds.SetRefreshToken(realm, service, tr.RefreshToken)
	}

	// `access_token` is equivalent to `token` and if both are specified
	// the choice is undefined.  Canonicalize `access_token` by sticking
	// things in `token`.
	if tr.AccessToken != "" {
		tr.Token = tr.AccessToken
	}

	if tr.Token == "" {
		return "", time.Time{}, ErrNoToken
	}

	if tr.ExpiresIn < minimumTokenLifetimeSeconds {
		// The default/minimum lifetime.
		tr.ExpiresIn = minimumTokenLifetimeSeconds
		logrus.Debugf("Increasing token expiration to: %d seconds", tr.ExpiresIn)
	}

	if tr.IssuedAt.IsZero() {
		// issued_at is optional in the token response.
		tr.IssuedAt = th.clock.Now().UTC()
	}

	return tr.Token, tr.IssuedAt.Add(time.Duration(tr.ExpiresIn) * time.Second), nil
}

func (th *tokenHandler) fetchToken(params map[string]string, scopes []string) (token string, expiration time.Time, err error) {
	realm, ok := params["realm"]
	if !ok {
		return "", time.Time{}, errors.New("no realm specified for token auth challenge")
	}

	// TODO(dmcgowan): Handle empty scheme and relative realm
	realmURL, err := url.Parse(realm)
	if err != nil {
		return "", time.Time{}, fmt.Errorf("invalid token auth challenge realm: %s", err)
	}

	service := params["service"]

	var refreshToken string

	if th.creds != nil {
		refreshToken = th.creds.RefreshToken(realmURL, service)
	}

	if refreshToken != "" || th.forceOAuth {
		return th.fetchTokenWithOAuth(realmURL, refreshToken, service, scopes)
	}

	return th.fetchTokenWithBasicAuth(realmURL, service, scopes)
}

type basicHandler struct {
	creds CredentialStore
}

// NewBasicHandler creaters a new authentiation handler which adds
// basic authentication credentials to a request.
func NewBasicHandler(creds CredentialStore) AuthenticationHandler {
	return &basicHandler{
		creds: creds,
	}
}

func (*basicHandler) Scheme() string {
	return "basic"
}

func (bh *basicHandler) AuthorizeRequest(req *http.Request, params map[string]string) error {
	if bh.creds != nil {
		username, password := bh.creds.Basic(req.URL)
		if username != "" && password != "" {
			req.SetBasicAuth(username, password)
			return nil
		}
	}
	return ErrNoBasicAuthCredentials
}
<|endoftext|>


<commit_before>package libzipfs

import (
	"fmt"
	"io/ioutil"
	"testing"

	cv "github.com/glycerine/goconvey/convey"
)

func Test001WeCanMountInTheTmpDir(t *testing.T) {

	cv.Convey("we should be able to mount a zipfile image in the tmp dir", t, func() {
		dir := "/tmp" // "" => use system tmp dir
		mountPoint, err := ioutil.TempDir(dir, "libzipfs")
		VPrintf("\n\n mountPoint = '%s'\n", mountPoint)
		cv.So(err, cv.ShouldEqual, nil)

		zipFile := "testfiles/hi.zip"
		z := NewFuseZipFs(zipFile, mountPoint)

		err = z.Start()
		if err != nil {
			panic(fmt.Sprintf("error during starting FuseZipFs "+
				"for file '%s' at mount point %s: '%s'", zipFile, mountPoint, err))
		}

		VPrintf("\n\n z.Start() succeeded, with mountPoint = '%s'\n", mountPoint)

		err = z.Stop()
		if err != nil {
			panic(fmt.Sprintf("error: could not z.Stop() FuseZipFs for file '%s' at %s: '%s'", zipFile, mountPoint, err))
		}

		VPrintf("\n\n z.Stop() succeeded, with mountPoint = '%s'\n", mountPoint)
	})

}
<commit_msg>atg. confirm zipfile content also readback correctly.<commit_after>package libzipfs

import (
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"testing"

	cv "github.com/glycerine/goconvey/convey"
)

func Test001WeCanMountInTheTmpDir(t *testing.T) {

	cv.Convey("we should be able to mount a zipfile image in the tmp dir", t, func() {
		dir := "/tmp" // => /tmp easier to debug/shorter to type.
		// dir := "" // => use system tmp dir
		mountPoint, err := ioutil.TempDir(dir, "libzipfs")
		VPrintf("\n\n mountPoint = '%s'\n", mountPoint)
		cv.So(err, cv.ShouldEqual, nil)

		zipFile := "testfiles/hi.zip"
		z := NewFuseZipFs(zipFile, mountPoint)

		err = z.Start()
		if err != nil {
			panic(fmt.Sprintf("error during starting FuseZipFs "+
				"for file '%s' at mount point %s: '%s'", zipFile, mountPoint, err))
		}

		VPrintf("\n\n z.Start() succeeded, with mountPoint = '%s'\n", mountPoint)
		expectedFile := path.Join(mountPoint, "dirA", "dirB", "hello")
		expectedFileContent := []byte("salutations\n")

		fmt.Printf("\n   we should be able to read back a file from the mounted filesystem without errors.\n")
		ef, err := os.Open(expectedFile)
		cv.So(err, cv.ShouldBeNil)
		cv.So(ef, cv.ShouldNotBeNil)
		ef.Close()
		ef = nil
		by, err := ioutil.ReadFile(expectedFile)
		cv.So(err, cv.ShouldBeNil)
		cv.So(len(expectedFileContent), cv.ShouldEqual, len(by))
		diff, err := compareByteSlices(expectedFileContent, by, min(len(expectedFileContent), len(by)))
		cv.So(err, cv.ShouldBeNil)
		cv.So(diff, cv.ShouldEqual, -1)

		err = z.Stop()
		if err != nil {
			panic(fmt.Sprintf("error: could not z.Stop() FuseZipFs for file '%s' at %s: '%s'", zipFile, mountPoint, err))
		}

		VPrintf("\n\n z.Stop() succeeded, with mountPoint = '%s'\n", mountPoint)
	})

}

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}
func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}
<|endoftext|>


<commit_before>package main

import (
	"database/sql"
	"encoding/hex"
	"errors"
	"time"

	"github.com/go-kit/kit/metrics"
	"github.com/piotrkowalczuk/mnemosyne"
	"github.com/piotrkowalczuk/protot"
)

const (
	postgresSchema = `
		CREATE SCHEMA IF NOT EXISTS mnemosyne;
		CREATE TABLE IF NOT EXISTS mnemosyne.session (
			access_token BYTEA PRIMARY KEY,
			subject_id TEXT NOT NULL,
			bag bytea NOT NULL,
			expire_at TIMESTAMPTZ NOT NULL
		)
    `
)

var (
	tmpKey = []byte(hex.EncodeToString([]byte("1")))
)

type postgresStorage struct {
	db        *sql.DB
	tableName string
	generator mnemosyne.RandomBytesGenerator
	monitor   *monitoring
}

func newPostgresStorage(tn string, db *sql.DB, m *monitoring) Storage {
	return &postgresStorage{
		db:        db,
		tableName: tn,
		generator: &mnemosyne.SystemRandomBytesGenerator{},
		monitor:   m,
	}
}

func initPostgresStorage(tn string, db *sql.DB, m *monitoring) func() (Storage, error) {
	return func() (Storage, error) {
		return newPostgresStorage(tn, db, m), nil
	}
}

// Create implements Storage interface.
func (ps *postgresStorage) Start(subjectID string, bag map[string]string) (*mnemosyne.Session, error) {
	accessToken, err := mnemosyne.RandomAccessToken(ps.generator, tmpKey)
	if err != nil {
		return nil, err
	}

	entity := &sessionEntity{
		AccessToken: accessToken,
		SubjectID:   subjectID,
		Bag:         bagpack(bag),
	}

	if err := ps.save(entity); err != nil {
		return nil, err
	}

	return newSessionFromSessionEntity(entity), nil
}

func (ps *postgresStorage) save(entity *sessionEntity) (err error) {
	query := `
		INSERT INTO mnemosyne.session (access_token, subject_id, bag, expire_at)
		VALUES ($1, $2, $3, NOW() + '30 minutes'::interval)
		RETURNING expire_at

	`
	field := metrics.Field{Key: "query", Value: query}

	err = ps.db.QueryRow(
		query,
		entity.AccessToken,
		entity.SubjectID,
		entity.Bag,
	).Scan(
		&entity.ExpireAt,
	)
	ps.monitor.postgres.queries.With(field).Add(1)

	return
}

// Get implements Storage interface.
func (ps *postgresStorage) Get(accessToken *mnemosyne.AccessToken) (*mnemosyne.Session, error) {
	var entity sessionEntity
	query := `
		SELECT subject_id, bag, expire_at
		FROM mnemosyne.session
		WHERE access_token = $1
		LIMIT 1
	`
	field := metrics.Field{Key: "query", Value: query}

	err := ps.db.QueryRow(query, accessToken).Scan(
		&entity.SubjectID,
		&entity.Bag,
		&entity.ExpireAt,
	)
	if err != nil {
		ps.monitor.postgres.errors.With(field).Add(1)
		if err == sql.ErrNoRows {
			return nil, errSessionNotFound
		}
		return nil, err
	}

	return &mnemosyne.Session{
		AccessToken: accessToken,
		SubjectId:   entity.SubjectID,
		Bag:         entity.Bag,
		ExpireAt:    protot.TimeToTimestamp(entity.ExpireAt),
	}, nil
}

// List implements Storage interface.
func (ps *postgresStorage) List(offset, limit int64, expiredAtFrom, expiredAtTo *time.Time) ([]*mnemosyne.Session, error) {
	if limit == 0 {
		return nil, errors.New("mnemosyned: cannot retrieve list of sessions, limit needs to be higher than 0")
	}

	args := []interface{}{offset, limit}
	query := "SELECT access_token, subject_id, bag, expire_at FROM mnemosyne.session"

	switch {
	case expiredAtFrom != nil && expiredAtTo == nil:
		query += "expire_at > $3"
		args = append(args, expiredAtFrom)
	case expiredAtFrom == nil && expiredAtTo != nil:
		query += "expire_at < $3"
		args = append(args, expiredAtTo)
	case expiredAtFrom != nil && expiredAtTo != nil:
		query += "expire_at > $4 AND expire_at < $5"
		args = append(args, expiredAtFrom, expiredAtTo)
	}

	query += " OFFSET $1 LIMIT $2"

	field := metrics.Field{Key: "query", Value: query}

	rows, err := ps.db.Query(query, args...)
	if err != nil {
		ps.monitor.postgres.errors.With(field).Add(1)
		return nil, err
	}
	defer rows.Close()

	ps.monitor.postgres.queries.With(field).Add(1)

	sessions := make([]*mnemosyne.Session, 0, limit)
	for rows.Next() {
		var entity sessionEntity

		err = rows.Scan(
			&entity.AccessToken,
			&entity.SubjectID,
			&entity.Bag,
			&entity.ExpireAt,
		)
		if err != nil {
			ps.monitor.postgres.errors.With(field).Add(1)
			return nil, err
		}

		sessions = append(sessions, &mnemosyne.Session{
			AccessToken: &entity.AccessToken,
			SubjectId:   entity.SubjectID,
			Bag:         entity.Bag,
			ExpireAt:    protot.TimeToTimestamp(entity.ExpireAt),
		})
	}
	if rows.Err() != nil {
		ps.monitor.postgres.errors.With(field).Add(1)
		return nil, rows.Err()
	}

	return sessions, nil
}

// Exists implements Storage interface.
func (ps *postgresStorage) Exists(accessToken *mnemosyne.AccessToken) (exists bool, err error) {
	query := `SELECT EXISTS(SELECT 1 FROM mnemosyne.session WHERE access_token = $1)`
	field := metrics.Field{Key: "query", Value: query}

	err = ps.db.QueryRow(query, *accessToken).Scan(
		&exists,
	)
	if err != nil {
		ps.monitor.postgres.errors.With(field).Add(1)
	}
	ps.monitor.postgres.queries.With(field).Add(1)

	return
}

// Abandon ...
func (ps *postgresStorage) Abandon(accessToken *mnemosyne.AccessToken) (bool, error) {
	query := `DELETE FROM mnemosyne.session WHERE access_token = $1`
	field := metrics.Field{Key: "query", Value: query}

	result, err := ps.db.Exec(query, *accessToken)
	if err != nil {
		ps.monitor.postgres.errors.With(field).Add(1)
		return false, err
	}

	ps.monitor.postgres.queries.With(field).Add(1)

	affected, err := result.RowsAffected()
	if err != nil {
		return false, err
	}

	if affected == 0 {
		return false, errSessionNotFound
	}

	return true, nil
}

// SetData implements Storage interface.
func (ps *postgresStorage) SetValue(accessToken *mnemosyne.AccessToken, key, value string) (map[string]string, error) {
	var err error

	entity := &sessionEntity{
		AccessToken: *accessToken,
	}
	selectQuery := `
		SELECT subject_id, bag, expire_at
		FROM mnemosyne.session
		WHERE access_token = $1
		FOR UPDATE
	`
	updateQuery := `
		UPDATE mnemosyne.session
		SET
			bag = $2
		WHERE access_token = $1
	`

	tx, err := ps.db.Begin()
	if err != nil {
		tx.Rollback()
		return nil, err
	}

	err = tx.QueryRow(selectQuery, *accessToken).Scan(
		&entity.SubjectID,
		&entity.Bag,
		&entity.ExpireAt,
	)
	if err != nil {
		ps.monitor.postgres.errors.With(metrics.Field{Key: "query", Value: selectQuery}).Add(1)
		tx.Rollback()
		if err == sql.ErrNoRows {
			return nil, errSessionNotFound
		}
		return nil, err
	}
	ps.monitor.postgres.queries.With(metrics.Field{Key: "query", Value: selectQuery}).Add(1)

	entity.Bag.Set(key, value)

	_, err = tx.Exec(updateQuery, *accessToken, entity.Bag)
	if err != nil {
		ps.monitor.postgres.errors.With(metrics.Field{Key: "query", Value: updateQuery}).Add(1)
		tx.Rollback()
		return nil, err
	}
	ps.monitor.postgres.queries.With(metrics.Field{Key: "query", Value: updateQuery}).Add(1)

	tx.Commit()

	return entity.Bag, nil
}

// Delete implements Storage interface.
func (ps *postgresStorage) Delete(accessToken *mnemosyne.AccessToken, expiredAtFrom, expiredAtTo *time.Time) (int64, error) {
	if accessToken == nil && expiredAtFrom == nil && expiredAtTo == nil {
		return 0, errors.New("mnemosyned: session cannot be deleted, no where parameter provided")
	}

	where, args := ps.where(accessToken, expiredAtFrom, expiredAtTo)
	query := "DELETE FROM mnemosyne.session WHERE " + where
	field := metrics.Field{Key: "query", Value: query}

	result, err := ps.db.Exec(query, args...)
	if err != nil {
		ps.monitor.postgres.errors.With(field).Add(1)
		return 0, err
	}
	ps.monitor.postgres.queries.With(field).Add(1)

	return result.RowsAffected()
}

// Setup implements Storage interface.
func (ps *postgresStorage) Setup() error {
	_, err := ps.db.Exec(postgresSchema)

	return err
}

// TearDown implements Storage interface.
func (ps *postgresStorage) TearDown() error {
	_, err := ps.db.Exec(`DROP SCHEMA mnemosyne`)

	return err
}

func (ps *postgresStorage) where(accessToken *mnemosyne.AccessToken, expiredAtFrom, expiredAtTo *time.Time) (string, []interface{}) {
	switch {
	case accessToken != nil && expiredAtFrom == nil && expiredAtTo == nil:
		return "access_token = $1", []interface{}{accessToken}
	case accessToken == nil && expiredAtFrom != nil && expiredAtTo == nil:
		return "expire_at > $1", []interface{}{expiredAtFrom}
	case accessToken == nil && expiredAtFrom == nil && expiredAtTo != nil:
		return "expire_at < $1", []interface{}{expiredAtTo}
	case accessToken != nil && expiredAtFrom != nil && expiredAtTo == nil:
		return "access_token = $1 AND expire_at > $2", []interface{}{accessToken, expiredAtFrom}
	case accessToken != nil && expiredAtFrom == nil && expiredAtTo != nil:
		return "access_token = $1 AND expire_at < $2", []interface{}{accessToken, expiredAtTo}
	case accessToken == nil && expiredAtFrom != nil && expiredAtTo != nil:
		return "expire_at > $1 AND expire_at < $2", []interface{}{expiredAtFrom, expiredAtTo}
	case accessToken != nil && expiredAtFrom != nil && expiredAtTo != nil:
		return "access_token = $1 AND expire_at > $2 AND expire_at < $3", []interface{}{accessToken, expiredAtFrom, expiredAtTo}
	default:
		return "", nil
	}
}

type sessionEntity struct {
	AccessToken mnemosyne.AccessToken `json:"accessToken"`
	SubjectID   string                `json:"subjectId"`
	Bag         bagpack               `json:"bag"`
	ExpireAt    time.Time             `json:"expireAt"`
}

func newSessionFromSessionEntity(entity *sessionEntity) *mnemosyne.Session {
	return &mnemosyne.Session{
		AccessToken: &entity.AccessToken,
		SubjectId:   entity.SubjectID,
		Bag:         entity.Bag,
		ExpireAt:    protot.TimeToTimestamp(entity.ExpireAt),
	}
}
<commit_msg>postgresSchemaSQL fix<commit_after>package main

import (
	"database/sql"
	"encoding/hex"
	"errors"
	"time"

	"github.com/go-kit/kit/metrics"
	"github.com/piotrkowalczuk/mnemosyne"
	"github.com/piotrkowalczuk/protot"
)

const (
	postgresSchema = `
		CREATE SCHEMA IF NOT EXISTS mnemosyne;
		CREATE TABLE IF NOT EXISTS mnemosyne.session (
			access_token BYTEA PRIMARY KEY,
			subject_id TEXT NOT NULL,
			bag bytea NOT NULL,
			expire_at TIMESTAMPTZ NOT NULL
		);
    `
)

var (
	tmpKey = []byte(hex.EncodeToString([]byte("1")))
)

type postgresStorage struct {
	db        *sql.DB
	tableName string
	generator mnemosyne.RandomBytesGenerator
	monitor   *monitoring
}

func newPostgresStorage(tn string, db *sql.DB, m *monitoring) Storage {
	return &postgresStorage{
		db:        db,
		tableName: tn,
		generator: &mnemosyne.SystemRandomBytesGenerator{},
		monitor:   m,
	}
}

func initPostgresStorage(tn string, db *sql.DB, m *monitoring) func() (Storage, error) {
	return func() (Storage, error) {
		return newPostgresStorage(tn, db, m), nil
	}
}

// Create implements Storage interface.
func (ps *postgresStorage) Start(subjectID string, bag map[string]string) (*mnemosyne.Session, error) {
	accessToken, err := mnemosyne.RandomAccessToken(ps.generator, tmpKey)
	if err != nil {
		return nil, err
	}

	entity := &sessionEntity{
		AccessToken: accessToken,
		SubjectID:   subjectID,
		Bag:         bagpack(bag),
	}

	if err := ps.save(entity); err != nil {
		return nil, err
	}

	return newSessionFromSessionEntity(entity), nil
}

func (ps *postgresStorage) save(entity *sessionEntity) (err error) {
	query := `
		INSERT INTO mnemosyne.session (access_token, subject_id, bag, expire_at)
		VALUES ($1, $2, $3, NOW() + '30 minutes'::interval)
		RETURNING expire_at

	`
	field := metrics.Field{Key: "query", Value: query}

	err = ps.db.QueryRow(
		query,
		entity.AccessToken,
		entity.SubjectID,
		entity.Bag,
	).Scan(
		&entity.ExpireAt,
	)
	ps.monitor.postgres.queries.With(field).Add(1)

	return
}

// Get implements Storage interface.
func (ps *postgresStorage) Get(accessToken *mnemosyne.AccessToken) (*mnemosyne.Session, error) {
	var entity sessionEntity
	query := `
		SELECT subject_id, bag, expire_at
		FROM mnemosyne.session
		WHERE access_token = $1
		LIMIT 1
	`
	field := metrics.Field{Key: "query", Value: query}

	err := ps.db.QueryRow(query, accessToken).Scan(
		&entity.SubjectID,
		&entity.Bag,
		&entity.ExpireAt,
	)
	if err != nil {
		ps.monitor.postgres.errors.With(field).Add(1)
		if err == sql.ErrNoRows {
			return nil, errSessionNotFound
		}
		return nil, err
	}

	return &mnemosyne.Session{
		AccessToken: accessToken,
		SubjectId:   entity.SubjectID,
		Bag:         entity.Bag,
		ExpireAt:    protot.TimeToTimestamp(entity.ExpireAt),
	}, nil
}

// List implements Storage interface.
func (ps *postgresStorage) List(offset, limit int64, expiredAtFrom, expiredAtTo *time.Time) ([]*mnemosyne.Session, error) {
	if limit == 0 {
		return nil, errors.New("mnemosyned: cannot retrieve list of sessions, limit needs to be higher than 0")
	}

	args := []interface{}{offset, limit}
	query := "SELECT access_token, subject_id, bag, expire_at FROM mnemosyne.session"

	switch {
	case expiredAtFrom != nil && expiredAtTo == nil:
		query += "expire_at > $3"
		args = append(args, expiredAtFrom)
	case expiredAtFrom == nil && expiredAtTo != nil:
		query += "expire_at < $3"
		args = append(args, expiredAtTo)
	case expiredAtFrom != nil && expiredAtTo != nil:
		query += "expire_at > $4 AND expire_at < $5"
		args = append(args, expiredAtFrom, expiredAtTo)
	}

	query += " OFFSET $1 LIMIT $2"

	field := metrics.Field{Key: "query", Value: query}

	rows, err := ps.db.Query(query, args...)
	if err != nil {
		ps.monitor.postgres.errors.With(field).Add(1)
		return nil, err
	}
	defer rows.Close()

	ps.monitor.postgres.queries.With(field).Add(1)

	sessions := make([]*mnemosyne.Session, 0, limit)
	for rows.Next() {
		var entity sessionEntity

		err = rows.Scan(
			&entity.AccessToken,
			&entity.SubjectID,
			&entity.Bag,
			&entity.ExpireAt,
		)
		if err != nil {
			ps.monitor.postgres.errors.With(field).Add(1)
			return nil, err
		}

		sessions = append(sessions, &mnemosyne.Session{
			AccessToken: &entity.AccessToken,
			SubjectId:   entity.SubjectID,
			Bag:         entity.Bag,
			ExpireAt:    protot.TimeToTimestamp(entity.ExpireAt),
		})
	}
	if rows.Err() != nil {
		ps.monitor.postgres.errors.With(field).Add(1)
		return nil, rows.Err()
	}

	return sessions, nil
}

// Exists implements Storage interface.
func (ps *postgresStorage) Exists(accessToken *mnemosyne.AccessToken) (exists bool, err error) {
	query := `SELECT EXISTS(SELECT 1 FROM mnemosyne.session WHERE access_token = $1)`
	field := metrics.Field{Key: "query", Value: query}

	err = ps.db.QueryRow(query, *accessToken).Scan(
		&exists,
	)
	if err != nil {
		ps.monitor.postgres.errors.With(field).Add(1)
	}
	ps.monitor.postgres.queries.With(field).Add(1)

	return
}

// Abandon ...
func (ps *postgresStorage) Abandon(accessToken *mnemosyne.AccessToken) (bool, error) {
	query := `DELETE FROM mnemosyne.session WHERE access_token = $1`
	field := metrics.Field{Key: "query", Value: query}

	result, err := ps.db.Exec(query, *accessToken)
	if err != nil {
		ps.monitor.postgres.errors.With(field).Add(1)
		return false, err
	}

	ps.monitor.postgres.queries.With(field).Add(1)

	affected, err := result.RowsAffected()
	if err != nil {
		return false, err
	}

	if affected == 0 {
		return false, errSessionNotFound
	}

	return true, nil
}

// SetData implements Storage interface.
func (ps *postgresStorage) SetValue(accessToken *mnemosyne.AccessToken, key, value string) (map[string]string, error) {
	var err error

	entity := &sessionEntity{
		AccessToken: *accessToken,
	}
	selectQuery := `
		SELECT subject_id, bag, expire_at
		FROM mnemosyne.session
		WHERE access_token = $1
		FOR UPDATE
	`
	updateQuery := `
		UPDATE mnemosyne.session
		SET
			bag = $2
		WHERE access_token = $1
	`

	tx, err := ps.db.Begin()
	if err != nil {
		tx.Rollback()
		return nil, err
	}

	err = tx.QueryRow(selectQuery, *accessToken).Scan(
		&entity.SubjectID,
		&entity.Bag,
		&entity.ExpireAt,
	)
	if err != nil {
		ps.monitor.postgres.errors.With(metrics.Field{Key: "query", Value: selectQuery}).Add(1)
		tx.Rollback()
		if err == sql.ErrNoRows {
			return nil, errSessionNotFound
		}
		return nil, err
	}
	ps.monitor.postgres.queries.With(metrics.Field{Key: "query", Value: selectQuery}).Add(1)

	entity.Bag.Set(key, value)

	_, err = tx.Exec(updateQuery, *accessToken, entity.Bag)
	if err != nil {
		ps.monitor.postgres.errors.With(metrics.Field{Key: "query", Value: updateQuery}).Add(1)
		tx.Rollback()
		return nil, err
	}
	ps.monitor.postgres.queries.With(metrics.Field{Key: "query", Value: updateQuery}).Add(1)

	tx.Commit()

	return entity.Bag, nil
}

// Delete implements Storage interface.
func (ps *postgresStorage) Delete(accessToken *mnemosyne.AccessToken, expiredAtFrom, expiredAtTo *time.Time) (int64, error) {
	if accessToken == nil && expiredAtFrom == nil && expiredAtTo == nil {
		return 0, errors.New("mnemosyned: session cannot be deleted, no where parameter provided")
	}

	where, args := ps.where(accessToken, expiredAtFrom, expiredAtTo)
	query := "DELETE FROM mnemosyne.session WHERE " + where
	field := metrics.Field{Key: "query", Value: query}

	result, err := ps.db.Exec(query, args...)
	if err != nil {
		ps.monitor.postgres.errors.With(field).Add(1)
		return 0, err
	}
	ps.monitor.postgres.queries.With(field).Add(1)

	return result.RowsAffected()
}

// Setup implements Storage interface.
func (ps *postgresStorage) Setup() error {
	_, err := ps.db.Exec(postgresSchema)

	return err
}

// TearDown implements Storage interface.
func (ps *postgresStorage) TearDown() error {
	_, err := ps.db.Exec(`DROP SCHEMA mnemosyne`)

	return err
}

func (ps *postgresStorage) where(accessToken *mnemosyne.AccessToken, expiredAtFrom, expiredAtTo *time.Time) (string, []interface{}) {
	switch {
	case accessToken != nil && expiredAtFrom == nil && expiredAtTo == nil:
		return "access_token = $1", []interface{}{accessToken}
	case accessToken == nil && expiredAtFrom != nil && expiredAtTo == nil:
		return "expire_at > $1", []interface{}{expiredAtFrom}
	case accessToken == nil && expiredAtFrom == nil && expiredAtTo != nil:
		return "expire_at < $1", []interface{}{expiredAtTo}
	case accessToken != nil && expiredAtFrom != nil && expiredAtTo == nil:
		return "access_token = $1 AND expire_at > $2", []interface{}{accessToken, expiredAtFrom}
	case accessToken != nil && expiredAtFrom == nil && expiredAtTo != nil:
		return "access_token = $1 AND expire_at < $2", []interface{}{accessToken, expiredAtTo}
	case accessToken == nil && expiredAtFrom != nil && expiredAtTo != nil:
		return "expire_at > $1 AND expire_at < $2", []interface{}{expiredAtFrom, expiredAtTo}
	case accessToken != nil && expiredAtFrom != nil && expiredAtTo != nil:
		return "access_token = $1 AND expire_at > $2 AND expire_at < $3", []interface{}{accessToken, expiredAtFrom, expiredAtTo}
	default:
		return "", nil
	}
}

type sessionEntity struct {
	AccessToken mnemosyne.AccessToken `json:"accessToken"`
	SubjectID   string                `json:"subjectId"`
	Bag         bagpack               `json:"bag"`
	ExpireAt    time.Time             `json:"expireAt"`
}

func newSessionFromSessionEntity(entity *sessionEntity) *mnemosyne.Session {
	return &mnemosyne.Session{
		AccessToken: &entity.AccessToken,
		SubjectId:   entity.SubjectID,
		Bag:         entity.Bag,
		ExpireAt:    protot.TimeToTimestamp(entity.ExpireAt),
	}
}
<|endoftext|>