<commit_before>package cloudflare

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"time"

	"github.com/pkg/errors"
)

// ZoneLockdown represents a Zone Lockdown rule. A rule only permits access to
// the provided URL pattern(s) from the given IP address(es) or subnet(s).
type ZoneLockdown struct {
	ID             string               `json:"id"`
	Description    string               `json:"description"`
	URLs           []string             `json:"urls"`
	Configurations []ZoneLockdownConfig `json:"configurations"`
	Paused         bool                 `json:"paused"`
	Priority       int                  `json:"priority,omitempty"`
	CreatedOn      *time.Time           `json:"created_on,omitempty"`
	ModifiedOn     *time.Time           `json:"modified_on,omitempty"`
}

// ZoneLockdownConfig represents a Zone Lockdown config, which comprises
// a Target ("ip" or "ip_range") and a Value (an IP address or IP+mask,
// respectively.)
type ZoneLockdownConfig struct {
	Target string `json:"target"`
	Value  string `json:"value"`
}

// ZoneLockdownResponse represents a response from the Zone Lockdown endpoint.
type ZoneLockdownResponse struct {
	Result ZoneLockdown `json:"result"`
	Response
}

// ZoneLockdownListResponse represents a response from the List Zone Lockdown
// endpoint.
type ZoneLockdownListResponse struct {
	Result []ZoneLockdown `json:"result"`
	Response
	ResultInfo `json:"result_info"`
}

// CreateZoneLockdown creates a Zone ZoneLockdown rule for the given zone ID.
//
// API reference: https://api.cloudflare.com/#zone-ZoneLockdown-create-a-ZoneLockdown-rule
func (api *API) CreateZoneLockdown(ctx context.Context, zoneID string, ld ZoneLockdown) (*ZoneLockdownResponse, error) {
	uri := fmt.Sprintf("/zones/%s/firewall/lockdowns", zoneID)
	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, ld)
	if err != nil {
		return nil, err
	}

	response := &ZoneLockdownResponse{}
	err = json.Unmarshal(res, &response)
	if err != nil {
		return nil, errors.Wrap(err, errUnmarshalError)
	}

	return response, nil
}

// UpdateZoneLockdown updates a Zone ZoneLockdown rule (based on the ID) for the
// given zone ID.
//
// API reference: https://api.cloudflare.com/#zone-ZoneLockdown-update-ZoneLockdown-rule
func (api *API) UpdateZoneLockdown(ctx context.Context, zoneID string, id string, ld ZoneLockdown) (*ZoneLockdownResponse, error) {
	uri := fmt.Sprintf("/zones/%s/firewall/lockdowns/%s", zoneID, id)
	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, ld)
	if err != nil {
		return nil, err
	}

	response := &ZoneLockdownResponse{}
	err = json.Unmarshal(res, &response)
	if err != nil {
		return nil, errors.Wrap(err, errUnmarshalError)
	}

	return response, nil
}

// DeleteZoneLockdown deletes a Zone ZoneLockdown rule (based on the ID) for the
// given zone ID.
//
// API reference: https://api.cloudflare.com/#zone-ZoneLockdown-delete-ZoneLockdown-rule
func (api *API) DeleteZoneLockdown(ctx context.Context, zoneID string, id string) (*ZoneLockdownResponse, error) {
	uri := fmt.Sprintf("/zones/%s/firewall/lockdowns/%s", zoneID, id)
	res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
	if err != nil {
		return nil, err
	}

	response := &ZoneLockdownResponse{}
	err = json.Unmarshal(res, &response)
	if err != nil {
		return nil, errors.Wrap(err, errUnmarshalError)
	}

	return response, nil
}

// ZoneLockdown retrieves a Zone ZoneLockdown rule (based on the ID) for the
// given zone ID.
//
// API reference: https://api.cloudflare.com/#zone-ZoneLockdown-ZoneLockdown-rule-details
func (api *API) ZoneLockdown(ctx context.Context, zoneID string, id string) (*ZoneLockdownResponse, error) {
	uri := fmt.Sprintf("/zones/%s/firewall/lockdowns/%s", zoneID, id)
	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return nil, err
	}

	response := &ZoneLockdownResponse{}
	err = json.Unmarshal(res, &response)
	if err != nil {
		return nil, errors.Wrap(err, errUnmarshalError)
	}

	return response, nil
}

// ListZoneLockdowns retrieves a list of Zone ZoneLockdown rules for a given
// zone ID by page number.
//
// API reference: https://api.cloudflare.com/#zone-ZoneLockdown-list-ZoneLockdown-rules
func (api *API) ListZoneLockdowns(ctx context.Context, zoneID string, page int) (*ZoneLockdownListResponse, error) {
	v := url.Values{}
	if page <= 0 {
		page = 1
	}

	v.Set("page", strconv.Itoa(page))
	v.Set("per_page", strconv.Itoa(100))

	uri := fmt.Sprintf("/zones/%s/firewall/lockdowns?%s", zoneID, v.Encode())
	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return nil, err
	}

	response := &ZoneLockdownListResponse{}
	err = json.Unmarshal(res, &response)
	if err != nil {
		return nil, errors.Wrap(err, errUnmarshalError)
	}

	return response, nil
}
<commit_msg>revert the removal of ZoneLockdownResponse.ResultInfo<commit_after>package cloudflare

import (
	"context"
	"encoding/json"
	"fmt"
	"net/http"
	"net/url"
	"strconv"
	"time"

	"github.com/pkg/errors"
)

// ZoneLockdown represents a Zone Lockdown rule. A rule only permits access to
// the provided URL pattern(s) from the given IP address(es) or subnet(s).
type ZoneLockdown struct {
	ID             string               `json:"id"`
	Description    string               `json:"description"`
	URLs           []string             `json:"urls"`
	Configurations []ZoneLockdownConfig `json:"configurations"`
	Paused         bool                 `json:"paused"`
	Priority       int                  `json:"priority,omitempty"`
	CreatedOn      *time.Time           `json:"created_on,omitempty"`
	ModifiedOn     *time.Time           `json:"modified_on,omitempty"`
}

// ZoneLockdownConfig represents a Zone Lockdown config, which comprises
// a Target ("ip" or "ip_range") and a Value (an IP address or IP+mask,
// respectively.)
type ZoneLockdownConfig struct {
	Target string `json:"target"`
	Value  string `json:"value"`
}

// ZoneLockdownResponse represents a response from the Zone Lockdown endpoint.
type ZoneLockdownResponse struct {
	Result ZoneLockdown `json:"result"`
	Response
	ResultInfo `json:"result_info"`
}

// ZoneLockdownListResponse represents a response from the List Zone Lockdown
// endpoint.
type ZoneLockdownListResponse struct {
	Result []ZoneLockdown `json:"result"`
	Response
	ResultInfo `json:"result_info"`
}

// CreateZoneLockdown creates a Zone ZoneLockdown rule for the given zone ID.
//
// API reference: https://api.cloudflare.com/#zone-ZoneLockdown-create-a-ZoneLockdown-rule
func (api *API) CreateZoneLockdown(ctx context.Context, zoneID string, ld ZoneLockdown) (*ZoneLockdownResponse, error) {
	uri := fmt.Sprintf("/zones/%s/firewall/lockdowns", zoneID)
	res, err := api.makeRequestContext(ctx, http.MethodPost, uri, ld)
	if err != nil {
		return nil, err
	}

	response := &ZoneLockdownResponse{}
	err = json.Unmarshal(res, &response)
	if err != nil {
		return nil, errors.Wrap(err, errUnmarshalError)
	}

	return response, nil
}

// UpdateZoneLockdown updates a Zone ZoneLockdown rule (based on the ID) for the
// given zone ID.
//
// API reference: https://api.cloudflare.com/#zone-ZoneLockdown-update-ZoneLockdown-rule
func (api *API) UpdateZoneLockdown(ctx context.Context, zoneID string, id string, ld ZoneLockdown) (*ZoneLockdownResponse, error) {
	uri := fmt.Sprintf("/zones/%s/firewall/lockdowns/%s", zoneID, id)
	res, err := api.makeRequestContext(ctx, http.MethodPut, uri, ld)
	if err != nil {
		return nil, err
	}

	response := &ZoneLockdownResponse{}
	err = json.Unmarshal(res, &response)
	if err != nil {
		return nil, errors.Wrap(err, errUnmarshalError)
	}

	return response, nil
}

// DeleteZoneLockdown deletes a Zone ZoneLockdown rule (based on the ID) for the
// given zone ID.
//
// API reference: https://api.cloudflare.com/#zone-ZoneLockdown-delete-ZoneLockdown-rule
func (api *API) DeleteZoneLockdown(ctx context.Context, zoneID string, id string) (*ZoneLockdownResponse, error) {
	uri := fmt.Sprintf("/zones/%s/firewall/lockdowns/%s", zoneID, id)
	res, err := api.makeRequestContext(ctx, http.MethodDelete, uri, nil)
	if err != nil {
		return nil, err
	}

	response := &ZoneLockdownResponse{}
	err = json.Unmarshal(res, &response)
	if err != nil {
		return nil, errors.Wrap(err, errUnmarshalError)
	}

	return response, nil
}

// ZoneLockdown retrieves a Zone ZoneLockdown rule (based on the ID) for the
// given zone ID.
//
// API reference: https://api.cloudflare.com/#zone-ZoneLockdown-ZoneLockdown-rule-details
func (api *API) ZoneLockdown(ctx context.Context, zoneID string, id string) (*ZoneLockdownResponse, error) {
	uri := fmt.Sprintf("/zones/%s/firewall/lockdowns/%s", zoneID, id)
	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return nil, err
	}

	response := &ZoneLockdownResponse{}
	err = json.Unmarshal(res, &response)
	if err != nil {
		return nil, errors.Wrap(err, errUnmarshalError)
	}

	return response, nil
}

// ListZoneLockdowns retrieves a list of Zone ZoneLockdown rules for a given
// zone ID by page number.
//
// API reference: https://api.cloudflare.com/#zone-ZoneLockdown-list-ZoneLockdown-rules
func (api *API) ListZoneLockdowns(ctx context.Context, zoneID string, page int) (*ZoneLockdownListResponse, error) {
	v := url.Values{}
	if page <= 0 {
		page = 1
	}

	v.Set("page", strconv.Itoa(page))
	v.Set("per_page", strconv.Itoa(100))

	uri := fmt.Sprintf("/zones/%s/firewall/lockdowns?%s", zoneID, v.Encode())
	res, err := api.makeRequestContext(ctx, http.MethodGet, uri, nil)
	if err != nil {
		return nil, err
	}

	response := &ZoneLockdownListResponse{}
	err = json.Unmarshal(res, &response)
	if err != nil {
		return nil, errors.Wrap(err, errUnmarshalError)
	}

	return response, nil
}
<|endoftext|>


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

package log_test

import (
	"../log"
	"testing"
)

func TestFatal(t *testing.T) {
	log.SetPriority(log.Pinfo)
	log.SetLayouts(log.Lstd | log.Lpriority | log.Llongfile)
	log.Fatal("=====fatal=====")
	log.Error("=====error=====")
	log.Warn("=====warn=====")
	log.Info("=====info=====")
	log.Debug("=====debug=====")
	log.Trace("=====trace=====")
}
<commit_msg>comment out fatal test<commit_after>// Copyright 2012 The Oneslang Authors.  All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package log_test

import (
	"../log"
	"testing"
)

func TestLogs(t *testing.T) {
	log.SetPriority(log.Pinfo)
	log.SetLayouts(log.Lstd | log.Lpriority | log.Llongfile)
	log.Error("=====error=====")
	log.Warn("=====warn=====")
	log.Info("=====info=====")
	log.Debug("=====debug=====")
	log.Trace("=====trace=====")
}

// XXX can't run this test because the program dies
//func TestFatal(t *testing.T) {
//	log.SetPriority(log.Pinfo)
//	log.SetLayouts(log.Lstd | log.Lpriority | log.Llongfile)
//	log.Fatal("-----death------")
//}
<|endoftext|>


<commit_before>package changeorg

import (
	"fmt"
	"testing"
)

var params map[string]string

func init() {
	params = make(map[string]string)
	params["API_KEY"] = "my_api_key"
	params["SECRET"] = "my_secret_token"
	params["URL"] = "https://www.change.org/p/sample-for-developers-using-change-org-api-sign-petition-via-change-org-api"
	params["PETITION_ID"] = "2268806"
}

func TestID(t *testing.T) {
	msg := "Welcome to gochangeorg!"
	change_org := NewChangeOrgClient(params["API_KEY"])
	id, err := change_org.GetPetitionId(PetitionIdArgs{
		PetitionURL: params["URL"],
	})
	if err != nil {
		t.Fatal(err)
	}
	fmt.Println(msg)
	fmt.Println("Petition Id:", *id)
}

func TestAuthKey(t *testing.T) {
	id := params["PETITION_ID"]
	if len(id) <= 0 {
		err := "Petition ID is empty. Use GetPetitionId first."
		t.Fatal(err)
	}
	msg := "Requesting Auth Key!\n"
	change_org := NewChangeOrgClient(params["API_KEY"])
	authKey, err := change_org.GetAuthKey(AuthKeysArgs{
		PetitionID:     id,
		SourceDesc:     "source_description",
		Source:         "source_that_is_using_the_api",
		RequesterEmail: "developer_email",
		TimeStamp:      GetTimeNow(),
		Endpoint:       "/v1/petitions/" + id + "/auth_keys",
		Callback:       "mycallback",
	}, params["SECRET"])
	if err != nil {
		t.Fatal(err)
	}
	msg += "Auth Key: " + authKey
	fmt.Println(msg)
}
<commit_msg>add test for SignPetition function<commit_after>package changeorg

import (
	"fmt"
	"testing"
)

var params map[string]string

func init() {
	params = make(map[string]string)
	params["API_KEY"] = "my_api_key"
	params["SECRET"] = "my_secret_token"
	params["URL"] = "https://www.change.org/p/sample-for-developers-using-change-org-api-sign-petition-via-change-org-api"
	params["PETITION_ID"] = "2268806"
	params["AUTH_KEY"] = "auth_key_for_petition"
}

func TestID(t *testing.T) {
	msg := "Welcome to gochangeorg!"
	change_org := NewChangeOrgClient(params["API_KEY"])
	id, err := change_org.GetPetitionId(PetitionIdArgs{
		PetitionURL: params["URL"],
	})
	if err != nil {
		t.Fatal(err)
	}
	fmt.Println(msg)
	fmt.Println("Petition Id:", *id)
}

func TestAuthKey(t *testing.T) {
	id := params["PETITION_ID"]
	if len(id) <= 0 {
		err := "Petition ID is empty. Use GetPetitionId first."
		t.Fatal(err)
	}
	msg := "Requesting Auth Key!\n"
	change_org := NewChangeOrgClient(params["API_KEY"])
	authKey, err := change_org.GetAuthKey(AuthKeysArgs{
		PetitionID:     id,
		SourceDesc:     "source_description",
		Source:         "source_that_is_using_the_api",
		RequesterEmail: "developer_email",
		TimeStamp:      GetTimeNow(),
		Endpoint:       "/v1/petitions/" + id + "/auth_keys",
		Callback:       "mycallback",
	}, params["SECRET"])
	if err != nil {
		t.Fatal(err)
	}
	msg += "Auth Key: " + authKey
	fmt.Println(msg)
}

func TestSignature(t *testing.T) {
	id := params["PETITION_ID"]
	if len(id) <= 0 {
		err := "Petition ID is empty. Use GetPetitionId first."
		t.Fatal(err)
	}
	auth_key := params["AUTH_KEY"]
	if len(auth_key) <= 0 {
		err := "Auth Key is empty. Use GetAuthKey first."
		t.Fatal(err)
	}
	msg := "Welcome to gochangeorg!\n"
	msg += "Go sign a petition via API! Pun intended ;)\n"
	change_org := NewChangeOrgClient(params["API_KEY"])
	response, err := change_org.SignPetition(PetitionArgs{
		PetitionID: id,
		AuthKey:    auth_key,
		Source:     "source_that_is_using_the_api",
		Email:      "email@example.com",
		FirstName:  "Grace",
		LastName:   "Hopper",
		Address:    "123 Address",
		City:       "New Haven",
		State:      "CT",
		ZIP:        "06520",
		Country:    "US",
		Hidden:     "true",
		TimeStamp:  GetTimeNow(),
		Endpoint:   "/v1/petitions/" + id + "/signatures",
	}, params["SECRET"])
	if err != nil {
		t.Fatal(err)
	}
	msg += "Response: " + response
	fmt.Println(msg)
}
<|endoftext|>


<commit_before>package main

import (
	"context"
	"flag"
	"fmt"
	"io"
	"os"
	"time"

	"github.com/jinzhu/gorm"
	"go.uber.org/zap"
	"golang.org/x/sync/errgroup"

	"github.com/oinume/lekcije/backend/cli"
	"github.com/oinume/lekcije/backend/crawler"
	"github.com/oinume/lekcije/backend/di"
	"github.com/oinume/lekcije/backend/domain/config"
	"github.com/oinume/lekcije/backend/infrastructure/dmm_eikaiwa"
	"github.com/oinume/lekcije/backend/logger"
	"github.com/oinume/lekcije/backend/model"
)

func main() {
	m := &crawlerMain{
		outStream: os.Stdout,
		errStream: os.Stderr,
	}
	if err := m.run(os.Args); err != nil {
		cli.WriteError(m.errStream, err)
		os.Exit(cli.ExitError)
	}
	os.Exit(cli.ExitOK)
}

type crawlerMain struct {
	outStream io.Writer
	errStream io.Writer
}

func (m *crawlerMain) run(args []string) error {
	flagSet := flag.NewFlagSet("crawler", flag.ContinueOnError)
	flagSet.SetOutput(m.errStream)
	var (
		concurrency     = flagSet.Int("concurrency", 1, "Concurrency of crawler. (default=1)")
		continueOnError = flagSet.Bool("continue", true, "Continue to crawl if any error occurred. (default=true)")
		specifiedIDs    = flagSet.String("ids", "", "Teacher IDs")
		followedOnly    = flagSet.Bool("followedOnly", false, "Crawl followedOnly teachers")
		all             = flagSet.Bool("all", false, "Crawl all teachers ordered by evaluation")
		newOnly         = flagSet.Bool("new", false, "Crawl all teachers ordered by new")
		interval        = flagSet.Duration("interval", 1*time.Second, "Fetch interval. (default=1s)")
		logLevel        = flag.String("log-level", "info", "Log level")
	)
	if err := flagSet.Parse(args[1:]); err != nil {
		return err
	}
	if *followedOnly && *specifiedIDs != "" {
		return fmt.Errorf("can't specify -followedOnly and -ids flags both")
	}

	config.MustProcessDefault()
	ctx := context.Background()
	startedAt := time.Now().UTC()
	appLogger := logger.NewAppLogger(os.Stderr, logger.NewLevel(*logLevel))
	appLogger.Info("crawler started")
	defer func() {
		elapsed := time.Now().UTC().Sub(startedAt) / time.Millisecond
		appLogger.Info("crawler finished", zap.Int("elapsed", int(elapsed)))
	}()

	db, err := model.OpenDB(config.DefaultVars.DBURL(), 1, config.DefaultVars.DebugSQL)
	if err != nil {
		return err
	}
	defer func() { _ = db.Close() }()

	mCountryService := model.NewMCountryService(db)
	mCountries, err := mCountryService.LoadAll(ctx)
	if err != nil {
		return err
	}

	loader := m.createLoader(db, *specifiedIDs, *followedOnly, *all, *newOnly)
	mCountryList := di.MustNewMCountryList(ctx, db.DB())
	lessonFetcher := dmm_eikaiwa.NewLessonFetcher(nil, *concurrency, false, mCountryList, appLogger)
	teacherService := model.NewTeacherService(db)
	for cursor := loader.GetInitialCursor(); cursor != ""; {
		var teacherIDs []uint32
		var err error
		teacherIDs, cursor, err = loader.Load(cursor)
		if err != nil {
			return err
		}

		// TODO: semaphore
		var g errgroup.Group
		for _, id := range teacherIDs {
			id := id
			g.Go(func() error {
				teacher, _, err := lessonFetcher.Fetch(ctx, uint(id))
				if err != nil {
					if *continueOnError {
						appLogger.Error("Error during LessonFetcher.Fetch", zap.Error(err))
						return nil
					} else {
						return err
					}
				}
				if err := teacherService.CreateOrUpdate(teacher); err != nil {
					if *continueOnError {
						appLogger.Error("Error during TeacherService.CreateOrUpdate", zap.Error(err))
						return nil
					} else {
						return err
					}
				}
				// TODO: update lessons

				return nil
			})
		}

		if err := g.Wait(); err != nil {
			return err
		}

		time.Sleep(*interval)
	}

	return nil
}

func (m *crawlerMain) createLoader(
	db *gorm.DB,
	specifiedIDs string,
	followed bool,
	all bool,
	newOnly bool,
) crawler.TeacherIDLoader {
	var loader crawler.TeacherIDLoader
	if specifiedIDs != "" {
		loader = crawler.NewSpecificTeacherIDLoader(specifiedIDs)
	} else if followed {
		loader = crawler.NewFollowedTeacherIDLoader(db)
	} else if all {
		loader = crawler.NewScrapingTeacherIDLoader(crawler.ByRating, nil)
	} else if newOnly {
		loader = crawler.NewScrapingTeacherIDLoader(crawler.ByNew, nil)
	} else {
		loader = crawler.NewScrapingTeacherIDLoader(crawler.ByRating, nil)
	}
	return loader
}
<commit_msg>Use TeacherRepository<commit_after>package main

import (
	"context"
	"flag"
	"fmt"
	"io"
	"os"
	"time"

	"github.com/jinzhu/gorm"
	"go.uber.org/zap"
	"golang.org/x/sync/errgroup"

	"github.com/oinume/lekcije/backend/cli"
	"github.com/oinume/lekcije/backend/crawler"
	"github.com/oinume/lekcije/backend/di"
	"github.com/oinume/lekcije/backend/domain/config"
	"github.com/oinume/lekcije/backend/infrastructure/dmm_eikaiwa"
	"github.com/oinume/lekcije/backend/infrastructure/mysql"
	"github.com/oinume/lekcije/backend/logger"
	"github.com/oinume/lekcije/backend/model"
)

func main() {
	m := &crawlerMain{
		outStream: os.Stdout,
		errStream: os.Stderr,
	}
	if err := m.run(os.Args); err != nil {
		cli.WriteError(m.errStream, err)
		os.Exit(cli.ExitError)
	}
	os.Exit(cli.ExitOK)
}

type crawlerMain struct {
	outStream io.Writer
	errStream io.Writer
}

func (m *crawlerMain) run(args []string) error {
	flagSet := flag.NewFlagSet("crawler", flag.ContinueOnError)
	flagSet.SetOutput(m.errStream)
	var (
		concurrency     = flagSet.Int("concurrency", 1, "Concurrency of crawler. (default=1)")
		continueOnError = flagSet.Bool("continue", true, "Continue to crawl if any error occurred. (default=true)")
		specifiedIDs    = flagSet.String("ids", "", "Teacher IDs")
		followedOnly    = flagSet.Bool("followedOnly", false, "Crawl followedOnly teachers")
		all             = flagSet.Bool("all", false, "Crawl all teachers ordered by evaluation")
		newOnly         = flagSet.Bool("new", false, "Crawl all teachers ordered by new")
		interval        = flagSet.Duration("interval", 1*time.Second, "Fetch interval. (default=1s)")
		logLevel        = flag.String("log-level", "info", "Log level")
	)
	if err := flagSet.Parse(args[1:]); err != nil {
		return err
	}
	if *followedOnly && *specifiedIDs != "" {
		return fmt.Errorf("can't specify -followedOnly and -ids flags both")
	}

	config.MustProcessDefault()
	ctx := context.Background()
	startedAt := time.Now().UTC()
	appLogger := logger.NewAppLogger(os.Stderr, logger.NewLevel(*logLevel))
	appLogger.Info("crawler started")
	defer func() {
		elapsed := time.Now().UTC().Sub(startedAt) / time.Millisecond
		appLogger.Info("crawler finished", zap.Int("elapsed", int(elapsed)))
	}()

	db, err := model.OpenDB(config.DefaultVars.DBURL(), 1, config.DefaultVars.DebugSQL)
	if err != nil {
		return err
	}
	defer func() { _ = db.Close() }()

	loader := m.createLoader(db, *specifiedIDs, *followedOnly, *all, *newOnly)
	mCountryList := di.MustNewMCountryList(ctx, db.DB())
	lessonFetcher := dmm_eikaiwa.NewLessonFetcher(nil, *concurrency, false, mCountryList, appLogger)
	teacherRepo := mysql.NewTeacherRepository(db.DB())
	for cursor := loader.GetInitialCursor(); cursor != ""; {
		var teacherIDs []uint32
		var err error
		teacherIDs, cursor, err = loader.Load(cursor)
		if err != nil {
			return err
		}

		// TODO: semaphore
		var g errgroup.Group
		for _, id := range teacherIDs {
			id := id
			g.Go(func() error {
				teacher, _, err := lessonFetcher.Fetch(ctx, uint(id))
				if err != nil {
					if *continueOnError {
						appLogger.Error("Error during LessonFetcher.Fetch", zap.Error(err))
						return nil
					} else {
						return err
					}
				}
				if err := teacherRepo.CreateOrUpdate(ctx, teacher); err != nil {
					if *continueOnError {
						appLogger.Error("Error during TeacherService.CreateOrUpdate", zap.Error(err))
						return nil
					} else {
						return err
					}
				}
				// TODO: update lessons

				return nil
			})
		}

		if err := g.Wait(); err != nil {
			return err
		}

		time.Sleep(*interval)
	}

	return nil
}

func (m *crawlerMain) createLoader(
	db *gorm.DB,
	specifiedIDs string,
	followed bool,
	all bool,
	newOnly bool,
) crawler.TeacherIDLoader {
	var loader crawler.TeacherIDLoader
	if specifiedIDs != "" {
		loader = crawler.NewSpecificTeacherIDLoader(specifiedIDs)
	} else if followed {
		loader = crawler.NewFollowedTeacherIDLoader(db)
	} else if all {
		loader = crawler.NewScrapingTeacherIDLoader(crawler.ByRating, nil)
	} else if newOnly {
		loader = crawler.NewScrapingTeacherIDLoader(crawler.ByNew, nil)
	} else {
		loader = crawler.NewScrapingTeacherIDLoader(crawler.ByRating, nil)
	}
	return loader
}
<|endoftext|>


<commit_before>package los

import (
	"fmt"
	"math"

	"github.com/phil-mansfield/gotetra/render/io"
	"github.com/phil-mansfield/gotetra/los/geom"
	"github.com/phil-mansfield/gotetra/math/mat"
)

type haloRing struct {
	ProfileRing
	phis []float32
	rot, irot mat.Matrix32

	// Workspace objects.
	dr geom.Vec
	t geom.Tetra
	pt geom.PluckerTetra
	poly geom.TetraSlice
}

func (hr *haloRing) Init(
	norm, origin *geom.Vec, rMin, rMax float64, bins, n int,
) {
	hr.ProfileRing.Init(rMin, rMax, bins, n)
	zAxis := &geom.Vec{0, 0, 1}

	hr.rot.Init(make([]float32, 9), 3, 3)
	hr.irot.Init(make([]float32, 9), 3, 3)
	geom.EulerMatrixBetweenAt(norm, zAxis, &hr.rot)
	geom.EulerMatrixBetweenAt(zAxis, norm, &hr.irot)
	hr.phis = make([]float32, n)
	for i := 0; i < n; i++ {
		hr.phis[i] = float32(i) / float32(n) * (2 * math.Pi)
	}

	hr.dr = *origin
	for i := 0; i < 3; i++ { hr.dr[i] *= -1 }
}

func (hr *haloRing) insert(t *geom.Tetra, rho float64) {
	hr.t = *t
	hr.t.Translate(&hr.dr)
	hr.t.Rotate(&hr.rot)
	hr.pt.Init(&hr.t) // This is slower than it has to be by a lot!

	if hr.t.ZPlaneSlice(&hr.pt, 0, &hr.poly) {
		lowPhi, phiWidth := hr.poly.AngleRange()
		lowIdx, idxWidth := geom.AngleBinRange(lowPhi, phiWidth, hr.n)

		idx := lowIdx
		var rEnter, rExit float64
		for i := 0; i < idxWidth; i++ {
			if idx >= hr.n { idx -= hr.n }
			l := &hr.Lines[idx]
			l1, l2 := hr.poly.IntersectingLines(hr.phis[idx])

			if l1 == nil { continue }

			if l2 != nil {
				enterX, enterY, _ := geom.Solve(l, l1)
				exitX, exitY, _ := geom.Solve(l, l2)

				rEnterSqr := enterX*enterX + enterY*enterY
				rExitSqr := exitX*exitX + exitY*exitY

				rEnter = math.Sqrt(float64(rEnterSqr))
				rExit = math.Sqrt(float64(rExitSqr))
				if rExit < rEnter { rEnter, rExit = rExit, rEnter }
			} else {
				exitX, exitY, _ := geom.Solve(l, l1)
				rExitSqr := exitX*exitX + exitY*exitY
				rExit = math.Sqrt(float64(rExitSqr))
				rEnter = 0.0
			}

			hr.Insert(rEnter, rExit, rho, idx)

			idx++
		}
	}
}

func (hr *haloRing) Count(t *geom.Tetra) { hr.insert(t, 1) }
func (hr *haloRing) Density(t *geom.Tetra, rho float64) { hr.insert(t, rho) }

type HaloProfiles struct {
	geom.Sphere

	rs []haloRing
	rMin, rMax float64
	id, bins, n int
}

func (hp *HaloProfiles) Init(
	id, rings int, origin *geom.Vec, rMin, rMax float64,
	bins, n int,
) *HaloProfiles {
	// We might be able to do better than this.
	solid, ok := geom.NewUniquePlatonicSolid(rings)
	if !ok {
		panic(fmt.Sprintf("Cannot uniformly space %d rings.", rings))
	}

	hp.rs = make([]haloRing, rings)
	hp.C, hp.R = *origin, float32(rMax)
	hp.rMin, hp.rMax = rMin, rMax
	hp.id, hp.bins, hp.n = id, bins, n

	norms := solid.UniqueNormals()
	println(rings, len(norms))
	for i := 0; i < rings; i++ {
		hp.rs[i].Init(&norms[i], origin, rMin, rMax, bins, n)
	}

	return hp
}

func (hp *HaloProfiles) Count(t *geom.Tetra) {
	for i := range hp.rs { hp.rs[i].Count(t) }
}

func (hp *HaloProfiles) Density(t *geom.Tetra, rho float64) {
	for i := range hp.rs { hp.rs[i].Density(t, rho) }
}

func wrapDist(x1, x2, width float32) float32 {
	dist := x1 - x2
	if dist > width / 2 {
		return dist - width
	} else if dist < width / -2 {
		return dist + width
	} else {
		return dist
	}
}

func inRange(x, r, low, width, tw float32) bool {
	return wrapDist(x, low, tw) > -r && wrapDist(x, low + width, tw) < r
}

func (hp *HaloProfiles) SheetIntersect(hd *io.SheetHeader) bool {
	tw := float32(hd.TotalWidth)
	return inRange(hp.C[0], hp.R, hd.Origin[0], hd.Width[0], tw) &&
		inRange(hp.C[1], hp.R, hd.Origin[1], hd.Width[1], tw) &&
		inRange(hp.C[2], hp.R, hd.Origin[2], hd.Width[2], tw)
}

func (hp *HaloProfiles) ChangeCenter(v *geom.Vec) {
	for i := 0; i < 3; i++ {
		hp.C[i] = v[i]
		for r := range hp.rs {
			hp.rs[r].dr[i] = -v[i]
		}
	}
}

func (hp *HaloProfiles) Mass(rhoM float64) float64 {
	vol := (4 * math.Pi / 3) * hp.R*hp.R*hp.R
	return float64(vol) * hp.Rho() * rhoM
}

func (hp *HaloProfiles) Rho() float64 {
	rBuf, rSum := make([]float64, hp.bins), make([]float64, hp.bins)
	profs := hp.n * len(hp.rs)

	// Find the spherically averaged rho profile
	for r := 0; r < len(hp.rs); r++ {
		for i := 0; i < hp.n; i++ {
			hp.rs[r].Retrieve(i, rBuf)
			for j := range rBuf { rSum[j] += rBuf[j] }
		}
	}
	for j := range rSum { rSum[j] /= float64(profs) }

	// Integrate
	dr := (hp.rMax - hp.rMin) / float64(len(rSum))
	sum := 0.0
	rs := make([]float64, len(rSum))
	for i, rho := range rSum {
		r := (float64(i) + 0.5)*dr + hp.rMin
		rs[i] = r
		sum += r*r*dr*rho
	}

	sum *= 4*math.Pi
	vol := (4 * math.Pi / 3) * hp.R*hp.R*hp.R
	return sum / float64(vol)
}
<commit_msg>Added norm configurations for rings = 1 and rings = 2.<commit_after>package los

import (
	"fmt"
	"math"

	"github.com/phil-mansfield/gotetra/render/io"
	"github.com/phil-mansfield/gotetra/los/geom"
	"github.com/phil-mansfield/gotetra/math/mat"
)

type haloRing struct {
	ProfileRing
	phis []float32
	rot, irot mat.Matrix32
	rMax float64

	// Workspace objects.
	dr geom.Vec
	t geom.Tetra
	pt geom.PluckerTetra
	poly geom.TetraSlice
}

func (hr *haloRing) Init(
	norm, origin *geom.Vec, rMin, rMax float64, bins, n int,
) {
	hr.ProfileRing.Init(rMin, rMax, bins, n)
	zAxis := &geom.Vec{0, 0, 1}

	hr.rot.Init(make([]float32, 9), 3, 3)
	hr.irot.Init(make([]float32, 9), 3, 3)
	geom.EulerMatrixBetweenAt(norm, zAxis, &hr.rot)
	geom.EulerMatrixBetweenAt(zAxis, norm, &hr.irot)
	hr.phis = make([]float32, n)
	for i := 0; i < n; i++ {
		hr.phis[i] = float32(i) / float32(n) * (2 * math.Pi)
	}

	hr.rMax = rMax
	hr.dr = *origin
	for i := 0; i < 3; i++ { hr.dr[i] *= -1 }
}

var (
	InsideDist, OutsideDist = 0.0, 0.0
)

func (hr *haloRing) insert(t *geom.Tetra, rho float64) {
	hr.t = *t
	hr.t.Translate(&hr.dr)
	hr.t.Rotate(&hr.rot)
	hr.pt.Init(&hr.t) // This is slower than it has to be by a lot!

	if hr.t.ZPlaneSlice(&hr.pt, 0, &hr.poly) {
		lowPhi, phiWidth := hr.poly.AngleRange()
		lowIdx, idxWidth := geom.AngleBinRange(lowPhi, phiWidth, hr.n)

		idx := lowIdx
		var rEnter, rExit float64
		for i := 0; i < idxWidth; i++ {
			if idx >= hr.n { idx -= hr.n }
			l := &hr.Lines[idx]
			l1, l2 := hr.poly.IntersectingLines(hr.phis[idx])

			if l1 == nil { continue }

			if l2 != nil {
				enterX, enterY, _ := geom.Solve(l, l1)
				exitX, exitY, _ := geom.Solve(l, l2)

				rEnterSqr := enterX*enterX + enterY*enterY
				rExitSqr := exitX*exitX + exitY*exitY

				rEnter = math.Sqrt(float64(rEnterSqr))
				rExit = math.Sqrt(float64(rExitSqr))
				if rExit < rEnter { rEnter, rExit = rExit, rEnter }
			} else {
				exitX, exitY, _ := geom.Solve(l, l1)
				rExitSqr := exitX*exitX + exitY*exitY
				rExit = math.Sqrt(float64(rExitSqr))
				rEnter = 0.0
			}

			if rEnter < hr.rMax && rExit > hr.rMax {
				InsideDist += hr.rMax - rEnter
				OutsideDist += rExit -  hr.rMax
			}
			hr.Insert(rEnter, rExit, rho, idx)

			idx++
		}
	}
}

func (hr *haloRing) Count(t *geom.Tetra) { hr.insert(t, 1) }
func (hr *haloRing) Density(t *geom.Tetra, rho float64) { hr.insert(t, rho) }

type HaloProfiles struct {
	geom.Sphere

	rs []haloRing
	rMin, rMax float64
	id, bins, n int
}

func (hp *HaloProfiles) Init(
	id, rings int, origin *geom.Vec, rMin, rMax float64,
	bins, n int,
) *HaloProfiles {
	// We might be able to do better than this.
	var norms []geom.Vec
	if rings >= 3 {
		solid, ok := geom.NewUniquePlatonicSolid(rings)
		norms = solid.UniqueNormals()
		if !ok {
			panic(fmt.Sprintf("Cannot uniformly space %d rings.", rings))
		}
	} else if rings == 1 {
		norms = []geom.Vec{{0, 0, 1}}
	} else if rings == 2 {
		norms = []geom.Vec{{0, 0, 1}, {0, 1, 0}}
	}

	hp.rs = make([]haloRing, rings)
	hp.C, hp.R = *origin, float32(rMax)
	hp.rMin, hp.rMax = rMin, rMax
	hp.id, hp.bins, hp.n = id, bins, n

	println(rings, len(norms))
	for i := 0; i < rings; i++ {
		hp.rs[i].Init(&norms[i], origin, rMin, rMax, bins, n)
	}

	return hp
}

func (hp *HaloProfiles) Count(t *geom.Tetra) {
	for i := range hp.rs { hp.rs[i].Count(t) }
}

func (hp *HaloProfiles) Density(t *geom.Tetra, rho float64) {
	for i := range hp.rs { hp.rs[i].Density(t, rho) }
}

func wrapDist(x1, x2, width float32) float32 {
	dist := x1 - x2
	if dist > width / 2 {
		return dist - width
	} else if dist < width / -2 {
		return dist + width
	} else {
		return dist
	}
}

func inRange(x, r, low, width, tw float32) bool {
	return wrapDist(x, low, tw) > -r && wrapDist(x, low + width, tw) < r
}

func (hp *HaloProfiles) SheetIntersect(hd *io.SheetHeader) bool {
	tw := float32(hd.TotalWidth)
	return inRange(hp.C[0], hp.R, hd.Origin[0], hd.Width[0], tw) &&
		inRange(hp.C[1], hp.R, hd.Origin[1], hd.Width[1], tw) &&
		inRange(hp.C[2], hp.R, hd.Origin[2], hd.Width[2], tw)
}

func (hp *HaloProfiles) ChangeCenter(v *geom.Vec) {
	for i := 0; i < 3; i++ {
		hp.C[i] = v[i]
		for r := range hp.rs {
			hp.rs[r].dr[i] = -v[i]
		}
	}
}

func (hp *HaloProfiles) Mass(rhoM float64) float64 {
	vol := (4 * math.Pi / 3) * hp.R*hp.R*hp.R
	return float64(vol) * hp.Rho() * rhoM
}

func (hp *HaloProfiles) Rho() float64 {
	rBuf, rSum := make([]float64, hp.bins), make([]float64, hp.bins)
	profs := hp.n * len(hp.rs)

	// Find the spherically averaged rho profile
	for r := 0; r < len(hp.rs); r++ {
		for i := 0; i < hp.n; i++ {
			hp.rs[r].Retrieve(i, rBuf)
			for j := range rBuf { rSum[j] += rBuf[j] }
		}
	}
	for j := range rSum { rSum[j] /= float64(profs) }

	// Integrate
	dr := (hp.rMax - hp.rMin) / float64(len(rSum))
	sum := 0.0
	rs := make([]float64, len(rSum))
	for i, rho := range rSum {
		r := (float64(i) + 0.5)*dr + hp.rMin
		rs[i] = r
		sum += r*r*dr*rho
	}

	sum *= 4*math.Pi
	vol := (4 * math.Pi / 3) * hp.R*hp.R*hp.R
	return sum / float64(vol)
}
<|endoftext|>


<commit_before>package bslack

import (
	"errors"
	"fmt"
	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/matterhook"
	log "github.com/Sirupsen/logrus"
	"github.com/nlopes/slack"
	"html"
	"regexp"
	"strings"
	"time"
)

type MMMessage struct {
	Text     string
	Channel  string
	Username string
	UserID   string
	Raw      *slack.MessageEvent
}

type Bslack struct {
	mh       *matterhook.Client
	sc       *slack.Client
	Config   *config.Protocol
	rtm      *slack.RTM
	Plus     bool
	Remote   chan config.Message
	Users    []slack.User
	Account  string
	si       *slack.Info
	channels []slack.Channel
}

var flog *log.Entry
var protocol = "slack"

func init() {
	flog = log.WithFields(log.Fields{"module": protocol})
}

func New(cfg config.Protocol, account string, c chan config.Message) *Bslack {
	b := &Bslack{}
	b.Config = &cfg
	b.Remote = c
	b.Account = account
	return b
}

func (b *Bslack) Command(cmd string) string {
	return ""
}

func (b *Bslack) Connect() error {
	if b.Config.WebhookBindAddress != "" {
		if b.Config.WebhookURL != "" {
			flog.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)")
			b.mh = matterhook.New(b.Config.WebhookURL,
				matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
					BindAddress: b.Config.WebhookBindAddress})
		} else if b.Config.Token != "" {
			flog.Info("Connecting using token (sending)")
			b.sc = slack.New(b.Config.Token)
			b.rtm = b.sc.NewRTM()
			go b.rtm.ManageConnection()
			flog.Info("Connecting using webhookbindaddress (receiving)")
			b.mh = matterhook.New(b.Config.WebhookURL,
				matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
					BindAddress: b.Config.WebhookBindAddress})
		} else {
			flog.Info("Connecting using webhookbindaddress (receiving)")
			b.mh = matterhook.New(b.Config.WebhookURL,
				matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
					BindAddress: b.Config.WebhookBindAddress})
		}
		go b.handleSlack()
		return nil
	}
	if b.Config.WebhookURL != "" {
		flog.Info("Connecting using webhookurl (sending)")
		b.mh = matterhook.New(b.Config.WebhookURL,
			matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
				DisableServer: true})
		if b.Config.Token != "" {
			flog.Info("Connecting using token (receiving)")
			b.sc = slack.New(b.Config.Token)
			b.rtm = b.sc.NewRTM()
			go b.rtm.ManageConnection()
			go b.handleSlack()
		}
	} else if b.Config.Token != "" {
		flog.Info("Connecting using token (sending and receiving)")
		b.sc = slack.New(b.Config.Token)
		b.rtm = b.sc.NewRTM()
		go b.rtm.ManageConnection()
		go b.handleSlack()
	}
	if b.Config.WebhookBindAddress == "" && b.Config.WebhookURL == "" && b.Config.Token == "" {
		return errors.New("No connection method found. See that you have WebhookBindAddress, WebhookURL or Token configured.")
	}
	return nil
}

func (b *Bslack) Disconnect() error {
	return nil

}

func (b *Bslack) JoinChannel(channel string) error {
	// we can only join channels using the API
	if b.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" {
		if strings.HasPrefix(b.Config.Token, "xoxb") {
			// TODO check if bot has already joined channel
			return nil
		}
		_, err := b.sc.JoinChannel(channel)
		if err != nil {
			if err.Error() != "name_taken" {
				return err
			}
		}
	}
	return nil
}

func (b *Bslack) Send(msg config.Message) error {
	flog.Debugf("Receiving %#v", msg)
	nick := msg.Username
	message := msg.Text
	channel := msg.Channel
	if b.Config.PrefixMessagesWithNick {
		message = nick + " " + message
	}
	if b.Config.WebhookURL != "" {
		matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL}
		matterMessage.Channel = channel
		matterMessage.UserName = nick
		matterMessage.Type = ""
		matterMessage.Text = message
		err := b.mh.Send(matterMessage)
		if err != nil {
			flog.Info(err)
			return err
		}
		return nil
	}
	schannel, err := b.getChannelByName(channel)
	if err != nil {
		return err
	}
	np := slack.NewPostMessageParameters()
	if b.Config.PrefixMessagesWithNick {
		np.AsUser = true
	}
	np.Username = nick
	np.IconURL = config.GetIconURL(&msg, b.Config)
	if msg.Avatar != "" {
		np.IconURL = msg.Avatar
	}
	np.Attachments = append(np.Attachments, slack.Attachment{CallbackID: "matterbridge"})
	b.sc.PostMessage(schannel.ID, message, np)

	/*
	   newmsg := b.rtm.NewOutgoingMessage(message, schannel.ID)
	   b.rtm.SendMessage(newmsg)
	*/

	return nil
}

func (b *Bslack) getAvatar(user string) string {
	var avatar string
	if b.Users != nil {
		for _, u := range b.Users {
			if user == u.Name {
				return u.Profile.Image48
			}
		}
	}
	return avatar
}

func (b *Bslack) getChannelByName(name string) (*slack.Channel, error) {
	if b.channels == nil {
		return nil, fmt.Errorf("%s: channel %s not found (no channels found)", b.Account, name)
	}
	for _, channel := range b.channels {
		if channel.Name == name {
			return &channel, nil
		}
	}
	return nil, fmt.Errorf("%s: channel %s not found", b.Account, name)
}

func (b *Bslack) getChannelByID(ID string) (*slack.Channel, error) {
	if b.channels == nil {
		return nil, fmt.Errorf("%s: channel %s not found (no channels found)", b.Account, ID)
	}
	for _, channel := range b.channels {
		if channel.ID == ID {
			return &channel, nil
		}
	}
	return nil, fmt.Errorf("%s: channel %s not found", b.Account, ID)
}

func (b *Bslack) handleSlack() {
	mchan := make(chan *MMMessage)
	if b.Config.WebhookBindAddress != "" {
		flog.Debugf("Choosing webhooks based receiving")
		go b.handleMatterHook(mchan)
	} else {
		flog.Debugf("Choosing token based receiving")
		go b.handleSlackClient(mchan)
	}
	time.Sleep(time.Second)
	flog.Debug("Start listening for Slack messages")
	for message := range mchan {
		// do not send messages from ourself
		if b.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" && message.Username == b.si.User.Name {
			continue
		}
		texts := strings.Split(message.Text, "\n")
		for _, text := range texts {
			text = b.replaceURL(text)
			text = html.UnescapeString(text)
			flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.Account)
			msg := config.Message{Text: text, Username: message.Username, Channel: message.Channel, Account: b.Account, Avatar: b.getAvatar(message.Username), UserID: message.UserID}
			b.Remote <- msg
		}
	}
}

func (b *Bslack) handleSlackClient(mchan chan *MMMessage) {
	count := 0
	for msg := range b.rtm.IncomingEvents {
		switch ev := msg.Data.(type) {
		case *slack.MessageEvent:
			// ignore first message
			if count > 0 {
				flog.Debugf("Receiving from slackclient %#v", ev)
				if len(ev.Attachments) > 0 {
					// skip messages we made ourselves
					if ev.Attachments[0].CallbackID == "matterbridge" {
						continue
					}
				}
				if !b.Config.EditDisable && ev.SubMessage != nil && ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp {
					flog.Debugf("SubMessage %#v", ev.SubMessage)
					ev.User = ev.SubMessage.User
					ev.Text = ev.SubMessage.Text + b.Config.EditSuffix
				}
				// use our own func because rtm.GetChannelInfo doesn't work for private channels
				channel, err := b.getChannelByID(ev.Channel)
				if err != nil {
					continue
				}
				m := &MMMessage{}
				if ev.BotID == "" {
					user, err := b.rtm.GetUserInfo(ev.User)
					if err != nil {
						continue
					}
					m.UserID = user.ID
					m.Username = user.Name
				}
				m.Channel = channel.Name
				m.Text = ev.Text
				if m.Text == "" {
					for _, attach := range ev.Attachments {
						if attach.Text != "" {
							m.Text = attach.Text
						} else {
							m.Text = attach.Fallback
						}
					}
				}
				m.Raw = ev
				m.Text = b.replaceMention(m.Text)
				if ev.BotID != "" {
					bot, err := b.rtm.GetBotInfo(ev.BotID)
					if err != nil {
						continue
					}
					if bot.Name != "" {
						m.Username = bot.Name
						m.UserID = bot.ID
					}
				}
				mchan <- m
			}
			count++
		case *slack.OutgoingErrorEvent:
			flog.Debugf("%#v", ev.Error())
		case *slack.ChannelJoinedEvent:
			b.Users, _ = b.sc.GetUsers()
		case *slack.ConnectedEvent:
			b.channels = ev.Info.Channels
			b.si = ev.Info
			b.Users, _ = b.sc.GetUsers()
			// add private channels
			groups, _ := b.sc.GetGroups(true)
			for _, g := range groups {
				channel := new(slack.Channel)
				channel.ID = g.ID
				channel.Name = g.Name
				b.channels = append(b.channels, *channel)
			}
		case *slack.InvalidAuthEvent:
			flog.Fatalf("Invalid Token %#v", ev)
		default:
		}
	}
}

func (b *Bslack) handleMatterHook(mchan chan *MMMessage) {
	for {
		message := b.mh.Receive()
		flog.Debugf("receiving from matterhook (slack) %#v", message)
		m := &MMMessage{}
		m.Username = message.UserName
		m.Text = message.Text
		m.Text = b.replaceMention(m.Text)
		m.Channel = message.ChannelName
		if m.Username == "slackbot" {
			continue
		}
		mchan <- m
	}
}

func (b *Bslack) userName(id string) string {
	for _, u := range b.Users {
		if u.ID == id {
			return u.Name
		}
	}
	return ""
}

func (b *Bslack) replaceMention(text string) string {
	results := regexp.MustCompile(`<@([a-zA-z0-9]+)>`).FindAllStringSubmatch(text, -1)
	for _, r := range results {
		text = strings.Replace(text, "<@"+r[1]+">", "@"+b.userName(r[1]), -1)

	}
	return text
}

func (b *Bslack) replaceURL(text string) string {
	results := regexp.MustCompile(`<(.*?)\|.*?>`).FindAllStringSubmatch(text, -1)
	for _, r := range results {
		text = strings.Replace(text, r[0], r[1], -1)
	}
	return text
}
<commit_msg>Disable message from other bots when using webhooks (slack)<commit_after>package bslack

import (
	"errors"
	"fmt"
	"github.com/42wim/matterbridge/bridge/config"
	"github.com/42wim/matterbridge/matterhook"
	log "github.com/Sirupsen/logrus"
	"github.com/nlopes/slack"
	"html"
	"regexp"
	"strings"
	"time"
)

type MMMessage struct {
	Text     string
	Channel  string
	Username string
	UserID   string
	Raw      *slack.MessageEvent
}

type Bslack struct {
	mh       *matterhook.Client
	sc       *slack.Client
	Config   *config.Protocol
	rtm      *slack.RTM
	Plus     bool
	Remote   chan config.Message
	Users    []slack.User
	Account  string
	si       *slack.Info
	channels []slack.Channel
}

var flog *log.Entry
var protocol = "slack"

func init() {
	flog = log.WithFields(log.Fields{"module": protocol})
}

func New(cfg config.Protocol, account string, c chan config.Message) *Bslack {
	b := &Bslack{}
	b.Config = &cfg
	b.Remote = c
	b.Account = account
	return b
}

func (b *Bslack) Command(cmd string) string {
	return ""
}

func (b *Bslack) Connect() error {
	if b.Config.WebhookBindAddress != "" {
		if b.Config.WebhookURL != "" {
			flog.Info("Connecting using webhookurl (sending) and webhookbindaddress (receiving)")
			b.mh = matterhook.New(b.Config.WebhookURL,
				matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
					BindAddress: b.Config.WebhookBindAddress})
		} else if b.Config.Token != "" {
			flog.Info("Connecting using token (sending)")
			b.sc = slack.New(b.Config.Token)
			b.rtm = b.sc.NewRTM()
			go b.rtm.ManageConnection()
			flog.Info("Connecting using webhookbindaddress (receiving)")
			b.mh = matterhook.New(b.Config.WebhookURL,
				matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
					BindAddress: b.Config.WebhookBindAddress})
		} else {
			flog.Info("Connecting using webhookbindaddress (receiving)")
			b.mh = matterhook.New(b.Config.WebhookURL,
				matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
					BindAddress: b.Config.WebhookBindAddress})
		}
		go b.handleSlack()
		return nil
	}
	if b.Config.WebhookURL != "" {
		flog.Info("Connecting using webhookurl (sending)")
		b.mh = matterhook.New(b.Config.WebhookURL,
			matterhook.Config{InsecureSkipVerify: b.Config.SkipTLSVerify,
				DisableServer: true})
		if b.Config.Token != "" {
			flog.Info("Connecting using token (receiving)")
			b.sc = slack.New(b.Config.Token)
			b.rtm = b.sc.NewRTM()
			go b.rtm.ManageConnection()
			go b.handleSlack()
		}
	} else if b.Config.Token != "" {
		flog.Info("Connecting using token (sending and receiving)")
		b.sc = slack.New(b.Config.Token)
		b.rtm = b.sc.NewRTM()
		go b.rtm.ManageConnection()
		go b.handleSlack()
	}
	if b.Config.WebhookBindAddress == "" && b.Config.WebhookURL == "" && b.Config.Token == "" {
		return errors.New("No connection method found. See that you have WebhookBindAddress, WebhookURL or Token configured.")
	}
	return nil
}

func (b *Bslack) Disconnect() error {
	return nil

}

func (b *Bslack) JoinChannel(channel string) error {
	// we can only join channels using the API
	if b.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" {
		if strings.HasPrefix(b.Config.Token, "xoxb") {
			// TODO check if bot has already joined channel
			return nil
		}
		_, err := b.sc.JoinChannel(channel)
		if err != nil {
			if err.Error() != "name_taken" {
				return err
			}
		}
	}
	return nil
}

func (b *Bslack) Send(msg config.Message) error {
	flog.Debugf("Receiving %#v", msg)
	nick := msg.Username
	message := msg.Text
	channel := msg.Channel
	if b.Config.PrefixMessagesWithNick {
		message = nick + " " + message
	}
	if b.Config.WebhookURL != "" {
		matterMessage := matterhook.OMessage{IconURL: b.Config.IconURL}
		matterMessage.Channel = channel
		matterMessage.UserName = nick
		matterMessage.Type = ""
		matterMessage.Text = message
		err := b.mh.Send(matterMessage)
		if err != nil {
			flog.Info(err)
			return err
		}
		return nil
	}
	schannel, err := b.getChannelByName(channel)
	if err != nil {
		return err
	}
	np := slack.NewPostMessageParameters()
	if b.Config.PrefixMessagesWithNick {
		np.AsUser = true
	}
	np.Username = nick
	np.IconURL = config.GetIconURL(&msg, b.Config)
	if msg.Avatar != "" {
		np.IconURL = msg.Avatar
	}
	np.Attachments = append(np.Attachments, slack.Attachment{CallbackID: "matterbridge"})
	b.sc.PostMessage(schannel.ID, message, np)

	/*
	   newmsg := b.rtm.NewOutgoingMessage(message, schannel.ID)
	   b.rtm.SendMessage(newmsg)
	*/

	return nil
}

func (b *Bslack) getAvatar(user string) string {
	var avatar string
	if b.Users != nil {
		for _, u := range b.Users {
			if user == u.Name {
				return u.Profile.Image48
			}
		}
	}
	return avatar
}

func (b *Bslack) getChannelByName(name string) (*slack.Channel, error) {
	if b.channels == nil {
		return nil, fmt.Errorf("%s: channel %s not found (no channels found)", b.Account, name)
	}
	for _, channel := range b.channels {
		if channel.Name == name {
			return &channel, nil
		}
	}
	return nil, fmt.Errorf("%s: channel %s not found", b.Account, name)
}

func (b *Bslack) getChannelByID(ID string) (*slack.Channel, error) {
	if b.channels == nil {
		return nil, fmt.Errorf("%s: channel %s not found (no channels found)", b.Account, ID)
	}
	for _, channel := range b.channels {
		if channel.ID == ID {
			return &channel, nil
		}
	}
	return nil, fmt.Errorf("%s: channel %s not found", b.Account, ID)
}

func (b *Bslack) handleSlack() {
	mchan := make(chan *MMMessage)
	if b.Config.WebhookBindAddress != "" {
		flog.Debugf("Choosing webhooks based receiving")
		go b.handleMatterHook(mchan)
	} else {
		flog.Debugf("Choosing token based receiving")
		go b.handleSlackClient(mchan)
	}
	time.Sleep(time.Second)
	flog.Debug("Start listening for Slack messages")
	for message := range mchan {
		// do not send messages from ourself
		if b.Config.WebhookURL == "" && b.Config.WebhookBindAddress == "" && message.Username == b.si.User.Name {
			continue
		}
		if message.Text == "" || message.Username == "" {
			continue
		}
		texts := strings.Split(message.Text, "\n")
		for _, text := range texts {
			text = b.replaceURL(text)
			text = html.UnescapeString(text)
			flog.Debugf("Sending message from %s on %s to gateway", message.Username, b.Account)
			msg := config.Message{Text: text, Username: message.Username, Channel: message.Channel, Account: b.Account, Avatar: b.getAvatar(message.Username), UserID: message.UserID}
			b.Remote <- msg
		}
	}
}

func (b *Bslack) handleSlackClient(mchan chan *MMMessage) {
	count := 0
	for msg := range b.rtm.IncomingEvents {
		switch ev := msg.Data.(type) {
		case *slack.MessageEvent:
			// ignore first message
			if count > 0 {
				flog.Debugf("Receiving from slackclient %#v", ev)
				if len(ev.Attachments) > 0 {
					// skip messages we made ourselves
					if ev.Attachments[0].CallbackID == "matterbridge" {
						continue
					}
				}
				if !b.Config.EditDisable && ev.SubMessage != nil && ev.SubMessage.ThreadTimestamp != ev.SubMessage.Timestamp {
					flog.Debugf("SubMessage %#v", ev.SubMessage)
					ev.User = ev.SubMessage.User
					ev.Text = ev.SubMessage.Text + b.Config.EditSuffix
				}
				// use our own func because rtm.GetChannelInfo doesn't work for private channels
				channel, err := b.getChannelByID(ev.Channel)
				if err != nil {
					continue
				}
				m := &MMMessage{}
				if ev.BotID == "" {
					user, err := b.rtm.GetUserInfo(ev.User)
					if err != nil {
						continue
					}
					m.UserID = user.ID
					m.Username = user.Name
				}
				m.Channel = channel.Name
				m.Text = ev.Text
				if m.Text == "" {
					for _, attach := range ev.Attachments {
						if attach.Text != "" {
							m.Text = attach.Text
						} else {
							m.Text = attach.Fallback
						}
					}
				}
				m.Raw = ev
				m.Text = b.replaceMention(m.Text)
				// when using webhookURL we can't check if it's our webhook or not for now
				if ev.BotID != "" && b.Config.WebhookURL == "" {
					bot, err := b.rtm.GetBotInfo(ev.BotID)
					if err != nil {
						continue
					}
					if bot.Name != "" {
						m.Username = bot.Name
						m.UserID = bot.ID
					}
				}
				mchan <- m
			}
			count++
		case *slack.OutgoingErrorEvent:
			flog.Debugf("%#v", ev.Error())
		case *slack.ChannelJoinedEvent:
			b.Users, _ = b.sc.GetUsers()
		case *slack.ConnectedEvent:
			b.channels = ev.Info.Channels
			b.si = ev.Info
			b.Users, _ = b.sc.GetUsers()
			// add private channels
			groups, _ := b.sc.GetGroups(true)
			for _, g := range groups {
				channel := new(slack.Channel)
				channel.ID = g.ID
				channel.Name = g.Name
				b.channels = append(b.channels, *channel)
			}
		case *slack.InvalidAuthEvent:
			flog.Fatalf("Invalid Token %#v", ev)
		default:
		}
	}
}

func (b *Bslack) handleMatterHook(mchan chan *MMMessage) {
	for {
		message := b.mh.Receive()
		flog.Debugf("receiving from matterhook (slack) %#v", message)
		m := &MMMessage{}
		m.Username = message.UserName
		m.Text = message.Text
		m.Text = b.replaceMention(m.Text)
		m.Channel = message.ChannelName
		if m.Username == "slackbot" {
			continue
		}
		mchan <- m
	}
}

func (b *Bslack) userName(id string) string {
	for _, u := range b.Users {
		if u.ID == id {
			return u.Name
		}
	}
	return ""
}

func (b *Bslack) replaceMention(text string) string {
	results := regexp.MustCompile(`<@([a-zA-z0-9]+)>`).FindAllStringSubmatch(text, -1)
	for _, r := range results {
		text = strings.Replace(text, "<@"+r[1]+">", "@"+b.userName(r[1]), -1)

	}
	return text
}

func (b *Bslack) replaceURL(text string) string {
	results := regexp.MustCompile(`<(.*?)\|.*?>`).FindAllStringSubmatch(text, -1)
	for _, r := range results {
		text = strings.Replace(text, r[0], r[1], -1)
	}
	return text
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"os"
	"regexp"
	"sort"
	"strings"
	"sync"

	"github.com/olekukonko/tablewriter"

	"github.com/lxc/lxd"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/gnuflag"
	"github.com/lxc/lxd/shared/i18n"
)

type column struct {
	Name           string
	Data           columnData
	NeedsState     bool
	NeedsSnapshots bool
}

type columnData func(shared.ContainerInfo, *shared.ContainerState, []shared.SnapshotInfo) string

type byName [][]string

func (a byName) Len() int {
	return len(a)
}

func (a byName) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}

func (a byName) Less(i, j int) bool {
	if a[i][0] == "" {
		return false
	}

	if a[j][0] == "" {
		return true
	}

	return a[i][0] < a[j][0]
}

type listCmd struct {
	chosenColumnRunes string
	fast              bool
}

func (c *listCmd) showByDefault() bool {
	return true
}

func (c *listCmd) usage() string {
	return i18n.G(
		`Lists the available resources.

lxc list [resource] [filters] [-c columns] [--fast]

The filters are:
* A single keyword like "web" which will list any container with "web" in its name.
* A key/value pair referring to a configuration item. For those, the namespace can be abreviated to the smallest unambiguous identifier:
* "user.blah=abc" will list all containers with the "blah" user property set to "abc"
* "u.blah=abc" will do the same
* "security.privileged=1" will list all privileged containers
* "s.privileged=1" will do the same

The columns are:
* 4 - IPv4 address
* 6 - IPv6 address
* a - architecture
* c - creation date
* n - name
* p - pid of container init process
* P - profiles
* s - state
* t - type (persistent or ephemeral)

Default column layout: ns46tS
Fast column layout: nsacPt`)
}

func (c *listCmd) flags() {
	gnuflag.StringVar(&c.chosenColumnRunes, "c", "ns46tS", i18n.G("Columns"))
	gnuflag.StringVar(&c.chosenColumnRunes, "columns", "ns46tS", i18n.G("Columns"))
	gnuflag.BoolVar(&c.fast, "fast", false, i18n.G("Fast mode (same as --columns=nsacPt"))
}

// This seems a little excessive.
func (c *listCmd) dotPrefixMatch(short string, full string) bool {
	fullMembs := strings.Split(full, ".")
	shortMembs := strings.Split(short, ".")

	if len(fullMembs) != len(shortMembs) {
		return false
	}

	for i, _ := range fullMembs {
		if !strings.HasPrefix(fullMembs[i], shortMembs[i]) {
			return false
		}
	}

	return true
}

func (c *listCmd) shouldShow(filters []string, state *shared.ContainerInfo) bool {
	for _, filter := range filters {
		if strings.Contains(filter, "=") {
			membs := strings.SplitN(filter, "=", 2)

			key := membs[0]
			var value string
			if len(membs) < 2 {
				value = ""
			} else {
				value = membs[1]
			}

			found := false
			for configKey, configValue := range state.Config {
				if c.dotPrefixMatch(key, configKey) {
					//try to test filter value as a regexp
					regexpValue := value
					if !(strings.Contains(value, "^") || strings.Contains(value, "$")) {
						regexpValue = "^" + regexpValue + "$"
					}
					r, err := regexp.Compile(regexpValue)
					//if not regexp compatible use original value
					if err != nil {
						if value == configValue {
							found = true
							break
						} else {
							// the property was found but didn't match
							return false
						}
					} else if r.MatchString(configValue) == true {
						found = true
						break
					}
				}
			}

			if !found {
				return false
			}
		} else {
			if !strings.Contains(state.Name, filter) {
				return false
			}
		}
	}

	return true
}

func (c *listCmd) listContainers(d *lxd.Client, cinfos []shared.ContainerInfo, filters []string, columns []column) error {
	headers := []string{}
	for _, column := range columns {
		headers = append(headers, column.Name)
	}

	threads := 10
	if len(cinfos) < threads {
		threads = len(cinfos)
	}

	cStates := map[string]*shared.ContainerState{}
	cStatesLock := sync.Mutex{}
	cStatesQueue := make(chan string, threads)
	cStatesWg := sync.WaitGroup{}

	cSnapshots := map[string][]shared.SnapshotInfo{}
	cSnapshotsLock := sync.Mutex{}
	cSnapshotsQueue := make(chan string, threads)
	cSnapshotsWg := sync.WaitGroup{}

	for i := 0; i < threads; i++ {
		cStatesWg.Add(1)
		go func() {
			for {
				cName, more := <-cStatesQueue
				if !more {
					break
				}

				state, err := d.ContainerState(cName)
				if err != nil {
					continue
				}

				cStatesLock.Lock()
				cStates[cName] = state
				cStatesLock.Unlock()
			}
			cStatesWg.Done()
		}()

		cSnapshotsWg.Add(1)
		go func() {
			for {
				cName, more := <-cSnapshotsQueue
				if !more {
					break
				}

				snaps, err := d.ListSnapshots(cName)
				if err != nil {
					continue
				}

				cSnapshotsLock.Lock()
				cSnapshots[cName] = snaps
				cSnapshotsLock.Unlock()
			}
			cSnapshotsWg.Done()
		}()
	}

	for _, cInfo := range cinfos {
		if !c.shouldShow(filters, &cInfo) {
			continue
		}

		for _, column := range columns {
			if column.NeedsState && cInfo.StatusCode != shared.Stopped {
				_, ok := cStates[cInfo.Name]
				if ok {
					continue
				}

				cStatesLock.Lock()
				cStates[cInfo.Name] = nil
				cStatesLock.Unlock()

				cStatesQueue <- cInfo.Name
			}

			if column.NeedsSnapshots {
				_, ok := cSnapshots[cInfo.Name]
				if ok {
					continue
				}

				cSnapshotsLock.Lock()
				cSnapshots[cInfo.Name] = nil
				cSnapshotsLock.Unlock()

				cSnapshotsQueue <- cInfo.Name
			}
		}
	}

	close(cStatesQueue)
	close(cSnapshotsQueue)
	cStatesWg.Wait()
	cSnapshotsWg.Wait()

	data := [][]string{}
	for _, cInfo := range cinfos {
		if !c.shouldShow(filters, &cInfo) {
			continue
		}

		col := []string{}
		for _, column := range columns {
			col = append(col, column.Data(cInfo, cStates[cInfo.Name], cSnapshots[cInfo.Name]))
		}
		data = append(data, col)
	}

	table := tablewriter.NewWriter(os.Stdout)
	table.SetAutoWrapText(false)
	table.SetAlignment(tablewriter.ALIGN_LEFT)
	table.SetRowLine(true)
	table.SetHeader(headers)
	sort.Sort(byName(data))
	table.AppendBulk(data)
	table.Render()

	return nil
}

func (c *listCmd) run(config *lxd.Config, args []string) error {
	var remote string
	name := ""

	filters := []string{}

	if len(args) != 0 {
		filters = args
		if strings.Contains(args[0], ":") {
			remote, name = config.ParseRemoteAndContainer(args[0])
			filters = args[1:]
		} else if !strings.Contains(args[0], "=") {
			remote = config.DefaultRemote
			name = args[0]
		}
	}

	if remote == "" {
		remote = config.DefaultRemote
	}

	d, err := lxd.NewClient(config, remote)
	if err != nil {
		return err
	}

	var cts []shared.ContainerInfo
	ctslist, err := d.ListContainers()
	if err != nil {
		return err
	}

	if name == "" {
		cts = ctslist
	} else {
		for _, cinfo := range ctslist {
			if len(cinfo.Name) >= len(name) && cinfo.Name[0:len(name)] == name {
				cts = append(cts, cinfo)
			}
		}
	}

	columns_map := map[rune]column{
		'4': column{i18n.G("IPV4"), c.IP4ColumnData, true, false},
		'6': column{i18n.G("IPV6"), c.IP6ColumnData, true, false},
		'a': column{i18n.G("ARCHITECTURE"), c.ArchitectureColumnData, false, false},
		'c': column{i18n.G("CREATED AT"), c.CreatedColumnData, false, false},
		'n': column{i18n.G("NAME"), c.nameColumnData, false, false},
		'p': column{i18n.G("PID"), c.PIDColumnData, true, false},
		'P': column{i18n.G("PROFILES"), c.ProfilesColumnData, false, false},
		'S': column{i18n.G("SNAPSHOTS"), c.numberSnapshotsColumnData, false, true},
		's': column{i18n.G("STATE"), c.statusColumnData, false, false},
		't': column{i18n.G("TYPE"), c.typeColumnData, false, false},
	}

	if c.fast {
		c.chosenColumnRunes = "nsacPt"
	}

	columns := []column{}
	for _, columnRune := range c.chosenColumnRunes {
		if column, ok := columns_map[columnRune]; ok {
			columns = append(columns, column)
		} else {
			return fmt.Errorf("%s does contain invalid column characters\n", c.chosenColumnRunes)
		}
	}

	return c.listContainers(d, cts, filters, columns)
}

func (c *listCmd) nameColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
	return cInfo.Name
}

func (c *listCmd) statusColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
	return strings.ToUpper(cInfo.Status)
}

func (c *listCmd) IP4ColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
	if cInfo.StatusCode != shared.Stopped {
		ipv4s := []string{}
		for netName, net := range cState.Network {
			if net.Type == "loopback" {
				continue
			}

			for _, addr := range net.Addresses {
				if shared.StringInSlice(addr.Scope, []string{"link", "local"}) {
					continue
				}

				if addr.Family == "inet" {
					ipv4s = append(ipv4s, fmt.Sprintf("%s (%s)", addr.Address, netName))
				}
			}
		}
		return strings.Join(ipv4s, "\n")
	} else {
		return ""
	}
}

func (c *listCmd) IP6ColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
	if cInfo.StatusCode != shared.Stopped {
		ipv6s := []string{}
		for netName, net := range cState.Network {
			if net.Type == "loopback" {
				continue
			}

			for _, addr := range net.Addresses {
				if shared.StringInSlice(addr.Scope, []string{"link", "local"}) {
					continue
				}

				if addr.Family == "inet6" {
					ipv6s = append(ipv6s, fmt.Sprintf("%s (%s)", addr.Address, netName))
				}
			}
		}
		return strings.Join(ipv6s, "\n")
	} else {
		return ""
	}
}

func (c *listCmd) typeColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
	if cInfo.Ephemeral {
		return i18n.G("EPHEMERAL")
	} else {
		return i18n.G("PERSISTENT")
	}
}

func (c *listCmd) numberSnapshotsColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
	return fmt.Sprintf("%d", len(cSnaps))
}

func (c *listCmd) PIDColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
	if cInfo.StatusCode != shared.Stopped {
		return fmt.Sprintf("%d", cState.Pid)
	}

	return ""
}

func (c *listCmd) ArchitectureColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
	return cInfo.Architecture
}

func (c *listCmd) ProfilesColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
	return strings.Join(cInfo.Profiles, "\n")
}

func (c *listCmd) CreatedColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
	layout := "2006/01/02 15:04 UTC"

	if cInfo.CreationDate.UTC().Unix() != 0 {
		return cInfo.CreationDate.UTC().Format(layout)
	}

	return ""
}
<commit_msg>Add missing column type S(snapshots) in "lxc list --help" column listing<commit_after>package main

import (
	"fmt"
	"os"
	"regexp"
	"sort"
	"strings"
	"sync"

	"github.com/olekukonko/tablewriter"

	"github.com/lxc/lxd"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/gnuflag"
	"github.com/lxc/lxd/shared/i18n"
)

type column struct {
	Name           string
	Data           columnData
	NeedsState     bool
	NeedsSnapshots bool
}

type columnData func(shared.ContainerInfo, *shared.ContainerState, []shared.SnapshotInfo) string

type byName [][]string

func (a byName) Len() int {
	return len(a)
}

func (a byName) Swap(i, j int) {
	a[i], a[j] = a[j], a[i]
}

func (a byName) Less(i, j int) bool {
	if a[i][0] == "" {
		return false
	}

	if a[j][0] == "" {
		return true
	}

	return a[i][0] < a[j][0]
}

type listCmd struct {
	chosenColumnRunes string
	fast              bool
}

func (c *listCmd) showByDefault() bool {
	return true
}

func (c *listCmd) usage() string {
	return i18n.G(
		`Lists the available resources.

lxc list [resource] [filters] [-c columns] [--fast]

The filters are:
* A single keyword like "web" which will list any container with "web" in its name.
* A key/value pair referring to a configuration item. For those, the namespace can be abreviated to the smallest unambiguous identifier:
* "user.blah=abc" will list all containers with the "blah" user property set to "abc"
* "u.blah=abc" will do the same
* "security.privileged=1" will list all privileged containers
* "s.privileged=1" will do the same

The columns are:
* 4 - IPv4 address
* 6 - IPv6 address
* a - architecture
* c - creation date
* n - name
* p - pid of container init process
* P - profiles
* s - state
* S - number of snapshots
* t - type (persistent or ephemeral)

Default column layout: ns46tS
Fast column layout: nsacPt`)
}

func (c *listCmd) flags() {
	gnuflag.StringVar(&c.chosenColumnRunes, "c", "ns46tS", i18n.G("Columns"))
	gnuflag.StringVar(&c.chosenColumnRunes, "columns", "ns46tS", i18n.G("Columns"))
	gnuflag.BoolVar(&c.fast, "fast", false, i18n.G("Fast mode (same as --columns=nsacPt"))
}

// This seems a little excessive.
func (c *listCmd) dotPrefixMatch(short string, full string) bool {
	fullMembs := strings.Split(full, ".")
	shortMembs := strings.Split(short, ".")

	if len(fullMembs) != len(shortMembs) {
		return false
	}

	for i, _ := range fullMembs {
		if !strings.HasPrefix(fullMembs[i], shortMembs[i]) {
			return false
		}
	}

	return true
}

func (c *listCmd) shouldShow(filters []string, state *shared.ContainerInfo) bool {
	for _, filter := range filters {
		if strings.Contains(filter, "=") {
			membs := strings.SplitN(filter, "=", 2)

			key := membs[0]
			var value string
			if len(membs) < 2 {
				value = ""
			} else {
				value = membs[1]
			}

			found := false
			for configKey, configValue := range state.Config {
				if c.dotPrefixMatch(key, configKey) {
					//try to test filter value as a regexp
					regexpValue := value
					if !(strings.Contains(value, "^") || strings.Contains(value, "$")) {
						regexpValue = "^" + regexpValue + "$"
					}
					r, err := regexp.Compile(regexpValue)
					//if not regexp compatible use original value
					if err != nil {
						if value == configValue {
							found = true
							break
						} else {
							// the property was found but didn't match
							return false
						}
					} else if r.MatchString(configValue) == true {
						found = true
						break
					}
				}
			}

			if !found {
				return false
			}
		} else {
			if !strings.Contains(state.Name, filter) {
				return false
			}
		}
	}

	return true
}

func (c *listCmd) listContainers(d *lxd.Client, cinfos []shared.ContainerInfo, filters []string, columns []column) error {
	headers := []string{}
	for _, column := range columns {
		headers = append(headers, column.Name)
	}

	threads := 10
	if len(cinfos) < threads {
		threads = len(cinfos)
	}

	cStates := map[string]*shared.ContainerState{}
	cStatesLock := sync.Mutex{}
	cStatesQueue := make(chan string, threads)
	cStatesWg := sync.WaitGroup{}

	cSnapshots := map[string][]shared.SnapshotInfo{}
	cSnapshotsLock := sync.Mutex{}
	cSnapshotsQueue := make(chan string, threads)
	cSnapshotsWg := sync.WaitGroup{}

	for i := 0; i < threads; i++ {
		cStatesWg.Add(1)
		go func() {
			for {
				cName, more := <-cStatesQueue
				if !more {
					break
				}

				state, err := d.ContainerState(cName)
				if err != nil {
					continue
				}

				cStatesLock.Lock()
				cStates[cName] = state
				cStatesLock.Unlock()
			}
			cStatesWg.Done()
		}()

		cSnapshotsWg.Add(1)
		go func() {
			for {
				cName, more := <-cSnapshotsQueue
				if !more {
					break
				}

				snaps, err := d.ListSnapshots(cName)
				if err != nil {
					continue
				}

				cSnapshotsLock.Lock()
				cSnapshots[cName] = snaps
				cSnapshotsLock.Unlock()
			}
			cSnapshotsWg.Done()
		}()
	}

	for _, cInfo := range cinfos {
		if !c.shouldShow(filters, &cInfo) {
			continue
		}

		for _, column := range columns {
			if column.NeedsState && cInfo.StatusCode != shared.Stopped {
				_, ok := cStates[cInfo.Name]
				if ok {
					continue
				}

				cStatesLock.Lock()
				cStates[cInfo.Name] = nil
				cStatesLock.Unlock()

				cStatesQueue <- cInfo.Name
			}

			if column.NeedsSnapshots {
				_, ok := cSnapshots[cInfo.Name]
				if ok {
					continue
				}

				cSnapshotsLock.Lock()
				cSnapshots[cInfo.Name] = nil
				cSnapshotsLock.Unlock()

				cSnapshotsQueue <- cInfo.Name
			}
		}
	}

	close(cStatesQueue)
	close(cSnapshotsQueue)
	cStatesWg.Wait()
	cSnapshotsWg.Wait()

	data := [][]string{}
	for _, cInfo := range cinfos {
		if !c.shouldShow(filters, &cInfo) {
			continue
		}

		col := []string{}
		for _, column := range columns {
			col = append(col, column.Data(cInfo, cStates[cInfo.Name], cSnapshots[cInfo.Name]))
		}
		data = append(data, col)
	}

	table := tablewriter.NewWriter(os.Stdout)
	table.SetAutoWrapText(false)
	table.SetAlignment(tablewriter.ALIGN_LEFT)
	table.SetRowLine(true)
	table.SetHeader(headers)
	sort.Sort(byName(data))
	table.AppendBulk(data)
	table.Render()

	return nil
}

func (c *listCmd) run(config *lxd.Config, args []string) error {
	var remote string
	name := ""

	filters := []string{}

	if len(args) != 0 {
		filters = args
		if strings.Contains(args[0], ":") {
			remote, name = config.ParseRemoteAndContainer(args[0])
			filters = args[1:]
		} else if !strings.Contains(args[0], "=") {
			remote = config.DefaultRemote
			name = args[0]
		}
	}

	if remote == "" {
		remote = config.DefaultRemote
	}

	d, err := lxd.NewClient(config, remote)
	if err != nil {
		return err
	}

	var cts []shared.ContainerInfo
	ctslist, err := d.ListContainers()
	if err != nil {
		return err
	}

	if name == "" {
		cts = ctslist
	} else {
		for _, cinfo := range ctslist {
			if len(cinfo.Name) >= len(name) && cinfo.Name[0:len(name)] == name {
				cts = append(cts, cinfo)
			}
		}
	}

	columns_map := map[rune]column{
		'4': column{i18n.G("IPV4"), c.IP4ColumnData, true, false},
		'6': column{i18n.G("IPV6"), c.IP6ColumnData, true, false},
		'a': column{i18n.G("ARCHITECTURE"), c.ArchitectureColumnData, false, false},
		'c': column{i18n.G("CREATED AT"), c.CreatedColumnData, false, false},
		'n': column{i18n.G("NAME"), c.nameColumnData, false, false},
		'p': column{i18n.G("PID"), c.PIDColumnData, true, false},
		'P': column{i18n.G("PROFILES"), c.ProfilesColumnData, false, false},
		'S': column{i18n.G("SNAPSHOTS"), c.numberSnapshotsColumnData, false, true},
		's': column{i18n.G("STATE"), c.statusColumnData, false, false},
		't': column{i18n.G("TYPE"), c.typeColumnData, false, false},
	}

	if c.fast {
		c.chosenColumnRunes = "nsacPt"
	}

	columns := []column{}
	for _, columnRune := range c.chosenColumnRunes {
		if column, ok := columns_map[columnRune]; ok {
			columns = append(columns, column)
		} else {
			return fmt.Errorf("%s does contain invalid column characters\n", c.chosenColumnRunes)
		}
	}

	return c.listContainers(d, cts, filters, columns)
}

func (c *listCmd) nameColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
	return cInfo.Name
}

func (c *listCmd) statusColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
	return strings.ToUpper(cInfo.Status)
}

func (c *listCmd) IP4ColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
	if cInfo.StatusCode != shared.Stopped {
		ipv4s := []string{}
		for netName, net := range cState.Network {
			if net.Type == "loopback" {
				continue
			}

			for _, addr := range net.Addresses {
				if shared.StringInSlice(addr.Scope, []string{"link", "local"}) {
					continue
				}

				if addr.Family == "inet" {
					ipv4s = append(ipv4s, fmt.Sprintf("%s (%s)", addr.Address, netName))
				}
			}
		}
		return strings.Join(ipv4s, "\n")
	} else {
		return ""
	}
}

func (c *listCmd) IP6ColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
	if cInfo.StatusCode != shared.Stopped {
		ipv6s := []string{}
		for netName, net := range cState.Network {
			if net.Type == "loopback" {
				continue
			}

			for _, addr := range net.Addresses {
				if shared.StringInSlice(addr.Scope, []string{"link", "local"}) {
					continue
				}

				if addr.Family == "inet6" {
					ipv6s = append(ipv6s, fmt.Sprintf("%s (%s)", addr.Address, netName))
				}
			}
		}
		return strings.Join(ipv6s, "\n")
	} else {
		return ""
	}
}

func (c *listCmd) typeColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
	if cInfo.Ephemeral {
		return i18n.G("EPHEMERAL")
	} else {
		return i18n.G("PERSISTENT")
	}
}

func (c *listCmd) numberSnapshotsColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
	return fmt.Sprintf("%d", len(cSnaps))
}

func (c *listCmd) PIDColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
	if cInfo.StatusCode != shared.Stopped {
		return fmt.Sprintf("%d", cState.Pid)
	}

	return ""
}

func (c *listCmd) ArchitectureColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
	return cInfo.Architecture
}

func (c *listCmd) ProfilesColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
	return strings.Join(cInfo.Profiles, "\n")
}

func (c *listCmd) CreatedColumnData(cInfo shared.ContainerInfo, cState *shared.ContainerState, cSnaps []shared.SnapshotInfo) string {
	layout := "2006/01/02 15:04 UTC"

	if cInfo.CreationDate.UTC().Unix() != 0 {
		return cInfo.CreationDate.UTC().Format(layout)
	}

	return ""
}
<|endoftext|>


<commit_before>package agent

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"os"
	"strconv"
	"strings"
	"sync"

	"github.com/armon/circbuf"
	consulapi "github.com/hashicorp/consul/api"
	consultemplate "github.com/marouenj/consul-template/core"
	"github.com/marouenj/consul/watch"
)

const (
	// Limit the size of a watch handlers's output to the
	// last WatchBufSize. Prevents an enormous buffer
	// from being captured
	WatchBufSize = 4 * 1024 // 4KB
	upAction     = "up"
	downAction   = "down"
)

// verifyWatchHandler does the pre-check for our handler configuration
func verifyWatchHandler(params interface{}) error {
	if params == nil {
		return fmt.Errorf("Must provide watch handler")
	}
	_, ok := params.(string)
	if !ok {
		return fmt.Errorf("Watch handler must be a string")
	}
	return nil
}

// makeWatchHandler returns a handler for the given watch
func makeWatchHandler(logOutput io.Writer, params interface{}) watch.HandlerFunc {
	script := params.(string)
	logger := log.New(logOutput, "", log.LstdFlags)
	fn := func(idx uint64, data interface{}) {
		// Create the command
		cmd, err := ExecScript(script)
		if err != nil {
			logger.Printf("[ERR] agent: Failed to setup watch: %v", err)
			return
		}
		cmd.Env = append(os.Environ(),
			"CONSUL_INDEX="+strconv.FormatUint(idx, 10),
		)

		// Collect the output
		output, _ := circbuf.NewBuffer(WatchBufSize)
		cmd.Stdout = output
		cmd.Stderr = output

		// Setup the input
		var inp bytes.Buffer
		enc := json.NewEncoder(&inp)
		if err := enc.Encode(data); err != nil {
			logger.Printf("[ERR] agent: Failed to encode data for watch '%s': %v", script, err)
			return
		}
		cmd.Stdin = &inp

		// Run the handler
		if err := cmd.Run(); err != nil {
			logger.Printf("[ERR] agent: Failed to invoke watch handler '%s': %v", script, err)
		}

		// Get the output, add a message about truncation
		outputStr := string(output.Bytes())
		if output.TotalWritten() > output.Size() {
			outputStr = fmt.Sprintf("Captured %d of %d bytes\n...\n%s",
				output.Size(), output.TotalWritten(), outputStr)
		}

		// Log the output
		logger.Printf("[DEBUG] agent: watch handler '%s' output: %s", script, outputStr)
	}
	return fn
}

// makeWatchHandler returns a handler that would control a consul-template instance
func makeWatchHandlerForArchetype(logOutput io.Writer, agent *Agent, archetypeIndex int) watch.HandlerFunc {
	logger := log.New(logOutput, "", log.LstdFlags)

	// get the key
	archetype := agent.config.Archetypes[archetypeIndex]
	key := strings.Join([]string{"archetype", "watch", archetype.PoolName, archetype.ID}, "/")

	// get the client
	client, _ := consulapi.NewClient(consulapi.DefaultConfig())

	// get the consul-template config
	config := consultemplate.DefaultConfig()
	address := []string{agent.config.Addresses.HTTP, strconv.Itoa(agent.config.Ports.HTTP)}
	config.Consul = strings.Join(address, ":")
	config.ConfigTemplates = append(config.ConfigTemplates, &agent.config.Archetypes[0].Template)

	// get the runner
	runner := agent.config.Runners[archetypeIndex]

	// define the selector
	var once sync.Once
	var selector func()

	fn := func(idx uint64, data interface{}) {
		logger.Printf("[INFO] agent: Calling watch handler on key %s", key)

		kv := client.KV()

		pair, _, err := kv.Get(key, nil)
		if err != nil {
			panic(err)
		}

		if pair == nil {
			return
		}
		val := string(pair.Value[:])

		actions := map[string]bool{
			upAction:   true,
			downAction: true,
		}

		if !actions[val] {
			return
		}

		// runner can be safely initialized
		// this bloc is executed once
		if runner == nil {
			logger.Printf("[INFO] agent: Initializing runner for key %s", key)
			if val != upAction {
				config.ConfigTemplates[0].First = true
			}
			runner, err = consultemplate.NewRunner(config, false, false)
			if err != nil {
				logger.Printf("[ERR] runner: Failed to launch: %v", err)
			}
			// define selector
			selector = func() {
			Outer:
				for {
					select {
					case err := <-runner.ErrCh:
						logger.Printf("%v", err)
					case <-runner.DoneCh:
						break Outer
					}
				}
			}

			if val == upAction {
				// runner.Up = true
				// runner.Init()
				go runner.Start()
			} else if val == downAction {
				go func() {
					once.Do(selector)
					once = sync.Once{}
				}()
			}
		}

		if val == upAction {
			go runner.Start()
			go func() {
				once.Do(selector)
				once = sync.Once{}
			}()
		} else if val == downAction {
			if err = runner.Stop(); err != nil {
				logger.Printf("%v", err)
			}
		}
	}
	return fn
}
<commit_msg>refactor address definition<commit_after>package agent

import (
	"bytes"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"os"
	"strconv"
	"strings"
	"sync"

	"github.com/armon/circbuf"
	consulapi "github.com/hashicorp/consul/api"
	consultemplate "github.com/marouenj/consul-template/core"
	"github.com/marouenj/consul/watch"
)

const (
	// Limit the size of a watch handlers's output to the
	// last WatchBufSize. Prevents an enormous buffer
	// from being captured
	WatchBufSize = 4 * 1024 // 4KB
	upAction     = "up"
	downAction   = "down"
)

// verifyWatchHandler does the pre-check for our handler configuration
func verifyWatchHandler(params interface{}) error {
	if params == nil {
		return fmt.Errorf("Must provide watch handler")
	}
	_, ok := params.(string)
	if !ok {
		return fmt.Errorf("Watch handler must be a string")
	}
	return nil
}

// makeWatchHandler returns a handler for the given watch
func makeWatchHandler(logOutput io.Writer, params interface{}) watch.HandlerFunc {
	script := params.(string)
	logger := log.New(logOutput, "", log.LstdFlags)
	fn := func(idx uint64, data interface{}) {
		// Create the command
		cmd, err := ExecScript(script)
		if err != nil {
			logger.Printf("[ERR] agent: Failed to setup watch: %v", err)
			return
		}
		cmd.Env = append(os.Environ(),
			"CONSUL_INDEX="+strconv.FormatUint(idx, 10),
		)

		// Collect the output
		output, _ := circbuf.NewBuffer(WatchBufSize)
		cmd.Stdout = output
		cmd.Stderr = output

		// Setup the input
		var inp bytes.Buffer
		enc := json.NewEncoder(&inp)
		if err := enc.Encode(data); err != nil {
			logger.Printf("[ERR] agent: Failed to encode data for watch '%s': %v", script, err)
			return
		}
		cmd.Stdin = &inp

		// Run the handler
		if err := cmd.Run(); err != nil {
			logger.Printf("[ERR] agent: Failed to invoke watch handler '%s': %v", script, err)
		}

		// Get the output, add a message about truncation
		outputStr := string(output.Bytes())
		if output.TotalWritten() > output.Size() {
			outputStr = fmt.Sprintf("Captured %d of %d bytes\n...\n%s",
				output.Size(), output.TotalWritten(), outputStr)
		}

		// Log the output
		logger.Printf("[DEBUG] agent: watch handler '%s' output: %s", script, outputStr)
	}
	return fn
}

// makeWatchHandler returns a handler that would control a consul-template instance
func makeWatchHandlerForArchetype(logOutput io.Writer, agent *Agent, archetypeIndex int) watch.HandlerFunc {
	logger := log.New(logOutput, "", log.LstdFlags)

	// get the key
	archetype := agent.config.Archetypes[archetypeIndex]
	key := strings.Join([]string{"archetype", "watch", archetype.PoolName, archetype.ID}, "/")

	// http address
	address := []string{agent.config.Addresses.HTTP, strconv.Itoa(agent.config.Ports.HTTP)}

	// get the client
	client, _ := consulapi.NewClient(consulapi.DefaultConfig())

	// get the consul-template config
	config := consultemplate.DefaultConfig()
	config.Consul = strings.Join(address, ":")
	config.ConfigTemplates = append(config.ConfigTemplates, &agent.config.Archetypes[0].Template)

	// get the runner
	runner := agent.config.Runners[archetypeIndex]

	// define the selector
	var once sync.Once
	var selector func()

	fn := func(idx uint64, data interface{}) {
		logger.Printf("[INFO] agent: Calling watch handler on key %s", key)

		kv := client.KV()

		pair, _, err := kv.Get(key, nil)
		if err != nil {
			panic(err)
		}

		if pair == nil {
			return
		}
		val := string(pair.Value[:])

		actions := map[string]bool{
			upAction:   true,
			downAction: true,
		}

		if !actions[val] {
			return
		}

		// runner can be safely initialized
		// this bloc is executed once
		if runner == nil {
			logger.Printf("[INFO] agent: Initializing runner for key %s", key)
			if val != upAction {
				config.ConfigTemplates[0].First = true
			}
			runner, err = consultemplate.NewRunner(config, false, false)
			if err != nil {
				logger.Printf("[ERR] runner: Failed to launch: %v", err)
			}
			// define selector
			selector = func() {
			Outer:
				for {
					select {
					case err := <-runner.ErrCh:
						logger.Printf("%v", err)
					case <-runner.DoneCh:
						break Outer
					}
				}
			}

			if val == upAction {
				// runner.Up = true
				// runner.Init()
				go runner.Start()
			} else if val == downAction {
				go func() {
					once.Do(selector)
					once = sync.Once{}
				}()
			}
		}

		if val == upAction {
			go runner.Start()
			go func() {
				once.Do(selector)
				once = sync.Once{}
			}()
		} else if val == downAction {
			if err = runner.Stop(); err != nil {
				logger.Printf("%v", err)
			}
		}
	}
	return fn
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"math/rand"
	"os"
	"os/exec"
	"os/signal"
	"runtime/pprof"
	"sync"
	"syscall"
	"time"

	"github.com/lxc/lxd"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/gnuflag"
)

var cpuProfile = gnuflag.String("cpuprofile", "", "Enable cpu profiling into the specified file.")
var debug = gnuflag.Bool("debug", false, "Enables debug mode.")
var group = gnuflag.String("group", "", "Group which owns the shared socket.")
var help = gnuflag.Bool("help", false, "Print this help message.")
var logfile = gnuflag.String("logfile", "", "Logfile to log to (e.g., /var/log/lxd/lxd.log).")
var memProfile = gnuflag.String("memprofile", "", "Enable memory profiling into the specified file.")
var printGoroutines = gnuflag.Int("print-goroutines-every", -1, "For debugging, print a complete stack trace every n seconds")
var syslogFlag = gnuflag.Bool("syslog", false, "Enables syslog logging.")
var timeout = gnuflag.Int("timeout", -1, "Timeout. Only used by the shutdown subcommand.")
var verbose = gnuflag.Bool("verbose", false, "Enables verbose mode.")
var version = gnuflag.Bool("version", false, "Print LXD's version number and exit.")

func init() {
	myGroup, err := shared.GroupName(os.Getgid())
	if err != nil {
		shared.Debugf("Problem finding default group %s", err)
	}
	*group = myGroup

	rand.Seed(time.Now().UTC().UnixNano())
}

func main() {
	if err := run(); err != nil {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(1)
	}
}

func run() error {
	gnuflag.Usage = func() {
		fmt.Printf("Usage: lxd [command] [options]\n\nOptions:\n")
		gnuflag.PrintDefaults()

		fmt.Printf("\nCommands:\n")
		fmt.Printf("    activateifneeded\n")
		fmt.Printf("        Check if LXD should be started (at boot) and if so, spawns it through socket activation\n")
		fmt.Printf("    shutdown [--timeout=60]\n")
		fmt.Printf("        Perform a clean shutdown of LXD and all running containers\n")
		fmt.Printf("    waitready [--timeout=15]\n")
		fmt.Printf("        Wait until LXD is ready to handle requests\n")

		fmt.Printf("\nInternal commands (don't call those directly):\n")
		fmt.Printf("    forkgetfile\n")
		fmt.Printf("        Grab a file from a running container\n")
		fmt.Printf("    forkmigrate\n")
		fmt.Printf("        Restore a container after migration\n")
		fmt.Printf("    forkputfile\n")
		fmt.Printf("        Push a file to a running container\n")
		fmt.Printf("    forkstart\n")
		fmt.Printf("        Start a container\n")
	}

	gnuflag.Parse(true)
	if *help {
		// The user asked for help via --help, so we shouldn't print to
		// stderr.
		gnuflag.SetOut(os.Stdout)
		gnuflag.Usage()
		return nil
	}

	if *version {
		fmt.Println(shared.Version)
		return nil
	}

	if len(shared.VarPath("unix.sock")) > 107 {
		return fmt.Errorf("LXD_DIR is too long, must be < %d", 107-len("unix.sock"))
	}

	// Configure logging
	syslog := ""
	if *syslogFlag {
		syslog = "lxd"
	}

	handler := eventsHandler{}

	err := shared.SetLogger(syslog, *logfile, *verbose, *debug, handler)
	if err != nil {
		fmt.Printf("%s", err)
		return nil
	}

	// Process sub-commands
	if len(os.Args) > 1 {
		// "forkputfile" and "forkgetfile" are handled specially in copyfile.go
		switch os.Args[1] {
		case "activateifneeded":
			return activateIfNeeded()
		case "forkmigrate":
			return MigrateContainer(os.Args[1:])
		case "forkstart":
			return startContainer(os.Args[1:])
		case "shutdown":
			return cleanShutdown()
		case "waitready":
			return waitReady()
		}
	}

	if gnuflag.NArg() != 0 {
		gnuflag.Usage()
		return fmt.Errorf("Unknown arguments")
	}

	if *cpuProfile != "" {
		f, err := os.Create(*cpuProfile)
		if err != nil {
			fmt.Printf("Error opening cpu profile file: %s\n", err)
			return nil
		}
		pprof.StartCPUProfile(f)
		defer pprof.StopCPUProfile()
	}

	if *memProfile != "" {
		go memProfiler()
	}

	neededPrograms := []string{"setfacl", "rsync", "tar", "xz"}
	for _, p := range neededPrograms {
		_, err := exec.LookPath(p)
		if err != nil {
			return err
		}
	}

	if *printGoroutines > 0 {
		go func() {
			for {
				time.Sleep(time.Duration(*printGoroutines) * time.Second)
				shared.PrintStack()
			}
		}()
	}

	d, err := startDaemon()

	if err != nil {
		if d != nil && d.db != nil {
			d.db.Close()
		}
		return err
	}

	var ret error
	var wg sync.WaitGroup
	wg.Add(1)

	go func() {
		ch := make(chan os.Signal)
		signal.Notify(ch, syscall.SIGPWR)
		sig := <-ch

		shared.Log.Info(
			fmt.Sprintf("Received '%s signal', shutting down containers.", sig))

		containersShutdown(d)

		ret = d.Stop()
		wg.Done()
	}()

	go func() {
		ch := make(chan os.Signal)
		signal.Notify(ch, syscall.SIGINT)
		signal.Notify(ch, syscall.SIGQUIT)
		signal.Notify(ch, syscall.SIGTERM)
		sig := <-ch

		shared.Log.Info(fmt.Sprintf("Received '%s signal', exiting.\n", sig))
		ret = d.Stop()
		wg.Done()
	}()

	wg.Wait()
	return ret
}

func cleanShutdown() error {
	if *timeout < 0 {
		*timeout = 60
	}

	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
	if err != nil {
		return err
	}

	serverStatus, err := c.ServerStatus()
	if err != nil {
		return err
	}

	pid := serverStatus.Environment.ServerPid
	if pid < 1 {
		return fmt.Errorf("Invalid server PID: %d", pid)
	}

	err = syscall.Kill(pid, syscall.SIGPWR)
	if err != nil {
		return err
	}

	monitor := make(chan error, 1)
	go func() {
		monitor <- c.Monitor(nil, func(m interface{}) {})
	}()

	select {
	case <-monitor:
		break
	case <-time.After(time.Second * time.Duration(*timeout)):
		return fmt.Errorf("LXD still running after %ds timeout.", *timeout)
	}

	return nil
}

func activateIfNeeded() error {
	// Don't start a full daemon, we just need DB access
	d := &Daemon{
		IsMock:                false,
		imagesDownloading:     map[string]chan bool{},
		imagesDownloadingLock: sync.RWMutex{},
	}

	err := initializeDbObject(d, shared.VarPath("lxd.db"))
	if err != nil {
		return err
	}

	// Look for network socket
	value, err := d.ConfigValueGet("core.https_address")
	if err != nil {
		return err
	}

	if value != "" {
		shared.Debugf("Daemon has core.https_address set, activating...")
		_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
		return err
	}

	// Look for auto-started or previously started containers
	d.IdmapSet, err = shared.DefaultIdmapSet()
	if err != nil {
		return err
	}

	containers, err := doContainersGet(d, true)
	if err != nil {
		return err
	}

	containerInfo := containers.(shared.ContainerInfoList)
	for _, container := range containerInfo {
		lastState := container.State.Config["volatile.last_state.power"]
		autoStart := container.State.ExpandedConfig["boot.autostart"]

		if lastState == "RUNNING" || lastState == "Running" || autoStart == "true" {
			shared.Debugf("Daemon has auto-started containers, activating...")
			_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
			return err
		}
	}

	shared.Debugf("No need to start the daemon now.")
	return nil
}

func waitReady() error {
	if *timeout < 0 {
		*timeout = 15
	}

	finger := make(chan error, 1)
	go func() {
		for {
			c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
			if err != nil {
				time.Sleep(500 * 1e6 * time.Nanosecond)
				continue
			}

			err = c.Finger()
			if err != nil {
				time.Sleep(500 * 1e6 * time.Nanosecond)
				continue
			}

			finger <- nil
			return
		}
	}()

	select {
	case <-finger:
		break
	case <-time.After(time.Second * time.Duration(*timeout)):
		return fmt.Errorf("LXD still not running after %ds timeout.", *timeout)
	}

	return nil
}
<commit_msg>s/those/these/<commit_after>package main

import (
	"fmt"
	"math/rand"
	"os"
	"os/exec"
	"os/signal"
	"runtime/pprof"
	"sync"
	"syscall"
	"time"

	"github.com/lxc/lxd"
	"github.com/lxc/lxd/shared"
	"github.com/lxc/lxd/shared/gnuflag"
)

var cpuProfile = gnuflag.String("cpuprofile", "", "Enable cpu profiling into the specified file.")
var debug = gnuflag.Bool("debug", false, "Enables debug mode.")
var group = gnuflag.String("group", "", "Group which owns the shared socket.")
var help = gnuflag.Bool("help", false, "Print this help message.")
var logfile = gnuflag.String("logfile", "", "Logfile to log to (e.g., /var/log/lxd/lxd.log).")
var memProfile = gnuflag.String("memprofile", "", "Enable memory profiling into the specified file.")
var printGoroutines = gnuflag.Int("print-goroutines-every", -1, "For debugging, print a complete stack trace every n seconds")
var syslogFlag = gnuflag.Bool("syslog", false, "Enables syslog logging.")
var timeout = gnuflag.Int("timeout", -1, "Timeout. Only used by the shutdown subcommand.")
var verbose = gnuflag.Bool("verbose", false, "Enables verbose mode.")
var version = gnuflag.Bool("version", false, "Print LXD's version number and exit.")

func init() {
	myGroup, err := shared.GroupName(os.Getgid())
	if err != nil {
		shared.Debugf("Problem finding default group %s", err)
	}
	*group = myGroup

	rand.Seed(time.Now().UTC().UnixNano())
}

func main() {
	if err := run(); err != nil {
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
		os.Exit(1)
	}
}

func run() error {
	gnuflag.Usage = func() {
		fmt.Printf("Usage: lxd [command] [options]\n\nOptions:\n")
		gnuflag.PrintDefaults()

		fmt.Printf("\nCommands:\n")
		fmt.Printf("    activateifneeded\n")
		fmt.Printf("        Check if LXD should be started (at boot) and if so, spawns it through socket activation\n")
		fmt.Printf("    shutdown [--timeout=60]\n")
		fmt.Printf("        Perform a clean shutdown of LXD and all running containers\n")
		fmt.Printf("    waitready [--timeout=15]\n")
		fmt.Printf("        Wait until LXD is ready to handle requests\n")

		fmt.Printf("\nInternal commands (don't call these directly):\n")
		fmt.Printf("    forkgetfile\n")
		fmt.Printf("        Grab a file from a running container\n")
		fmt.Printf("    forkmigrate\n")
		fmt.Printf("        Restore a container after migration\n")
		fmt.Printf("    forkputfile\n")
		fmt.Printf("        Push a file to a running container\n")
		fmt.Printf("    forkstart\n")
		fmt.Printf("        Start a container\n")
	}

	gnuflag.Parse(true)
	if *help {
		// The user asked for help via --help, so we shouldn't print to
		// stderr.
		gnuflag.SetOut(os.Stdout)
		gnuflag.Usage()
		return nil
	}

	if *version {
		fmt.Println(shared.Version)
		return nil
	}

	if len(shared.VarPath("unix.sock")) > 107 {
		return fmt.Errorf("LXD_DIR is too long, must be < %d", 107-len("unix.sock"))
	}

	// Configure logging
	syslog := ""
	if *syslogFlag {
		syslog = "lxd"
	}

	handler := eventsHandler{}

	err := shared.SetLogger(syslog, *logfile, *verbose, *debug, handler)
	if err != nil {
		fmt.Printf("%s", err)
		return nil
	}

	// Process sub-commands
	if len(os.Args) > 1 {
		// "forkputfile" and "forkgetfile" are handled specially in copyfile.go
		switch os.Args[1] {
		case "activateifneeded":
			return activateIfNeeded()
		case "forkmigrate":
			return MigrateContainer(os.Args[1:])
		case "forkstart":
			return startContainer(os.Args[1:])
		case "shutdown":
			return cleanShutdown()
		case "waitready":
			return waitReady()
		}
	}

	if gnuflag.NArg() != 0 {
		gnuflag.Usage()
		return fmt.Errorf("Unknown arguments")
	}

	if *cpuProfile != "" {
		f, err := os.Create(*cpuProfile)
		if err != nil {
			fmt.Printf("Error opening cpu profile file: %s\n", err)
			return nil
		}
		pprof.StartCPUProfile(f)
		defer pprof.StopCPUProfile()
	}

	if *memProfile != "" {
		go memProfiler()
	}

	neededPrograms := []string{"setfacl", "rsync", "tar", "xz"}
	for _, p := range neededPrograms {
		_, err := exec.LookPath(p)
		if err != nil {
			return err
		}
	}

	if *printGoroutines > 0 {
		go func() {
			for {
				time.Sleep(time.Duration(*printGoroutines) * time.Second)
				shared.PrintStack()
			}
		}()
	}

	d, err := startDaemon()

	if err != nil {
		if d != nil && d.db != nil {
			d.db.Close()
		}
		return err
	}

	var ret error
	var wg sync.WaitGroup
	wg.Add(1)

	go func() {
		ch := make(chan os.Signal)
		signal.Notify(ch, syscall.SIGPWR)
		sig := <-ch

		shared.Log.Info(
			fmt.Sprintf("Received '%s signal', shutting down containers.", sig))

		containersShutdown(d)

		ret = d.Stop()
		wg.Done()
	}()

	go func() {
		ch := make(chan os.Signal)
		signal.Notify(ch, syscall.SIGINT)
		signal.Notify(ch, syscall.SIGQUIT)
		signal.Notify(ch, syscall.SIGTERM)
		sig := <-ch

		shared.Log.Info(fmt.Sprintf("Received '%s signal', exiting.\n", sig))
		ret = d.Stop()
		wg.Done()
	}()

	wg.Wait()
	return ret
}

func cleanShutdown() error {
	if *timeout < 0 {
		*timeout = 60
	}

	c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
	if err != nil {
		return err
	}

	serverStatus, err := c.ServerStatus()
	if err != nil {
		return err
	}

	pid := serverStatus.Environment.ServerPid
	if pid < 1 {
		return fmt.Errorf("Invalid server PID: %d", pid)
	}

	err = syscall.Kill(pid, syscall.SIGPWR)
	if err != nil {
		return err
	}

	monitor := make(chan error, 1)
	go func() {
		monitor <- c.Monitor(nil, func(m interface{}) {})
	}()

	select {
	case <-monitor:
		break
	case <-time.After(time.Second * time.Duration(*timeout)):
		return fmt.Errorf("LXD still running after %ds timeout.", *timeout)
	}

	return nil
}

func activateIfNeeded() error {
	// Don't start a full daemon, we just need DB access
	d := &Daemon{
		IsMock:                false,
		imagesDownloading:     map[string]chan bool{},
		imagesDownloadingLock: sync.RWMutex{},
	}

	err := initializeDbObject(d, shared.VarPath("lxd.db"))
	if err != nil {
		return err
	}

	// Look for network socket
	value, err := d.ConfigValueGet("core.https_address")
	if err != nil {
		return err
	}

	if value != "" {
		shared.Debugf("Daemon has core.https_address set, activating...")
		_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
		return err
	}

	// Look for auto-started or previously started containers
	d.IdmapSet, err = shared.DefaultIdmapSet()
	if err != nil {
		return err
	}

	containers, err := doContainersGet(d, true)
	if err != nil {
		return err
	}

	containerInfo := containers.(shared.ContainerInfoList)
	for _, container := range containerInfo {
		lastState := container.State.Config["volatile.last_state.power"]
		autoStart := container.State.ExpandedConfig["boot.autostart"]

		if lastState == "RUNNING" || lastState == "Running" || autoStart == "true" {
			shared.Debugf("Daemon has auto-started containers, activating...")
			_, err := lxd.NewClient(&lxd.DefaultConfig, "local")
			return err
		}
	}

	shared.Debugf("No need to start the daemon now.")
	return nil
}

func waitReady() error {
	if *timeout < 0 {
		*timeout = 15
	}

	finger := make(chan error, 1)
	go func() {
		for {
			c, err := lxd.NewClient(&lxd.DefaultConfig, "local")
			if err != nil {
				time.Sleep(500 * 1e6 * time.Nanosecond)
				continue
			}

			err = c.Finger()
			if err != nil {
				time.Sleep(500 * 1e6 * time.Nanosecond)
				continue
			}

			finger <- nil
			return
		}
	}()

	select {
	case <-finger:
		break
	case <-time.After(time.Second * time.Duration(*timeout)):
		return fmt.Errorf("LXD still not running after %ds timeout.", *timeout)
	}

	return nil
}
<|endoftext|>