<commit_before>// Copyright 2013, Homin Lee. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"./subtitle"
	"errors"
	"fmt"
	"github.com/0xe2-0x9a-0x9b/Go-SDL/sdl"
	"github.com/0xe2-0x9a-0x9b/Go-SDL/ttf"
	"log"
	"os"
	"sort"
	"strings"
	"time"
)

var (
	BG_COLOR         = sdl.Color{0, 0, 0, 0}
	TEXT_COLOR       = sdl.Color{255, 255, 255, 0}
	DEBUG_TEXT_COLOR = sdl.Color{32, 32, 32, 0}
	waitFinishC      = make(chan bool)
)

type Screen struct {
	surface *sdl.Surface
	fps     int
	w, h    uint16

	currScript *subtitle.Script
	lineHeight uint16

	fontSize int
	fontPath string
	font     *ttf.Font

	debugFont       *ttf.Font
	debugLineHeight uint16

	updateC chan int
}

func NewScreen(w, h int) *Screen {
	if sdl.Init(sdl.INIT_EVERYTHING) != 0 {
		log.Fatal("failed to init sdl", sdl.GetError())
		return nil
	}

	if ttf.Init() != 0 {
		log.Fatal("failed to init ttf", sdl.GetError())
		return nil
	}

	var ctx Screen
	if err := ctx.setSurface(w, h); err != nil {
		log.Fatal(err)
	}

	var vInfo = sdl.GetVideoInfo()
	log.Println("HW_available = ", vInfo.HW_available)
	log.Println("WM_available = ", vInfo.WM_available)
	log.Println("Video_mem = ", vInfo.Video_mem, "kb")

	/* title := "Subtitle Player" */
	title := os.Args[0]
	icon := "" // path/to/icon
	sdl.WM_SetCaption(title, icon)

	sdl.EnableUNICODE(1)

	ctx.fps = opt.fps

	err := ctx.setFont(opt.fontPath, opt.fontSize)
	if err != nil {
		log.Fatal("failed to set default font")
		return nil
	}

	ctx.debugFont = ttf.OpenFont(DFLT_FONT_PATH, 20)
	if ctx.debugFont == nil {
		errMsg := fmt.Sprintf("failed to open debug font: %s",
			sdl.GetError())
		/* return errors.New(errMsg) */
		log.Fatal(errMsg)
	}
	ctx.debugLineHeight = uint16(ctx.debugFont.LineSkip())

	ctx.updateC = make(chan int)

	return &ctx
}

func (c *Screen) Release() {
	if c.font != nil {
		c.font.Close()
	}
	if c.surface != nil {
		c.surface.Free()
	}
	ttf.Quit()
	sdl.Quit()

	/* log.Printf("sdl Released...") */
}

func (c *Screen) DisplayScript(script *subtitle.Script) {
	c.displayScript(script, false)
}

func (c *Screen) Clear() {
	log.Println("clear")
	c.surface.FillRect(&sdl.Rect{0, int16(c.debugLineHeight), c.w, c.h}, 0 /* BG_COLOR */)
	c.updateC <- 1
	c.currScript = nil
}

func (c *Screen) setFont(path string, size int) error {
	if c.font != nil {
		c.font.Close()
	}

	if size < 10 {
		size = 10
	}
	c.font = ttf.OpenFont(path, size)
	if c.font == nil {
		errMsg := fmt.Sprintf("failed to open font from %s: %s",
			path, sdl.GetError())
		return errors.New(errMsg)
	}
	/* c.font.SetStyle(ttf.STYLE_UNDERLINE) */

	c.fontSize = size
	c.fontPath = path
	c.lineHeight = uint16(c.font.LineSkip())

	log.Printf("fontsize=%d lineheight=%d\n", c.fontSize, c.lineHeight)
	return nil
}

func (c *Screen) changeFontSize(by int) {
	if c.font == nil {
		return
	}
	c.setFont(c.fontPath, c.fontSize+by)
	c.displayScript(c.currScript, true)
}

func (c *Screen) setSurface(w, h int) error {
	log.Printf("setSurface to %dx%d", w, h)
	if opt.fullscreen {
		c.surface = sdl.SetVideoMode(w, h, 32, sdl.FULLSCREEN)
	} else {
		c.surface = sdl.SetVideoMode(w, h, 32, sdl.RESIZABLE)
	}
	if c.surface == nil {
		errMsg := fmt.Sprintf("sdl: failed to set video to %dx%d: %s",
			w, h, sdl.GetError())
		return errors.New(errMsg)
	}

	c.w, c.h = uint16(w), uint16(h)
	c.displayScript(c.currScript, true)

	return nil
}

func graphicLoop(c *Screen) {
	fpsTicker := time.NewTicker(time.Second / time.Duration(c.fps)) // 30fps
	dirtyCnt := 0

GRAPHIC_LOOP:
	select {
	case u := <-c.updateC:
		dirtyCnt += u
	case <-fpsTicker.C:
		if dirtyCnt > 0 {
			if dirtyCnt > 1 {
				log.Println("Lost some frame?. dirtyCnt =", dirtyCnt)
			}
			c.surface.Flip()
			dirtyCnt = 0
		}
	}
	goto GRAPHIC_LOOP
}

func (c *Screen) displayDebug(text string) {
	c.surface.FillRect(&sdl.Rect{0, 0, c.w, c.debugLineHeight}, 0 /* BG_COLOR */)
	glypse := ttf.RenderUTF8_Solid(c.debugFont, text, DEBUG_TEXT_COLOR)
	c.surface.Blit(&sdl.Rect{0, 0, 0, 0}, glypse, nil)
	c.updateC <- 1
	/* c.surface.Flip() */
}

func (c *Screen) displayScript(script *subtitle.Script, forceUpdate bool) {
	if script == nil {
		return
	}
	if forceUpdate == false && c.currScript == script {
		return
	}
	c.currScript = script

	log.Printf("display %d.%s", script.Idx, script.TextWithoutMarkup())

	c.surface.FillRect(&sdl.Rect{0, int16(c.debugLineHeight), c.w, c.h}, 0 /* BG_COLOR */)
	offsetY := c.debugLineHeight

	for _, line := range strings.Split(script.TextWithoutMarkup(), "\n") {
		if strings.TrimSpace(line) == "" {
			offsetY += c.lineHeight
			continue
		}
		runeLine := []rune(line)
		runeLineLen := len(runeLine)
		runeLineStart := 0

		for runeLineStart != runeLineLen {
			/* log.Println("start =", runeLineStart, "len =", runeLineLen) */
			runeSubLine := runeLine[runeLineStart:]
			runeSubLineLen := len(runeSubLine)
			i := sort.Search(runeSubLineLen, func(i int) bool {
				w, _, _ := c.font.SizeUTF8(string(runeSubLine[:i]))
				return w+20 >= int(c.w)
			})
			/* log.Printf("runeSubLine=%s, i=%d\n", string(runeSubLine), i) */

			if i > runeSubLineLen {
				i = runeSubLineLen
			}

			w, _, _ := c.font.SizeUTF8(string(runeSubLine[:i]))
			for w > int(c.w) {
				i -= 1
				w, _, _ = c.font.SizeUTF8(string(runeSubLine[:i]))
			}

			/* log.Println("returned i=", i) */

			subline := string(runeLine[runeLineStart : runeLineStart+i])
			subline = strings.TrimSpace(subline)
			/* log.Println("subline=", subline) */
			runeLineStart += i
			if runeLineStart > runeLineLen {
				runeLineStart = runeLineLen
			}

			offsetX := 10
			if opt.alignCenter {
				w, _, err := c.font.SizeUTF8(subline)
				if err != 0 {
					log.Fatal("Failed to get size of the font")
				}
				offsetX = (int(c.w) - w) / 2
			}

			glypse := ttf.RenderUTF8_Blended(c.font, subline, TEXT_COLOR)
			lt := sdl.Rect{int16(offsetX), int16(offsetY), 0, 0}
			c.surface.Blit(&lt, glypse, nil)
			offsetY += c.lineHeight
		}
	}
	c.updateC <- 1
}
<commit_msg>More clean up<commit_after>// Copyright 2013, Homin Lee. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

import (
	"./subtitle"
	"errors"
	"fmt"
	"github.com/0xe2-0x9a-0x9b/Go-SDL/sdl"
	"github.com/0xe2-0x9a-0x9b/Go-SDL/ttf"
	"log"
	"os"
	"sort"
	"strings"
	"time"
)

var (
	BG_COLOR         = sdl.Color{0, 0, 0, 0}
	TEXT_COLOR       = sdl.Color{255, 255, 255, 0}
	DEBUG_TEXT_COLOR = sdl.Color{32, 32, 32, 0}
	waitFinishC      = make(chan bool)
)

type Screen struct {
	surface *sdl.Surface
	fps     int
	w, h    uint16

	currScript *subtitle.Script
	lineHeight uint16

	fontSize int
	fontPath string
	font     *ttf.Font

	debugFont       *ttf.Font
	debugLineHeight uint16

	updateC chan int
}

func NewScreen(w, h int) *Screen {
	if sdl.Init(sdl.INIT_EVERYTHING) != 0 {
		log.Fatal("failed to init sdl", sdl.GetError())
		return nil
	}

	if ttf.Init() != 0 {
		log.Fatal("failed to init ttf", sdl.GetError())
		return nil
	}

	var ctx Screen
	if err := ctx.setSurface(w, h); err != nil {
		log.Fatal(err)
	}

	var vInfo = sdl.GetVideoInfo()
	log.Println("HW_available = ", vInfo.HW_available)
	log.Println("WM_available = ", vInfo.WM_available)
	log.Println("Video_mem = ", vInfo.Video_mem, "kb")

	/* title := "Subtitle Player" */
	title := os.Args[0]
	icon := "" // path/to/icon
	sdl.WM_SetCaption(title, icon)

	sdl.EnableUNICODE(1)

	ctx.fps = opt.fps

	err := ctx.setFont(opt.fontPath, opt.fontSize)
	if err != nil {
		log.Fatal("failed to set default font")
		return nil
	}

	ctx.debugFont = ttf.OpenFont(DFLT_FONT_PATH, 20)
	if ctx.debugFont == nil {
		errMsg := fmt.Sprintf("failed to open debug font: %s",
			sdl.GetError())
		/* return errors.New(errMsg) */
		log.Fatal(errMsg)
	}
	ctx.debugLineHeight = uint16(ctx.debugFont.LineSkip())

	ctx.updateC = make(chan int)

	return &ctx
}

func (c *Screen) Release() {
	if c.font != nil {
		c.font.Close()
	}
	if c.surface != nil {
		c.surface.Free()
	}
	ttf.Quit()
	sdl.Quit()

	/* log.Printf("sdl Released...") */
}

func (c *Screen) Clear() {
	log.Println("clear")
	c.surface.FillRect(&sdl.Rect{0, int16(c.debugLineHeight), c.w, c.h}, 0 /* BG_COLOR */)
	c.updateC <- 1
	c.currScript = nil
}

func (c *Screen) setFont(path string, size int) error {
	if c.font != nil {
		c.font.Close()
	}

	if size < 10 {
		size = 10
	}
	c.font = ttf.OpenFont(path, size)
	if c.font == nil {
		errMsg := fmt.Sprintf("failed to open font from %s: %s",
			path, sdl.GetError())
		return errors.New(errMsg)
	}
	/* c.font.SetStyle(ttf.STYLE_UNDERLINE) */

	c.fontSize = size
	c.fontPath = path
	c.lineHeight = uint16(c.font.LineSkip())

	log.Printf("fontsize=%d lineheight=%d\n", c.fontSize, c.lineHeight)
	return nil
}

func (c *Screen) changeFontSize(by int) {
	if c.font == nil {
		return
	}
	c.setFont(c.fontPath, c.fontSize+by)
	c.DisplayScript(c.currScript)
}

func (c *Screen) setSurface(w, h int) error {
	log.Printf("setSurface to %dx%d", w, h)
	if opt.fullscreen {
		c.surface = sdl.SetVideoMode(w, h, 32, sdl.FULLSCREEN)
	} else {
		c.surface = sdl.SetVideoMode(w, h, 32, sdl.RESIZABLE)
	}
	if c.surface == nil {
		errMsg := fmt.Sprintf("sdl: failed to set video to %dx%d: %s",
			w, h, sdl.GetError())
		return errors.New(errMsg)
	}

	c.w, c.h = uint16(w), uint16(h)
	c.DisplayScript(c.currScript)

	return nil
}

func graphicLoop(c *Screen) {
	fpsTicker := time.NewTicker(time.Second / time.Duration(c.fps)) // 30fps
	dirtyCnt := 0

GRAPHIC_LOOP:
	select {
	case u := <-c.updateC:
		dirtyCnt += u
	case <-fpsTicker.C:
		if dirtyCnt > 0 {
			if dirtyCnt > 1 {
				log.Println("Lost some frame?. dirtyCnt =", dirtyCnt)
			}
			c.surface.Flip()
			dirtyCnt = 0
		}
	}
	goto GRAPHIC_LOOP
}

func (c *Screen) displayDebug(text string) {
	c.surface.FillRect(&sdl.Rect{0, 0, c.w, c.debugLineHeight}, 0 /* BG_COLOR */)
	glypse := ttf.RenderUTF8_Solid(c.debugFont, text, DEBUG_TEXT_COLOR)
	c.surface.Blit(&sdl.Rect{0, 0, 0, 0}, glypse, nil)
	c.updateC <- 1
	/* c.surface.Flip() */
}

func (c *Screen) DisplayScript(script *subtitle.Script) {
	if script == nil {
		return
	}

	c.currScript = script

	log.Printf("display %d.%s", script.Idx, script.TextWithoutMarkup())

	c.surface.FillRect(&sdl.Rect{0, int16(c.debugLineHeight), c.w, c.h}, 0 /* BG_COLOR */)
	offsetY := c.debugLineHeight

	for _, line := range strings.Split(script.TextWithoutMarkup(), "\n") {
		if strings.TrimSpace(line) == "" {
			offsetY += c.lineHeight
			continue
		}
		runeLine := []rune(line)
		runeLineLen := len(runeLine)
		runeLineStart := 0

		for runeLineStart != runeLineLen {
			/* log.Println("start =", runeLineStart, "len =", runeLineLen) */
			runeSubLine := runeLine[runeLineStart:]
			runeSubLineLen := len(runeSubLine)
			i := sort.Search(runeSubLineLen, func(i int) bool {
				w, _, _ := c.font.SizeUTF8(string(runeSubLine[:i]))
				return w+20 >= int(c.w)
			})
			/* log.Printf("runeSubLine=%s, i=%d\n", string(runeSubLine), i) */

			if i > runeSubLineLen {
				i = runeSubLineLen
			}

			w, _, _ := c.font.SizeUTF8(string(runeSubLine[:i]))
			for w > int(c.w) {
				i -= 1
				w, _, _ = c.font.SizeUTF8(string(runeSubLine[:i]))
			}

			subline := string(runeLine[runeLineStart : runeLineStart+i])
			subline = strings.TrimSpace(subline)
			/* log.Println("subline=", subline) */
			runeLineStart += i
			if runeLineStart > runeLineLen {
				runeLineStart = runeLineLen
			}

			offsetX := 10
			if opt.alignCenter {
				w, _, err := c.font.SizeUTF8(subline)
				if err != 0 {
					log.Fatal("Failed to get size of the font")
				}
				offsetX = (int(c.w) - w) / 2
			}

			glypse := ttf.RenderUTF8_Blended(c.font, subline, TEXT_COLOR)
			lt := sdl.Rect{int16(offsetX), int16(offsetY), 0, 0}
			c.surface.Blit(&lt, glypse, nil)
			offsetY += c.lineHeight
		}
	}
	c.updateC <- 1
}
<|endoftext|>


<commit_before>// Copyright 2021 The LUCI Authors.
//
// 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 handler

import (
	"context"
	"fmt"
	"time"

	"google.golang.org/protobuf/proto"

	"go.chromium.org/luci/common/clock"
	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/common/logging"
	"go.chromium.org/luci/common/sync/parallel"
	"go.chromium.org/luci/gae/service/datastore"

	"go.chromium.org/luci/cv/internal/common"
	"go.chromium.org/luci/cv/internal/configs/prjcfg"
	"go.chromium.org/luci/cv/internal/gerrit"
	"go.chromium.org/luci/cv/internal/migration/migrationcfg"
	"go.chromium.org/luci/cv/internal/run"
	"go.chromium.org/luci/cv/internal/run/impl/state"
	"go.chromium.org/luci/cv/internal/tryjob"
)

const (
	treeCheckInterval          = time.Minute
	clRefreshInterval          = 10 * time.Minute
	tryjobRefreshInterval      = 10 * time.Minute
	treeStatusFailureTimeLimit = 10 * time.Minute
)

// Poke implements Handler interface.
func (impl *Impl) Poke(ctx context.Context, rs *state.RunState) (*Result, error) {
	rs = rs.ShallowCopy()
	if shouldCheckTree(ctx, rs.Status, rs.Submission) {
		rs.Submission = proto.Clone(rs.Submission).(*run.Submission)
		switch open, err := rs.CheckTree(ctx, impl.TreeClient); {
		case err != nil && clock.Since(ctx, rs.Submission.TreeErrorSince.AsTime()) > treeStatusFailureTimeLimit:
			// The tree has been erroring for too long. Cancel the triggers and
			// fail the run.
			cg, err := prjcfg.GetConfigGroup(ctx, rs.ID.LUCIProject(), rs.ConfigGroupID)
			if err != nil {
				return nil, err
			}
			rims := make(map[common.CLID]reviewInputMeta, len(rs.CLs))
			for _, cid := range rs.CLs {
				rims[common.CLID(cid)] = reviewInputMeta{
					notify: gerrit.Whoms{gerrit.Owner, gerrit.CQVoters},
					// Add the same set of group/people to the attention set.
					addToAttention: gerrit.Whoms{gerrit.Owner, gerrit.CQVoters},
					reason:         submissionFailureAttentionReason,
					message:        fmt.Sprintf(persistentTreeStatusAppFailureTemplate, cg.Content.GetVerifiers().GetTreeStatus().GetUrl()),
				}
			}
			scheduleTriggersCancellation(ctx, rs, rims, run.Status_FAILED)
			return &Result{
				State: rs,
			}, nil
		case err != nil:
			logging.Warningf(ctx, "tree status check failed with error: %s", err)
			fallthrough
		case !open:
			if err := impl.RM.PokeAfter(ctx, rs.ID, treeCheckInterval); err != nil {
				return nil, err
			}
		default:
			return impl.OnReadyForSubmission(ctx, rs)
		}
	}

	// If it's scheduled to be cancelled, skip the refresh.
	// The long op might have been expired, but it should be removed at the end
	// of this call first, and then the next Poke() will run this check again.
	if !isTriggersCancellationOngoing(rs) && shouldRefreshCLs(ctx, rs) {
		cg, runCLs, cls, err := loadCLsAndConfig(ctx, rs, rs.CLs)
		if err != nil {
			return nil, err
		}
		switch ok, err := checkRunCreate(ctx, rs, cg, runCLs, cls); {
		case err != nil:
			return nil, err
		case ok:
			if err := impl.CLUpdater.ScheduleBatch(ctx, rs.ID.LUCIProject(), cls); err != nil {
				return nil, err
			}
			rs.LatestCLsRefresh = datastore.RoundTime(clock.Now(ctx).UTC())
		}
	}

	switch {
	case !rs.UseCVTryjobExecutor:
		// once a Run decides not to use CV for tryjob execution, it keeps in that
		// state for the whole lifetime.
	case hasExecuteTryjobLongOp(rs):
		// wait for the existing execute tryjob long op to finish before handing
		// the control of tryjob to CQDaemon.
	default:
		var err error
		rs.UseCVTryjobExecutor, err = migrationcfg.IsCVInChargeOfTryjob(ctx, impl.Env, rs.ID.LUCIProject())
		if err != nil {
			return nil, err
		}
	}

	if rs.UseCVTryjobExecutor && shouldRefreshTryjobs(ctx, rs) {
		executions := rs.Tryjobs.GetState().GetExecutions()
		errs := errors.NewLazyMultiError(len(executions))
		poolErr := parallel.WorkPool(min(8, len(executions)), func(workCh chan<- func() error) {
			for i, execution := range executions {
				i := i
				if len(execution.GetAttempts()) == 0 {
					continue
				}
				// Only care about the latest attempt with the assumption that all
				// earlier attempt should have been ended already.
				latestAttempt := execution.GetAttempts()[len(execution.GetAttempts())-1]
				if latestAttempt.GetExternalId() == "" {
					// There's no point to update Tryjob if Tryjob hasn't been triggered
					// yet.
					continue
				}
				workCh <- func() error {
					errs.Assign(i, impl.TN.ScheduleUpdate(ctx,
						common.TryjobID(latestAttempt.GetTryjobId()),
						tryjob.ExternalID(latestAttempt.GetExternalId())))
					return nil
				}
			}
		})
		switch {
		case poolErr != nil:
			panic(poolErr)
		case errs.Get() != nil:
			return nil, common.MostSevereError(errs.Get())
		default:
			rs.LatestTryjobsRefresh = datastore.RoundTime(clock.Now(ctx).UTC())
		}
	}

	return impl.processExpiredLongOps(ctx, rs)
}

func shouldCheckTree(ctx context.Context, st run.Status, sub *run.Submission) bool {
	switch {
	case st != run.Status_WAITING_FOR_SUBMISSION:
	case sub.GetLastTreeCheckTime() == nil:
		return true
	case !sub.GetTreeOpen():
		return clock.Now(ctx).Sub(sub.GetLastTreeCheckTime().AsTime()) >= treeCheckInterval
	}
	return false
}

func shouldRefreshCLs(ctx context.Context, rs *state.RunState) bool {
	return shouldRefresh(ctx, rs, rs.LatestCLsRefresh, clRefreshInterval)
}

func shouldRefreshTryjobs(ctx context.Context, rs *state.RunState) bool {
	return shouldRefresh(ctx, rs, rs.LatestTryjobsRefresh, tryjobRefreshInterval)
}

func shouldRefresh(ctx context.Context, rs *state.RunState, last time.Time, interval time.Duration) bool {
	switch {
	case run.IsEnded(rs.Status):
		return false
	case last.IsZero():
		last = rs.CreateTime
		fallthrough
	default:
		return clock.Since(ctx, last) > interval
	}
}
<commit_msg>cv: shorten the tryjob refresh interval to 2.5 minutes<commit_after>// Copyright 2021 The LUCI Authors.
//
// 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 handler

import (
	"context"
	"fmt"
	"time"

	"google.golang.org/protobuf/proto"

	"go.chromium.org/luci/common/clock"
	"go.chromium.org/luci/common/errors"
	"go.chromium.org/luci/common/logging"
	"go.chromium.org/luci/common/sync/parallel"
	"go.chromium.org/luci/gae/service/datastore"

	"go.chromium.org/luci/cv/internal/common"
	"go.chromium.org/luci/cv/internal/configs/prjcfg"
	"go.chromium.org/luci/cv/internal/gerrit"
	"go.chromium.org/luci/cv/internal/migration/migrationcfg"
	"go.chromium.org/luci/cv/internal/run"
	"go.chromium.org/luci/cv/internal/run/impl/state"
	"go.chromium.org/luci/cv/internal/tryjob"
)

const (
	treeCheckInterval          = time.Minute
	clRefreshInterval          = 10 * time.Minute
	tryjobRefreshInterval      = 150 * time.Second
	treeStatusFailureTimeLimit = 10 * time.Minute
)

// Poke implements Handler interface.
func (impl *Impl) Poke(ctx context.Context, rs *state.RunState) (*Result, error) {
	rs = rs.ShallowCopy()
	if shouldCheckTree(ctx, rs.Status, rs.Submission) {
		rs.Submission = proto.Clone(rs.Submission).(*run.Submission)
		switch open, err := rs.CheckTree(ctx, impl.TreeClient); {
		case err != nil && clock.Since(ctx, rs.Submission.TreeErrorSince.AsTime()) > treeStatusFailureTimeLimit:
			// The tree has been erroring for too long. Cancel the triggers and
			// fail the run.
			cg, err := prjcfg.GetConfigGroup(ctx, rs.ID.LUCIProject(), rs.ConfigGroupID)
			if err != nil {
				return nil, err
			}
			rims := make(map[common.CLID]reviewInputMeta, len(rs.CLs))
			for _, cid := range rs.CLs {
				rims[common.CLID(cid)] = reviewInputMeta{
					notify: gerrit.Whoms{gerrit.Owner, gerrit.CQVoters},
					// Add the same set of group/people to the attention set.
					addToAttention: gerrit.Whoms{gerrit.Owner, gerrit.CQVoters},
					reason:         submissionFailureAttentionReason,
					message:        fmt.Sprintf(persistentTreeStatusAppFailureTemplate, cg.Content.GetVerifiers().GetTreeStatus().GetUrl()),
				}
			}
			scheduleTriggersCancellation(ctx, rs, rims, run.Status_FAILED)
			return &Result{
				State: rs,
			}, nil
		case err != nil:
			logging.Warningf(ctx, "tree status check failed with error: %s", err)
			fallthrough
		case !open:
			if err := impl.RM.PokeAfter(ctx, rs.ID, treeCheckInterval); err != nil {
				return nil, err
			}
		default:
			return impl.OnReadyForSubmission(ctx, rs)
		}
	}

	// If it's scheduled to be cancelled, skip the refresh.
	// The long op might have been expired, but it should be removed at the end
	// of this call first, and then the next Poke() will run this check again.
	if !isTriggersCancellationOngoing(rs) && shouldRefreshCLs(ctx, rs) {
		cg, runCLs, cls, err := loadCLsAndConfig(ctx, rs, rs.CLs)
		if err != nil {
			return nil, err
		}
		switch ok, err := checkRunCreate(ctx, rs, cg, runCLs, cls); {
		case err != nil:
			return nil, err
		case ok:
			if err := impl.CLUpdater.ScheduleBatch(ctx, rs.ID.LUCIProject(), cls); err != nil {
				return nil, err
			}
			rs.LatestCLsRefresh = datastore.RoundTime(clock.Now(ctx).UTC())
		}
	}

	switch {
	case !rs.UseCVTryjobExecutor:
		// once a Run decides not to use CV for tryjob execution, it keeps in that
		// state for the whole lifetime.
	case hasExecuteTryjobLongOp(rs):
		// wait for the existing execute tryjob long op to finish before handing
		// the control of tryjob to CQDaemon.
	default:
		var err error
		rs.UseCVTryjobExecutor, err = migrationcfg.IsCVInChargeOfTryjob(ctx, impl.Env, rs.ID.LUCIProject())
		if err != nil {
			return nil, err
		}
	}

	if rs.UseCVTryjobExecutor && shouldRefreshTryjobs(ctx, rs) {
		executions := rs.Tryjobs.GetState().GetExecutions()
		errs := errors.NewLazyMultiError(len(executions))
		poolErr := parallel.WorkPool(min(8, len(executions)), func(workCh chan<- func() error) {
			for i, execution := range executions {
				i := i
				if len(execution.GetAttempts()) == 0 {
					continue
				}
				// Only care about the latest attempt with the assumption that all
				// earlier attempt should have been ended already.
				latestAttempt := execution.GetAttempts()[len(execution.GetAttempts())-1]
				if latestAttempt.GetExternalId() == "" {
					// There's no point to update Tryjob if Tryjob hasn't been triggered
					// yet.
					continue
				}
				workCh <- func() error {
					errs.Assign(i, impl.TN.ScheduleUpdate(ctx,
						common.TryjobID(latestAttempt.GetTryjobId()),
						tryjob.ExternalID(latestAttempt.GetExternalId())))
					return nil
				}
			}
		})
		switch {
		case poolErr != nil:
			panic(poolErr)
		case errs.Get() != nil:
			return nil, common.MostSevereError(errs.Get())
		default:
			rs.LatestTryjobsRefresh = datastore.RoundTime(clock.Now(ctx).UTC())
		}
	}

	return impl.processExpiredLongOps(ctx, rs)
}

func shouldCheckTree(ctx context.Context, st run.Status, sub *run.Submission) bool {
	switch {
	case st != run.Status_WAITING_FOR_SUBMISSION:
	case sub.GetLastTreeCheckTime() == nil:
		return true
	case !sub.GetTreeOpen():
		return clock.Now(ctx).Sub(sub.GetLastTreeCheckTime().AsTime()) >= treeCheckInterval
	}
	return false
}

func shouldRefreshCLs(ctx context.Context, rs *state.RunState) bool {
	return shouldRefresh(ctx, rs, rs.LatestCLsRefresh, clRefreshInterval)
}

func shouldRefreshTryjobs(ctx context.Context, rs *state.RunState) bool {
	return shouldRefresh(ctx, rs, rs.LatestTryjobsRefresh, tryjobRefreshInterval)
}

func shouldRefresh(ctx context.Context, rs *state.RunState, last time.Time, interval time.Duration) bool {
	switch {
	case run.IsEnded(rs.Status):
		return false
	case last.IsZero():
		last = rs.CreateTime
		fallthrough
	default:
		return clock.Since(ctx, last) > interval
	}
}
<|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.

// A small helper for using gcsfuse with mount(8).
//
// Can be invoked using a command-line of the form expected for mount helpers.
// Calls the gcsfuse binary, which must be in $PATH, and waits for it to
// complete. The device is passed as --bucket, and other known options are
// converted to appropriate flags.
//
// This binary does not daemonize, and therefore must be used with a wrapper
// that performs daemonization if it is to be used directly with mount(8).
package main

// Example invocation on OS X:
//
//     mount -t porp -o key_file=/some\ file.json -o ro,blah bucket ~/tmp/mp
//
// becomes the following arguments:
//
//     Arg 0: "/path/to/gcsfuse_mount_helper"
//     Arg 1: "-o"
//     Arg 2: "key_file=/some file.json"
//     Arg 3: "-o"
//     Arg 4: "ro"
//     Arg 5: "-o"
//     Arg 6: "blah"
//     Arg 7: "bucket"
//     Arg 8: "/path/to/mp"
//
// On Linux, the fstab entry
//
//     bucket /path/to/mp porp user,key_file=/some\040file.json
//
// becomes
//
//     Arg 0: "/path/to/gcsfuse_mount_helper"
//     Arg 1: "bucket"
//     Arg 2: "/path/to/mp"
//     Arg 3: "-o"
//     Arg 4: "rw,noexec,nosuid,nodev,user,key_file=/some file.json"
//

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"

	"github.com/googlecloudplatform/gcsfuse/mount"
)

var fOptions = mount.OptionFlag("o", "Mount options. May be repeated.")

// Parse positional arguments.
func parseArgs(args []string) (device string, mountPoint string, err error) {
	if len(args) != 2 {
		err = fmt.Errorf("Expected two positional arguments, but got %d", len(args))
		return
	}

	device = args[0]
	mountPoint = args[1]

	return
}

// Turn mount-style options into gcsfuse arguments. Skip known detritus that
// the mount command gives us.
//
// The result of this function should be appended to exec.Command.Args.
func makeGcsfuseArgs(
	device string,
	mountPoint string,
	opts map[string]string) (args []string, err error) {
	// Deal with options.
	for name, value := range opts {
		switch name {
		case "key_file":
			args = append(args, "--key_file="+value)

		case "fuse_debug":
			args = append(args, "--fuse.debug")

		case "gcs_debug":
			args = append(args, "--gcs.debug")

			// Pass through everything else.
		default:
			var formatted string
			if value == "" {
				formatted = name
			} else {
				formatted = fmt.Sprintf("%s=%s", name, value)
			}

			args = append(args, "-o", formatted)
		}
	}

	// Set the bucket.
	args = append(args, "--bucket="+device)

	// Set the mount point.
	args = append(args, "--mount_point="+mountPoint)

	return
}

func main() {
	flag.Parse()

	// Print out each argument.
	args := flag.Args()
	for i, arg := range args {
		log.Printf("Arg %d: %q", i, arg)
	}

	// Attempt to parse arguments.
	device, mountPoint, err := parseArgs(args)
	if err != nil {
		log.Fatalf("parseArgs: %v", err)
	}

	// Print what we gleaned.
	log.Printf("Device: %q", device)
	log.Printf("Mount point: %q", mountPoint)
	for name, value := range fOptions {
		log.Printf("Option %q: %q", name, value)
	}

	// Choose gcsfuse args.
	gcsfuseArgs, err := makeGcsfuseArgs(device, mountPoint, fOptions)
	if err != nil {
		log.Fatalf("makeGcsfuseArgs: %v", err)
	}

	for _, a := range gcsfuseArgs {
		log.Printf("gcsfuse arg: %q", a)
	}

	// Run gcsfuse and wait for it to complete.
	cmd := exec.Command("gcsfuse", gcsfuseArgs...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	err = cmd.Run()
	if err != nil {
		log.Fatalf("gcsfuse failed or failed to run: %v", err)
	}

	log.Println("gcsfuse completed successfully.")
}
<commit_msg>Added mount helper support for mode options.<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.

// A small helper for using gcsfuse with mount(8).
//
// Can be invoked using a command-line of the form expected for mount helpers.
// Calls the gcsfuse binary, which must be in $PATH, and waits for it to
// complete. The device is passed as --bucket, and other known options are
// converted to appropriate flags.
//
// This binary does not daemonize, and therefore must be used with a wrapper
// that performs daemonization if it is to be used directly with mount(8).
package main

// Example invocation on OS X:
//
//     mount -t porp -o key_file=/some\ file.json -o ro,blah bucket ~/tmp/mp
//
// becomes the following arguments:
//
//     Arg 0: "/path/to/gcsfuse_mount_helper"
//     Arg 1: "-o"
//     Arg 2: "key_file=/some file.json"
//     Arg 3: "-o"
//     Arg 4: "ro"
//     Arg 5: "-o"
//     Arg 6: "blah"
//     Arg 7: "bucket"
//     Arg 8: "/path/to/mp"
//
// On Linux, the fstab entry
//
//     bucket /path/to/mp porp user,key_file=/some\040file.json
//
// becomes
//
//     Arg 0: "/path/to/gcsfuse_mount_helper"
//     Arg 1: "bucket"
//     Arg 2: "/path/to/mp"
//     Arg 3: "-o"
//     Arg 4: "rw,noexec,nosuid,nodev,user,key_file=/some file.json"
//

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"

	"github.com/googlecloudplatform/gcsfuse/mount"
)

var fOptions = mount.OptionFlag("o", "Mount options. May be repeated.")

// Parse positional arguments.
func parseArgs(args []string) (device string, mountPoint string, err error) {
	if len(args) != 2 {
		err = fmt.Errorf("Expected two positional arguments, but got %d", len(args))
		return
	}

	device = args[0]
	mountPoint = args[1]

	return
}

// Turn mount-style options into gcsfuse arguments. Skip known detritus that
// the mount command gives us.
//
// The result of this function should be appended to exec.Command.Args.
func makeGcsfuseArgs(
	device string,
	mountPoint string,
	opts map[string]string) (args []string, err error) {
	// Deal with options.
	for name, value := range opts {
		switch name {
		case "key_file":
			args = append(args, "--key_file="+value)

		case "fuse_debug":
			args = append(args, "--fuse.debug")

		case "gcs_debug":
			args = append(args, "--gcs.debug")

		case "file_mode":
			args = append(args, "--file_mode="+value)

		case "dir_mode":
			args = append(args, "--dir_mode="+value)

		// Pass through everything else.
		default:
			var formatted string
			if value == "" {
				formatted = name
			} else {
				formatted = fmt.Sprintf("%s=%s", name, value)
			}

			args = append(args, "-o", formatted)
		}
	}

	// Set the bucket.
	args = append(args, "--bucket="+device)

	// Set the mount point.
	args = append(args, "--mount_point="+mountPoint)

	return
}

func main() {
	flag.Parse()

	// Print out each argument.
	args := flag.Args()
	for i, arg := range args {
		log.Printf("Arg %d: %q", i, arg)
	}

	// Attempt to parse arguments.
	device, mountPoint, err := parseArgs(args)
	if err != nil {
		log.Fatalf("parseArgs: %v", err)
	}

	// Print what we gleaned.
	log.Printf("Device: %q", device)
	log.Printf("Mount point: %q", mountPoint)
	for name, value := range fOptions {
		log.Printf("Option %q: %q", name, value)
	}

	// Choose gcsfuse args.
	gcsfuseArgs, err := makeGcsfuseArgs(device, mountPoint, fOptions)
	if err != nil {
		log.Fatalf("makeGcsfuseArgs: %v", err)
	}

	for _, a := range gcsfuseArgs {
		log.Printf("gcsfuse arg: %q", a)
	}

	// Run gcsfuse and wait for it to complete.
	cmd := exec.Command("gcsfuse", gcsfuseArgs...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	err = cmd.Run()
	if err != nil {
		log.Fatalf("gcsfuse failed or failed to run: %v", err)
	}

	log.Println("gcsfuse completed successfully.")
}
<|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.

// A small helper for using gcsfuse with mount(8).
//
// Can be invoked using a command-line of the form expected for mount helpers.
// Calls the gcsfuse binary, which must be in $PATH, and waits for it to
// complete. The device is passed as --bucket, and other known options are
// converted to appropriate flags.
//
// This binary does not daemonize, and therefore must be used with a wrapper
// that performs daemonization if it is to be used directly with mount(8).
package main

import (
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"
)

var fOptions OptionSlice

func init() {
	flag.Var(&fOptions, "o", "Mount options. May be repeated.")
}

// A 'name=value' mount option. If '=value' is not present, only the name will
// be filled in.
type Option struct {
	Name  string
	Value string
}

// A slice of options that knows how to parse command-line flags into the
// slice, implementing flag.Value.
type OptionSlice []Option

// Parse a single comma-separated list of mount options.
func parseOpts(s string) (opts []Option, err error) {
	// NOTE(jacobsa): The man pages don't define how escaping works, and as far
	// as I can tell there is no way to properly escape or quote a comma in the
	// options list for an fstab entry. So put our fingers in our ears and hope
	// that nobody needs a comma.
	for _, p := range strings.Split(s, ",") {
		var opt Option

		// Split on the first equals sign.
		if equalsIndex := strings.IndexByte(p, '='); equalsIndex != -1 {
			opt.Name = p[:equalsIndex]
			opt.Value = p[equalsIndex+1:]
		} else {
			opt.Name = p
		}

		opts = append(opts, opt)
	}

	return
}

// Attempt to parse the terrible undocumented format that mount(8) gives us.
// Return the 'device' (aka 'special' on OS X), the mount point, and a list of
// mount options encountered.
func parseArgs() (device string, mountPoint string, opts []Option, err error) {
	// Example invocation on OS X:
	//
	//     mount -t porp -o key_file=/some\ file.json -o ro,blah bucket ~/tmp/mp
	//
	// becomes the following arguments:
	//
	//     Arg 0: "/path/to/gcsfuse_mount_helper"
	//     Arg 1: "-o"
	//     Arg 2: "key_file=/some file.json"
	//     Arg 3: "-o"
	//     Arg 4: "ro"
	//     Arg 5: "-o"
	//     Arg 6: "blah"
	//     Arg 7: "bucket"
	//     Arg 8: "/path/to/mp"
	//
	// On Linux, the fstab entry
	//
	//     bucket /path/to/mp porp user,key_file=/some\040file.json
	//
	// becomes
	//
	//     Arg 0: "/path/to/gcsfuse_mount_helper"
	//     Arg 1: "bucket"
	//     Arg 2: "/path/to/mp"
	//     Arg 3: "-o"
	//     Arg 4: "rw,noexec,nosuid,nodev,user,key_file=/some file.json"
	//

	// Linux and OS X differ on the position of the options. So scan all
	// arguments (aside from the name of the binary), and:
	//
	//  *  Treat the first argument not following "-o" as the device name.
	//  *  Treat the second argument not following "-o" as the mount point.
	//  *  Treat the third argument not following "-o" as an error.
	//  *  Treat all arguments following "-o" as comma-separated options lists.
	//
	rawArgs := 0
	for i, arg := range os.Args {
		// Skip the binary name.
		if i == 0 {
			continue
		}

		// Skip "-o"; we will look back on the next iteration.
		if arg == "-o" {
			continue
		}

		// If the previous argument was "-o", this is a list of options.
		if os.Args[i-1] == "-o" {
			var tmp []Option
			tmp, err = parseOpts(arg)
			if err != nil {
				err = fmt.Errorf("parseOpts(%q): %v", arg, err)
				return
			}

			opts = append(opts, tmp...)
			continue
		}

		// Otherwise this is a non-option argument.
		switch rawArgs {
		case 0:
			device = arg

		case 1:
			mountPoint = arg

		default:
			err = fmt.Errorf(
				"Too many non-option arguments. The straw that broke the "+
					"camel's back: %q",
				arg)
			return
		}

		rawArgs++
	}

	// Did we see all of the raw arguments we expected?
	if rawArgs != 2 {
		err = fmt.Errorf("Expected 2 non-option arguments; got %d", rawArgs)
	}

	return
}

// Turn mount-style options into gcsfuse arguments. Skip known detritus that
// the mount command gives us.
//
// The result of this function should be appended to exec.Command.Args.
func makeGcsfuseArgs(
	device string,
	mountPoint string,
	opts []Option) (args []string, err error) {
	// Deal with options.
	for _, opt := range opts {
		switch opt.Name {
		case "key_file":
			args = append(args, "--key_file="+opt.Value)

		case "fuse_debug":
			args = append(args, "--fuse.debug")

		case "gcs_debug":
			args = append(args, "--gcs.debug")

		case "ro":
			args = append(args, "--read_only")

		// Ignore arguments for default and unsupported behavior automatically
		// added by mount(8) on Linux.
		case "rw":
		case "noexec":
		case "nosuid":
		case "nodev":
		case "user":

		default:
			err = fmt.Errorf(
				"Unrecognized mount option: %q (value %q)",
				opt.Name,
				opt.Value)

			return
		}
	}

	// Set the bucket.
	args = append(args, "--bucket="+device)

	// Set the mount point.
	args = append(args, "--mount_point="+mountPoint)

	return
}

func main() {
	// Print out each argument.
	for i, arg := range os.Args {
		log.Printf("Arg %d: %q", i, arg)
	}

	// Attempt to parse arguments.
	device, mountPoint, opts, err := parseArgs()
	if err != nil {
		log.Fatalf("parseArgs: %v", err)
	}

	// Print what we gleaned.
	log.Printf("Device: %q", device)
	log.Printf("Mount point: %q", mountPoint)
	for _, opt := range opts {
		log.Printf("Option %q: %q", opt.Name, opt.Value)
	}

	// Choose gcsfuse args.
	gcsfuseArgs, err := makeGcsfuseArgs(device, mountPoint, opts)
	if err != nil {
		log.Fatalf("makeGcsfuseArgs: %v", err)
	}

	for _, a := range gcsfuseArgs {
		log.Printf("gcsfuse arg: %q", a)
	}

	// Run gcsfuse and wait for it to complete.
	cmd := exec.Command("gcsfuse", gcsfuseArgs...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	err = cmd.Run()
	if err != nil {
		log.Fatalf("gcsfuse failed or failed to run: %v", err)
	}

	log.Println("gcsfuse completed successfully.")
}
<commit_msg>Fixed build errors.<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.

// A small helper for using gcsfuse with mount(8).
//
// Can be invoked using a command-line of the form expected for mount helpers.
// Calls the gcsfuse binary, which must be in $PATH, and waits for it to
// complete. The device is passed as --bucket, and other known options are
// converted to appropriate flags.
//
// This binary does not daemonize, and therefore must be used with a wrapper
// that performs daemonization if it is to be used directly with mount(8).
package main

import (
	"errors"
	"flag"
	"fmt"
	"log"
	"os"
	"os/exec"
	"strings"
)

var fOptions OptionSlice

func init() {
	flag.Var(&fOptions, "o", "Mount options. May be repeated.")
}

// A 'name=value' mount option. If '=value' is not present, only the name will
// be filled in.
type Option struct {
	Name  string
	Value string
}

// A slice of options that knows how to parse command-line flags into the
// slice, implementing flag.Value.
type OptionSlice []Option

func (os *OptionSlice) String() string {
	return fmt.Sprint(*os)
}

func (os *OptionSlice) Set(s string) (err error) {
	err = errors.New("TODO: Set")
	return
}

// Parse a single comma-separated list of mount options.
func parseOpts(s string) (opts []Option, err error) {
	// NOTE(jacobsa): The man pages don't define how escaping works, and as far
	// as I can tell there is no way to properly escape or quote a comma in the
	// options list for an fstab entry. So put our fingers in our ears and hope
	// that nobody needs a comma.
	for _, p := range strings.Split(s, ",") {
		var opt Option

		// Split on the first equals sign.
		if equalsIndex := strings.IndexByte(p, '='); equalsIndex != -1 {
			opt.Name = p[:equalsIndex]
			opt.Value = p[equalsIndex+1:]
		} else {
			opt.Name = p
		}

		opts = append(opts, opt)
	}

	return
}

// Attempt to parse the terrible undocumented format that mount(8) gives us.
// Return the 'device' (aka 'special' on OS X), the mount point, and a list of
// mount options encountered.
func parseArgs() (device string, mountPoint string, opts []Option, err error) {
	// Example invocation on OS X:
	//
	//     mount -t porp -o key_file=/some\ file.json -o ro,blah bucket ~/tmp/mp
	//
	// becomes the following arguments:
	//
	//     Arg 0: "/path/to/gcsfuse_mount_helper"
	//     Arg 1: "-o"
	//     Arg 2: "key_file=/some file.json"
	//     Arg 3: "-o"
	//     Arg 4: "ro"
	//     Arg 5: "-o"
	//     Arg 6: "blah"
	//     Arg 7: "bucket"
	//     Arg 8: "/path/to/mp"
	//
	// On Linux, the fstab entry
	//
	//     bucket /path/to/mp porp user,key_file=/some\040file.json
	//
	// becomes
	//
	//     Arg 0: "/path/to/gcsfuse_mount_helper"
	//     Arg 1: "bucket"
	//     Arg 2: "/path/to/mp"
	//     Arg 3: "-o"
	//     Arg 4: "rw,noexec,nosuid,nodev,user,key_file=/some file.json"
	//

	// Linux and OS X differ on the position of the options. So scan all
	// arguments (aside from the name of the binary), and:
	//
	//  *  Treat the first argument not following "-o" as the device name.
	//  *  Treat the second argument not following "-o" as the mount point.
	//  *  Treat the third argument not following "-o" as an error.
	//  *  Treat all arguments following "-o" as comma-separated options lists.
	//
	rawArgs := 0
	for i, arg := range os.Args {
		// Skip the binary name.
		if i == 0 {
			continue
		}

		// Skip "-o"; we will look back on the next iteration.
		if arg == "-o" {
			continue
		}

		// If the previous argument was "-o", this is a list of options.
		if os.Args[i-1] == "-o" {
			var tmp []Option
			tmp, err = parseOpts(arg)
			if err != nil {
				err = fmt.Errorf("parseOpts(%q): %v", arg, err)
				return
			}

			opts = append(opts, tmp...)
			continue
		}

		// Otherwise this is a non-option argument.
		switch rawArgs {
		case 0:
			device = arg

		case 1:
			mountPoint = arg

		default:
			err = fmt.Errorf(
				"Too many non-option arguments. The straw that broke the "+
					"camel's back: %q",
				arg)
			return
		}

		rawArgs++
	}

	// Did we see all of the raw arguments we expected?
	if rawArgs != 2 {
		err = fmt.Errorf("Expected 2 non-option arguments; got %d", rawArgs)
	}

	return
}

// Turn mount-style options into gcsfuse arguments. Skip known detritus that
// the mount command gives us.
//
// The result of this function should be appended to exec.Command.Args.
func makeGcsfuseArgs(
	device string,
	mountPoint string,
	opts []Option) (args []string, err error) {
	// Deal with options.
	for _, opt := range opts {
		switch opt.Name {
		case "key_file":
			args = append(args, "--key_file="+opt.Value)

		case "fuse_debug":
			args = append(args, "--fuse.debug")

		case "gcs_debug":
			args = append(args, "--gcs.debug")

		case "ro":
			args = append(args, "--read_only")

		// Ignore arguments for default and unsupported behavior automatically
		// added by mount(8) on Linux.
		case "rw":
		case "noexec":
		case "nosuid":
		case "nodev":
		case "user":

		default:
			err = fmt.Errorf(
				"Unrecognized mount option: %q (value %q)",
				opt.Name,
				opt.Value)

			return
		}
	}

	// Set the bucket.
	args = append(args, "--bucket="+device)

	// Set the mount point.
	args = append(args, "--mount_point="+mountPoint)

	return
}

func main() {
	// Print out each argument.
	for i, arg := range os.Args {
		log.Printf("Arg %d: %q", i, arg)
	}

	// Attempt to parse arguments.
	device, mountPoint, opts, err := parseArgs()
	if err != nil {
		log.Fatalf("parseArgs: %v", err)
	}

	// Print what we gleaned.
	log.Printf("Device: %q", device)
	log.Printf("Mount point: %q", mountPoint)
	for _, opt := range opts {
		log.Printf("Option %q: %q", opt.Name, opt.Value)
	}

	// Choose gcsfuse args.
	gcsfuseArgs, err := makeGcsfuseArgs(device, mountPoint, opts)
	if err != nil {
		log.Fatalf("makeGcsfuseArgs: %v", err)
	}

	for _, a := range gcsfuseArgs {
		log.Printf("gcsfuse arg: %q", a)
	}

	// Run gcsfuse and wait for it to complete.
	cmd := exec.Command("gcsfuse", gcsfuseArgs...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	err = cmd.Run()
	if err != nil {
		log.Fatalf("gcsfuse failed or failed to run: %v", err)
	}

	log.Println("gcsfuse completed successfully.")
}
<|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 gcsproxy_test

import (
	"testing"
	"time"

	"golang.org/x/net/context"

	"github.com/googlecloudplatform/gcsfuse/gcsproxy"
	"github.com/googlecloudplatform/gcsfuse/lease"
	"github.com/googlecloudplatform/gcsfuse/timeutil"
	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/gcs/gcsfake"
	"github.com/jacobsa/gcloud/gcs/gcsutil"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
)

func TestIntegration(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

const fileLeaserLimit = 1 << 10

type IntegrationTest struct {
	ctx    context.Context
	bucket gcs.Bucket
	leaser lease.FileLeaser
	clock  timeutil.SimulatedClock

	mo *checkingMutableObject
}

var _ SetUpInterface = &IntegrationTest{}
var _ TearDownInterface = &IntegrationTest{}

func init() { RegisterTestSuite(&IntegrationTest{}) }

func (t *IntegrationTest) SetUp(ti *TestInfo) {
	t.ctx = ti.Ctx
	t.bucket = gcsfake.NewFakeBucket(&t.clock, "some_bucket")
	t.leaser = lease.NewFileLeaser("", fileLeaserLimit)

	// Set up a fixed, non-zero time.
	t.clock.SetTime(time.Date(2012, 8, 15, 22, 56, 0, 0, time.Local))
}

func (t *IntegrationTest) TearDown() {
	if t.mo != nil {
		t.mo.Destroy()
	}
}

func (t *IntegrationTest) create(o *gcs.Object) {
	// Ensure invariants are checked.
	t.mo = &checkingMutableObject{
		ctx: t.ctx,
		wrapped: gcsproxy.NewMutableObject(
			o,
			t.bucket,
			t.leaser,
			&t.clock),
	}
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *IntegrationTest) ReadThenSync() {
	AssertTrue(false, "TODO")
}

func (t *IntegrationTest) WriteThenSync() {
	AssertTrue(false, "TODO")
}

func (t *IntegrationTest) TruncateThenSync() {
	AssertTrue(false, "TODO")
}

func (t *IntegrationTest) Stat_Clean() {
	AssertTrue(false, "TODO")
}

func (t *IntegrationTest) Stat_Dirty() {
	AssertTrue(false, "TODO")
}

func (t *IntegrationTest) SmallerThanLeaserLimit() {
	AssertTrue(false, "TODO")
}

func (t *IntegrationTest) LargerThanLeaserLimit() {
	AssertTrue(false, "TODO")
}

func (t *IntegrationTest) BackingObjectHasBeenDeleted_BeforeReading() {
	// Create an object to obtain a record, then delete it.
	createTime := t.clock.Now()
	o, err := gcsutil.CreateObject(t.ctx, t.bucket, "foo", "taco")
	AssertEq(nil, err)
	t.clock.AdvanceTime(time.Second)

	err = t.bucket.DeleteObject(t.ctx, o.Name)
	AssertEq(nil, err)

	// Create a mutable object around it.
	t.create(o)

	// Synchronously-available things should work.
	ExpectEq(o.Name, t.mo.Name())
	ExpectEq(o.Generation, t.mo.SourceGeneration())

	sr, err := t.mo.Stat(true)
	AssertEq(nil, err)
	ExpectEq(o.Size, sr.Size)
	ExpectThat(sr.Mtime, timeutil.TimeEq(createTime))
	ExpectTrue(sr.Clobbered)

	// Sync doesn't need to do anything.
	err = t.mo.Sync()
	ExpectEq(nil, err)

	// Anything that needs to fault in the contents should fail.
	_, err = t.mo.ReadAt([]byte{}, 0)
	ExpectThat(err, Error(HasSubstr("not found")))

	err = t.mo.Truncate(10)
	ExpectThat(err, Error(HasSubstr("not found")))

	_, err = t.mo.WriteAt([]byte{}, 0)
	ExpectThat(err, Error(HasSubstr("not found")))
}

func (t *IntegrationTest) BackingObjectHasBeenDeleted_AfterReading() {
	AssertTrue(false, "TODO")
}

func (t *IntegrationTest) BackingObjectHasBeenOverwritten_BeforeReading() {
	// Create an object, then create the mutable object wrapper around it.
	createTime := t.clock.Now()
	o, err := gcsutil.CreateObject(t.ctx, t.bucket, "foo", "taco")
	AssertEq(nil, err)
	t.clock.AdvanceTime(time.Second)

	t.create(o)

	// Overwrite the GCS object.
	_, err = gcsutil.CreateObject(t.ctx, t.bucket, "foo", "burrito")
	AssertEq(nil, err)

	// Synchronously-available things should work.
	ExpectEq(o.Name, t.mo.Name())
	ExpectEq(o.Generation, t.mo.SourceGeneration())

	sr, err := t.mo.Stat(true)
	AssertEq(nil, err)
	ExpectEq(o.Size, sr.Size)
	ExpectThat(sr.Mtime, timeutil.TimeEq(createTime))
	ExpectTrue(sr.Clobbered)

	// Sync doesn't need to do anything.
	err = t.mo.Sync()
	ExpectEq(nil, err)

	// Anything that needs to fault in the contents should fail.
	_, err = t.mo.ReadAt([]byte{}, 0)
	ExpectThat(err, Error(HasSubstr("not found")))

	err = t.mo.Truncate(10)
	ExpectThat(err, Error(HasSubstr("not found")))

	_, err = t.mo.WriteAt([]byte{}, 0)
	ExpectThat(err, Error(HasSubstr("not found")))
}

func (t *IntegrationTest) BackingObjectHasBeenOverwritten_AfterReading() {
	AssertTrue(false, "TODO")
}
<commit_msg>IntegrationTest.ReadThenSync<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 gcsproxy_test

import (
	"fmt"
	"io"
	"math"
	"testing"
	"time"

	"golang.org/x/net/context"

	"github.com/googlecloudplatform/gcsfuse/gcsproxy"
	"github.com/googlecloudplatform/gcsfuse/lease"
	"github.com/googlecloudplatform/gcsfuse/timeutil"
	"github.com/jacobsa/gcloud/gcs"
	"github.com/jacobsa/gcloud/gcs/gcsfake"
	"github.com/jacobsa/gcloud/gcs/gcsutil"
	. "github.com/jacobsa/oglematchers"
	. "github.com/jacobsa/ogletest"
)

func TestIntegration(t *testing.T) { RunTests(t) }

////////////////////////////////////////////////////////////////////////
// Boilerplate
////////////////////////////////////////////////////////////////////////

const fileLeaserLimit = 1 << 10

type IntegrationTest struct {
	ctx    context.Context
	bucket gcs.Bucket
	leaser lease.FileLeaser
	clock  timeutil.SimulatedClock

	mo *checkingMutableObject
}

var _ SetUpInterface = &IntegrationTest{}
var _ TearDownInterface = &IntegrationTest{}

func init() { RegisterTestSuite(&IntegrationTest{}) }

func (t *IntegrationTest) SetUp(ti *TestInfo) {
	t.ctx = ti.Ctx
	t.bucket = gcsfake.NewFakeBucket(&t.clock, "some_bucket")
	t.leaser = lease.NewFileLeaser("", fileLeaserLimit)

	// Set up a fixed, non-zero time.
	t.clock.SetTime(time.Date(2012, 8, 15, 22, 56, 0, 0, time.Local))
}

func (t *IntegrationTest) TearDown() {
	if t.mo != nil {
		t.mo.Destroy()
	}
}

func (t *IntegrationTest) create(o *gcs.Object) {
	// Ensure invariants are checked.
	t.mo = &checkingMutableObject{
		ctx: t.ctx,
		wrapped: gcsproxy.NewMutableObject(
			o,
			t.bucket,
			t.leaser,
			&t.clock),
	}
}

// Return the object generation, or -1 if non-existent. Panic on error.
func (t *IntegrationTest) objectGeneration(name string) (gen int64) {
	// Stat.
	req := &gcs.StatObjectRequest{Name: name}
	o, err := t.bucket.StatObject(t.ctx, req)

	if _, ok := err.(*gcs.NotFoundError); ok {
		gen = -1
		return
	}

	if err != nil {
		panic(err)
	}

	// Check the result.
	if o.Generation > math.MaxInt64 {
		panic(fmt.Sprintf("Out of range: %v", o.Generation))
	}

	gen = o.Generation
	return
}

////////////////////////////////////////////////////////////////////////
// Tests
////////////////////////////////////////////////////////////////////////

func (t *IntegrationTest) ReadThenSync() {
	// Create.
	o, err := gcsutil.CreateObject(t.ctx, t.bucket, "foo", "taco")
	AssertEq(nil, err)

	t.create(o)

	// Read the contents.
	buf := make([]byte, 1024)
	n, err := t.mo.ReadAt(buf, 0)

	AssertThat(err, AnyOf(io.EOF, nil))
	ExpectEq(len("taco"), n)
	ExpectEq("taco", string(buf[:n]))

	// Sync doesn't need to do anything.
	err = t.mo.Sync()
	ExpectEq(nil, err)

	ExpectEq(o.Generation, t.mo.SourceGeneration())
	ExpectEq(o.Generation, t.objectGeneration("foo"))
}

func (t *IntegrationTest) WriteThenSync() {
	AssertTrue(false, "TODO")
}

func (t *IntegrationTest) TruncateThenSync() {
	AssertTrue(false, "TODO")
}

func (t *IntegrationTest) Stat_Clean() {
	AssertTrue(false, "TODO")
}

func (t *IntegrationTest) Stat_Dirty() {
	AssertTrue(false, "TODO")
}

func (t *IntegrationTest) SmallerThanLeaserLimit() {
	AssertTrue(false, "TODO")
}

func (t *IntegrationTest) LargerThanLeaserLimit() {
	AssertTrue(false, "TODO")
}

func (t *IntegrationTest) BackingObjectHasBeenDeleted_BeforeReading() {
	// Create an object to obtain a record, then delete it.
	createTime := t.clock.Now()
	o, err := gcsutil.CreateObject(t.ctx, t.bucket, "foo", "taco")
	AssertEq(nil, err)
	t.clock.AdvanceTime(time.Second)

	err = t.bucket.DeleteObject(t.ctx, o.Name)
	AssertEq(nil, err)

	// Create a mutable object around it.
	t.create(o)

	// Synchronously-available things should work.
	ExpectEq(o.Name, t.mo.Name())
	ExpectEq(o.Generation, t.mo.SourceGeneration())

	sr, err := t.mo.Stat(true)
	AssertEq(nil, err)
	ExpectEq(o.Size, sr.Size)
	ExpectThat(sr.Mtime, timeutil.TimeEq(createTime))
	ExpectTrue(sr.Clobbered)

	// Sync doesn't need to do anything.
	err = t.mo.Sync()
	ExpectEq(nil, err)

	// Anything that needs to fault in the contents should fail.
	_, err = t.mo.ReadAt([]byte{}, 0)
	ExpectThat(err, Error(HasSubstr("not found")))

	err = t.mo.Truncate(10)
	ExpectThat(err, Error(HasSubstr("not found")))

	_, err = t.mo.WriteAt([]byte{}, 0)
	ExpectThat(err, Error(HasSubstr("not found")))
}

func (t *IntegrationTest) BackingObjectHasBeenDeleted_AfterReading() {
	AssertTrue(false, "TODO")
}

func (t *IntegrationTest) BackingObjectHasBeenOverwritten_BeforeReading() {
	// Create an object, then create the mutable object wrapper around it.
	createTime := t.clock.Now()
	o, err := gcsutil.CreateObject(t.ctx, t.bucket, "foo", "taco")
	AssertEq(nil, err)
	t.clock.AdvanceTime(time.Second)

	t.create(o)

	// Overwrite the GCS object.
	_, err = gcsutil.CreateObject(t.ctx, t.bucket, "foo", "burrito")
	AssertEq(nil, err)

	// Synchronously-available things should work.
	ExpectEq(o.Name, t.mo.Name())
	ExpectEq(o.Generation, t.mo.SourceGeneration())

	sr, err := t.mo.Stat(true)
	AssertEq(nil, err)
	ExpectEq(o.Size, sr.Size)
	ExpectThat(sr.Mtime, timeutil.TimeEq(createTime))
	ExpectTrue(sr.Clobbered)

	// Sync doesn't need to do anything.
	err = t.mo.Sync()
	ExpectEq(nil, err)

	// Anything that needs to fault in the contents should fail.
	_, err = t.mo.ReadAt([]byte{}, 0)
	ExpectThat(err, Error(HasSubstr("not found")))

	err = t.mo.Truncate(10)
	ExpectThat(err, Error(HasSubstr("not found")))

	_, err = t.mo.WriteAt([]byte{}, 0)
	ExpectThat(err, Error(HasSubstr("not found")))
}

func (t *IntegrationTest) BackingObjectHasBeenOverwritten_AfterReading() {
	AssertTrue(false, "TODO")
}
<|endoftext|>


<commit_before>package dataset_test

import (
	"fmt"
	"testing"
	"time"

	"github.com/m-lab/annotation-service/handler/dataset"
	"github.com/m-lab/annotation-service/handler/geoip"
	"gopkg.in/check.v1"
)

func TestExtractDateFromFilename(t *testing.T) {
	date, err := dataset.ExtractDateFromFilename("Maxmind/2017/05/08/20170508T080000Z-GeoLiteCity.dat.gz")
	if date.Year() != 2017 || date.Month() != 5 || date.Day() != 8 || err != nil {
		t.Errorf("Did not extract data correctly. Expected %d, got %v, %+v.", 20170508, date, err)
	}

	date2, err := dataset.ExtractDateFromFilename("Maxmind/2017/10/05/20171005T033334Z-GeoLite2-City-CSV.zip")
	if date2.Year() != 2017 || date2.Month() != 10 || date2.Day() != 5 || err != nil {
		t.Errorf("Did not extract data correctly. Expected %d, got %v, %+v.", 20171005, date2, err)
	}
}

func TestSelectGeoLegacyFile(t *testing.T) {
	testBucket := "downloader-mlab-testing"
	err := dataset.UpdateFilenamelist(testBucket)
	if err != nil {
		t.Errorf("cannot load test datasets")
	}
	date1, _ := time.Parse("January 2, 2006", "January 3, 2011")
	filename, err := dataset.SelectGeoLegacyFile(date1, testBucket)
	if filename != "Maxmind/2013/08/28/20130828T184800Z-GeoLiteCity.dat.gz" || err != nil {
		t.Errorf("Did not select correct dataset. Expected %s, got %s, %+v.",
			"Maxmind/2013/08/28/20130828T184800Z-GeoLiteCity.dat.gz", filename, err)
	}

	date2, _ := time.Parse("January 2, 2006", "March 7, 2014")
	filename2, err := dataset.SelectGeoLegacyFile(date2, testBucket)
	if filename2 != "Maxmind/2014/03/07/20140307T160000Z-GeoLiteCity.dat.gz" || err != nil {
		t.Errorf("Did not select correct dataset. Expected %s, got %s, %+v.",
			"Maxmind/2014/03/07/20140307T160000Z-GeoLiteCity.dat.gz", filename2, err)
	}

	// before the cutoff date.
	date3, _ := time.Parse("January 2, 2006", "August 14, 2017")
	filename3, err := dataset.SelectGeoLegacyFile(date3, testBucket)
	if filename3 != "Maxmind/2017/08/08/20170808T080000Z-GeoLiteCity.dat.gz" || err != nil {
		t.Errorf("Did not select correct dataset. Expected %s, got %s, %+v.",
			"Maxmind/2017/08/08/20170808T080000Z-GeoLiteCity.dat.gz", filename3, err)
	}

	// after the cutoff date.
	date4, _ := time.Parse("January 2, 2006", "August 15, 2017")
	filename4, err := dataset.SelectGeoLegacyFile(date4, testBucket)
	if filename4 != "Maxmind/2017/08/15/20170815T200946Z-GeoLite2-City-CSV.zip" || err != nil {
		t.Errorf("Did not select correct dataset. Expected %s, got %s, %+v.",
			"Maxmind/2017/08/15/20170815T200946Z-GeoLite2-City-CSV.zip", filename4, err)
	}

	// return the latest available dataset.
	date5, _ := time.Parse("January 2, 2006", "August 15, 2037")
	filename5, err := dataset.SelectGeoLegacyFile(date5, testBucket)
	if filename5 != "Maxmind/2018/09/12/20180912T054119Z-GeoLite2-City-CSV.zip" || err != nil {
		t.Errorf("Did not select correct dataset. Expected %s, got %s, %+v.",
			"Maxmind/2018/09/12/20180912T054119Z-GeoLite2-City-CSV.zip", filename5, err)
	}
}

// Hook up gocheck into the gotest runner.
func Test(t *testing.T) { TestingT(t) }

type GeoIPSuite struct {
}

var _ = Suite(&GeoIPSuite{})

func (s *GeoIPSuite) TestLoadLegacyGeoliteDataset(c *C) {
	date1, _ := time.Parse("January 2, 2006", "February 3, 2014")
	gi, err := dataset.LoadLegacyGeoliteDataset(date1, "downloader-mlab-testing")
	fmt.Printf("%v", err)
	if gi != nil {
		record := gi.GetRecord("207.171.7.51")
		c.Assert(record, NotNil)
		c.Check(
			*record,
			Equals,
			geoip.GeoIPRecord{
				CountryCode:   "US",
				CountryCode3:  "USA",
				CountryName:   "United States",
				Region:        "CA",
				City:          "El Segundo",
				PostalCode:    "90245",
				Latitude:      33.9164,
				Longitude:     -118.4041,
				AreaCode:      310,
				MetroCode:     803,
				CharSet:       1,
				ContinentCode: "NA",
			},
		)
	}
}
<commit_msg>unittest fix<commit_after>package dataset_test

import (
	"fmt"
	"testing"
	"time"

	"github.com/m-lab/annotation-service/handler/dataset"
	"github.com/m-lab/annotation-service/handler/geoip"
	check "gopkg.in/check.v1"
)

func TestExtractDateFromFilename(t *testing.T) {
	date, err := dataset.ExtractDateFromFilename("Maxmind/2017/05/08/20170508T080000Z-GeoLiteCity.dat.gz")
	if date.Year() != 2017 || date.Month() != 5 || date.Day() != 8 || err != nil {
		t.Errorf("Did not extract data correctly. Expected %d, got %v, %+v.", 20170508, date, err)
	}

	date2, err := dataset.ExtractDateFromFilename("Maxmind/2017/10/05/20171005T033334Z-GeoLite2-City-CSV.zip")
	if date2.Year() != 2017 || date2.Month() != 10 || date2.Day() != 5 || err != nil {
		t.Errorf("Did not extract data correctly. Expected %d, got %v, %+v.", 20171005, date2, err)
	}
}

func TestSelectGeoLegacyFile(t *testing.T) {
	testBucket := "downloader-mlab-testing"
	err := dataset.UpdateFilenamelist(testBucket)
	if err != nil {
		t.Errorf("cannot load test datasets")
	}
	date1, _ := time.Parse("January 2, 2006", "January 3, 2011")
	filename, err := dataset.SelectGeoLegacyFile(date1, testBucket)
	if filename != "Maxmind/2013/08/28/20130828T184800Z-GeoLiteCity.dat.gz" || err != nil {
		t.Errorf("Did not select correct dataset. Expected %s, got %s, %+v.",
			"Maxmind/2013/08/28/20130828T184800Z-GeoLiteCity.dat.gz", filename, err)
	}

	date2, _ := time.Parse("January 2, 2006", "March 7, 2014")
	filename2, err := dataset.SelectGeoLegacyFile(date2, testBucket)
	if filename2 != "Maxmind/2014/03/07/20140307T160000Z-GeoLiteCity.dat.gz" || err != nil {
		t.Errorf("Did not select correct dataset. Expected %s, got %s, %+v.",
			"Maxmind/2014/03/07/20140307T160000Z-GeoLiteCity.dat.gz", filename2, err)
	}

	// before the cutoff date.
	date3, _ := time.Parse("January 2, 2006", "August 14, 2017")
	filename3, err := dataset.SelectGeoLegacyFile(date3, testBucket)
	if filename3 != "Maxmind/2017/08/08/20170808T080000Z-GeoLiteCity.dat.gz" || err != nil {
		t.Errorf("Did not select correct dataset. Expected %s, got %s, %+v.",
			"Maxmind/2017/08/08/20170808T080000Z-GeoLiteCity.dat.gz", filename3, err)
	}

	// after the cutoff date.
	date4, _ := time.Parse("January 2, 2006", "August 15, 2017")
	filename4, err := dataset.SelectGeoLegacyFile(date4, testBucket)
	if filename4 != "Maxmind/2017/08/15/20170815T200946Z-GeoLite2-City-CSV.zip" || err != nil {
		t.Errorf("Did not select correct dataset. Expected %s, got %s, %+v.",
			"Maxmind/2017/08/15/20170815T200946Z-GeoLite2-City-CSV.zip", filename4, err)
	}

	// return the latest available dataset.
	date5, _ := time.Parse("January 2, 2006", "August 15, 2037")
	filename5, err := dataset.SelectGeoLegacyFile(date5, testBucket)
	if filename5 != "Maxmind/2018/09/12/20180912T054119Z-GeoLite2-City-CSV.zip" || err != nil {
		t.Errorf("Did not select correct dataset. Expected %s, got %s, %+v.",
			"Maxmind/2018/09/12/20180912T054119Z-GeoLite2-City-CSV.zip", filename5, err)
	}
}

// Hook up gocheck into the gotest runner.
func Test(t *testing.T) { TestingT(t) }

type GeoIPSuite struct {
}

var _ = Suite(&GeoIPSuite{})

func (s *GeoIPSuite) TestLoadLegacyGeoliteDataset(c *check.C) {
	date1, _ := time.Parse("January 2, 2006", "February 3, 2014")
	gi, err := dataset.LoadLegacyGeoliteDataset(date1, "downloader-mlab-testing")
	fmt.Printf("%v", err)
	if gi != nil {
		record := gi.GetRecord("207.171.7.51")
		c.Assert(record, NotNil)
		c.Check(
			*record,
			Equals,
			geoip.GeoIPRecord{
				CountryCode:   "US",
				CountryCode3:  "USA",
				CountryName:   "United States",
				Region:        "CA",
				City:          "El Segundo",
				PostalCode:    "90245",
				Latitude:      33.9164,
				Longitude:     -118.4041,
				AreaCode:      310,
				MetroCode:     803,
				CharSet:       1,
				ContinentCode: "NA",
			},
		)
	}
}
<|endoftext|>


<commit_before>package gui

/*
#cgo pkg-config: gdk-3.0
#include <gdk/gdk.h>
*/
import "C"

import (
	"container/list"
	"fmt"
	"github.com/amrhassan/psmpc/mpdinfo"
	"github.com/amrhassan/psmpc/resources"
	"github.com/conformal/gotk3/gdk"
	"github.com/conformal/gotk3/glib"
	"github.com/conformal/gotk3/gtk"
	"log"
	"os"
	"unsafe"
)

/*
 * The paths where the static resources are looked up from. The paths are tried in the order
 * they are listed in, and the first one that exists is used.
 */
var static_resource_file_paths = []string{
	".",
	"~/.local/share/psmpc/",
	"/usr/local/share/psmpc/",
	"/usr/share/psmpc/",
}

// An action type label
type Action int

// Handlers for fired actions from the GUI
type ActionHandler func([]interface{})

// Player actions that can be performed from the GUI
const (
	ACTION_PLAY      = iota
	ACTION_PAUSE     = iota
	ACTION_PLAYPAUSE = iota
	ACTION_NEXT      = iota
	ACTION_PREVIOUS  = iota
	ACTION_QUIT      = iota
)

type GUI struct {
	builder                    *gtk.Builder
	main_window                *gtk.Window
	title_label                *gtk.Label
	artist_label               *gtk.Label
	stopped_header             *gtk.Box
	playback_header            *gtk.Box
	controls_box               *gtk.Box
	artist_box                 *gtk.Box
	previous_button            *gtk.Button
	playpause_button           *gtk.Button
	next_button                *gtk.Button
	play_image                 *gtk.Image
	pause_image                *gtk.Image
	status_icon                *gtk.StatusIcon
	album_box                  *gtk.Box
	album_label                *gtk.Label
	album_art_image            *gtk.Image
	registered_action_handlers map[Action]*list.List
	buttonKeyMap               map[int]Action
	resourceManager            *resources.ResourceManager
}

func error_panic(message string, err error) {
	panic(message + ": " + err.Error())
}

func path_exists(path string) bool {
	_, path_error := os.Stat(path)
	return path_error == nil || os.IsExist(path_error)
}

// A global cahe of static resources. Used by get_static_resource_path()
var staticResources = make(map[string]string)

func get_static_resource_path(resourceName string) string {

	entry, exists := staticResources[resourceName]
	if exists {
		return entry
	}

	for _, path := range static_resource_file_paths {
		full_path := path + "/gui/" + resourceName
		if path_exists(full_path) {
			log.Printf("Using %s file from: %s", resourceName, full_path)
			staticResources[resourceName] = full_path
			return full_path
		}
	}

	log.Panicf("Can't find %s file", resourceName)
	return ""
}

// Constructs and initializes a new GUI instance
// Args:
// 	buttonKeyMap: a mapping between button key values and GUI actions
func NewGUI(buttonKeyMap map[int]Action) *GUI {
	gtk.Init(nil)

	builder, err := gtk.BuilderNew()
	if err != nil {
		error_panic("Failed to create gtk.Builder", err)
	}

	err = builder.AddFromFile(get_static_resource_path("ui.glade"))
	if err != nil {
		error_panic("Failed to load the Glade UI file", err)
	}

	getGtkObject := func(name string) glib.IObject {
		object, err := builder.GetObject(name)
		if err != nil {
			panic("Failed to retrieve GTK object " + name)
		}
		return object
	}

	main_window := getGtkObject("main_window").(*gtk.Window)
	main_window.SetIconFromFile(get_static_resource_path("icon.png"))

	artist_label := getGtkObject("artist_label").(*gtk.Label)
	title_label := getGtkObject("title_label").(*gtk.Label)
	playback_header := getGtkObject("playback_header_box").(*gtk.Box)
	controls_box := getGtkObject("controls_box").(*gtk.Box)
	artist_box := getGtkObject("artist_box").(*gtk.Box)
	playpause_button := getGtkObject("play-pause_button").(*gtk.Button)
	previous_button := getGtkObject("previous_button").(*gtk.Button)
	next_button := getGtkObject("next_button").(*gtk.Button)
	pause_image, _ := gtk.ImageNewFromIconName("gtk-media-pause", gtk.ICON_SIZE_BUTTON)
	play_image := getGtkObject("play_image").(*gtk.Image)
	status_icon, _ := gtk.StatusIconNewFromFile(get_static_resource_path("icon.png"))
	album_box := getGtkObject("album_box").(*gtk.Box)
	album_label := getGtkObject("album_label").(*gtk.Label)
	album_art_image := getGtkObject("album_art").(*gtk.Image)

	return &GUI{
		builder:                    builder,
		main_window:                main_window,
		title_label:                title_label,
		artist_label:               artist_label,
		controls_box:               controls_box,
		artist_box:                 artist_box,
		playpause_button:           playpause_button,
		previous_button:            previous_button,
		next_button:                next_button,
		playback_header:            playback_header,
		play_image:                 play_image,
		pause_image:                pause_image,
		status_icon:                status_icon,
		album_box:                  album_box,
		album_label:                album_label,
		album_art_image:            album_art_image,
		registered_action_handlers: make(map[Action]*list.List),
		buttonKeyMap:               buttonKeyMap,
		resourceManager:            resources.NewResourceManager(),
	}
}

// Initiates the GUI
func (this *GUI) Run() {

	this.main_window.Connect("destroy", func() {
		this.fireAction(ACTION_QUIT)
	})

	this.status_icon.Connect("activate", func() {
		log.Println("Status Icon clicked!")
		this.main_window.SetVisible(!this.main_window.GetVisible())
	})

	this.playpause_button.Connect("clicked", func() {
		this.fireAction(ACTION_PLAYPAUSE)
	})

	this.previous_button.Connect("clicked", func() {
		this.fireAction(ACTION_PREVIOUS)
	})

	this.next_button.Connect("clicked", func() {
		this.fireAction(ACTION_NEXT)
	})

	this.main_window.Connect("key-release-event", func(window *gtk.Window, event *gdk.Event) {
		key := extract_key_from_gdk_event(event)
		action, mapped := this.buttonKeyMap[key.value]

		if mapped {
			this.fireAction(action)
		}
	})

	this.main_window.ShowAll()
	gtk.Main()
}

// A keyboard key
type key struct {
	value          int
	representation string
}

// Extracts a key instance from the GdkEventKey wrapped in the given gdk.Event
func extract_key_from_gdk_event(gdk_key_event *gdk.Event) key {
	log.Printf("Extracting pressed key")
	value := (*C.GdkEventKey)(unsafe.Pointer(gdk_key_event.Native())).keyval
	repr := (*C.char)(C.gdk_keyval_name(value))
	return key{
		value:          int(value),
		representation: C.GoString(repr),
	}
}

// Shuts down the GUI
func (this *GUI) Quit() {
	glib.IdleAdd(func() {
		gtk.MainQuit()
	})
}

// Updates the GUI with the currently-playing song information
func (this *GUI) UpdateCurrentSong(current_song *mpdinfo.CurrentSong) {
	log.Printf("Updating current song: %v", current_song)

	executeInGlibLoop(func() {
		this.title_label.SetText(current_song.Title)
		this.artist_label.SetText(current_song.Artist)

		if current_song.Album != "" {
			this.album_label.SetText(current_song.Album)
		} else {
			this.album_box.Hide()
		}

		this.main_window.SetTitle(fmt.Sprintf("psmpc: %s - %s", current_song.Artist, current_song.Title))
		this.status_icon.SetTooltipText(fmt.Sprintf("psmpc: %s - %s", current_song.Artist, current_song.Title))
		this.album_art_image.SetFromFile(get_static_resource_path("album.png"))
	})

	go func() {
		album_art_fp, err :=
			this.resourceManager.GetResourceAsFilePath(&resources.Track{current_song}, resources.ALBUM_ART)
		if err != nil {
			log.Println("Failed to get album art for %s", current_song)
		} else {
			executeInGlibLoop(func() {
				this.album_art_image.SetFromFile(album_art_fp)
			})
		}
	}()
}

// The only thread-safe way to execute GTK-manipulating code.
func executeInGlibLoop(code func()) {
	_, err := glib.IdleAdd(code)
	if err != nil {
		log.Fatal("Failed to do glib.IdleAdd()")
	}
}

// Updates the GUI with the current MPD status
func (this *GUI) UpdateCurrentStatus(current_status *mpdinfo.Status) {
	log.Printf("Updating current status: %v", current_status)
	_, err := glib.IdleAdd(func() {
		switch current_status.State {

		case mpdinfo.STATE_STOPPED:
			this.controls_box.Hide()
			this.artist_box.Hide()
			this.album_box.Hide()
			this.title_label.SetText("Stopped")
			this.main_window.SetTitle("psmpc")
			this.status_icon.SetTooltipText("psmpc")
			this.album_art_image.SetFromFile(get_static_resource_path("album.png"))

		case mpdinfo.STATE_PLAYING:
			this.controls_box.Show()
			this.artist_box.Show()
			this.album_box.Show()
			this.playpause_button.SetImage(this.pause_image)

		case mpdinfo.STATE_PAUSED:
			this.controls_box.Show()
			this.artist_box.Show()
			this.album_box.Show()
			this.playpause_button.SetImage(this.play_image)
		}
	})

	if err != nil {
		log.Fatal("Failed to do glib.IdleAdd()")
	}
}

// Fires the action specified by the given Action, passing the given arguments to all the
// subscribed handlers
func (this *GUI) fireAction(action_type Action, args ...interface{}) {
	log.Printf("Firing action %v", action_type)

	handlers, any := this.registered_action_handlers[action_type]

	if any == false {
		// None are registered
		log.Println("No action handlers found")
		return
	}

	log.Println("Handlers found ", handlers)

	for e := handlers.Front(); e != nil; e = e.Next() {
		log.Println("Executing handler", e.Value)
		handler := e.Value.(ActionHandler)
		go handler(args)
	}
}

// Registers an action handler to be executed when a specific action is fired
func (this *GUI) RegisterActionHandler(action_type Action, handler ActionHandler) {
	_, handlers_exist := this.registered_action_handlers[action_type]
	if !handlers_exist {
		this.registered_action_handlers[action_type] = list.New()
	}

	this.registered_action_handlers[action_type].PushFront(handler)
}
<commit_msg>Avoided redundant GUI updating with the same song<commit_after>package gui

/*
#cgo pkg-config: gdk-3.0
#include <gdk/gdk.h>
*/
import "C"

import (
	"container/list"
	"fmt"
	"github.com/amrhassan/psmpc/mpdinfo"
	"github.com/amrhassan/psmpc/resources"
	"github.com/conformal/gotk3/gdk"
	"github.com/conformal/gotk3/glib"
	"github.com/conformal/gotk3/gtk"
	"log"
	"os"
	"unsafe"
)

/*
 * The paths where the static resources are looked up from. The paths are tried in the order
 * they are listed in, and the first one that exists is used.
 */
var static_resource_file_paths = []string{
	".",
	"~/.local/share/psmpc/",
	"/usr/local/share/psmpc/",
	"/usr/share/psmpc/",
}

// An action type label
type Action int

// Handlers for fired actions from the GUI
type ActionHandler func([]interface{})

// Player actions that can be performed from the GUI
const (
	ACTION_PLAY      = iota
	ACTION_PAUSE     = iota
	ACTION_PLAYPAUSE = iota
	ACTION_NEXT      = iota
	ACTION_PREVIOUS  = iota
	ACTION_QUIT      = iota
)

type GUI struct {
	builder                    *gtk.Builder
	main_window                *gtk.Window
	title_label                *gtk.Label
	artist_label               *gtk.Label
	stopped_header             *gtk.Box
	playback_header            *gtk.Box
	controls_box               *gtk.Box
	artist_box                 *gtk.Box
	previous_button            *gtk.Button
	playpause_button           *gtk.Button
	next_button                *gtk.Button
	play_image                 *gtk.Image
	pause_image                *gtk.Image
	status_icon                *gtk.StatusIcon
	album_box                  *gtk.Box
	album_label                *gtk.Label
	album_art_image            *gtk.Image
	registered_action_handlers map[Action]*list.List
	buttonKeyMap               map[int]Action
	resourceManager            *resources.ResourceManager
	currentSong                *mpdinfo.CurrentSong
}

func error_panic(message string, err error) {
	panic(message + ": " + err.Error())
}

func path_exists(path string) bool {
	_, path_error := os.Stat(path)
	return path_error == nil || os.IsExist(path_error)
}

// A global cahe of static resources. Used by get_static_resource_path()
var staticResources = make(map[string]string)

func get_static_resource_path(resourceName string) string {

	entry, exists := staticResources[resourceName]
	if exists {
		return entry
	}

	for _, path := range static_resource_file_paths {
		full_path := path + "/gui/" + resourceName
		if path_exists(full_path) {
			log.Printf("Using %s file from: %s", resourceName, full_path)
			staticResources[resourceName] = full_path
			return full_path
		}
	}

	log.Panicf("Can't find %s file", resourceName)
	return ""
}

// Constructs and initializes a new GUI instance
// Args:
// 	buttonKeyMap: a mapping between button key values and GUI actions
func NewGUI(buttonKeyMap map[int]Action) *GUI {
	gtk.Init(nil)

	builder, err := gtk.BuilderNew()
	if err != nil {
		error_panic("Failed to create gtk.Builder", err)
	}

	err = builder.AddFromFile(get_static_resource_path("ui.glade"))
	if err != nil {
		error_panic("Failed to load the Glade UI file", err)
	}

	getGtkObject := func(name string) glib.IObject {
		object, err := builder.GetObject(name)
		if err != nil {
			panic("Failed to retrieve GTK object " + name)
		}
		return object
	}

	main_window := getGtkObject("main_window").(*gtk.Window)
	main_window.SetIconFromFile(get_static_resource_path("icon.png"))

	artist_label := getGtkObject("artist_label").(*gtk.Label)
	title_label := getGtkObject("title_label").(*gtk.Label)
	playback_header := getGtkObject("playback_header_box").(*gtk.Box)
	controls_box := getGtkObject("controls_box").(*gtk.Box)
	artist_box := getGtkObject("artist_box").(*gtk.Box)
	playpause_button := getGtkObject("play-pause_button").(*gtk.Button)
	previous_button := getGtkObject("previous_button").(*gtk.Button)
	next_button := getGtkObject("next_button").(*gtk.Button)
	pause_image, _ := gtk.ImageNewFromIconName("gtk-media-pause", gtk.ICON_SIZE_BUTTON)
	play_image := getGtkObject("play_image").(*gtk.Image)
	status_icon, _ := gtk.StatusIconNewFromFile(get_static_resource_path("icon.png"))
	album_box := getGtkObject("album_box").(*gtk.Box)
	album_label := getGtkObject("album_label").(*gtk.Label)
	album_art_image := getGtkObject("album_art").(*gtk.Image)

	return &GUI{
		builder:                    builder,
		main_window:                main_window,
		title_label:                title_label,
		artist_label:               artist_label,
		controls_box:               controls_box,
		artist_box:                 artist_box,
		playpause_button:           playpause_button,
		previous_button:            previous_button,
		next_button:                next_button,
		playback_header:            playback_header,
		play_image:                 play_image,
		pause_image:                pause_image,
		status_icon:                status_icon,
		album_box:                  album_box,
		album_label:                album_label,
		album_art_image:            album_art_image,
		registered_action_handlers: make(map[Action]*list.List),
		buttonKeyMap:               buttonKeyMap,
		resourceManager:            resources.NewResourceManager(),
	}
}

// Initiates the GUI
func (this *GUI) Run() {

	this.main_window.Connect("destroy", func() {
		this.fireAction(ACTION_QUIT)
	})

	this.status_icon.Connect("activate", func() {
		log.Println("Status Icon clicked!")
		this.main_window.SetVisible(!this.main_window.GetVisible())
	})

	this.playpause_button.Connect("clicked", func() {
		this.fireAction(ACTION_PLAYPAUSE)
	})

	this.previous_button.Connect("clicked", func() {
		this.fireAction(ACTION_PREVIOUS)
	})

	this.next_button.Connect("clicked", func() {
		this.fireAction(ACTION_NEXT)
	})

	this.main_window.Connect("key-release-event", func(window *gtk.Window, event *gdk.Event) {
		key := extract_key_from_gdk_event(event)
		action, mapped := this.buttonKeyMap[key.value]

		if mapped {
			this.fireAction(action)
		}
	})

	this.main_window.ShowAll()
	gtk.Main()
}

// A keyboard key
type key struct {
	value          int
	representation string
}

// Extracts a key instance from the GdkEventKey wrapped in the given gdk.Event
func extract_key_from_gdk_event(gdk_key_event *gdk.Event) key {
	log.Printf("Extracting pressed key")
	value := (*C.GdkEventKey)(unsafe.Pointer(gdk_key_event.Native())).keyval
	repr := (*C.char)(C.gdk_keyval_name(value))
	return key{
		value:          int(value),
		representation: C.GoString(repr),
	}
}

// Shuts down the GUI
func (this *GUI) Quit() {
	glib.IdleAdd(func() {
		gtk.MainQuit()
	})
}

// Updates the GUI with the currently-playing song information
func (this *GUI) UpdateCurrentSong(current_song *mpdinfo.CurrentSong) {

	if this.currentSong != nil && *current_song == *(this.currentSong) {
		return
	}

	log.Printf("Updating current song: %v", current_song)
	this.currentSong = current_song

	executeInGlibLoop(func() {
		this.title_label.SetText(current_song.Title)
		this.artist_label.SetText(current_song.Artist)

		if current_song.Album != "" {
			this.album_label.SetText(current_song.Album)
		} else {
			this.album_box.Hide()
		}

		this.main_window.SetTitle(fmt.Sprintf("psmpc: %s - %s", current_song.Artist, current_song.Title))
		this.status_icon.SetTooltipText(fmt.Sprintf("psmpc: %s - %s", current_song.Artist, current_song.Title))
		this.album_art_image.SetFromFile(get_static_resource_path("album.png"))
	})

	go func() {
		album_art_fp, err :=
			this.resourceManager.GetResourceAsFilePath(&resources.Track{current_song}, resources.ALBUM_ART)
		if err != nil {
			log.Println("Failed to get album art for %s", current_song)
		} else {
			executeInGlibLoop(func() {
				this.album_art_image.SetFromFile(album_art_fp)
			})
		}
	}()
}

// The only thread-safe way to execute GTK-manipulating code.
func executeInGlibLoop(code func()) {
	_, err := glib.IdleAdd(code)
	if err != nil {
		log.Fatal("Failed to do glib.IdleAdd()")
	}
}

// Updates the GUI with the current MPD status
func (this *GUI) UpdateCurrentStatus(current_status *mpdinfo.Status) {
	log.Printf("Updating current status: %v", current_status)
	_, err := glib.IdleAdd(func() {
		switch current_status.State {

		case mpdinfo.STATE_STOPPED:
			this.controls_box.Hide()
			this.artist_box.Hide()
			this.album_box.Hide()
			this.title_label.SetText("Stopped")
			this.main_window.SetTitle("psmpc")
			this.status_icon.SetTooltipText("psmpc")
			this.album_art_image.SetFromFile(get_static_resource_path("album.png"))

		case mpdinfo.STATE_PLAYING:
			this.controls_box.Show()
			this.artist_box.Show()
			this.album_box.Show()
			this.playpause_button.SetImage(this.pause_image)

		case mpdinfo.STATE_PAUSED:
			this.controls_box.Show()
			this.artist_box.Show()
			this.album_box.Show()
			this.playpause_button.SetImage(this.play_image)
		}
	})

	if err != nil {
		log.Fatal("Failed to do glib.IdleAdd()")
	}
}

// Fires the action specified by the given Action, passing the given arguments to all the
// subscribed handlers
func (this *GUI) fireAction(action_type Action, args ...interface{}) {
	log.Printf("Firing action %v", action_type)

	handlers, any := this.registered_action_handlers[action_type]

	if any == false {
		// None are registered
		log.Println("No action handlers found")
		return
	}

	log.Println("Handlers found ", handlers)

	for e := handlers.Front(); e != nil; e = e.Next() {
		log.Println("Executing handler", e.Value)
		handler := e.Value.(ActionHandler)
		go handler(args)
	}
}

// Registers an action handler to be executed when a specific action is fired
func (this *GUI) RegisterActionHandler(action_type Action, handler ActionHandler) {
	_, handlers_exist := this.registered_action_handlers[action_type]
	if !handlers_exist {
		this.registered_action_handlers[action_type] = list.New()
	}

	this.registered_action_handlers[action_type].PushFront(handler)
}
<|endoftext|>


<commit_before>// Copyright 2012, Bryan Matsuo. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

/*  Filename:    gutterd.go
 *  Author:      Bryan Matsuo <bmatsuo@soe.ucsc.edu>
 *  Created:     2012-03-04 17:28:31.728667 -0800 PST
 *  Description: Main source file in gutterd
 */

import (
	"errors"
	"os"
	"os/signal"
	"path/filepath"
	"strings"

	"github.com/golang/glog"
	"github.com/howeyc/fsnotify"

	"github.com/bmatsuo/gutterd/handler"
	"github.com/bmatsuo/gutterd/metadata"
	"github.com/bmatsuo/gutterd/statsd"
	"github.com/bmatsuo/gutterd/watcher"
)

var (
	config   *Config            // Deamon configuration.
	handlers []*handler.Handler // The ordered set of torrent handlers.
	opt      *Options           // Command line options.
	fs       *watcher.Watcher   // Filesystem event watcher
)

func HomeDirectory() (home string, err error) {
	if home = os.Getenv("HOME"); home == "" {
		err = errors.New("Environment variable HOME not set.")
	}
	return
}

// Read the config file and setup global variables.
func init() {
	opt = parseFlags()

	// Read the deamon configuration. flag overrides default (~/.config/gutterd.json)
	var err error
	defconfig := &Config{}
	configPath := opt.ConfigPath
	if configPath == "" {
		home, err := HomeDirectory()
		if err != nil {
			glog.Fatalf("unable to locate home directory: %v", err)
		}
		configPath = filepath.Join(home, ".config", "gutterd.json")
	}
	if config, err = LoadConfig(configPath, defconfig); err != nil {
		glog.Fatalf("unable to load configuration: %v", err)
	}

	if config.Statsd != "" {
		err := statsd.Init(config.Statsd, "gutterd")
		if err != nil {
			glog.Warningf("statsd init error (no stats will be recorded); %v", err)
		}
		statsd.Incr("proc.start", 1, 1)
	}

	handlers = config.MakeHandlers()

	// command line flag overrides
	if opt.Watch != nil {
		config.Watch = opt.Watch
	}
	if opt.HTTP != "" {
		config.HTTP = opt.HTTP
	}

	statsd.Incr("proc.boot", 1, 1)
}

// Handle a .torrent file.
func handleFile(path string) {
	torrent, err := metadata.ReadMetadataFile(path)
	if err != nil {
		statsd.Incr("torrent.error", 1, 1)
		glog.Errorf("error reading torrent (%q); %v", path, err)
		return
	}
	// Find the first handler matching the supplied torrent.
	for _, handler := range handlers {
		if handler.Match(torrent) {
			name := "torrent.match." + handler.Name
			statsd.Incr(name, 1, 1)
			glog.Infof("match file:%q handler:%q watch:%q",
				torrent.Info.Name,
				handler.Name,
				handler.Watch,
			)
			mvpath := filepath.Join(handler.Watch, filepath.Base(path))
			if err := os.Rename(path, mvpath); err != nil {
				// TODO id associated with the match
				// XXX can i get the filename here?
				glog.Error("watch import failed (%q); %v", torrent.Info.Name, err)
			}
			return
		}
	}
	statsd.Incr("torrent.no-match", 1, 1)
	glog.Warningf("no handler matched torrent: %q", torrent.Info.Name)
}

func signalHandler() {
	sig := make(chan os.Signal, 2)
	signal.Notify(sig, os.Kill, os.Interrupt)
	for _ = range sig {
		fs.Close()
	}
}

func fsInit() (err error) {
	fs, err = watcher.NewInstr(
		func(event *fsnotify.FileEvent) bool {
			statsd.Incr("watcher.fs.events", 1, 1) //  filter sees all events
			return event.IsCreate() && strings.HasSuffix(event.Name, ".torrent")
		},
		func(err error) {
			statsd.Incr("watcher.fs.errors", 1, 1)
			glog.Warningf("watcher error: %v", err)
		})
	if err != nil {
		return
	}

	if err = fs.Watch(config.Watch...); err != nil {
		return
	}

	return
}

func main() {
	if config.HTTP != "" {
		go ListenAndServe()
		glog.Infof("serving http at %v", config.HTTP)
	}
	if err := fsInit(); err != nil {
		glog.Error("error initializing file system watcher; %v", err)
		os.Exit(1)
	}
	for event := range fs.Event {
		statsd.Incr("torrents.matches", 1, 1)
		handleFile(event.Name)
	}
}
<commit_msg>tweaks<commit_after>// Copyright 2012, Bryan Matsuo. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package main

/*  Filename:    gutterd.go
 *  Author:      Bryan Matsuo <bmatsuo@soe.ucsc.edu>
 *  Created:     2012-03-04 17:28:31.728667 -0800 PST
 *  Description: Main source file in gutterd
 */

import (
	"fmt"
	"os"
	"os/signal"
	"path/filepath"
	"strings"

	"github.com/golang/glog"
	"github.com/howeyc/fsnotify"

	"github.com/bmatsuo/gutterd/handler"
	"github.com/bmatsuo/gutterd/metadata"
	"github.com/bmatsuo/gutterd/statsd"
	"github.com/bmatsuo/gutterd/watcher"
)

var (
	config   *Config            // Deamon configuration.
	handlers []*handler.Handler // The ordered set of torrent handlers.
	opt      *Options           // Command line options.
	fs       *watcher.Watcher   // Filesystem event watcher
)

func HomeDirectory() (home string, err error) {
	if home = os.Getenv("HOME"); home == "" {
		err = fmt.Errorf("HOME is not set")
	}
	return
}

// Read the config file and setup global variables.
func init() {
	opt = parseFlags()

	// Read the deamon configuration. flag overrides default (~/.config/gutterd.json)
	var err error
	defconfig := &Config{}
	configPath := opt.ConfigPath
	if configPath == "" {
		home, err := HomeDirectory()
		if err != nil {
			glog.Fatalf("unable to locate home directory: %v", err)
		}
		configPath = filepath.Join(home, ".config", "gutterd.json")
	}
	if config, err = LoadConfig(configPath, defconfig); err != nil {
		glog.Fatalf("unable to load configuration: %v", err)
	}

	if config.Statsd != "" {
		err := statsd.Init(config.Statsd, "gutterd")
		if err != nil {
			glog.Warningf("statsd init error (no stats will be recorded); %v", err)
		}
		statsd.Incr("proc.start", 1, 1)
	}

	handlers = config.MakeHandlers()

	// command line flag overrides
	if opt.Watch != nil {
		config.Watch = opt.Watch
	}
	if opt.HTTP != "" {
		config.HTTP = opt.HTTP
	}

	statsd.Incr("proc.boot", 1, 1)
}

// Handle a .torrent file.
func handleFile(path string) {
	torrent, err := metadata.ReadMetadataFile(path)
	if err != nil {
		statsd.Incr("torrent.error", 1, 1)
		glog.Errorf("error reading torrent (%q); %v", path, err)
		return
	}
	// Find the first handler matching the supplied torrent.
	for _, handler := range handlers {
		if handler.Match(torrent) {
			name := "torrent.match." + handler.Name
			statsd.Incr(name, 1, 1)
			glog.Infof("match file:%q handler:%q watch:%q",
				torrent.Info.Name,
				handler.Name,
				handler.Watch,
			)
			mvpath := filepath.Join(handler.Watch, filepath.Base(path))
			if err := os.Rename(path, mvpath); err != nil {
				glog.Error("watch import failed (%q); %v", torrent.Info.Name, err)
			}
			return
		}
	}
	statsd.Incr("torrent.no-match", 1, 1)
	glog.Warningf("no handler matched torrent: %q", torrent.Info.Name)
}

func signalHandler() {
	sig := make(chan os.Signal, 2)
	signal.Notify(sig, os.Kill, os.Interrupt)
	for _ = range sig {
		fs.Close()
	}
}

func fsInit() (err error) {
	fs, err = watcher.NewInstr(
		func(event *fsnotify.FileEvent) bool {
			statsd.Incr("watcher.fs.events", 1, 1) //  filter sees all events
			return event.IsCreate() && strings.HasSuffix(event.Name, ".torrent")
		},
		func(err error) {
			statsd.Incr("watcher.fs.errors", 1, 1)
			glog.Warningf("watcher error: %v", err)
		})
	if err != nil {
		return
	}

	if err = fs.Watch(config.Watch...); err != nil {
		return
	}

	return
}

func main() {
	if config.HTTP != "" {
		go ListenAndServe()
		glog.Infof("serving http at %v", config.HTTP)
	}
	if err := fsInit(); err != nil {
		glog.Error("error initializing file system watcher; %v", err)
		os.Exit(1)
	}
	for event := range fs.Event {
		statsd.Incr("torrents.matches", 1, 1)
		handleFile(event.Name)
	}
}
<|endoftext|>


<commit_before>package cors

import (
	"log"
	"strings"

	"net/http"
)

// Handler executes CORS and handles the middleware chain to the next in stack
type Handler struct {
	cfg  Middleware
	next http.Handler
}

// Runs the CORS specification on the request before passing it to the next middleware chain
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h.prepResponse(w)

	if r.Method == optionsMethod {
		h.handlePreflight(w, r)
	} else {
		h.handleRequest(w, r)
	}

	h.next.ServeHTTP(w, r)
}

// Runs the CORS specification for OPTION requests
func (h *Handler) handlePreflight(w http.ResponseWriter, r *http.Request) {
	method := r.Header.Get(requestMethodHeader)
	if method == "" {
		method = r.Method
	}

	h.handleCommon(w, r, method)
}

// Runs the CORS specification for standard requests
func (h *Handler) handleRequest(w http.ResponseWriter, r *http.Request) {
	method := r.Method
	h.handleCommon(w, r, method)
}

// Shares common functionality for prefilght and standard requests
func (h *Handler) handleCommon(w http.ResponseWriter, r *http.Request, method string) {
	origin := r.Header.Get(originHeader)
	if !h.cfg.isOriginAllowed(origin) {
		h.requestDenied(w, errorBadOrigin)
		return
	}

	if !h.cfg.isMethodAllowed(method, origin) {
		h.requestDenied(w, errorBadMethod)
		return
	}

	headers := strings.Split(r.Header.Get(requestHeadersHeader), ",")
	if !h.cfg.areHeadersAllowed(headers, origin) {
		h.requestDenied(w, errorBadHeader)
		return
	}

	h.buildResponse(w, r, origin, method, headers)
}

// Sets the HTTP status to forbidden and logs error message
func (h *Handler) requestDenied(w http.ResponseWriter, m string) {
	log.Println(errorRoot, ": ", m)
	w.WriteHeader(http.StatusForbidden)
	return
}

// Preconfigure headers on the response
func (h *Handler) prepResponse(w http.ResponseWriter) {
	w.Header().Add(varyHeader, originHeader)
}

// Writes the Access Control response headers
func (h *Handler) buildResponse(w http.ResponseWriter, r *http.Request, origin string, method string, headers []string) {
	w.Header().Set(allowOriginHeader, origin)
	w.Header().Set(allowMethodsHeader, method)
	w.Header().Set(allowHeadersHeader, strings.Join(headers, ","))
}
<commit_msg>Small refactor update to reduce an extra slice join operation.<commit_after>package cors

import (
	"log"
	"strings"

	"net/http"
)

// Handler executes CORS and handles the middleware chain to the next in stack
type Handler struct {
	cfg  Middleware
	next http.Handler
}

// Runs the CORS specification on the request before passing it to the next middleware chain
func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
	h.prepResponse(w)

	if r.Method == optionsMethod {
		h.handlePreflight(w, r)
	} else {
		h.handleRequest(w, r)
	}

	h.next.ServeHTTP(w, r)
}

// Runs the CORS specification for OPTION requests
func (h *Handler) handlePreflight(w http.ResponseWriter, r *http.Request) {
	method := r.Header.Get(requestMethodHeader)
	if method == "" {
		method = r.Method
	}

	h.handleCommon(w, r, method)
}

// Runs the CORS specification for standard requests
func (h *Handler) handleRequest(w http.ResponseWriter, r *http.Request) {
	method := r.Method
	h.handleCommon(w, r, method)
}

// Shares common functionality for prefilght and standard requests
func (h *Handler) handleCommon(w http.ResponseWriter, r *http.Request, method string) {
	origin := r.Header.Get(originHeader)
	if !h.cfg.isOriginAllowed(origin) {
		h.requestDenied(w, errorBadOrigin)
		return
	}

	if !h.cfg.isMethodAllowed(method, origin) {
		h.requestDenied(w, errorBadMethod)
		return
	}

	headers := r.Header.Get(requestHeadersHeader)
	if !h.cfg.areHeadersAllowed(strings.Split(headers, ","), origin) {
		h.requestDenied(w, errorBadHeader)
		return
	}

	h.buildResponse(w, r, origin, method, headers)
}

// Sets the HTTP status to forbidden and logs error message
func (h *Handler) requestDenied(w http.ResponseWriter, m string) {
	log.Println(errorRoot, ": ", m)
	w.WriteHeader(http.StatusForbidden)
	return
}

// Preconfigure headers on the response
func (h *Handler) prepResponse(w http.ResponseWriter) {
	w.Header().Add(varyHeader, originHeader)
}

// Writes the Access Control response headers
func (h *Handler) buildResponse(w http.ResponseWriter, r *http.Request, origin string, method string, headers string) {
	w.Header().Set(allowOriginHeader, origin)
	w.Header().Set(allowMethodsHeader, method)
	w.Header().Set(allowHeadersHeader, headers)
}
<|endoftext|>