<commit_before>package kafkaadmin

import (
	"fmt"
	"log"
	"strconv"
	"strings"

	"github.com/hashicorp/terraform/helper/schema"
	kafka "github.com/packetloop/go-kafkaesque"
)

func resourceKafkaTopic() *schema.Resource {
	return &schema.Resource{
		Create: resourceKafkaTopicCreate,
		Read:   resourceKafkaTopicRead,
		Exists: resourceKafkaTopicExists,
		Update: resourceKafkaTopicUpdate,
		Delete: resourceKafkaTopicDelete,
		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:        schema.TypeString,
				Required:    true,
				ForceNew:    true,
				Description: "Kafka topic name",
			},
			"partitions": &schema.Schema{
				Type:         schema.TypeInt,
				Optional:     true,
				Description:  "number of partitions for the topic. Must be > 0",
				Default:      1,
				ValidateFunc: validateGreaterThanZero,
			},
			"replication_factor": &schema.Schema{
				Type:         schema.TypeInt,
				Optional:     true,
				ForceNew:     true,
				Description:  "the replication factor for the topic. Must be > 0",
				Default:      1,
				ValidateFunc: validateGreaterThanZero,
			},
			"retention_ms": &schema.Schema{
				Type:        schema.TypeInt,
				Optional:    true,
				Description: "the retention period in milliseconds for the topic. If set to -1, no time limit is applied",
				Default:     -1,
			},
			"cleanup_policy": &schema.Schema{
				Type:         schema.TypeString,
				Optional:     true,
				Description:  "the clean up policy for the topic. Either delete or compact",
				Default:      "compact",
				ValidateFunc: validateCleanupPolicy,
			},
			"segment_bytes": &schema.Schema{
				Type:         schema.TypeInt,
				Optional:     true,
				Description:  "the segment file size for the log",
				Default:      1073741824,
				ValidateFunc: validateSegmentBytes,
			},
			"min_insync_replicas": &schema.Schema{
				Type:         schema.TypeInt,
				Optional:     true,
				Description:  "the minimum number of insync replicas. Must be > 0",
				Default:      1,
				ValidateFunc: validateGreaterThanZero,
			},
			"segment_ms": &schema.Schema{
				Type:         schema.TypeInt,
				Optional:     true,
				Description:  "the time after which Kafka will force the log to roll. Must be > 0",
				Default:      604800000,
				ValidateFunc: validateGreaterThanZero,
			},
			"retention_bytes": &schema.Schema{
				Type:        schema.TypeInt,
				Optional:    true,
				Description: "the retention bytes for the topic",
				Default:     -1,
			},
		},
	}
}

func resourceKafkaTopicCreate(d *schema.ResourceData, m interface{}) error {

	id := strings.ToLower(d.Get("name").(string))
	d.SetId(id)
	return createRequest(d, m)
}

func resourceKafkaTopicExists(d *schema.ResourceData, m interface{}) (b bool, e error) {
	// Exists - This is called to verify a resource still exists. It is called prior to Read,
	// and lowers the burden of Read to be able to assume the resource exists.
	client := clientConn(m)
	_, err := client.GetTopic(d.Id())
	if err != nil {
		return false, fmt.Errorf("TOPIC '%v' DOES NOT EXIST: %v", d.Id(), err)
	}
	return true, nil

}

func createRequest(d *schema.ResourceData, m interface{}) error {
	id := strings.ToLower(d.Get("name").(string))
	partitions := strconv.Itoa(d.Get("partitions").(int))
	replicationFactor := strconv.Itoa(d.Get("replication_factor").(int))
	retentionMs := strconv.Itoa(d.Get("retention_ms").(int))
	cleanupPolicy := d.Get("cleanup_policy").(string)
	segmentBytes := strconv.Itoa(d.Get("segment_bytes").(int))
	retentionBytes := strconv.Itoa(d.Get("retention_bytes").(int))
	segmentMs := strconv.Itoa(d.Get("segment_ms").(int))
	minInsyncReplicas := strconv.Itoa(d.Get("min_insync_replicas").(int))

	log.Printf("[TRACE] creating kafka topic '%s'...", id)
	client := clientConn(m)
	t := kafka.NewTopic(id).
		SetReplicationFactor(replicationFactor).
		SetPartitions(partitions).
		BuildTopic()
	t.Config = &kafka.Config{
		RetentionMs:       retentionMs,
		SegmentBytes:      segmentBytes,
		CleanupPolicy:     cleanupPolicy,
		MinInsyncReplicas: minInsyncReplicas,
		RetentionBytes:    retentionBytes,
		SegmentMs:         segmentMs,
	}
	resp, err := client.CreateTopic(t)
	if err != nil {
		log.Printf("[DEBUG] Error Response %v", err)
	}

	return checkResponse(d, m, resp, err)
}

func checkResponse(d *schema.ResourceData, m interface{}, r kafka.Response, err error) error {
	log.Printf("[TRACE] Create Topic %v", r)
	if err != nil {
		return fmt.Errorf("CREATE TOPIC '%s' ERROR: %v", d.Id(), err)
	}
	return resourceKafkaTopicRead(d, m)
}

// resourceKafkaTopicRead is called to resync the local state with the remote state.
// Terraform guarantees that an existing ID will be set. This ID should be used
// to look up the resource. Any remote data should be updated into the local data.
// No changes to the remote resource are to be made.
func resourceKafkaTopicRead(d *schema.ResourceData, m interface{}) error {
	client := clientConn(m)

	r, err := client.GetTopic(d.Id())
	if err != nil {
		return fmt.Errorf("GET TOPIC '%s' ERROR: %v", d.Id(), err)
	}

	// Unfortunately get topics does not return name of topic, only its config params.
	d.Set("name", strings.ToLower(d.Id()))
	d.Set("partitions", r.GetPartitions())
	d.Set("replication_factor", r.GetReplicationFactor())
	d.Set("retention_ms", r.GetRetentionMs())
	d.Set("cleanup_policy", r.GetCleanupPolicy())
	d.Set("segment_bytes", r.GetSegmentBytes())
	d.Set("min_insync_replicas", r.GetMinInSyncReplicas())
	d.Set("retention_bytes", r.GetRetentionBytes())
	d.Set("segment_ms", r.GetSegmentMs())
	return nil

}

func resourceKafkaTopicUpdate(d *schema.ResourceData, m interface{}) error {
	d.Partial(true)

	if d.HasChange("name") ||
		d.HasChange("replication_factor") ||
		d.HasChange("partitions") ||
		d.HasChange("retention_ms") ||
		d.HasChange("cleanup_policy") ||
		d.HasChange("segment_bytes") ||
		d.HasChange("segment_ms") ||
		d.HasChange("min_insync_replicas") ||
		d.HasChange("retention_bytes") {
		log.Printf("[TRACE] UPDATE TOPIC '%s' success", d.Id())
		d.Partial(false)

		return updateRequest(d, m)
	}
	return nil
}

func updateRequest(d *schema.ResourceData, m interface{}) error {
	id := strings.ToLower(d.Get("name").(string))
	partitions := strconv.Itoa(d.Get("partitions").(int))
	replicationFactor := strconv.Itoa(d.Get("replication_factor").(int))
	retentionMs := strconv.Itoa(d.Get("retention_ms").(int))
	cleanupPolicy := d.Get("cleanup_policy").(string)
	segmentBytes := strconv.Itoa(d.Get("segment_bytes").(int))
	retentionBytes := strconv.Itoa(d.Get("retention_bytes").(int))
	segmentMs := strconv.Itoa(d.Get("segment_ms").(int))
	minInsyncReplicas := strconv.Itoa(d.Get("min_insync_replicas").(int))

	log.Printf("[TRACE] UPDATE KAFKA TOPIC '%s'...", id)

	client := clientConn(m)

	t := kafka.NewTopic(id).
		SetReplicationFactor(replicationFactor).
		SetPartitions(partitions).
		BuildTopic()
	t.Config = &kafka.Config{
		RetentionMs:       retentionMs,
		SegmentBytes:      segmentBytes,
		CleanupPolicy:     cleanupPolicy,
		MinInsyncReplicas: minInsyncReplicas,
		RetentionBytes:    retentionBytes,
		SegmentMs:         segmentMs,
	}

	resp, err := client.UpdateTopic(t)
	if err != nil {
		log.Printf("[DEBUG] Error Response %v", err)
	}

	return checkResponse(d, m, resp, err)
}

func resourceKafkaTopicDelete(d *schema.ResourceData, m interface{}) error {
	a := deleteRequest(d.Id())
	return a(d, m)
}

func deleteRequest(id string) (f func(d *schema.ResourceData, m interface{}) error) {
	return func(d *schema.ResourceData, m interface{}) error {
		client := clientConn(m)
		// Return 'Ok' when successful. Otherwise, this throws an error. Hence,
		// we can safely ignore this.
		_, err := client.DeleteTopic(id)
		if err != nil {
			return err
		}
		d.SetId("")
		return nil
	}
}
<commit_msg>MAYH-10089 fix-typo-default value<commit_after>package kafkaadmin

import (
	"fmt"
	"log"
	"strconv"
	"strings"

	"github.com/hashicorp/terraform/helper/schema"
	kafka "github.com/packetloop/go-kafkaesque"
)

func resourceKafkaTopic() *schema.Resource {
	return &schema.Resource{
		Create: resourceKafkaTopicCreate,
		Read:   resourceKafkaTopicRead,
		Exists: resourceKafkaTopicExists,
		Update: resourceKafkaTopicUpdate,
		Delete: resourceKafkaTopicDelete,
		Schema: map[string]*schema.Schema{
			"name": &schema.Schema{
				Type:        schema.TypeString,
				Required:    true,
				ForceNew:    true,
				Description: "Kafka topic name",
			},
			"partitions": &schema.Schema{
				Type:         schema.TypeInt,
				Optional:     true,
				Description:  "number of partitions for the topic. Must be > 0",
				Default:      1,
				ValidateFunc: validateGreaterThanZero,
			},
			"replication_factor": &schema.Schema{
				Type:         schema.TypeInt,
				Optional:     true,
				ForceNew:     true,
				Description:  "the replication factor for the topic. Must be > 0",
				Default:      1,
				ValidateFunc: validateGreaterThanZero,
			},
			"retention_ms": &schema.Schema{
				Type:        schema.TypeInt,
				Optional:    true,
				Description: "the retention period in milliseconds for the topic. If set to -1, no time limit is applied",
				Default:     -1,
			},
			"cleanup_policy": &schema.Schema{
				Type:         schema.TypeString,
				Optional:     true,
				Description:  "the clean up policy for the topic. Either delete or compact",
				Default:      "delete",
				ValidateFunc: validateCleanupPolicy,
			},
			"segment_bytes": &schema.Schema{
				Type:         schema.TypeInt,
				Optional:     true,
				Description:  "the segment file size for the log",
				Default:      1073741824,
				ValidateFunc: validateSegmentBytes,
			},
			"min_insync_replicas": &schema.Schema{
				Type:         schema.TypeInt,
				Optional:     true,
				Description:  "the minimum number of insync replicas. Must be > 0",
				Default:      1,
				ValidateFunc: validateGreaterThanZero,
			},
			"segment_ms": &schema.Schema{
				Type:         schema.TypeInt,
				Optional:     true,
				Description:  "the time after which Kafka will force the log to roll. Must be > 0",
				Default:      604800000,
				ValidateFunc: validateGreaterThanZero,
			},
			"retention_bytes": &schema.Schema{
				Type:        schema.TypeInt,
				Optional:    true,
				Description: "the retention bytes for the topic",
				Default:     -1,
			},
		},
	}
}

func resourceKafkaTopicCreate(d *schema.ResourceData, m interface{}) error {

	id := strings.ToLower(d.Get("name").(string))
	d.SetId(id)
	return createRequest(d, m)
}

func resourceKafkaTopicExists(d *schema.ResourceData, m interface{}) (b bool, e error) {
	// Exists - This is called to verify a resource still exists. It is called prior to Read,
	// and lowers the burden of Read to be able to assume the resource exists.
	client := clientConn(m)
	_, err := client.GetTopic(d.Id())
	if err != nil {
		return false, fmt.Errorf("TOPIC '%v' DOES NOT EXIST: %v", d.Id(), err)
	}
	return true, nil

}

func createRequest(d *schema.ResourceData, m interface{}) error {
	id := strings.ToLower(d.Get("name").(string))
	partitions := strconv.Itoa(d.Get("partitions").(int))
	replicationFactor := strconv.Itoa(d.Get("replication_factor").(int))
	retentionMs := strconv.Itoa(d.Get("retention_ms").(int))
	cleanupPolicy := d.Get("cleanup_policy").(string)
	segmentBytes := strconv.Itoa(d.Get("segment_bytes").(int))
	retentionBytes := strconv.Itoa(d.Get("retention_bytes").(int))
	segmentMs := strconv.Itoa(d.Get("segment_ms").(int))
	minInsyncReplicas := strconv.Itoa(d.Get("min_insync_replicas").(int))

	log.Printf("[TRACE] creating kafka topic '%s'...", id)
	client := clientConn(m)
	t := kafka.NewTopic(id).
		SetReplicationFactor(replicationFactor).
		SetPartitions(partitions).
		BuildTopic()
	t.Config = &kafka.Config{
		RetentionMs:       retentionMs,
		SegmentBytes:      segmentBytes,
		CleanupPolicy:     cleanupPolicy,
		MinInsyncReplicas: minInsyncReplicas,
		RetentionBytes:    retentionBytes,
		SegmentMs:         segmentMs,
	}
	resp, err := client.CreateTopic(t)
	if err != nil {
		log.Printf("[DEBUG] Error Response %v", err)
	}

	return checkResponse(d, m, resp, err)
}

func checkResponse(d *schema.ResourceData, m interface{}, r kafka.Response, err error) error {
	log.Printf("[TRACE] Create Topic %v", r)
	if err != nil {
		return fmt.Errorf("CREATE TOPIC '%s' ERROR: %v", d.Id(), err)
	}
	return resourceKafkaTopicRead(d, m)
}

// resourceKafkaTopicRead is called to resync the local state with the remote state.
// Terraform guarantees that an existing ID will be set. This ID should be used
// to look up the resource. Any remote data should be updated into the local data.
// No changes to the remote resource are to be made.
func resourceKafkaTopicRead(d *schema.ResourceData, m interface{}) error {
	client := clientConn(m)

	r, err := client.GetTopic(d.Id())
	if err != nil {
		return fmt.Errorf("GET TOPIC '%s' ERROR: %v", d.Id(), err)
	}

	// Unfortunately get topics does not return name of topic, only its config params.
	d.Set("name", strings.ToLower(d.Id()))
	d.Set("partitions", r.GetPartitions())
	d.Set("replication_factor", r.GetReplicationFactor())
	d.Set("retention_ms", r.GetRetentionMs())
	d.Set("cleanup_policy", r.GetCleanupPolicy())
	d.Set("segment_bytes", r.GetSegmentBytes())
	d.Set("min_insync_replicas", r.GetMinInSyncReplicas())
	d.Set("retention_bytes", r.GetRetentionBytes())
	d.Set("segment_ms", r.GetSegmentMs())
	return nil

}

func resourceKafkaTopicUpdate(d *schema.ResourceData, m interface{}) error {
	d.Partial(true)

	if d.HasChange("name") ||
		d.HasChange("replication_factor") ||
		d.HasChange("partitions") ||
		d.HasChange("retention_ms") ||
		d.HasChange("cleanup_policy") ||
		d.HasChange("segment_bytes") ||
		d.HasChange("segment_ms") ||
		d.HasChange("min_insync_replicas") ||
		d.HasChange("retention_bytes") {
		log.Printf("[TRACE] UPDATE TOPIC '%s' success", d.Id())
		d.Partial(false)

		return updateRequest(d, m)
	}
	return nil
}

func updateRequest(d *schema.ResourceData, m interface{}) error {
	id := strings.ToLower(d.Get("name").(string))
	partitions := strconv.Itoa(d.Get("partitions").(int))
	replicationFactor := strconv.Itoa(d.Get("replication_factor").(int))
	retentionMs := strconv.Itoa(d.Get("retention_ms").(int))
	cleanupPolicy := d.Get("cleanup_policy").(string)
	segmentBytes := strconv.Itoa(d.Get("segment_bytes").(int))
	retentionBytes := strconv.Itoa(d.Get("retention_bytes").(int))
	segmentMs := strconv.Itoa(d.Get("segment_ms").(int))
	minInsyncReplicas := strconv.Itoa(d.Get("min_insync_replicas").(int))

	log.Printf("[TRACE] UPDATE KAFKA TOPIC '%s'...", id)

	client := clientConn(m)

	t := kafka.NewTopic(id).
		SetReplicationFactor(replicationFactor).
		SetPartitions(partitions).
		BuildTopic()
	t.Config = &kafka.Config{
		RetentionMs:       retentionMs,
		SegmentBytes:      segmentBytes,
		CleanupPolicy:     cleanupPolicy,
		MinInsyncReplicas: minInsyncReplicas,
		RetentionBytes:    retentionBytes,
		SegmentMs:         segmentMs,
	}

	resp, err := client.UpdateTopic(t)
	if err != nil {
		log.Printf("[DEBUG] Error Response %v", err)
	}

	return checkResponse(d, m, resp, err)
}

func resourceKafkaTopicDelete(d *schema.ResourceData, m interface{}) error {
	a := deleteRequest(d.Id())
	return a(d, m)
}

func deleteRequest(id string) (f func(d *schema.ResourceData, m interface{}) error) {
	return func(d *schema.ResourceData, m interface{}) error {
		client := clientConn(m)
		// Return 'Ok' when successful. Otherwise, this throws an error. Hence,
		// we can safely ignore this.
		_, err := client.DeleteTopic(id)
		if err != nil {
			return err
		}
		d.SetId("")
		return nil
	}
}
<|endoftext|>


<commit_before>/*
 * Simple caching library with expiration capabilities
 *     Copyright (c) 2013, Christian Muehlhaeuser <muesli@gmail.com>
 *
 *   For license see LICENSE.txt
 */

package cache2go

import (
	"errors"
	"log"
	"sync"
	"time"
)

// Structure of a table with items in the cache.
type CacheTable struct {
	sync.RWMutex

	// The table's name.
	name string
	// All cached items.
	items map[interface{}]*CacheItem

	// Timer responsible for triggering cleanup.
	cleanupTimer *time.Timer
	// Current timer duration.
	cleanupInterval time.Duration

	// The logger used for this table.
	logger *log.Logger

	// Callback method triggered when trying to load a non-existing key.
	loadData func(interface{}) *CacheItem
	// Callback method triggered when adding a new item to the cache.
	addedItem func(*CacheItem)
	// Callback method triggered before deleting an item from the cache.
	aboutToDeleteItem func(*CacheItem)
}

// Returns how many items are currently stored in the cache.
func (table *CacheTable) Count() int {
	table.RLock()
	defer table.RUnlock()
	return len(table.items)
}

// Configures a data-loader callback, which will be called when trying
// to use access a non-existing key.
func (table *CacheTable) SetDataLoader(f func(interface{}) *CacheItem) {
	table.Lock()
	defer table.Unlock()
	table.loadData = f
}

// Configures a callback, which will be called every time a new item
// is added to the cache.
func (table *CacheTable) SetAddedItemCallback(f func(*CacheItem)) {
	table.Lock()
	defer table.Unlock()
	table.addedItem = f
}

// Configures a callback, which will be called every time an item
// is about to be removed from the cache.
func (table *CacheTable) SetAboutToDeleteItemCallback(f func(*CacheItem)) {
	table.Lock()
	defer table.Unlock()
	table.aboutToDeleteItem = f
}

// Sets the logger to be used by this cache table.
func (table *CacheTable) SetLogger(logger *log.Logger) {
	table.Lock()
	defer table.Unlock()
	table.logger = logger
}

// Expiration check loop, triggered by a self-adjusting timer.
func (table *CacheTable) expirationCheck() {
	table.Lock()
	if table.cleanupTimer != nil {
		table.cleanupTimer.Stop()
	}
	if table.cleanupInterval > 0 {
		table.log("Expiration check triggered after", table.cleanupInterval, "for table", table.name)
	} else {
		table.log("Expiration check installed for table", table.name)
	}

	// Cache value so we don't keep blocking the mutex.
	items := table.items
	table.Unlock()

	// To be more accurate with timers, we would need to update 'now' on every
	// loop iteration. Not sure it's really efficient though.
	now := time.Now()
	smallestDuration := 0 * time.Second
	for key, item := range items {
		// Cache values so we don't keep blocking the mutex.
		item.RLock()
		lifeSpan := item.lifeSpan
		accessedOn := item.accessedOn
		item.RUnlock()

		if lifeSpan == 0 {
			continue
		}
		if now.Sub(accessedOn) >= lifeSpan {
			// Item has excessed its lifespan.
			table.Delete(key)
		} else {
			// Find the item chronologically closest to its end-of-lifespan.
			if smallestDuration == 0 || lifeSpan < smallestDuration {
				smallestDuration = lifeSpan - now.Sub(accessedOn)
			}
		}
	}

	// Setup the interval for the next cleanup run.
	table.Lock()
	table.cleanupInterval = smallestDuration
	if smallestDuration > 0 {
		table.cleanupTimer = time.AfterFunc(smallestDuration, func() {
			go table.expirationCheck()
		})
	}
	table.Unlock()
}

// Adds a key/value pair to the cache.
// Parameter key is the item's cache-key.
// Parameter lifeSpan determines after which time period without an access the item
// will get removed from the cache.
// Parameter data is the item's value.
func (table *CacheTable) Cache(key interface{}, lifeSpan time.Duration, data interface{}) *CacheItem {
	item := CreateCacheItem(key, lifeSpan, data)

	// Add item to cache.
	table.Lock()
	table.log("Adding item with key", key, "and lifespan of", lifeSpan, "to table", table.name)
	table.items[key] = &item

	// Cache values so we don't keep blocking the mutex.
	expDur := table.cleanupInterval
	addedItem := table.addedItem
	table.Unlock()

	// Trigger callback after adding an item to cache.
	if addedItem != nil {
		addedItem(&item)
	}

	// If we haven't set up any expiration check timer or found a more imminent item.
	if lifeSpan > 0 && (expDur == 0 || lifeSpan < expDur) {
		table.expirationCheck()
	}

	return &item
}

// Delete an item from the cache.
func (table *CacheTable) Delete(key interface{}) (*CacheItem, error) {
	table.RLock()
	r, ok := table.items[key]
	if !ok {
		table.RUnlock()
		return nil, ErrKeyNotFound
	}

	// Cache value so we don't keep blocking the mutex.
	aboutToDeleteItem := table.aboutToDeleteItem
	table.RUnlock()

	// Trigger callbacks before deleting an item from cache.
	if aboutToDeleteItem != nil {
		aboutToDeleteItem(r)
	}

	r.RLock()
	defer r.RUnlock()
	if r.aboutToExpire != nil {
		r.aboutToExpire(key)
	}

	table.Lock()
	defer table.Unlock()
	table.log("Deleting item with key", key, "created on", r.createdOn, "and hit", r.accessCount, "times from table", table.name)
	delete(table.items, key)

	return r, nil
}

// Test whether an item exists in the cache. Unlike the Value method
// Exists neither tries to fetch data via the loadData callback nor
// does it keep the item alive in the cache.
func (table *CacheTable) Exists(key interface{}) bool {
	table.RLock()
	defer table.RUnlock()
	_, ok := table.items[key]

	return ok
}

// Get an item from the cache and mark it to be kept alive.
func (table *CacheTable) Value(key interface{}) (*CacheItem, error) {
	table.RLock()
	r, ok := table.items[key]
	loadData := table.loadData
	table.RUnlock()

	if ok {
		// Update access counter and timestamp.
		r.KeepAlive()
		return r, nil
	}

	// Item doesn't exist in cache. Try and fetch it with a data-loader.
	if loadData != nil {
		item := loadData(key)
		if item != nil {
			table.Cache(key, item.lifeSpan, item.data)
			return item, nil
		}

		return nil, ErrKeyNotFoundOrLoadable
	}

	return nil, ErrKeyNotFound
}

// Delete all items from cache.
func (table *CacheTable) Flush() {
	table.Lock()
	defer table.Unlock()

	table.log("Flushing table", table.name)

	table.items = make(map[interface{}]*CacheItem)
	table.cleanupInterval = 0
	if table.cleanupTimer != nil {
		table.cleanupTimer.Stop()
	}
}

// Internal logging method for convenience.
func (table *CacheTable) log(v ...interface{}) {
	if table.logger == nil {
		return
	}

	table.logger.Println(v)
}
<commit_msg>cleaned imports<commit_after>/*
 * Simple caching library with expiration capabilities
 *     Copyright (c) 2013, Christian Muehlhaeuser <muesli@gmail.com>
 *
 *   For license see LICENSE.txt
 */

package cache2go

import (
	"log"
	"sync"
	"time"
)

// Structure of a table with items in the cache.
type CacheTable struct {
	sync.RWMutex

	// The table's name.
	name string
	// All cached items.
	items map[interface{}]*CacheItem

	// Timer responsible for triggering cleanup.
	cleanupTimer *time.Timer
	// Current timer duration.
	cleanupInterval time.Duration

	// The logger used for this table.
	logger *log.Logger

	// Callback method triggered when trying to load a non-existing key.
	loadData func(interface{}) *CacheItem
	// Callback method triggered when adding a new item to the cache.
	addedItem func(*CacheItem)
	// Callback method triggered before deleting an item from the cache.
	aboutToDeleteItem func(*CacheItem)
}

// Returns how many items are currently stored in the cache.
func (table *CacheTable) Count() int {
	table.RLock()
	defer table.RUnlock()
	return len(table.items)
}

// Configures a data-loader callback, which will be called when trying
// to use access a non-existing key.
func (table *CacheTable) SetDataLoader(f func(interface{}) *CacheItem) {
	table.Lock()
	defer table.Unlock()
	table.loadData = f
}

// Configures a callback, which will be called every time a new item
// is added to the cache.
func (table *CacheTable) SetAddedItemCallback(f func(*CacheItem)) {
	table.Lock()
	defer table.Unlock()
	table.addedItem = f
}

// Configures a callback, which will be called every time an item
// is about to be removed from the cache.
func (table *CacheTable) SetAboutToDeleteItemCallback(f func(*CacheItem)) {
	table.Lock()
	defer table.Unlock()
	table.aboutToDeleteItem = f
}

// Sets the logger to be used by this cache table.
func (table *CacheTable) SetLogger(logger *log.Logger) {
	table.Lock()
	defer table.Unlock()
	table.logger = logger
}

// Expiration check loop, triggered by a self-adjusting timer.
func (table *CacheTable) expirationCheck() {
	table.Lock()
	if table.cleanupTimer != nil {
		table.cleanupTimer.Stop()
	}
	if table.cleanupInterval > 0 {
		table.log("Expiration check triggered after", table.cleanupInterval, "for table", table.name)
	} else {
		table.log("Expiration check installed for table", table.name)
	}

	// Cache value so we don't keep blocking the mutex.
	items := table.items
	table.Unlock()

	// To be more accurate with timers, we would need to update 'now' on every
	// loop iteration. Not sure it's really efficient though.
	now := time.Now()
	smallestDuration := 0 * time.Second
	for key, item := range items {
		// Cache values so we don't keep blocking the mutex.
		item.RLock()
		lifeSpan := item.lifeSpan
		accessedOn := item.accessedOn
		item.RUnlock()

		if lifeSpan == 0 {
			continue
		}
		if now.Sub(accessedOn) >= lifeSpan {
			// Item has excessed its lifespan.
			table.Delete(key)
		} else {
			// Find the item chronologically closest to its end-of-lifespan.
			if smallestDuration == 0 || lifeSpan < smallestDuration {
				smallestDuration = lifeSpan - now.Sub(accessedOn)
			}
		}
	}

	// Setup the interval for the next cleanup run.
	table.Lock()
	table.cleanupInterval = smallestDuration
	if smallestDuration > 0 {
		table.cleanupTimer = time.AfterFunc(smallestDuration, func() {
			go table.expirationCheck()
		})
	}
	table.Unlock()
}

// Adds a key/value pair to the cache.
// Parameter key is the item's cache-key.
// Parameter lifeSpan determines after which time period without an access the item
// will get removed from the cache.
// Parameter data is the item's value.
func (table *CacheTable) Cache(key interface{}, lifeSpan time.Duration, data interface{}) *CacheItem {
	item := CreateCacheItem(key, lifeSpan, data)

	// Add item to cache.
	table.Lock()
	table.log("Adding item with key", key, "and lifespan of", lifeSpan, "to table", table.name)
	table.items[key] = &item

	// Cache values so we don't keep blocking the mutex.
	expDur := table.cleanupInterval
	addedItem := table.addedItem
	table.Unlock()

	// Trigger callback after adding an item to cache.
	if addedItem != nil {
		addedItem(&item)
	}

	// If we haven't set up any expiration check timer or found a more imminent item.
	if lifeSpan > 0 && (expDur == 0 || lifeSpan < expDur) {
		table.expirationCheck()
	}

	return &item
}

// Delete an item from the cache.
func (table *CacheTable) Delete(key interface{}) (*CacheItem, error) {
	table.RLock()
	r, ok := table.items[key]
	if !ok {
		table.RUnlock()
		return nil, ErrKeyNotFound
	}

	// Cache value so we don't keep blocking the mutex.
	aboutToDeleteItem := table.aboutToDeleteItem
	table.RUnlock()

	// Trigger callbacks before deleting an item from cache.
	if aboutToDeleteItem != nil {
		aboutToDeleteItem(r)
	}

	r.RLock()
	defer r.RUnlock()
	if r.aboutToExpire != nil {
		r.aboutToExpire(key)
	}

	table.Lock()
	defer table.Unlock()
	table.log("Deleting item with key", key, "created on", r.createdOn, "and hit", r.accessCount, "times from table", table.name)
	delete(table.items, key)

	return r, nil
}

// Test whether an item exists in the cache. Unlike the Value method
// Exists neither tries to fetch data via the loadData callback nor
// does it keep the item alive in the cache.
func (table *CacheTable) Exists(key interface{}) bool {
	table.RLock()
	defer table.RUnlock()
	_, ok := table.items[key]

	return ok
}

// Get an item from the cache and mark it to be kept alive.
func (table *CacheTable) Value(key interface{}) (*CacheItem, error) {
	table.RLock()
	r, ok := table.items[key]
	loadData := table.loadData
	table.RUnlock()

	if ok {
		// Update access counter and timestamp.
		r.KeepAlive()
		return r, nil
	}

	// Item doesn't exist in cache. Try and fetch it with a data-loader.
	if loadData != nil {
		item := loadData(key)
		if item != nil {
			table.Cache(key, item.lifeSpan, item.data)
			return item, nil
		}

		return nil, ErrKeyNotFoundOrLoadable
	}

	return nil, ErrKeyNotFound
}

// Delete all items from cache.
func (table *CacheTable) Flush() {
	table.Lock()
	defer table.Unlock()

	table.log("Flushing table", table.name)

	table.items = make(map[interface{}]*CacheItem)
	table.cleanupInterval = 0
	if table.cleanupTimer != nil {
		table.cleanupTimer.Stop()
	}
}

// Internal logging method for convenience.
func (table *CacheTable) log(v ...interface{}) {
	if table.logger == nil {
		return
	}

	table.logger.Println(v)
}
<|endoftext|>


<commit_before>// This package provides all the input fields logic and customization methods.
package fields

import (
	"github.com/kirves/go-form-it/widgets"
	"html/template"
)

// Field is a generic type containing all data associated to an input field.
type Field struct {
	fieldType  string
	Widget     widgets.WidgetInterface // Public Widget field for widget customization
	name       string
	class      []string
	id         string
	params     map[string]string
	css        map[string]string
	text       string
	label      string
	choices    map[string][]InputChoice
	labelClass []string
	tag        []string
	value      string
}

// FieldInterface defines the interface an object must implement to be used in a form. Every method returns a FieldInterface object
// to allow methods chaining.
type FieldInterface interface {
	Name() string
	Render() template.HTML
	AddClass(class string) FieldInterface
	RemoveClass(class string) FieldInterface
	AddTag(class string) FieldInterface
	RemoveTag(class string) FieldInterface
	SetId(id string) FieldInterface
	SetParam(key, value string) FieldInterface
	DeleteParam(key string) FieldInterface
	AddCss(key, value string) FieldInterface
	RemoveCss(key string) FieldInterface
	SetStyle(style string) FieldInterface
	SetText(text string) FieldInterface
	SetLabel(label string) FieldInterface
	AddLabelClass(class string) FieldInterface
	RemoveLabelClass(class string) FieldInterface
	SetChoices(choices map[string][]InputChoice) FieldInterface
	SetValue(value string) FieldInterface
	Disabled() FieldInterface
	Enabled() FieldInterface
}

// FieldWithType creates an empty field of the given type and identified by name.
func FieldWithType(name, t string) Field {
	return Field{
		t,
		nil,
		name,
		[]string{},
		"",
		map[string]string{},
		map[string]string{},
		"",
		"",
		map[string][]InputChoice{},
		[]string{},
		[]string{},
		"",
	}
}

// SetStyle sets the style (e.g.: BASE, BOOTSTRAP) of the field, correctly populating the Widget field.
func (f *Field) SetStyle(style string) FieldInterface {
	f.Widget = widgets.BaseWidget(style, f.fieldType)
	return f
}

// Name returns the name of the field.
func (f *Field) Name() string {
	return f.name
}

// Render packs all data and executes widget render method.
func (f *Field) Render() template.HTML {
	if f.Widget != nil {
		data := map[string]interface{}{
			"classes":      f.class,
			"id":           f.id,
			"name":         f.name,
			"params":       f.params,
			"css":          f.css,
			"text":         f.text,
			"type":         f.fieldType,
			"label":        f.label,
			"choices":      f.choices,
			"labelClasses": f.labelClass,
			"tags":         f.tag,
			"value":        f.value,
		}
		return template.HTML(f.Widget.Render(data))
	}
	return template.HTML("")
}

// AddClass adds a class to the field.
func (f *Field) AddClass(class string) FieldInterface {
	f.class = append(f.class, class)
	return f
}

// RemoveClass removes a class from the field, if it was present.
func (f *Field) RemoveClass(class string) FieldInterface {
	ind := -1
	for i, v := range f.class {
		if v == class {
			ind = i
			break
		}
	}

	if ind != -1 {
		f.class = append(f.class[:ind], f.class[ind+1:]...)
	}
	return f
}

// SetId associates the given id to the field, overwriting any previous id.
func (f *Field) SetId(id string) FieldInterface {
	f.id = id
	return f
}

// SetText saves the provided text as content of the field, usually a TextAreaField.
func (f *Field) SetText(text string) FieldInterface {
	f.text = text
	return f
}

// SetLabel saves the label to be rendered along with the field.
func (f *Field) SetLabel(label string) FieldInterface {
	f.label = label
	return f
}

// SetLablClass allows to define custom classes for the label.
func (f *Field) AddLabelClass(class string) FieldInterface {
	f.labelClass = append(f.labelClass, class)
	return f
}

// RemoveLabelClass removes the given class from the field label.
func (f *Field) RemoveLabelClass(class string) FieldInterface {
	ind := -1
	for i, v := range f.labelClass {
		if v == class {
			ind = i
			break
		}
	}

	if ind != -1 {
		f.labelClass = append(f.labelClass[:ind], f.labelClass[ind+1:]...)
	}
	return f
}

// SetChoices takes as input a dictionary whose key-value entries are defined as follows: key is the group name (the empty string
// is the default group that is not explicitly rendered) and value is the list of choices belonging to that group.
// Grouping is only useful for Select fields, while groups are ignored in Radio fields.
func (f *Field) SetChoices(choices map[string][]InputChoice) FieldInterface {
	f.choices = choices
	return f
}

// SetParam adds a parameter (defined as key-value pair) in the field.
func (f *Field) SetParam(key, value string) FieldInterface {
	f.params[key] = value
	return f
}

// DeleteParam removes a parameter identified by key from the field.
func (f *Field) DeleteParam(key string) FieldInterface {
	delete(f.params, key)
	return f
}

// AddCss adds a custom CSS style the field.
func (f *Field) AddCss(key, value string) FieldInterface {
	f.css[key] = value
	return f
}

// RemoveCss removes CSS options identified by key from the field.
func (f *Field) RemoveCss(key string) FieldInterface {
	delete(f.css, key)
	return f
}

// Disabled add the "disabled" tag to the field, making it unresponsive in some environments (e.g. Bootstrap).
func (f *Field) Disabled() FieldInterface {
	f.AddTag("disabled")
	return f
}

// Enabled removes the "disabled" tag from the field, making it responsive.
func (f *Field) Enabled() FieldInterface {
	f.RemoveTag("disabled")
	return f
}

// AddTag adds a no-value parameter (e.g.: checked, disabled) to the field.
func (f *Field) AddTag(tag string) FieldInterface {
	f.tag = append(f.tag, tag)
	return f
}

// RemoveTag removes a no-value parameter from the field.
func (f *Field) RemoveTag(tag string) FieldInterface {
	ind := -1
	for i, v := range f.tag {
		if v == tag {
			ind = i
			break
		}
	}

	if ind != -1 {
		f.tag = append(f.tag[:ind], f.tag[ind+1:]...)
	}
	return f
}

// SetValue sets the value parameter for the field.
func (f *Field) SetValue(value string) FieldInterface {
	f.value = value
	return f
}
<commit_msg>Added helptext and errors support<commit_after>// This package provides all the input fields logic and customization methods.
package fields

import (
	"github.com/kirves/go-form-it/widgets"
	"html/template"
)

// Field is a generic type containing all data associated to an input field.
type Field struct {
	fieldType  string
	Widget     widgets.WidgetInterface // Public Widget field for widget customization
	name       string
	class      []string
	id         string
	params     map[string]string
	css        map[string]string
	text       string
	label      string
	choices    map[string][]InputChoice
	labelClass []string
	tag        []string
	value      string
	helptext   string
	errors     []string
}

// FieldInterface defines the interface an object must implement to be used in a form. Every method returns a FieldInterface object
// to allow methods chaining.
type FieldInterface interface {
	Name() string
	Render() template.HTML
	AddClass(class string) FieldInterface
	RemoveClass(class string) FieldInterface
	AddTag(class string) FieldInterface
	RemoveTag(class string) FieldInterface
	SetId(id string) FieldInterface
	SetParam(key, value string) FieldInterface
	DeleteParam(key string) FieldInterface
	AddCss(key, value string) FieldInterface
	RemoveCss(key string) FieldInterface
	SetStyle(style string) FieldInterface
	SetText(text string) FieldInterface
	SetLabel(label string) FieldInterface
	AddLabelClass(class string) FieldInterface
	RemoveLabelClass(class string) FieldInterface
	SetChoices(choices map[string][]InputChoice) FieldInterface
	SetValue(value string) FieldInterface
	Disabled() FieldInterface
	Enabled() FieldInterface
	SetHelptext(text string) FieldInterface
	AddError(err string) FieldInterface
}

// FieldWithType creates an empty field of the given type and identified by name.
func FieldWithType(name, t string) Field {
	return Field{
		t,
		nil,
		name,
		[]string{},
		"",
		map[string]string{},
		map[string]string{},
		"",
		"",
		map[string][]InputChoice{},
		[]string{},
		[]string{},
		"",
		"",
		[]string{},
	}
}

// SetStyle sets the style (e.g.: BASE, BOOTSTRAP) of the field, correctly populating the Widget field.
func (f *Field) SetStyle(style string) FieldInterface {
	f.Widget = widgets.BaseWidget(style, f.fieldType)
	return f
}

// Name returns the name of the field.
func (f *Field) Name() string {
	return f.name
}

// Render packs all data and executes widget render method.
func (f *Field) Render() template.HTML {
	if f.Widget != nil {
		data := map[string]interface{}{
			"classes":      f.class,
			"id":           f.id,
			"name":         f.name,
			"params":       f.params,
			"css":          f.css,
			"text":         f.text,
			"type":         f.fieldType,
			"label":        f.label,
			"choices":      f.choices,
			"labelClasses": f.labelClass,
			"tags":         f.tag,
			"value":        f.value,
			"helptext":     f.helptext,
			"errors":       f.errors,
		}
		return template.HTML(f.Widget.Render(data))
	}
	return template.HTML("")
}

// AddClass adds a class to the field.
func (f *Field) AddClass(class string) FieldInterface {
	f.class = append(f.class, class)
	return f
}

// RemoveClass removes a class from the field, if it was present.
func (f *Field) RemoveClass(class string) FieldInterface {
	ind := -1
	for i, v := range f.class {
		if v == class {
			ind = i
			break
		}
	}

	if ind != -1 {
		f.class = append(f.class[:ind], f.class[ind+1:]...)
	}
	return f
}

// SetId associates the given id to the field, overwriting any previous id.
func (f *Field) SetId(id string) FieldInterface {
	f.id = id
	return f
}

// SetText saves the provided text as content of the field, usually a TextAreaField.
func (f *Field) SetText(text string) FieldInterface {
	f.text = text
	return f
}

// SetLabel saves the label to be rendered along with the field.
func (f *Field) SetLabel(label string) FieldInterface {
	f.label = label
	return f
}

// SetLablClass allows to define custom classes for the label.
func (f *Field) AddLabelClass(class string) FieldInterface {
	f.labelClass = append(f.labelClass, class)
	return f
}

// RemoveLabelClass removes the given class from the field label.
func (f *Field) RemoveLabelClass(class string) FieldInterface {
	ind := -1
	for i, v := range f.labelClass {
		if v == class {
			ind = i
			break
		}
	}

	if ind != -1 {
		f.labelClass = append(f.labelClass[:ind], f.labelClass[ind+1:]...)
	}
	return f
}

// SetChoices takes as input a dictionary whose key-value entries are defined as follows: key is the group name (the empty string
// is the default group that is not explicitly rendered) and value is the list of choices belonging to that group.
// Grouping is only useful for Select fields, while groups are ignored in Radio fields.
func (f *Field) SetChoices(choices map[string][]InputChoice) FieldInterface {
	f.choices = choices
	return f
}

// SetParam adds a parameter (defined as key-value pair) in the field.
func (f *Field) SetParam(key, value string) FieldInterface {
	f.params[key] = value
	return f
}

// DeleteParam removes a parameter identified by key from the field.
func (f *Field) DeleteParam(key string) FieldInterface {
	delete(f.params, key)
	return f
}

// AddCss adds a custom CSS style the field.
func (f *Field) AddCss(key, value string) FieldInterface {
	f.css[key] = value
	return f
}

// RemoveCss removes CSS options identified by key from the field.
func (f *Field) RemoveCss(key string) FieldInterface {
	delete(f.css, key)
	return f
}

// Disabled add the "disabled" tag to the field, making it unresponsive in some environments (e.g. Bootstrap).
func (f *Field) Disabled() FieldInterface {
	f.AddTag("disabled")
	return f
}

// Enabled removes the "disabled" tag from the field, making it responsive.
func (f *Field) Enabled() FieldInterface {
	f.RemoveTag("disabled")
	return f
}

// AddTag adds a no-value parameter (e.g.: checked, disabled) to the field.
func (f *Field) AddTag(tag string) FieldInterface {
	f.tag = append(f.tag, tag)
	return f
}

// RemoveTag removes a no-value parameter from the field.
func (f *Field) RemoveTag(tag string) FieldInterface {
	ind := -1
	for i, v := range f.tag {
		if v == tag {
			ind = i
			break
		}
	}

	if ind != -1 {
		f.tag = append(f.tag[:ind], f.tag[ind+1:]...)
	}
	return f
}

// SetValue sets the value parameter for the field.
func (f *Field) SetValue(value string) FieldInterface {
	f.value = value
	return f
}

// SetHelptext saves the field helptext.
func (f *Field) SetHelptext(text string) FieldInterface {
	f.helptext = text
	return f
}

// AddError adds an error string to the field. It's valid only for Bootstrap forms.
func (f *Field) AddError(err string) FieldInterface {
	f.errors = append(f.errors, err)
	return f
}
<|endoftext|>


<commit_before>package jobsupervisor

import (
	boshalert "bosh/agent/alert"
)

type JobFailureHandler func(boshalert.MonitAlert) error

type JobSupervisor interface {
	Reload() error

	// Actions taken on all services
	Start() error
	Stop() error
	Unmonitor() error

	Status() string

	AddJob(jobName string, jobIndex int, configPath string) error

	MonitorJobFailures(handler JobFailureHandler) error
}
<commit_msg>note that Start/Stop command should still work after Unmonitor command is executed<commit_after>package jobsupervisor

import (
	boshalert "bosh/agent/alert"
)

type JobFailureHandler func(boshalert.MonitAlert) error

type JobSupervisor interface {
	Reload() error

	// Actions taken on all services
	Start() error
	Stop() error

	// Start and Stop should still function after Unmonitor.
	// Calling Start after Unmonitor should re-monitor all jobs.
	// Calling Stop after Unmonitor should not re-monitor all jobs.
	// (Monit complies to above requirements.)
	Unmonitor() error

	Status() string

	AddJob(jobName string, jobIndex int, configPath string) error

	MonitorJobFailures(handler JobFailureHandler) error
}
<|endoftext|>


<commit_before>// POST /stream/<name> or PUT /stream/<name>
//     Broadcast a WebM video/audio file.
//
//     Accepted input: valid WebM split into arbitrarily many requests in absolutely
//     any way. Multiple files can be concatenated into a single stream as long as they
//     contain exactly the same tracks (i.e. their number, codecs, and dimensions.
//     Otherwise any connected decoders will error and have to restart. Changing,
//     for example, bitrate or tags is fine.)
//
// GET /stream/<name>
//     Receive a published WebM stream. Note that the server makes no attempt
//     at buffering; if the stream is being broadcast faster than its native framerate,
//     the client will have to buffer and/or drop frames.
//
// GET /stream/<name> [Upgrade: websocket]
//     Connect to a JSON-RPC v2.0 node.
//
//     Methods of `Chat`:
//
//        * `SetName(string)`: assign a (unique) name to this client. This is required to...
//        * `SendMessage(string)`: broadcast a simple text message to all viewers.
//        * `RequestHistory()`: ask the server to emit notifications containing the last
//          few broadcasted text messages.
//
//     TODO Methods of `Stream`.
//
//     Notifications:
//
//        * `Chat.AcquiredName(user string)`: upon a successful `SetName`.
//          May be emitted automatically at the start of a connection if already logged in.
//        * `Chat.Message(user string, text string)`: a broadcasted text message.
//
package main

import (
	"golang.org/x/net/websocket"
	"log"
	"net/http"
	"strings"
	"sync"
)

type RetransmissionHandler struct {
	BroadcastSet
	chatLock sync.Mutex
	chats    map[string]*Chat
	*Context
}

func NewRetransmissionHandler(c *Context) *RetransmissionHandler {
	ctx := &RetransmissionHandler{chats: make(map[string]*Chat), Context: c}
	ctx.Timeout = c.StreamKeepAlive
	ctx.OnStreamClose = func(id string) {
		ctx.chatLock.Lock()
		if chat, ok := ctx.chats[id]; ok {
			chat.Close()
			delete(ctx.chats, id)
		}
		ctx.chatLock.Unlock()
		if err := ctx.StopStream(id); err != nil {
			log.Println("Error stopping the stream: ", err)
		}
	}
	ctx.OnStreamTrackInfo = func(id string, info *StreamTrackInfo) {
		if err := ctx.SetStreamTrackInfo(id, info); err != nil {
			log.Println("Error setting stream metadata: ", err)
		}
	}
	return ctx
}

func (ctx *RetransmissionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
	switch {
	case r.URL.Path == "/stream/" || strings.ContainsRune(r.URL.Path[8:], '/'):
		return RenderError(w, http.StatusNotFound, "")
	case r.Method == "GET":
		return ctx.watch(w, r, r.URL.Path[8:])
	case r.Method == "POST" || r.Method == "PUT":
		return ctx.stream(w, r, r.URL.Path[8:])
	default:
		return RenderInvalidMethod(w, "GET, PUT, POST")
	}
}

func wantsWebsocket(r *http.Request) bool {
	if upgrade, ok := r.Header["Upgrade"]; ok {
		for i := range upgrade {
			if strings.ToLower(upgrade[i]) == "websocket" {
				return true
			}
		}
	}
	return false
}

func (ctx *RetransmissionHandler) watch(w http.ResponseWriter, r *http.Request, id string) error {
	stream, ok := ctx.Readable(id)
	if !ok {
		switch server, err := ctx.GetStreamServer(id); err {
		case ErrStreamNotHere:
			if wantsWebsocket(r) {
				// simply redirecting won't do -- browsers will throw an error.
				websocket.Handler(func(ws *websocket.Conn) {
					RPCPushEvent(ws, "RPC.Redirect", "//"+server+r.URL.Path)
				}).ServeHTTP(w, r)
				return nil
			}
			http.Redirect(w, r, "//"+server+r.URL.Path, http.StatusTemporaryRedirect)
			return nil
		case ErrStreamOffline, nil:
			return RenderError(w, http.StatusNotFound, "Stream offline.")
		case ErrStreamNotExist:
			return RenderError(w, http.StatusNotFound, "Invalid stream name.")
		default:
			return err
		}
	}

	if wantsWebsocket(r) {
		auth, err := ctx.GetAuthInfo(r)
		if err != nil && err != ErrUserNotExist {
			return err
		}
		websocket.Handler(func(ws *websocket.Conn) {
			ctx.chatLock.Lock()
			chat, ok := ctx.chats[id]
			if !ok {
				chat = NewChat(20)
				ctx.chats[id] = chat
			}
			ctx.chatLock.Unlock()
			chat.RunRPC(ws, auth)
		}).ServeHTTP(w, r)
		return nil
	}

	header := w.Header()
	header.Set("Access-Control-Allow-Origin", "*")
	header.Set("Cache-Control", "no-cache")
	header.Set("Content-Type", "video/webm")
	w.WriteHeader(http.StatusOK)

	ch := make(chan []byte, 60)
	defer close(ch)

	stream.Connect(ch, false)
	defer stream.Disconnect(ch)

	for chunk := range ch {
		if _, err := w.Write(chunk); err != nil || stream.Closed {
			break
		}
	}
	return nil
}

func (ctx *RetransmissionHandler) stream(w http.ResponseWriter, r *http.Request, id string) error {
	switch err := ctx.StartStream(id, r.URL.RawQuery); err {
	case ErrInvalidToken:
		return RenderError(w, http.StatusForbidden, "Invalid token.")
	case ErrStreamNotExist:
		return RenderError(w, http.StatusNotFound, "Invalid stream ID.")
	case ErrStreamNotHere:
		return RenderError(w, http.StatusBadRequest, "Wrong server.")
	default:
		return err
	case nil:
	}

	stream, ok := ctx.Writable(id)
	if !ok {
		return RenderError(w, http.StatusForbidden, "Stream ID already taken.")
	}
	defer stream.Close()

	buffer := [16384]byte{}
	for {
		n, err := r.Body.Read(buffer[:])
		if n != 0 {
			if _, err := stream.Write(buffer[:n]); err != nil {
				stream.Reset()
				return RenderError(w, http.StatusBadRequest, err.Error())
			}
		}
		if err != nil {
			w.WriteHeader(http.StatusNoContent)
			return nil
		}
	}
}
<commit_msg>Display an error when trying to follow the "publish" link.<commit_after>// POST /stream/<name> or PUT /stream/<name>
//     Broadcast a WebM video/audio file.
//
//     Accepted input: valid WebM split into arbitrarily many requests in absolutely
//     any way. Multiple files can be concatenated into a single stream as long as they
//     contain exactly the same tracks (i.e. their number, codecs, and dimensions.
//     Otherwise any connected decoders will error and have to restart. Changing,
//     for example, bitrate or tags is fine.)
//
// GET /stream/<name>
//     Receive a published WebM stream. Note that the server makes no attempt
//     at buffering; if the stream is being broadcast faster than its native framerate,
//     the client will have to buffer and/or drop frames.
//
// GET /stream/<name> [Upgrade: websocket]
//     Connect to a JSON-RPC v2.0 node.
//
//     Methods of `Chat`:
//
//        * `SetName(string)`: assign a (unique) name to this client. This is required to...
//        * `SendMessage(string)`: broadcast a simple text message to all viewers.
//        * `RequestHistory()`: ask the server to emit notifications containing the last
//          few broadcasted text messages.
//
//     TODO Methods of `Stream`.
//
//     Notifications:
//
//        * `Chat.AcquiredName(user string)`: upon a successful `SetName`.
//          May be emitted automatically at the start of a connection if already logged in.
//        * `Chat.Message(user string, text string)`: a broadcasted text message.
//
package main

import (
	"golang.org/x/net/websocket"
	"log"
	"net/http"
	"strings"
	"sync"
)

type RetransmissionHandler struct {
	BroadcastSet
	chatLock sync.Mutex
	chats    map[string]*Chat
	*Context
}

func NewRetransmissionHandler(c *Context) *RetransmissionHandler {
	ctx := &RetransmissionHandler{chats: make(map[string]*Chat), Context: c}
	ctx.Timeout = c.StreamKeepAlive
	ctx.OnStreamClose = func(id string) {
		ctx.chatLock.Lock()
		if chat, ok := ctx.chats[id]; ok {
			chat.Close()
			delete(ctx.chats, id)
		}
		ctx.chatLock.Unlock()
		if err := ctx.StopStream(id); err != nil {
			log.Println("Error stopping the stream: ", err)
		}
	}
	ctx.OnStreamTrackInfo = func(id string, info *StreamTrackInfo) {
		if err := ctx.SetStreamTrackInfo(id, info); err != nil {
			log.Println("Error setting stream metadata: ", err)
		}
	}
	return ctx
}

func (ctx *RetransmissionHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) error {
	switch {
	case r.URL.Path == "/stream/" || strings.ContainsRune(r.URL.Path[8:], '/'):
		return RenderError(w, http.StatusNotFound, "")
	case r.Method == "GET":
		return ctx.watch(w, r, r.URL.Path[8:])
	case r.Method == "POST" || r.Method == "PUT":
		return ctx.stream(w, r, r.URL.Path[8:])
	default:
		return RenderInvalidMethod(w, "GET, PUT, POST")
	}
}

func wantsWebsocket(r *http.Request) bool {
	if upgrade, ok := r.Header["Upgrade"]; ok {
		for i := range upgrade {
			if strings.ToLower(upgrade[i]) == "websocket" {
				return true
			}
		}
	}
	return false
}

func (ctx *RetransmissionHandler) watch(w http.ResponseWriter, r *http.Request, id string) error {
	if r.URL.RawQuery != "" {
		return RenderError(w, http.StatusBadRequest, "Send WebMs here, watch using the other links.")
	}

	stream, ok := ctx.Readable(id)
	if !ok {
		switch server, err := ctx.GetStreamServer(id); err {
		case ErrStreamNotHere:
			if wantsWebsocket(r) {
				// simply redirecting won't do -- browsers will throw an error.
				websocket.Handler(func(ws *websocket.Conn) {
					RPCPushEvent(ws, "RPC.Redirect", "//"+server+r.URL.Path)
				}).ServeHTTP(w, r)
				return nil
			}
			http.Redirect(w, r, "//"+server+r.URL.Path, http.StatusTemporaryRedirect)
			return nil
		case ErrStreamOffline, nil:
			return RenderError(w, http.StatusNotFound, "Stream offline.")
		case ErrStreamNotExist:
			return RenderError(w, http.StatusNotFound, "Invalid stream name.")
		default:
			return err
		}
	}

	if wantsWebsocket(r) {
		auth, err := ctx.GetAuthInfo(r)
		if err != nil && err != ErrUserNotExist {
			return err
		}
		websocket.Handler(func(ws *websocket.Conn) {
			ctx.chatLock.Lock()
			chat, ok := ctx.chats[id]
			if !ok {
				chat = NewChat(20)
				ctx.chats[id] = chat
			}
			ctx.chatLock.Unlock()
			chat.RunRPC(ws, auth)
		}).ServeHTTP(w, r)
		return nil
	}

	header := w.Header()
	header.Set("Access-Control-Allow-Origin", "*")
	header.Set("Cache-Control", "no-cache")
	header.Set("Content-Type", "video/webm")
	w.WriteHeader(http.StatusOK)

	ch := make(chan []byte, 60)
	defer close(ch)

	stream.Connect(ch, false)
	defer stream.Disconnect(ch)

	for chunk := range ch {
		if _, err := w.Write(chunk); err != nil || stream.Closed {
			break
		}
	}
	return nil
}

func (ctx *RetransmissionHandler) stream(w http.ResponseWriter, r *http.Request, id string) error {
	switch err := ctx.StartStream(id, r.URL.RawQuery); err {
	case ErrInvalidToken:
		return RenderError(w, http.StatusForbidden, "Invalid token.")
	case ErrStreamNotExist:
		return RenderError(w, http.StatusNotFound, "Invalid stream ID.")
	case ErrStreamNotHere:
		return RenderError(w, http.StatusBadRequest, "Wrong server.")
	default:
		return err
	case nil:
	}

	stream, ok := ctx.Writable(id)
	if !ok {
		return RenderError(w, http.StatusForbidden, "Stream ID already taken.")
	}
	defer stream.Close()

	buffer := [16384]byte{}
	for {
		n, err := r.Body.Read(buffer[:])
		if n != 0 {
			if _, err := stream.Write(buffer[:n]); err != nil {
				stream.Reset()
				return RenderError(w, http.StatusBadRequest, err.Error())
			}
		}
		if err != nil {
			w.WriteHeader(http.StatusNoContent)
			return nil
		}
	}
}
<|endoftext|>


<commit_before><commit_msg>fix package name<commit_after><|endoftext|>


<commit_before>package wikidump

import (
	"bytes"
	"golang.org/x/text/unicode/norm"
	"html"
	"regexp"
	"strings"
	"unicode"
	"unicode/utf8"
)

var (
	special  = regexp.MustCompile(`{{|{\||\|}|}}|<[a-z][a-z0-9 "=]*/?>|</[a-z]+>`)
	starttag = regexp.MustCompile(`<[a-z].*>`)
	endtag   = regexp.MustCompile(`</[a-z]+>`)
)

// Get rid of tables, template calls, quasi-XML. Throws away their content.
//
// Assumes tables, templates and tags are properly nested, except for spurious
// end-of-{table,template,element} tags, which are ignored.
func Cleanup(s string) string {
	var depth int
	output := bytes.NewBuffer(make([]byte, 0, len(s)))

	for {
		next := special.FindStringIndex(s)
		if next == nil {
			if depth == 0 {
				output.WriteString(s)
			}
			break
		}
		i, j := next[0], next[1]

		if depth == 0 {
			output.WriteString(s[:i])
		}

		tag := s[i:j]
		switch {
		case tag == "{{":
			depth++
		case tag == "{|":
			depth++
		case starttag.MatchString(tag):
			depth++
		case tag == "}}":
			fallthrough
		case tag == "|}":
			fallthrough
		case endtag.MatchString(tag):
			if depth > 0 {
				depth--
			}
		}

		s = s[j:]
	}
	return norm.NFC.String(html.UnescapeString(output.String()))
}

type Link struct {
	Anchor, Target string
}

var (
	linkRE     = regexp.MustCompile(`(\w*)\[\[([^]]+)\]\](\w*)`)
	whitespace = regexp.MustCompile(`[\s_]+`)
)

func normSpace(s string) string {
	s = whitespace.ReplaceAllString(s, " ")
	return strings.TrimSpace(s)
}

// Extract all the wikilinks from s. Returns a frequency table.
func ExtractLinks(s string) map[Link]int {
	freq := make(map[Link]int)

	for _, candidate := range linkRE.FindAllStringSubmatch(s, -1) {
		before, l, after := candidate[1], candidate[2], candidate[3]

		var target, anchor string
		if pipe := strings.IndexByte(l, '|'); pipe != -1 {
			target, anchor = l[:pipe], l[pipe+1:]
		} else {
			target = l
			anchor = l
		}

		// If the anchor contains a colon, assume it's a file or category link.
		// XXX Maybe skip matches for `:\s`? Proper solution would parse the
		// dump to find non-main namespace prefixes.
		if strings.IndexByte(target, ':') != -1 {
			continue
		}

		// Remove section links.
		if hash := strings.IndexByte(target, '#'); hash == 0 {
			continue
		} else if hash != -1 {
			target = target[:hash]
		}

		// Normalize to the format used in <redirect> elements:
		// uppercase first character, spaces instead of underscores.
		target = normSpace(target)
		first, size := utf8.DecodeRuneInString(target)
		// XXX Upper case or title case? Should look up the difference...
		if !unicode.IsUpper(first) {
			target = string(unicode.ToUpper(first)) + target[size:]
		}

		anchor = before + anchor + after
		freq[Link{anchor, target}]++
	}
	return freq
}
<commit_msg>tiny micro-optimizations<commit_after>package wikidump

import (
	"bytes"
	"golang.org/x/text/unicode/norm"
	"html"
	"regexp"
	"strings"
	"unicode"
	"unicode/utf8"
)

var (
	special  = regexp.MustCompile(`{{|{\||\|}|}}|<[a-z][a-z0-9 "=]*/?>|</[a-z]+>`)
	starttag = regexp.MustCompile(`<[a-z].*>`)
	endtag   = regexp.MustCompile(`</[a-z]+>`)
)

// Get rid of tables, template calls, quasi-XML. Throws away their content.
//
// Assumes tables, templates and tags are properly nested, except for spurious
// end-of-{table,template,element} tags, which are ignored.
func Cleanup(s string) string {
	var depth int
	output := bytes.NewBuffer(make([]byte, 0, len(s)))

	for {
		next := special.FindStringIndex(s)
		if next == nil {
			if depth == 0 {
				output.WriteString(s)
			}
			break
		}
		i, j := next[0], next[1]

		if depth == 0 {
			output.WriteString(s[:i])
		}

		tag := s[i:j]
		switch {
		case tag == "{{":
			depth++
		case tag == "{|":
			depth++
		case starttag.MatchString(tag):
			depth++
		case tag == "}}":
			fallthrough
		case tag == "|}":
			fallthrough
		case endtag.MatchString(tag):
			if depth > 0 {
				depth--
			}
		}

		s = s[j:]
	}
	return norm.NFC.String(html.UnescapeString(output.String()))
}

type Link struct {
	Anchor, Target string
}

var (
	linkRE     = regexp.MustCompile(`(\w*)\[\[([^]]+)\]\](\w*)`)
	whitespace = regexp.MustCompile(`[\s_]+`)
)

func normSpace(s string) string {
	s = whitespace.ReplaceAllLiteralString(s, " ")
	return strings.TrimSpace(s)
}

// Extract all the wikilinks from s. Returns a frequency table.
func ExtractLinks(s string) map[Link]int {
	freq := make(map[Link]int)

	for _, candidate := range linkRE.FindAllStringSubmatch(s, -1) {
		before, l, after := candidate[1], candidate[2], candidate[3]

		var target, anchor string
		if pipe := strings.IndexByte(l, '|'); pipe != -1 {
			target, anchor = l[:pipe], l[pipe+1:]
		} else {
			target = l
			anchor = l
		}

		// If the anchor contains a colon, assume it's a file or category link.
		// XXX Maybe skip matches for `:\s`? Proper solution would parse the
		// dump to find non-main namespace prefixes.
		if strings.IndexByte(target, ':') != -1 {
			continue
		}

		// Remove section links.
		if hash := strings.IndexByte(target, '#'); hash == 0 {
			continue
		} else if hash != -1 {
			target = target[:hash]
		}

		// Normalize to the format used in <redirect> elements:
		// uppercase first character, spaces instead of underscores.
		target = normSpace(target)
		first, size := utf8.DecodeRuneInString(target)
		// XXX Upper case or title case? Should look up the difference...
		if unicode.IsLower(first) {
			target = string(unicode.ToUpper(first)) + target[size:]
		}

		anchor = before + anchor + after
		freq[Link{anchor, target}]++
	}
	return freq
}
<|endoftext|>


<commit_before>// Copyright 2015 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 lease

import (
	"fmt"
	"io"
	"log"
	"sync"
)

// Create a ReadLease that never expires, unless voluntarily revoked or
// upgraded.
//
// The supplied function will be used to obtain the read lease contents, the
// first time and whenever the supplied file leaser decides to expire the
// temporary copy thus obtained. It must return the same contents every time,
// and the contents must be of the given size.
//
// This magic is not preserved after the lease is upgraded.
func NewAutoRefreshingReadLease(
	fl FileLeaser,
	size int64,
	f func() (io.ReadCloser, error)) (rl ReadLease) {
	rl = &autoRefreshingReadLease{
		leaser: fl,
		size:   size,
		f:      f,
	}

	return
}

type autoRefreshingReadLease struct {
	mu sync.Mutex

	/////////////////////////
	// Constant data
	/////////////////////////

	size int64

	/////////////////////////
	// Dependencies
	/////////////////////////

	leaser FileLeaser
	f      func() (io.ReadCloser, error)

	/////////////////////////
	// Mutable state
	/////////////////////////

	// The current wrapped lease, or nil if one has never been issued.
	//
	// GUARDED_BY(mu)
	wrapped ReadLease
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

// Attempt to clean up after the supplied read/write lease.
func destroyReadWriteLease(rwl ReadWriteLease) {
	var err error
	defer func() {
		if err != nil {
			log.Printf("Error destroying read/write lease: %v", err)
		}
	}()

	// Downgrade to a read lease.
	rl, err := rwl.Downgrade()
	if err != nil {
		err = fmt.Errorf("Downgrade: %v", err)
		return
	}

	// Revoke the read lease.
	rl.Revoke()
}

// Set up a read/write lease and fill in our contents.
//
// REQUIRES: The caller has observed that rl.lease has expired.
//
// LOCKS_REQUIRED(rl.mu)
func (rl *autoRefreshingReadLease) getContents() (
	rwl ReadWriteLease, err error) {
	// Obtain some space to write the contents.
	rwl, err = rl.leaser.NewFile()
	if err != nil {
		err = fmt.Errorf("NewFile: %v", err)
		return
	}

	// Attempt to clean up if we exit early.
	defer func() {
		if err != nil {
			destroyReadWriteLease(rwl)
		}
	}()

	// Obtain the reader for our contents.
	rc, err := rl.f()
	if err != nil {
		err = fmt.Errorf("User function: %v", err)
		return
	}

	defer func() {
		closeErr := rc.Close()
		if closeErr != nil && err == nil {
			err = fmt.Errorf("Close: %v", closeErr)
		}
	}()

	// Copy into the read/write lease.
	copied, err := io.Copy(rwl, rc)
	if err != nil {
		err = fmt.Errorf("Copy: %v", err)
		return
	}

	// Did the user lie about the size?
	if copied != rl.Size() {
		err = fmt.Errorf("Copied %v bytes; expected %v", copied, rl.Size())
		return
	}

	return
}

// Downgrade and save the supplied read/write lease obtained with getContents
// for later use.
//
// LOCKS_REQUIRED(rl.mu)
func (rl *autoRefreshingReadLease) saveContents(rwl ReadWriteLease) {
	downgraded, err := rwl.Downgrade()
	if err != nil {
		log.Printf("Failed to downgrade write lease (%q); abandoning.", err.Error())
		return
	}

	rl.wrapped = downgraded
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////

func (rl *autoRefreshingReadLease) Read(p []byte) (n int, err error) {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	// Common case: is the existing lease still valid?
	if rl.wrapped != nil {
		panic("TODO")
	}

	// Get hold of a read/write lease containing our contents.
	rwl, err := rl.getContents()
	if err != nil {
		err = fmt.Errorf("getContents: %v", err)
		return
	}

	defer rl.saveContents(rwl)

	// Serve from the read/write lease.
	n, err = rwl.Read(p)

	return
}

func (rl *autoRefreshingReadLease) Seek(
	offset int64,
	whence int) (off int64, err error) {
	panic("TODO")
}

func (rl *autoRefreshingReadLease) ReadAt(p []byte, off int64) (n int, err error) {
	panic("TODO")
}

func (rl *autoRefreshingReadLease) Size() (size int64) {
	size = rl.size
	return
}

func (rl *autoRefreshingReadLease) Revoked() (revoked bool) {
	panic("TODO")
}

func (rl *autoRefreshingReadLease) Upgrade() (rwl ReadWriteLease, err error) {
	panic("TODO")
}

func (rl *autoRefreshingReadLease) Revoke() {
	panic("TODO")
}
<commit_msg>autoRefreshingReadLease.ReadAt<commit_after>// Copyright 2015 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 lease

import (
	"fmt"
	"io"
	"log"
	"sync"
)

// Create a ReadLease that never expires, unless voluntarily revoked or
// upgraded.
//
// The supplied function will be used to obtain the read lease contents, the
// first time and whenever the supplied file leaser decides to expire the
// temporary copy thus obtained. It must return the same contents every time,
// and the contents must be of the given size.
//
// This magic is not preserved after the lease is upgraded.
func NewAutoRefreshingReadLease(
	fl FileLeaser,
	size int64,
	f func() (io.ReadCloser, error)) (rl ReadLease) {
	rl = &autoRefreshingReadLease{
		leaser: fl,
		size:   size,
		f:      f,
	}

	return
}

type autoRefreshingReadLease struct {
	mu sync.Mutex

	/////////////////////////
	// Constant data
	/////////////////////////

	size int64

	/////////////////////////
	// Dependencies
	/////////////////////////

	leaser FileLeaser
	f      func() (io.ReadCloser, error)

	/////////////////////////
	// Mutable state
	/////////////////////////

	// The current wrapped lease, or nil if one has never been issued.
	//
	// GUARDED_BY(mu)
	wrapped ReadLease
}

////////////////////////////////////////////////////////////////////////
// Helpers
////////////////////////////////////////////////////////////////////////

// Attempt to clean up after the supplied read/write lease.
func destroyReadWriteLease(rwl ReadWriteLease) {
	var err error
	defer func() {
		if err != nil {
			log.Printf("Error destroying read/write lease: %v", err)
		}
	}()

	// Downgrade to a read lease.
	rl, err := rwl.Downgrade()
	if err != nil {
		err = fmt.Errorf("Downgrade: %v", err)
		return
	}

	// Revoke the read lease.
	rl.Revoke()
}

// Set up a read/write lease and fill in our contents.
//
// REQUIRES: The caller has observed that rl.lease has expired.
//
// LOCKS_REQUIRED(rl.mu)
func (rl *autoRefreshingReadLease) getContents() (
	rwl ReadWriteLease, err error) {
	// Obtain some space to write the contents.
	rwl, err = rl.leaser.NewFile()
	if err != nil {
		err = fmt.Errorf("NewFile: %v", err)
		return
	}

	// Attempt to clean up if we exit early.
	defer func() {
		if err != nil {
			destroyReadWriteLease(rwl)
		}
	}()

	// Obtain the reader for our contents.
	rc, err := rl.f()
	if err != nil {
		err = fmt.Errorf("User function: %v", err)
		return
	}

	defer func() {
		closeErr := rc.Close()
		if closeErr != nil && err == nil {
			err = fmt.Errorf("Close: %v", closeErr)
		}
	}()

	// Copy into the read/write lease.
	copied, err := io.Copy(rwl, rc)
	if err != nil {
		err = fmt.Errorf("Copy: %v", err)
		return
	}

	// Did the user lie about the size?
	if copied != rl.Size() {
		err = fmt.Errorf("Copied %v bytes; expected %v", copied, rl.Size())
		return
	}

	return
}

// Downgrade and save the supplied read/write lease obtained with getContents
// for later use.
//
// LOCKS_REQUIRED(rl.mu)
func (rl *autoRefreshingReadLease) saveContents(rwl ReadWriteLease) {
	downgraded, err := rwl.Downgrade()
	if err != nil {
		log.Printf("Failed to downgrade write lease (%q); abandoning.", err.Error())
		return
	}

	rl.wrapped = downgraded
}

////////////////////////////////////////////////////////////////////////
// Public interface
////////////////////////////////////////////////////////////////////////

func (rl *autoRefreshingReadLease) Read(p []byte) (n int, err error) {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	// Common case: is the existing lease still valid?
	if rl.wrapped != nil {
		panic("TODO")
	}

	// Get hold of a read/write lease containing our contents.
	rwl, err := rl.getContents()
	if err != nil {
		err = fmt.Errorf("getContents: %v", err)
		return
	}

	defer rl.saveContents(rwl)

	// Serve from the read/write lease.
	n, err = rwl.Read(p)

	return
}

func (rl *autoRefreshingReadLease) Seek(
	offset int64,
	whence int) (off int64, err error) {
	panic("TODO")
}

func (rl *autoRefreshingReadLease) ReadAt(
	p []byte,
	off int64) (n int, err error) {
	rl.mu.Lock()
	defer rl.mu.Unlock()

	// Common case: is the existing lease still valid?
	if rl.wrapped != nil {
		panic("TODO")
	}

	// Get hold of a read/write lease containing our contents.
	rwl, err := rl.getContents()
	if err != nil {
		err = fmt.Errorf("getContents: %v", err)
		return
	}

	defer rl.saveContents(rwl)

	// Serve from the read/write lease.
	n, err = rwl.ReadAt(p, off)

	return
}

func (rl *autoRefreshingReadLease) Size() (size int64) {
	size = rl.size
	return
}

func (rl *autoRefreshingReadLease) Revoked() (revoked bool) {
	panic("TODO")
}

func (rl *autoRefreshingReadLease) Upgrade() (rwl ReadWriteLease, err error) {
	panic("TODO")
}

func (rl *autoRefreshingReadLease) Revoke() {
	panic("TODO")
}
<|endoftext|>


<commit_before>package main

import (
	"bytes"
	"crypto/rand"
	"encoding/base64"
	"encoding/gob"
	"io"
	"log"
	"net"
	"os"

	"github.com/dotcloud/docker/term"
	"github.com/flynn/go-discover/discover"
	"github.com/flynn/lorne/types"
	"github.com/flynn/sampi/client"
	"github.com/flynn/sampi/types"
	"github.com/titanous/go-dockerclient"
)

func main() {
	disc, err := discover.NewClient()
	if err != nil {
		log.Fatal(err)
	}

	scheduler, err := client.New()
	if err != nil {
		log.Fatal(err)
	}

	state, err := scheduler.State()
	if err != nil {
		log.Fatal(err)
	}

	var firstHost string
	for k := range state {
		firstHost = k
		break
	}
	if firstHost == "" {
		log.Fatal("no hosts")
	}

	id := randomID()

	services := disc.Services("flynn-lorne-attach." + firstHost)
	conn, err := net.Dial("tcp", services.OnlineAddrs()[0])
	if err != nil {
		log.Fatal(err)
	}
	ws, _ := term.GetWinsize(os.Stdin.Fd())
	err = gob.NewEncoder(conn).Encode(&lorne.AttachReq{
		JobID:  id,
		Flags:  lorne.AttachFlagStdout | lorne.AttachFlagStderr | lorne.AttachFlagStdin | lorne.AttachFlagStream,
		Height: int(ws.Height),
		Width:  int(ws.Width),
	})
	if err != nil {
		log.Fatal(err)
	}
	attachState := make([]byte, 1)
	if _, err := conn.Read(attachState); err != nil {
		log.Fatal(err)
	}
	switch attachState[0] {
	case lorne.AttachWaiting:
		log.Print("attach waiting")
	case lorne.AttachError:
		log.Fatal("attach error")
	}

	schedReq := &sampi.ScheduleReq{
		Incremental: true,
		HostJobs: map[string][]*sampi.Job{firstHost: {{ID: id, Config: &docker.Config{
			Image:        "titanous/redis",
			Cmd:          []string{"/redis/src/redis-cli", "-h", "10.0.2.15"},
			Tty:          true,
			AttachStdin:  true,
			AttachStdout: true,
			AttachStderr: true,
			OpenStdin:    true,
			StdinOnce:    true,
		}}}},
	}
	if _, err := scheduler.Schedule(schedReq); err != nil {
		log.Fatal(err)
	}

	if _, err := conn.Read(attachState); err != nil {
		log.Fatal(err)
	}

	oldState, err := term.SetRawTerminal(os.Stdin.Fd())
	if err != nil {
		log.Fatal(err)
	}
	go io.Copy(conn, os.Stdin)
	if _, err := io.Copy(os.Stdout, conn); err != nil {
		log.Fatal(err)
	}
	term.RestoreTerminal(os.Stdin.Fd(), oldState)
}

func randomID() string {
	b := make([]byte, 16)
	enc := make([]byte, 24)
	_, err := io.ReadFull(rand.Reader, b)
	if err != nil {
		panic(err) // This shouldn't ever happen, right?
	}
	base64.URLEncoding.Encode(enc, b)
	return string(bytes.TrimRight(enc, "="))
}
<commit_msg>sampi: example: Run bash instead of redis-cli<commit_after>package main

import (
	"bytes"
	"crypto/rand"
	"encoding/base64"
	"encoding/gob"
	"io"
	"log"
	"net"
	"os"
	"strconv"

	"github.com/dotcloud/docker/term"
	"github.com/flynn/go-discover/discover"
	"github.com/flynn/lorne/types"
	"github.com/flynn/sampi/client"
	"github.com/flynn/sampi/types"
	"github.com/titanous/go-dockerclient"
)

func main() {
	disc, err := discover.NewClient()
	if err != nil {
		log.Fatal(err)
	}

	scheduler, err := client.New()
	if err != nil {
		log.Fatal(err)
	}

	state, err := scheduler.State()
	if err != nil {
		log.Fatal(err)
	}

	var firstHost string
	for k := range state {
		firstHost = k
		break
	}
	if firstHost == "" {
		log.Fatal("no hosts")
	}

	id := randomID()

	services := disc.Services("flynn-lorne-attach." + firstHost)
	conn, err := net.Dial("tcp", services.OnlineAddrs()[0])
	if err != nil {
		log.Fatal(err)
	}
	ws, _ := term.GetWinsize(os.Stdin.Fd())
	err = gob.NewEncoder(conn).Encode(&lorne.AttachReq{
		JobID:  id,
		Flags:  lorne.AttachFlagStdout | lorne.AttachFlagStderr | lorne.AttachFlagStdin | lorne.AttachFlagStream,
		Height: int(ws.Height),
		Width:  int(ws.Width),
	})
	if err != nil {
		log.Fatal(err)
	}
	attachState := make([]byte, 1)
	if _, err := conn.Read(attachState); err != nil {
		log.Fatal(err)
	}
	switch attachState[0] {
	case lorne.AttachError:
		log.Fatal("attach error")
	}

	schedReq := &sampi.ScheduleReq{
		Incremental: true,
		HostJobs: map[string][]*sampi.Job{firstHost: {{ID: id, Config: &docker.Config{
			Image:        "titanous/redis",
			Cmd:          []string{"/bin/bash", "-i"},
			Tty:          true,
			AttachStdin:  true,
			AttachStdout: true,
			AttachStderr: true,
			OpenStdin:    true,
			StdinOnce:    true,
			Env: []string{
				"COLUMNS=" + strconv.Itoa(int(ws.Width)),
				"LINES=" + strconv.Itoa(int(ws.Height)),
				"TERM=" + os.Getenv("TERM"),
			},
		}}}},
	}
	if _, err := scheduler.Schedule(schedReq); err != nil {
		log.Fatal(err)
	}

	if _, err := conn.Read(attachState); err != nil {
		log.Fatal(err)
	}

	oldState, err := term.SetRawTerminal(os.Stdin.Fd())
	if err != nil {
		log.Fatal(err)
	}
	go io.Copy(conn, os.Stdin)
	if _, err := io.Copy(os.Stdout, conn); err != nil {
		log.Fatal(err)
	}
	term.RestoreTerminal(os.Stdin.Fd(), oldState)
}

func randomID() string {
	b := make([]byte, 16)
	enc := make([]byte, 24)
	_, err := io.ReadFull(rand.Reader, b)
	if err != nil {
		panic(err) // This shouldn't ever happen, right?
	}
	base64.URLEncoding.Encode(enc, b)
	return string(bytes.TrimRight(enc, "="))
}
<|endoftext|>


<commit_before>// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package build

import (
	"crypto/hmac"
	"fmt"
	"http"
	"json"
	"os"

	"appengine"
	"appengine/datastore"
	"cache"
)

const commitsPerPage = 30

// defaultPackages specifies the Package records to be created by initHandler.
var defaultPackages = []*Package{
	&Package{Name: "Go"},
}

// commitHandler retrieves commit data or records a new commit.
//
// For GET requests it returns a Commit value for the specified
// packagePath and hash.
//
// For POST requests it reads a JSON-encoded Commit value from the request
// body and creates a new Commit entity. It also updates the "tip" Tag for
// each new commit at tip.
//
// This handler is used by a gobuilder process in -commit mode.
func commitHandler(r *http.Request) (interface{}, os.Error) {
	c := appengine.NewContext(r)
	com := new(Commit)

	if r.Method == "GET" {
		com.PackagePath = r.FormValue("packagePath")
		com.Hash = r.FormValue("hash")
		if err := datastore.Get(c, com.Key(c), com); err != nil {
			return nil, fmt.Errorf("getting Commit: %v", err)
		}
		return com, nil
	}
	if r.Method != "POST" {
		return nil, errBadMethod(r.Method)
	}

	// POST request
	defer r.Body.Close()
	if err := json.NewDecoder(r.Body).Decode(com); err != nil {
		return nil, fmt.Errorf("decoding Body: %v", err)
	}
	if len(com.Desc) > maxDatastoreStringLen {
		com.Desc = com.Desc[:maxDatastoreStringLen]
	}
	if err := com.Valid(); err != nil {
		return nil, fmt.Errorf("validating Commit: %v", err)
	}
	defer cache.Tick(c)
	tx := func(c appengine.Context) os.Error {
		return addCommit(c, com)
	}
	return nil, datastore.RunInTransaction(c, tx, nil)
}

// addCommit adds the Commit entity to the datastore and updates the tip Tag.
// It must be run inside a datastore transaction.
func addCommit(c appengine.Context, com *Commit) os.Error {
	var tc Commit // temp value so we don't clobber com
	err := datastore.Get(c, com.Key(c), &tc)
	if err != datastore.ErrNoSuchEntity {
		// if this commit is already in the datastore, do nothing
		if err == nil {
			return nil
		}
		return fmt.Errorf("getting Commit: %v", err)
	}
	// get the next commit number
	p, err := GetPackage(c, com.PackagePath)
	if err != nil {
		return fmt.Errorf("GetPackage: %v", err)
	}
	com.Num = p.NextNum
	p.NextNum++
	if _, err := datastore.Put(c, p.Key(c), p); err != nil {
		return fmt.Errorf("putting Package: %v", err)
	}
	// if this isn't the first Commit test the parent commit exists
	if com.Num > 0 {
		n, err := datastore.NewQuery("Commit").
			Filter("Hash =", com.ParentHash).
			Ancestor(p.Key(c)).
			Count(c)
		if err != nil {
			return fmt.Errorf("testing for parent Commit: %v", err)
		}
		if n == 0 {
			return os.NewError("parent commit not found")
		}
	}
	// update the tip Tag if this is the Go repo
	if p.Path == "" {
		t := &Tag{Kind: "tip", Hash: com.Hash}
		if _, err = datastore.Put(c, t.Key(c), t); err != nil {
			return fmt.Errorf("putting Tag: %v", err)
		}
	}
	// put the Commit
	if _, err = datastore.Put(c, com.Key(c), com); err != nil {
		return fmt.Errorf("putting Commit: %v", err)
	}
	return nil
}

// tagHandler records a new tag. It reads a JSON-encoded Tag value from the
// request body and updates the Tag entity for the Kind of tag provided.
//
// This handler is used by a gobuilder process in -commit mode.
func tagHandler(r *http.Request) (interface{}, os.Error) {
	if r.Method != "POST" {
		return nil, errBadMethod(r.Method)
	}

	t := new(Tag)
	defer r.Body.Close()
	if err := json.NewDecoder(r.Body).Decode(t); err != nil {
		return nil, err
	}
	if err := t.Valid(); err != nil {
		return nil, err
	}
	c := appengine.NewContext(r)
	defer cache.Tick(c)
	_, err := datastore.Put(c, t.Key(c), t)
	return nil, err
}

// Todo is a todoHandler response.
type Todo struct {
	Kind string // "build-go-commit" or "build-package"
	Data interface{}
}

// todoHandler returns the next action to be performed by a builder.
// It expects "builder" and "kind" query parameters and returns a *Todo value.
// Multiple "kind" parameters may be specified.
func todoHandler(r *http.Request) (interface{}, os.Error) {
	c := appengine.NewContext(r)
	now := cache.Now(c)
	key := "build-todo-" + r.Form.Encode()
	cachedTodo := new(Todo)
	if cache.Get(r, now, key, cachedTodo) {
		return cachedTodo, nil
	}
	var todo *Todo
	var err os.Error
	builder := r.FormValue("builder")
	for _, kind := range r.Form["kind"] {
		var data interface{}
		switch kind {
		case "build-go-commit":
			data, err = buildTodo(c, builder, "", "")
		case "build-package":
			packagePath := r.FormValue("packagePath")
			goHash := r.FormValue("goHash")
			data, err = buildTodo(c, builder, packagePath, goHash)
		}
		if data != nil || err != nil {
			todo = &Todo{Kind: kind, Data: data}
			break
		}
	}
	if err == nil {
		cache.Set(r, now, key, todo)
	}
	return todo, err
}

// buildTodo returns the next Commit to be built (or nil if none available).
//
// If packagePath and goHash are empty, it scans the first 20 Go Commits in
// Num-descending order and returns the first one it finds that doesn't have a
// Result for this builder.
//
// If provided with non-empty packagePath and goHash args, it scans the first
// 20 Commits in Num-descending order for the specified packagePath and
// returns the first that doesn't have a Result for this builder and goHash.
func buildTodo(c appengine.Context, builder, packagePath, goHash string) (interface{}, os.Error) {
	p, err := GetPackage(c, packagePath)
	if err != nil {
		return nil, err
	}

	t := datastore.NewQuery("Commit").
		Ancestor(p.Key(c)).
		Limit(commitsPerPage).
		Order("-Num").
		Run(c)
	for {
		com := new(Commit)
		if _, err := t.Next(com); err != nil {
			if err == datastore.Done {
				err = nil
			}
			return nil, err
		}
		if com.Result(builder, goHash) == nil {
			return com, nil
		}
	}
	panic("unreachable")
}

// packagesHandler returns a list of the non-Go Packages monitored
// by the dashboard.
func packagesHandler(r *http.Request) (interface{}, os.Error) {
	c := appengine.NewContext(r)
	now := cache.Now(c)
	const key = "build-packages"
	var p []*Package
	if cache.Get(r, now, key, &p) {
		return p, nil
	}
	p, err := Packages(c)
	if err != nil {
		return nil, err
	}
	cache.Set(r, now, key, p)
	return p, nil
}

// resultHandler records a build result.
// It reads a JSON-encoded Result value from the request body,
// creates a new Result entity, and updates the relevant Commit entity.
// If the Log field is not empty, resultHandler creates a new Log entity
// and updates the LogHash field before putting the Commit entity.
func resultHandler(r *http.Request) (interface{}, os.Error) {
	if r.Method != "POST" {
		return nil, errBadMethod(r.Method)
	}

	c := appengine.NewContext(r)
	res := new(Result)
	defer r.Body.Close()
	if err := json.NewDecoder(r.Body).Decode(res); err != nil {
		return nil, fmt.Errorf("decoding Body: %v", err)
	}
	if err := res.Valid(); err != nil {
		return nil, fmt.Errorf("validating Result: %v", err)
	}
	defer cache.Tick(c)
	// store the Log text if supplied
	if len(res.Log) > 0 {
		hash, err := PutLog(c, res.Log)
		if err != nil {
			return nil, fmt.Errorf("putting Log: %v", err)
		}
		res.LogHash = hash
	}
	tx := func(c appengine.Context) os.Error {
		// check Package exists
		if _, err := GetPackage(c, res.PackagePath); err != nil {
			return fmt.Errorf("GetPackage: %v", err)
		}
		// put Result
		if _, err := datastore.Put(c, res.Key(c), res); err != nil {
			return fmt.Errorf("putting Result: %v", err)
		}
		// add Result to Commit
		com := &Commit{PackagePath: res.PackagePath, Hash: res.Hash}
		if err := com.AddResult(c, res); err != nil {
			return fmt.Errorf("AddResult: %v", err)
		}
		// Send build failure notifications, if necessary.
		// Note this must run after the call AddResult, which
		// populates the Commit's ResultData field.
		return notifyOnFailure(c, com, res.Builder)
	}
	return nil, datastore.RunInTransaction(c, tx, nil)
}

// logHandler displays log text for a given hash.
// It handles paths like "/log/hash".
func logHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-type", "text/plain")
	c := appengine.NewContext(r)
	hash := r.URL.Path[len("/log/"):]
	key := datastore.NewKey(c, "Log", hash, 0, nil)
	l := new(Log)
	if err := datastore.Get(c, key, l); err != nil {
		logErr(w, r, err)
		return
	}
	b, err := l.Text()
	if err != nil {
		logErr(w, r, err)
		return
	}
	w.Write(b)
}

type dashHandler func(*http.Request) (interface{}, os.Error)

type dashResponse struct {
	Response interface{}
	Error    string
}

// errBadMethod is returned by a dashHandler when
// the request has an unsuitable method.
type errBadMethod string

func (e errBadMethod) String() string {
	return "bad method: " + string(e)
}

// AuthHandler wraps a http.HandlerFunc with a handler that validates the
// supplied key and builder query parameters.
func AuthHandler(h dashHandler) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		c := appengine.NewContext(r)

		// Put the URL Query values into r.Form to avoid parsing the
		// request body when calling r.FormValue.
		r.Form = r.URL.Query()

		var err os.Error
		var resp interface{}

		// Validate key query parameter for POST requests only.
		key := r.FormValue("key")
		builder := r.FormValue("builder")
		if r.Method == "POST" && !validKey(c, key, builder) {
			err = os.NewError("invalid key: " + key)
		}

		// Call the original HandlerFunc and return the response.
		if err == nil {
			resp, err = h(r)
		}

		// Write JSON response.
		dashResp := &dashResponse{Response: resp}
		if err != nil {
			c.Errorf("%v", err)
			dashResp.Error = err.String()
		}
		w.Header().Set("Content-Type", "application/json")
		if err = json.NewEncoder(w).Encode(dashResp); err != nil {
			c.Criticalf("encoding response: %v", err)
		}
	}
}

func initHandler(w http.ResponseWriter, r *http.Request) {
	// TODO(adg): devise a better way of bootstrapping new packages
	c := appengine.NewContext(r)
	defer cache.Tick(c)
	for _, p := range defaultPackages {
		if err := datastore.Get(c, p.Key(c), new(Package)); err == nil {
			continue
		} else if err != datastore.ErrNoSuchEntity {
			logErr(w, r, err)
			return
		}
		if _, err := datastore.Put(c, p.Key(c), p); err != nil {
			logErr(w, r, err)
			return
		}
	}
	fmt.Fprint(w, "OK")
}

func keyHandler(w http.ResponseWriter, r *http.Request) {
	builder := r.FormValue("builder")
	if builder == "" {
		logErr(w, r, os.NewError("must supply builder in query string"))
		return
	}
	c := appengine.NewContext(r)
	fmt.Fprint(w, builderKey(c, builder))
}

func init() {
	// admin handlers
	http.HandleFunc("/init", initHandler)
	http.HandleFunc("/key", keyHandler)

	// authenticated handlers
	http.HandleFunc("/commit", AuthHandler(commitHandler))
	http.HandleFunc("/packages", AuthHandler(packagesHandler))
	http.HandleFunc("/result", AuthHandler(resultHandler))
	http.HandleFunc("/tag", AuthHandler(tagHandler))
	http.HandleFunc("/todo", AuthHandler(todoHandler))

	// public handlers
	http.HandleFunc("/log/", logHandler)
}

func validHash(hash string) bool {
	// TODO(adg): correctly validate a hash
	return hash != ""
}

func validKey(c appengine.Context, key, builder string) bool {
	if appengine.IsDevAppServer() {
		return true
	}
	if key == secretKey(c) {
		return true
	}
	return key == builderKey(c, builder)
}

func builderKey(c appengine.Context, builder string) string {
	h := hmac.NewMD5([]byte(secretKey(c)))
	h.Write([]byte(builder))
	return fmt.Sprintf("%x", h.Sum())
}

func logErr(w http.ResponseWriter, r *http.Request, err os.Error) {
	appengine.NewContext(r).Errorf("Error: %v", err)
	w.WriteHeader(http.StatusInternalServerError)
	fmt.Fprint(w, "Error: ", err)
}
<commit_msg>dashboard: fix todo caching nil<commit_after>// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package build

import (
	"crypto/hmac"
	"fmt"
	"http"
	"json"
	"os"

	"appengine"
	"appengine/datastore"
	"cache"
)

const commitsPerPage = 30

// defaultPackages specifies the Package records to be created by initHandler.
var defaultPackages = []*Package{
	&Package{Name: "Go"},
}

// commitHandler retrieves commit data or records a new commit.
//
// For GET requests it returns a Commit value for the specified
// packagePath and hash.
//
// For POST requests it reads a JSON-encoded Commit value from the request
// body and creates a new Commit entity. It also updates the "tip" Tag for
// each new commit at tip.
//
// This handler is used by a gobuilder process in -commit mode.
func commitHandler(r *http.Request) (interface{}, os.Error) {
	c := appengine.NewContext(r)
	com := new(Commit)

	if r.Method == "GET" {
		com.PackagePath = r.FormValue("packagePath")
		com.Hash = r.FormValue("hash")
		if err := datastore.Get(c, com.Key(c), com); err != nil {
			return nil, fmt.Errorf("getting Commit: %v", err)
		}
		return com, nil
	}
	if r.Method != "POST" {
		return nil, errBadMethod(r.Method)
	}

	// POST request
	defer r.Body.Close()
	if err := json.NewDecoder(r.Body).Decode(com); err != nil {
		return nil, fmt.Errorf("decoding Body: %v", err)
	}
	if len(com.Desc) > maxDatastoreStringLen {
		com.Desc = com.Desc[:maxDatastoreStringLen]
	}
	if err := com.Valid(); err != nil {
		return nil, fmt.Errorf("validating Commit: %v", err)
	}
	defer cache.Tick(c)
	tx := func(c appengine.Context) os.Error {
		return addCommit(c, com)
	}
	return nil, datastore.RunInTransaction(c, tx, nil)
}

// addCommit adds the Commit entity to the datastore and updates the tip Tag.
// It must be run inside a datastore transaction.
func addCommit(c appengine.Context, com *Commit) os.Error {
	var tc Commit // temp value so we don't clobber com
	err := datastore.Get(c, com.Key(c), &tc)
	if err != datastore.ErrNoSuchEntity {
		// if this commit is already in the datastore, do nothing
		if err == nil {
			return nil
		}
		return fmt.Errorf("getting Commit: %v", err)
	}
	// get the next commit number
	p, err := GetPackage(c, com.PackagePath)
	if err != nil {
		return fmt.Errorf("GetPackage: %v", err)
	}
	com.Num = p.NextNum
	p.NextNum++
	if _, err := datastore.Put(c, p.Key(c), p); err != nil {
		return fmt.Errorf("putting Package: %v", err)
	}
	// if this isn't the first Commit test the parent commit exists
	if com.Num > 0 {
		n, err := datastore.NewQuery("Commit").
			Filter("Hash =", com.ParentHash).
			Ancestor(p.Key(c)).
			Count(c)
		if err != nil {
			return fmt.Errorf("testing for parent Commit: %v", err)
		}
		if n == 0 {
			return os.NewError("parent commit not found")
		}
	}
	// update the tip Tag if this is the Go repo
	if p.Path == "" {
		t := &Tag{Kind: "tip", Hash: com.Hash}
		if _, err = datastore.Put(c, t.Key(c), t); err != nil {
			return fmt.Errorf("putting Tag: %v", err)
		}
	}
	// put the Commit
	if _, err = datastore.Put(c, com.Key(c), com); err != nil {
		return fmt.Errorf("putting Commit: %v", err)
	}
	return nil
}

// tagHandler records a new tag. It reads a JSON-encoded Tag value from the
// request body and updates the Tag entity for the Kind of tag provided.
//
// This handler is used by a gobuilder process in -commit mode.
func tagHandler(r *http.Request) (interface{}, os.Error) {
	if r.Method != "POST" {
		return nil, errBadMethod(r.Method)
	}

	t := new(Tag)
	defer r.Body.Close()
	if err := json.NewDecoder(r.Body).Decode(t); err != nil {
		return nil, err
	}
	if err := t.Valid(); err != nil {
		return nil, err
	}
	c := appengine.NewContext(r)
	defer cache.Tick(c)
	_, err := datastore.Put(c, t.Key(c), t)
	return nil, err
}

// Todo is a todoHandler response.
type Todo struct {
	Kind string // "build-go-commit" or "build-package"
	Data interface{}
}

// todoHandler returns the next action to be performed by a builder.
// It expects "builder" and "kind" query parameters and returns a *Todo value.
// Multiple "kind" parameters may be specified.
func todoHandler(r *http.Request) (interface{}, os.Error) {
	c := appengine.NewContext(r)
	now := cache.Now(c)
	key := "build-todo-" + r.Form.Encode()
	var todo *Todo
	if cache.Get(r, now, key, &todo) {
		return todo, nil
	}
	var err os.Error
	builder := r.FormValue("builder")
	for _, kind := range r.Form["kind"] {
		var data interface{}
		switch kind {
		case "build-go-commit":
			data, err = buildTodo(c, builder, "", "")
		case "build-package":
			packagePath := r.FormValue("packagePath")
			goHash := r.FormValue("goHash")
			data, err = buildTodo(c, builder, packagePath, goHash)
		}
		if data != nil || err != nil {
			todo = &Todo{Kind: kind, Data: data}
			break
		}
	}
	if err == nil {
		cache.Set(r, now, key, todo)
	}
	return todo, err
}

// buildTodo returns the next Commit to be built (or nil if none available).
//
// If packagePath and goHash are empty, it scans the first 20 Go Commits in
// Num-descending order and returns the first one it finds that doesn't have a
// Result for this builder.
//
// If provided with non-empty packagePath and goHash args, it scans the first
// 20 Commits in Num-descending order for the specified packagePath and
// returns the first that doesn't have a Result for this builder and goHash.
func buildTodo(c appengine.Context, builder, packagePath, goHash string) (interface{}, os.Error) {
	p, err := GetPackage(c, packagePath)
	if err != nil {
		return nil, err
	}

	t := datastore.NewQuery("Commit").
		Ancestor(p.Key(c)).
		Limit(commitsPerPage).
		Order("-Num").
		Run(c)
	for {
		com := new(Commit)
		if _, err := t.Next(com); err != nil {
			if err == datastore.Done {
				err = nil
			}
			return nil, err
		}
		if com.Result(builder, goHash) == nil {
			return com, nil
		}
	}
	panic("unreachable")
}

// packagesHandler returns a list of the non-Go Packages monitored
// by the dashboard.
func packagesHandler(r *http.Request) (interface{}, os.Error) {
	c := appengine.NewContext(r)
	now := cache.Now(c)
	const key = "build-packages"
	var p []*Package
	if cache.Get(r, now, key, &p) {
		return p, nil
	}
	p, err := Packages(c)
	if err != nil {
		return nil, err
	}
	cache.Set(r, now, key, p)
	return p, nil
}

// resultHandler records a build result.
// It reads a JSON-encoded Result value from the request body,
// creates a new Result entity, and updates the relevant Commit entity.
// If the Log field is not empty, resultHandler creates a new Log entity
// and updates the LogHash field before putting the Commit entity.
func resultHandler(r *http.Request) (interface{}, os.Error) {
	if r.Method != "POST" {
		return nil, errBadMethod(r.Method)
	}

	c := appengine.NewContext(r)
	res := new(Result)
	defer r.Body.Close()
	if err := json.NewDecoder(r.Body).Decode(res); err != nil {
		return nil, fmt.Errorf("decoding Body: %v", err)
	}
	if err := res.Valid(); err != nil {
		return nil, fmt.Errorf("validating Result: %v", err)
	}
	defer cache.Tick(c)
	// store the Log text if supplied
	if len(res.Log) > 0 {
		hash, err := PutLog(c, res.Log)
		if err != nil {
			return nil, fmt.Errorf("putting Log: %v", err)
		}
		res.LogHash = hash
	}
	tx := func(c appengine.Context) os.Error {
		// check Package exists
		if _, err := GetPackage(c, res.PackagePath); err != nil {
			return fmt.Errorf("GetPackage: %v", err)
		}
		// put Result
		if _, err := datastore.Put(c, res.Key(c), res); err != nil {
			return fmt.Errorf("putting Result: %v", err)
		}
		// add Result to Commit
		com := &Commit{PackagePath: res.PackagePath, Hash: res.Hash}
		if err := com.AddResult(c, res); err != nil {
			return fmt.Errorf("AddResult: %v", err)
		}
		// Send build failure notifications, if necessary.
		// Note this must run after the call AddResult, which
		// populates the Commit's ResultData field.
		return notifyOnFailure(c, com, res.Builder)
	}
	return nil, datastore.RunInTransaction(c, tx, nil)
}

// logHandler displays log text for a given hash.
// It handles paths like "/log/hash".
func logHandler(w http.ResponseWriter, r *http.Request) {
	w.Header().Set("Content-type", "text/plain")
	c := appengine.NewContext(r)
	hash := r.URL.Path[len("/log/"):]
	key := datastore.NewKey(c, "Log", hash, 0, nil)
	l := new(Log)
	if err := datastore.Get(c, key, l); err != nil {
		logErr(w, r, err)
		return
	}
	b, err := l.Text()
	if err != nil {
		logErr(w, r, err)
		return
	}
	w.Write(b)
}

type dashHandler func(*http.Request) (interface{}, os.Error)

type dashResponse struct {
	Response interface{}
	Error    string
}

// errBadMethod is returned by a dashHandler when
// the request has an unsuitable method.
type errBadMethod string

func (e errBadMethod) String() string {
	return "bad method: " + string(e)
}

// AuthHandler wraps a http.HandlerFunc with a handler that validates the
// supplied key and builder query parameters.
func AuthHandler(h dashHandler) http.HandlerFunc {
	return func(w http.ResponseWriter, r *http.Request) {
		c := appengine.NewContext(r)

		// Put the URL Query values into r.Form to avoid parsing the
		// request body when calling r.FormValue.
		r.Form = r.URL.Query()

		var err os.Error
		var resp interface{}

		// Validate key query parameter for POST requests only.
		key := r.FormValue("key")
		builder := r.FormValue("builder")
		if r.Method == "POST" && !validKey(c, key, builder) {
			err = os.NewError("invalid key: " + key)
		}

		// Call the original HandlerFunc and return the response.
		if err == nil {
			resp, err = h(r)
		}

		// Write JSON response.
		dashResp := &dashResponse{Response: resp}
		if err != nil {
			c.Errorf("%v", err)
			dashResp.Error = err.String()
		}
		w.Header().Set("Content-Type", "application/json")
		if err = json.NewEncoder(w).Encode(dashResp); err != nil {
			c.Criticalf("encoding response: %v", err)
		}
	}
}

func initHandler(w http.ResponseWriter, r *http.Request) {
	// TODO(adg): devise a better way of bootstrapping new packages
	c := appengine.NewContext(r)
	defer cache.Tick(c)
	for _, p := range defaultPackages {
		if err := datastore.Get(c, p.Key(c), new(Package)); err == nil {
			continue
		} else if err != datastore.ErrNoSuchEntity {
			logErr(w, r, err)
			return
		}
		if _, err := datastore.Put(c, p.Key(c), p); err != nil {
			logErr(w, r, err)
			return
		}
	}
	fmt.Fprint(w, "OK")
}

func keyHandler(w http.ResponseWriter, r *http.Request) {
	builder := r.FormValue("builder")
	if builder == "" {
		logErr(w, r, os.NewError("must supply builder in query string"))
		return
	}
	c := appengine.NewContext(r)
	fmt.Fprint(w, builderKey(c, builder))
}

func init() {
	// admin handlers
	http.HandleFunc("/init", initHandler)
	http.HandleFunc("/key", keyHandler)

	// authenticated handlers
	http.HandleFunc("/commit", AuthHandler(commitHandler))
	http.HandleFunc("/packages", AuthHandler(packagesHandler))
	http.HandleFunc("/result", AuthHandler(resultHandler))
	http.HandleFunc("/tag", AuthHandler(tagHandler))
	http.HandleFunc("/todo", AuthHandler(todoHandler))

	// public handlers
	http.HandleFunc("/log/", logHandler)
}

func validHash(hash string) bool {
	// TODO(adg): correctly validate a hash
	return hash != ""
}

func validKey(c appengine.Context, key, builder string) bool {
	if appengine.IsDevAppServer() {
		return true
	}
	if key == secretKey(c) {
		return true
	}
	return key == builderKey(c, builder)
}

func builderKey(c appengine.Context, builder string) string {
	h := hmac.NewMD5([]byte(secretKey(c)))
	h.Write([]byte(builder))
	return fmt.Sprintf("%x", h.Sum())
}

func logErr(w http.ResponseWriter, r *http.Request, err os.Error) {
	appengine.NewContext(r).Errorf("Error: %v", err)
	w.WriteHeader(http.StatusInternalServerError)
	fmt.Fprint(w, "Error: ", err)
}
<|endoftext|>


<commit_before>package stripper

import (
	"bufio"
	"bytes"
	"io"
	"unicode"
)

type LeadingWhitespaceStripper struct {
	// it's on like
	donkeyKong bool

	r   *bufio.Reader
	buf bytes.Buffer
}

func NewLeadingWhitespaceStripper(r io.Reader) *LeadingWhitespaceStripper {
	return &LeadingWhitespaceStripper{
		donkeyKong: true,

		r: bufio.NewReader(r),
	}
}

func (r *LeadingWhitespaceStripper) Read(p []byte) (int, error) {
	if r.donkeyKong {
		for {
			char, _, err := r.r.ReadRune()
			if err != nil {
				return 0, err
			}
			if !unicode.IsSpace(char) {
				r.donkeyKong = false
				err = r.r.UnreadRune()
				if err != nil {
					return 0, err
				}
				break
			}
		}
	}
	return r.r.Read(p)
}
<commit_msg>Remove unneeded "LeadingWhitespaceStripper"<commit_after><|endoftext|>