<commit_before>/*
Copyright 2016 - 2017 Huawei Technologies Co., Ltd. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"fmt"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"
)

// Parse CO_DATA value, and return CoreDNS repository URI and action (build/test/release).
func parseEnv(env string) (uri, action, release string, err error) {
	files := strings.Fields(env)
	if len(files) == 0 {
		return "", "", "", fmt.Errorf("CO_DATA value is null\n")
	}

	for _, v := range files {
		s := strings.Split(v, "=")
		key, value := s[0], s[1]

		switch key {
		case "coredns":
			uri = value
		case "action":
			action = value
		case "release":
			release = value
		default:
			fmt.Fprintf(os.Stdout, "[COUT] Unknown Parameter: [%s]\n", s)
		}
	}

	return uri, action, release, nil
}

// Git clone the CoreDNS repository, and process will redirect to system stdout.
func gitClone(repo, dest string) error {
	cmd := exec.Command("git", "clone", repo, dest)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		fmt.Fprintf(os.Stderr, "[COUT] Git clone error: %s\n", err.Error())
		fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")
		os.Exit(1)
	}

	return nil
}

// Execute `make coredns` in the CoreDNS folder.
func corednsBuild() error {
	cmd := exec.Command("make", "coredns")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		fmt.Fprintf(os.Stderr, "[COUT] Make build error: %s\n", err.Error())
		fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")

		return err
	}

	return nil
}

// Execute `make test` in the CoreDNS folder.
func corednsTest() error {
	cmd := exec.Command("make", "test")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		fmt.Fprintf(os.Stderr, "[COUT] Make test error: %s\n", err.Error())
		fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")

		return err
	}

	return nil
}

// Execute `make build` in the CoreDNS folder, then upload the coredns binary file to the artifact repository.
func corednsRelease(repo, release string) error {
	if err := corednsBuild(); err != nil {
		return err
	}

	filePath := fmt.Sprintf("%s/coredns", repo)

	if f, err := os.Open(filePath); err != nil {
		fmt.Fprintf(os.Stderr, "[COUT] Read coredns binary file error: %s\n", err.Error())
		fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")

		return err
	} else {
		defer f.Close()

		// Pattern: <domain>/<namespace>/<repository>/<tag>
		// hub.opshub.sh/containerops/cncf-demo/demo

		domain := strings.Split(release, "/")[0]
		namespace := strings.Split(release, "/")[1]
		repository := strings.Split(release, "/")[2]
		tag := strings.Split(release, "/")[3]

		if req, err := http.NewRequest(http.MethodPut,
			fmt.Sprintf("https://%s/binary/v1/%s/%s/binary/%s/%s",
				domain, namespace, repository, filepath.Base(filePath), tag), f); err != nil {

			fmt.Fprintf(os.Stderr, "[COUT] Upload coredns binary file error: %s\n", err.Error())
			fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")

			return err
		} else {
			req.Header.Set("Content-Type", "text/plain")

			client := &http.Client{}
			if resp, err := client.Do(req); err != nil {
				fmt.Fprintf(os.Stderr, "[COUT] Upload coredns binary file error: %s\n", err.Error())
				fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")

				return err
			} else {
				defer resp.Body.Close()

				switch resp.StatusCode {
				case http.StatusOK:
					return nil
				case http.StatusBadRequest:
					fmt.Fprint(os.Stderr, "[COUT] Upload coredns binary file, service return 400 error.")
					fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")

					return fmt.Errorf("Binary upload failed.")
				case http.StatusUnauthorized:
					fmt.Fprint(os.Stderr, "[COUT] Upload coredns binary file, service return 401 error.")
					fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")

					return fmt.Errorf("Action unauthorized.")
				default:
					fmt.Fprint(os.Stderr, "[COUT] Upload coredns binary file, service return unknown error.")
					fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")

					return fmt.Errorf("Unknown error.")
				}
			}
		}
	}

	return nil
}

func main() {
	//Get the CO_DATA from environment parameter "CO_DATA"
	data := os.Getenv("CO_DATA")
	if len(data) == 0 {
		fmt.Fprintf(os.Stderr, "[COUT] %s\n", "The CO_DATA value is null.")
		fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "The CO_DATA value is null.")
		os.Exit(1)
	}

	if corednsRepo, action, release, err := parseEnv(data); err != nil {
		fmt.Fprintf(os.Stderr, "[COUT] Parse the CO_DATA error: %s\n", err.Error())
		fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")
		os.Exit(1)
	} else {
		//Create the base path within GOPATH.
		basePath := path.Join(os.Getenv("GOPATH"), "src", "github.com", "coredns", "coredns")
		os.MkdirAll(basePath, os.ModePerm)

		//Clone the git repository
		if err := gitClone(corednsRepo, basePath); err != nil {
			fmt.Fprintf(os.Stderr, "[COUT] Clone the coredns repository error: %s\n", err.Error())
			fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")
			os.Exit(1)
		}

		//Execute action
		switch action {
		case "build":

			if err := corednsBuild(); err != nil {
				os.Exit(1)
			}

		case "test":

			if err := corednsTest(); err != nil {
				os.Exit(1)
			}

		case "release":

			if err := corednsRelease(basePath, release); err != nil {
				os.Exit(1)
			}

		default:
			fmt.Fprintf(os.Stderr, "[COUT] %s\n", "Unknown action, the component only support build, test and release action.")
			fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")
			os.Exit(1)
		}

	}

	//Print result
	fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "true")
	os.Exit(0)
}
<commit_msg>Add COREDNS_URI as component output value.<commit_after>/*
Copyright 2016 - 2017 Huawei Technologies Co., Ltd. All rights reserved.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package main

import (
	"fmt"
	"net/http"
	"os"
	"os/exec"
	"path"
	"path/filepath"
	"strings"
)

// Parse CO_DATA value, and return CoreDNS repository URI and action (build/test/release).
func parseEnv(env string) (uri, action, release string, err error) {
	files := strings.Fields(env)
	if len(files) == 0 {
		return "", "", "", fmt.Errorf("CO_DATA value is null\n")
	}

	for _, v := range files {
		s := strings.Split(v, "=")
		key, value := s[0], s[1]

		switch key {
		case "coredns":
			uri = value
		case "action":
			action = value
		case "release":
			release = value
		default:
			fmt.Fprintf(os.Stdout, "[COUT] Unknown Parameter: [%s]\n", s)
		}
	}

	return uri, action, release, nil
}

// Git clone the CoreDNS repository, and process will redirect to system stdout.
func gitClone(repo, dest string) error {
	cmd := exec.Command("git", "clone", repo, dest)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		fmt.Fprintf(os.Stderr, "[COUT] Git clone error: %s\n", err.Error())
		fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")
		os.Exit(1)
	}

	return nil
}

// Execute `make coredns` in the CoreDNS folder.
func corednsBuild() error {
	cmd := exec.Command("make", "coredns")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		fmt.Fprintf(os.Stderr, "[COUT] Make build error: %s\n", err.Error())
		fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")

		return err
	}

	return nil
}

// Execute `make test` in the CoreDNS folder.
func corednsTest() error {
	cmd := exec.Command("make", "test")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr

	if err := cmd.Run(); err != nil {
		fmt.Fprintf(os.Stderr, "[COUT] Make test error: %s\n", err.Error())
		fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")

		return err
	}

	return nil
}

// Execute `make build` in the CoreDNS folder, then upload the coredns binary file to the artifact repository.
func corednsRelease(repo, release string) error {
	if err := corednsBuild(); err != nil {
		return err
	}

	filePath := fmt.Sprintf("%s/coredns", repo)

	if f, err := os.Open(filePath); err != nil {
		fmt.Fprintf(os.Stderr, "[COUT] Read coredns binary file error: %s\n", err.Error())
		fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")

		return err
	} else {
		defer f.Close()

		// Pattern: <domain>/<namespace>/<repository>/<tag>
		// hub.opshub.sh/containerops/cncf-demo/demo

		domain := strings.Split(release, "/")[0]
		namespace := strings.Split(release, "/")[1]
		repository := strings.Split(release, "/")[2]
		tag := strings.Split(release, "/")[3]

		if req, err := http.NewRequest(http.MethodPut,
			fmt.Sprintf("https://%s/binary/v1/%s/%s/binary/%s/%s",
				domain, namespace, repository, filepath.Base(filePath), tag), f); err != nil {

			fmt.Fprintf(os.Stderr, "[COUT] Upload coredns binary file error: %s\n", err.Error())
			fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")

			return err
		} else {
			req.Header.Set("Content-Type", "text/plain")

			client := &http.Client{}
			if resp, err := client.Do(req); err != nil {
				fmt.Fprintf(os.Stderr, "[COUT] Upload coredns binary file error: %s\n", err.Error())
				fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")

				return err
			} else {
				defer resp.Body.Close()

				switch resp.StatusCode {
				case http.StatusOK:
					uri := fmt.Sprintf("https://%s/binary/v1/%s/%s/binary/%s/%s",
						domain, namespace, repository, filepath.Base(filePath), tag)
					fmt.Fprintf(os.Stdout, "[COUT] COREDNS_URI = %s", uri)

					return nil
				case http.StatusBadRequest:
					fmt.Fprint(os.Stderr, "[COUT] Upload coredns binary file, service return 400 error.")
					fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")

					return fmt.Errorf("Binary upload failed.")
				case http.StatusUnauthorized:
					fmt.Fprint(os.Stderr, "[COUT] Upload coredns binary file, service return 401 error.")
					fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")

					return fmt.Errorf("Action unauthorized.")
				default:
					fmt.Fprint(os.Stderr, "[COUT] Upload coredns binary file, service return unknown error.")
					fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")

					return fmt.Errorf("Unknown error.")
				}
			}
		}
	}

	return nil
}

func main() {
	//Get the CO_DATA from environment parameter "CO_DATA"
	data := os.Getenv("CO_DATA")
	if len(data) == 0 {
		fmt.Fprintf(os.Stderr, "[COUT] %s\n", "The CO_DATA value is null.")
		fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "The CO_DATA value is null.")
		os.Exit(1)
	}

	if corednsRepo, action, release, err := parseEnv(data); err != nil {
		fmt.Fprintf(os.Stderr, "[COUT] Parse the CO_DATA error: %s\n", err.Error())
		fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")
		os.Exit(1)
	} else {
		//Create the base path within GOPATH.
		basePath := path.Join(os.Getenv("GOPATH"), "src", "github.com", "coredns", "coredns")
		os.MkdirAll(basePath, os.ModePerm)

		//Clone the git repository
		if err := gitClone(corednsRepo, basePath); err != nil {
			fmt.Fprintf(os.Stderr, "[COUT] Clone the coredns repository error: %s\n", err.Error())
			fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")
			os.Exit(1)
		}

		//Execute action
		switch action {
		case "build":

			if err := corednsBuild(); err != nil {
				os.Exit(1)
			}

		case "test":

			if err := corednsTest(); err != nil {
				os.Exit(1)
			}

		case "release":

			if err := corednsRelease(basePath, release); err != nil {
				os.Exit(1)
			}

		default:
			fmt.Fprintf(os.Stderr, "[COUT] %s\n", "Unknown action, the component only support build, test and release action.")
			fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "false")
			os.Exit(1)
		}

	}

	//Print result
	fmt.Fprintf(os.Stdout, "[COUT] CO_RESULT = %s\n", "true")
	os.Exit(0)
}
<|endoftext|>


<commit_before>// Package sentences provides a scanner for Unicode text segmentation sentence boundaries: https://unicode.org/reports/tr29/#Sentence_Boundaries
package sentences

import (
	"bufio"
	"fmt"
	"io"
)

// NewScanner tokenizes a reader into a stream of sentence tokens according to Unicode Text Segmentation sentence boundaries https://unicode.org/reports/tr29/#Sentence_Boundaries
// Iterate through the stream by calling Scan() until false.
//	text := "This is an example. And another!"
//	reader := strings.NewReader(text)
//
//	scanner := sentences.NewScanner(reader)
//	for scanner.Scan() {
//		fmt.Printf("%s\n", scanner.Text())
//	}
//	if err := scanner.Err(); err != nil {
//		log.Fatal(err)
//	}
func NewScanner(r io.Reader) *bufio.Scanner {
	scanner := bufio.NewScanner(r)
	scanner.Split(SplitFunc)
	return scanner
}

var trie = newSentencesTrie(0)

// is determines if lookup intersects propert(ies)
func (lookup property) is(properties property) bool {
	return (lookup & properties) != 0
}

var _SATerm = _STerm | _ATerm
var _ParaSep = _Sep | _CR | _LF
var _Ignore = _Extend | _Format

// SplitFunc is a bufio.SplitFunc implementation of sentence segmentation, for use with bufio.Scanner
func SplitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) {
	if atEOF && len(data) == 0 {
		return 0, nil, nil
	}

	// These vars are stateful across loop iterations
	var pos, w int
	var current property

	for {
		if pos == len(data) && !atEOF {
			// Request more data
			return 0, nil, nil
		}

		sot := pos == 0 // "start of text"
		eof := len(data) == pos

		// https://unicode.org/reports/tr29/#SB1
		if sot && !eof {
			current, w = trie.lookup(data[pos:])
			pos += w
			continue
		}

		// https://unicode.org/reports/tr29/#SB2
		if eof {
			break
		}

		// Rules are usually of the form Cat1 × Cat2; "current" refers to the first property
		// to the right of the ×, from which we look back or forward

		last := current

		current, w = trie.lookup(data[pos:])
		if w == 0 {
			return 0, nil, fmt.Errorf("error decoding rune at byte 0x%x", data[pos])
		}

		// https://unicode.org/reports/tr29/#SB3
		if current.is(_LF) && last.is(_CR) {
			pos += w
			continue
		}

		// https://unicode.org/reports/tr29/#SB4
		if last.is(_ParaSep) {
			break
		}

		// https://unicode.org/reports/tr29/#SB5
		if current.is(_Extend | _Format) {
			pos += w
			continue
		}

		// SB5 applies to subsequent rules; there is an implied "ignoring Extend & Format"
		// https://unicode.org/reports/tr29/#Grapheme_Cluster_and_Format_Rules
		// The previous/subsequent methods are shorthand for "seek a property but skip over Extend & Format on the way"

		// https://unicode.org/reports/tr29/#SB6
		if current.is(_Numeric) && previous(_ATerm, data[:pos]) {
			pos += w
			continue
		}

		// https://unicode.org/reports/tr29/#SB7
		if current.is(_Upper) {
			pi := previousIndex(_ATerm, data[:pos])
			if pi >= 0 && previous(_Upper|_Lower, data[:pi]) {
				pos += w
				continue
			}
		}

		// https://unicode.org/reports/tr29/#SB8
		{
			p := pos

			// ( ¬(OLetter | Upper | Lower | ParaSep | SATerm) )*
			// Zero or more of not-the-above properties
			for p < len(data) {
				lookup, w := trie.lookup(data[p:])

				if !lookup.is(_OLetter | _Upper | _Lower | _ParaSep | _SATerm) {
					p += w
					continue
				}
				break
			}

			if subsequent(_Lower, data[p:]) {
				p2 := pos

				// Zero or more Sp
				sp := pos
				for {
					sp = previousIndex(_Sp, data[:sp])
					if sp < 0 {
						break
					}
					p2 = sp
				}

				// Zero or more Close
				close := p2
				for {
					close = previousIndex(_Close, data[:close])
					if close < 0 {
						break
					}
					p2 = close
				}

				// Having looked back past Sp's, Close's, and intervening Extend|Format,
				// is there an ATerm?
				if previous(_ATerm, data[:p2]) {
					pos += w
					continue
				}
			}
		}

		// https://unicode.org/reports/tr29/#SB8a
		if current.is(_SContinue | _SATerm) {
			p := pos

			// Zero or more Sp
			sp := p
			for {
				sp = previousIndex(_Sp, data[:sp])
				if sp < 0 {
					break
				}
				p = sp
			}

			// Zero or more Close
			close := p
			for {
				close = previousIndex(_Close, data[:close])
				if close < 0 {
					break
				}
				p = close
			}

			// Having looked back past Sp, Close, and intervening Extend|Format,
			// is there an SATerm?
			if previous(_SATerm, data[:p]) {
				pos += w
				continue
			}
		}

		// https://unicode.org/reports/tr29/#SB9
		if current.is(_Close | _Sp | _ParaSep) {
			p := pos

			// Zero or more Close's
			close := p
			for {
				close = previousIndex(_Close, data[:close])
				if close < 0 {
					break
				}
				p = close
			}

			// Having looked back past Close's and intervening Extend|Format,
			// is there an SATerm?
			if previous(_SATerm, data[:p]) {
				pos += w
				continue
			}
		}

		// https://unicode.org/reports/tr29/#SB10
		if current.is(_Sp | _ParaSep) {
			p := pos

			// Zero or more Sp's
			sp := p
			for {
				sp = previousIndex(_Sp, data[:sp])
				if sp < 0 {
					break
				}
				p = sp
			}

			// Zero or more Close's
			close := p
			for {
				close = previousIndex(_Close, data[:close])
				if close < 0 {
					break
				}
				p = close
			}

			// Having looked back past Sp's, Close's, and intervening Extend|Format,
			// is there an SATerm?
			if previous(_SATerm, data[:p]) {
				pos += w
				continue
			}
		}

		// https://unicode.org/reports/tr29/#SB11
		{
			p := pos

			// Zero or one Sp|ParaSep
			ps := previousIndex(_Sp|_ParaSep, data[:p])
			if ps >= 0 {
				p = ps
			}

			// Zero or more Sp's
			sp := p
			for {
				sp = previousIndex(_Sp, data[:sp])
				if sp < 0 {
					break
				}
				p = sp
			}

			// Zero or more Close's
			close := p
			for {
				close = previousIndex(_Close, data[:close])
				if close < 0 {
					break
				}
				p = close
			}

			// Having looked back past Sp|ParaSep, Sp's, Close's, and intervening Extend|Format,
			// is there an SATerm?
			if previous(_SATerm, data[:p]) {
				break
			}
		}

		// https://unicode.org/reports/tr29/#SB998
		pos += w
	}

	// Return token
	return pos, data[:pos], nil
}
<commit_msg>SB11 property should not include Sp<commit_after>// Package sentences provides a scanner for Unicode text segmentation sentence boundaries: https://unicode.org/reports/tr29/#Sentence_Boundaries
package sentences

import (
	"bufio"
	"fmt"
	"io"
)

// NewScanner tokenizes a reader into a stream of sentence tokens according to Unicode Text Segmentation sentence boundaries https://unicode.org/reports/tr29/#Sentence_Boundaries
// Iterate through the stream by calling Scan() until false.
//	text := "This is an example. And another!"
//	reader := strings.NewReader(text)
//
//	scanner := sentences.NewScanner(reader)
//	for scanner.Scan() {
//		fmt.Printf("%s\n", scanner.Text())
//	}
//	if err := scanner.Err(); err != nil {
//		log.Fatal(err)
//	}
func NewScanner(r io.Reader) *bufio.Scanner {
	scanner := bufio.NewScanner(r)
	scanner.Split(SplitFunc)
	return scanner
}

var trie = newSentencesTrie(0)

// is determines if lookup intersects propert(ies)
func (lookup property) is(properties property) bool {
	return (lookup & properties) != 0
}

var _SATerm = _STerm | _ATerm
var _ParaSep = _Sep | _CR | _LF
var _Ignore = _Extend | _Format

// SplitFunc is a bufio.SplitFunc implementation of sentence segmentation, for use with bufio.Scanner
func SplitFunc(data []byte, atEOF bool) (advance int, token []byte, err error) {
	if atEOF && len(data) == 0 {
		return 0, nil, nil
	}

	// These vars are stateful across loop iterations
	var pos, w int
	var current property

	for {
		if pos == len(data) && !atEOF {
			// Request more data
			return 0, nil, nil
		}

		sot := pos == 0 // "start of text"
		eof := len(data) == pos

		// https://unicode.org/reports/tr29/#SB1
		if sot && !eof {
			current, w = trie.lookup(data[pos:])
			pos += w
			continue
		}

		// https://unicode.org/reports/tr29/#SB2
		if eof {
			break
		}

		// Rules are usually of the form Cat1 × Cat2; "current" refers to the first property
		// to the right of the ×, from which we look back or forward

		last := current

		current, w = trie.lookup(data[pos:])
		if w == 0 {
			return 0, nil, fmt.Errorf("error decoding rune at byte 0x%x", data[pos])
		}

		// https://unicode.org/reports/tr29/#SB3
		if current.is(_LF) && last.is(_CR) {
			pos += w
			continue
		}

		// https://unicode.org/reports/tr29/#SB4
		if last.is(_ParaSep) {
			break
		}

		// https://unicode.org/reports/tr29/#SB5
		if current.is(_Extend | _Format) {
			pos += w
			continue
		}

		// SB5 applies to subsequent rules; there is an implied "ignoring Extend & Format"
		// https://unicode.org/reports/tr29/#Grapheme_Cluster_and_Format_Rules
		// The previous/subsequent methods are shorthand for "seek a property but skip over Extend & Format on the way"

		// https://unicode.org/reports/tr29/#SB6
		if current.is(_Numeric) && previous(_ATerm, data[:pos]) {
			pos += w
			continue
		}

		// https://unicode.org/reports/tr29/#SB7
		if current.is(_Upper) {
			pi := previousIndex(_ATerm, data[:pos])
			if pi >= 0 && previous(_Upper|_Lower, data[:pi]) {
				pos += w
				continue
			}
		}

		// https://unicode.org/reports/tr29/#SB8
		{
			p := pos

			// ( ¬(OLetter | Upper | Lower | ParaSep | SATerm) )*
			// Zero or more of not-the-above properties
			for p < len(data) {
				lookup, w := trie.lookup(data[p:])

				if !lookup.is(_OLetter | _Upper | _Lower | _ParaSep | _SATerm) {
					p += w
					continue
				}
				break
			}

			if subsequent(_Lower, data[p:]) {
				p2 := pos

				// Zero or more Sp
				sp := pos
				for {
					sp = previousIndex(_Sp, data[:sp])
					if sp < 0 {
						break
					}
					p2 = sp
				}

				// Zero or more Close
				close := p2
				for {
					close = previousIndex(_Close, data[:close])
					if close < 0 {
						break
					}
					p2 = close
				}

				// Having looked back past Sp's, Close's, and intervening Extend|Format,
				// is there an ATerm?
				if previous(_ATerm, data[:p2]) {
					pos += w
					continue
				}
			}
		}

		// https://unicode.org/reports/tr29/#SB8a
		if current.is(_SContinue | _SATerm) {
			p := pos

			// Zero or more Sp
			sp := p
			for {
				sp = previousIndex(_Sp, data[:sp])
				if sp < 0 {
					break
				}
				p = sp
			}

			// Zero or more Close
			close := p
			for {
				close = previousIndex(_Close, data[:close])
				if close < 0 {
					break
				}
				p = close
			}

			// Having looked back past Sp, Close, and intervening Extend|Format,
			// is there an SATerm?
			if previous(_SATerm, data[:p]) {
				pos += w
				continue
			}
		}

		// https://unicode.org/reports/tr29/#SB9
		if current.is(_Close | _Sp | _ParaSep) {
			p := pos

			// Zero or more Close's
			close := p
			for {
				close = previousIndex(_Close, data[:close])
				if close < 0 {
					break
				}
				p = close
			}

			// Having looked back past Close's and intervening Extend|Format,
			// is there an SATerm?
			if previous(_SATerm, data[:p]) {
				pos += w
				continue
			}
		}

		// https://unicode.org/reports/tr29/#SB10
		if current.is(_Sp | _ParaSep) {
			p := pos

			// Zero or more Sp's
			sp := p
			for {
				sp = previousIndex(_Sp, data[:sp])
				if sp < 0 {
					break
				}
				p = sp
			}

			// Zero or more Close's
			close := p
			for {
				close = previousIndex(_Close, data[:close])
				if close < 0 {
					break
				}
				p = close
			}

			// Having looked back past Sp's, Close's, and intervening Extend|Format,
			// is there an SATerm?
			if previous(_SATerm, data[:p]) {
				pos += w
				continue
			}
		}

		// https://unicode.org/reports/tr29/#SB11
		{
			p := pos

			// Zero or one ParaSep
			ps := previousIndex(_ParaSep, data[:p])
			if ps >= 0 {
				p = ps
			}

			// Zero or more Sp's
			sp := p
			for {
				sp = previousIndex(_Sp, data[:sp])
				if sp < 0 {
					break
				}
				p = sp
			}

			// Zero or more Close's
			close := p
			for {
				close = previousIndex(_Close, data[:close])
				if close < 0 {
					break
				}
				p = close
			}

			// Having looked back past ParaSep, Sp's, Close's, and intervening Extend|Format,
			// is there an SATerm?
			if previous(_SATerm, data[:p]) {
				break
			}
		}

		// https://unicode.org/reports/tr29/#SB998
		pos += w
	}

	// Return token
	return pos, data[:pos], nil
}
<|endoftext|>


<commit_before>package server

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"

	"github.com/bouk/httprouter"
	"github.com/influxdata/chronograf"
)

type dashboardLinks struct {
	Self string `json:"self"` // Self link mapping to this resource
}

type dashboardResponse struct {
	chronograf.Dashboard
	Links dashboardLinks `json:"links"`
}

type getDashboardsResponse struct {
	Dashboards []dashboardResponse `json:"dashboards"`
}

func newDashboardResponse(d chronograf.Dashboard) dashboardResponse {
	base := "/chronograf/v1/dashboards"
	return dashboardResponse{
		Dashboard: d,
		Links: dashboardLinks{
			Self: fmt.Sprintf("%s/%d", base, d.ID),
		},
	}
}

// Dashboards returns all dashboards within the store
func (s *Service) Dashboards(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	dashboards, err := s.DashboardsStore.All(ctx)
	if err != nil {
		Error(w, http.StatusInternalServerError, "Error loading layouts", s.Logger)
		return
	}

	res := getDashboardsResponse{
		Dashboards: []dashboardResponse{},
	}

	for _, dashboard := range dashboards {
		res.Dashboards = append(res.Dashboards, newDashboardResponse(dashboard))
	}

	encodeJSON(w, http.StatusOK, res, s.Logger)
}

// DashboardID returns a single specified dashboard
func (s *Service) DashboardID(w http.ResponseWriter, r *http.Request) {
	id, err := paramID("id", r)
	if err != nil {
		Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
		return
	}

	ctx := r.Context()
	e, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id))
	if err != nil {
		notFound(w, id, s.Logger)
		return
	}

	res := newDashboardResponse(*e)
	encodeJSON(w, http.StatusOK, res, s.Logger)
}

// NewDashboard creates and returns a new dashboard object
func (s *Service) NewDashboard(w http.ResponseWriter, r *http.Request) {
	var dashboard *chronograf.Dashboard
	if err := json.NewDecoder(r.Body).Decode(&dashboard); err != nil {
		invalidJSON(w, s.Logger)
		return
	}

	var err error
	if dashboard, err = s.DashboardsStore.Add(r.Context(), dashboard); err != nil {
		msg := fmt.Errorf("Error storing layout %v: %v", dashboard, err)
		unknownErrorWithMessage(w, msg, s.Logger)
		return
	}

	res := newDashboardResponse(*dashboard)
	w.Header().Add("Location", res.Links.Self)
	encodeJSON(w, http.StatusCreated, res, s.Logger)
}

// RemoveDashboard deletes a dashboard
func (s *Service) RemoveDashboard(w http.ResponseWriter, r *http.Request) {
	id, err := paramID("id", r)
	if err != nil {
		Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
		return
	}

	ctx := r.Context()
	e, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id))
	if err != nil {
		notFound(w, id, s.Logger)
		return
	}

	if err := s.DashboardsStore.Delete(ctx, e); err != nil {
		unknownErrorWithMessage(w, err, s.Logger)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

// UpdateDashboard replaces a dashboard
func (s *Service) UpdateDashboard(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	idParam, err := strconv.Atoi(httprouter.GetParamFromContext(ctx, "id"))
	if err != nil {
		msg := fmt.Sprintf("Could not parse dashboard ID: %s", err)
		Error(w, http.StatusInternalServerError, msg, s.Logger)
	}
	id := chronograf.DashboardID(idParam)

	_, err = s.DashboardsStore.Get(ctx, id)
	if err != nil {
		Error(w, http.StatusNotFound, fmt.Sprintf("ID %s not found", id), s.Logger)
		return
	}

	var req *chronograf.Dashboard
	if err := json.NewDecoder(r.Body).Decode(req); err != nil {
		invalidJSON(w, s.Logger)
		return
	}
	req.ID = id

	if err := s.DashboardsStore.Update(ctx, req); err != nil {
		msg := fmt.Sprintf("Error updating dashboard ID %s: %v", id, err)
		Error(w, http.StatusInternalServerError, msg, s.Logger)
		return
	}

	res := newDashboardResponse(*req)
	encodeJSON(w, http.StatusOK, res, s.Logger)
}
<commit_msg>wording<commit_after>package server

import (
	"encoding/json"
	"fmt"
	"net/http"
	"strconv"

	"github.com/bouk/httprouter"
	"github.com/influxdata/chronograf"
)

type dashboardLinks struct {
	Self string `json:"self"` // Self link mapping to this resource
}

type dashboardResponse struct {
	chronograf.Dashboard
	Links dashboardLinks `json:"links"`
}

type getDashboardsResponse struct {
	Dashboards []dashboardResponse `json:"dashboards"`
}

func newDashboardResponse(d chronograf.Dashboard) dashboardResponse {
	base := "/chronograf/v1/dashboards"
	return dashboardResponse{
		Dashboard: d,
		Links: dashboardLinks{
			Self: fmt.Sprintf("%s/%d", base, d.ID),
		},
	}
}

// Dashboards returns all dashboards within the store
func (s *Service) Dashboards(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	dashboards, err := s.DashboardsStore.All(ctx)
	if err != nil {
		Error(w, http.StatusInternalServerError, "Error loading dashboards", s.Logger)
		return
	}

	res := getDashboardsResponse{
		Dashboards: []dashboardResponse{},
	}

	for _, dashboard := range dashboards {
		res.Dashboards = append(res.Dashboards, newDashboardResponse(dashboard))
	}

	encodeJSON(w, http.StatusOK, res, s.Logger)
}

// DashboardID returns a single specified dashboard
func (s *Service) DashboardID(w http.ResponseWriter, r *http.Request) {
	id, err := paramID("id", r)
	if err != nil {
		Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
		return
	}

	ctx := r.Context()
	e, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id))
	if err != nil {
		notFound(w, id, s.Logger)
		return
	}

	res := newDashboardResponse(*e)
	encodeJSON(w, http.StatusOK, res, s.Logger)
}

// NewDashboard creates and returns a new dashboard object
func (s *Service) NewDashboard(w http.ResponseWriter, r *http.Request) {
	var dashboard *chronograf.Dashboard
	if err := json.NewDecoder(r.Body).Decode(&dashboard); err != nil {
		invalidJSON(w, s.Logger)
		return
	}

	var err error
	if dashboard, err = s.DashboardsStore.Add(r.Context(), dashboard); err != nil {
		msg := fmt.Errorf("Error storing dashboard %v: %v", dashboard, err)
		unknownErrorWithMessage(w, msg, s.Logger)
		return
	}

	res := newDashboardResponse(*dashboard)
	w.Header().Add("Location", res.Links.Self)
	encodeJSON(w, http.StatusCreated, res, s.Logger)
}

// RemoveDashboard deletes a dashboard
func (s *Service) RemoveDashboard(w http.ResponseWriter, r *http.Request) {
	id, err := paramID("id", r)
	if err != nil {
		Error(w, http.StatusUnprocessableEntity, err.Error(), s.Logger)
		return
	}

	ctx := r.Context()
	e, err := s.DashboardsStore.Get(ctx, chronograf.DashboardID(id))
	if err != nil {
		notFound(w, id, s.Logger)
		return
	}

	if err := s.DashboardsStore.Delete(ctx, e); err != nil {
		unknownErrorWithMessage(w, err, s.Logger)
		return
	}
	w.WriteHeader(http.StatusNoContent)
}

// UpdateDashboard replaces a dashboard
func (s *Service) UpdateDashboard(w http.ResponseWriter, r *http.Request) {
	ctx := r.Context()
	idParam, err := strconv.Atoi(httprouter.GetParamFromContext(ctx, "id"))
	if err != nil {
		msg := fmt.Sprintf("Could not parse dashboard ID: %s", err)
		Error(w, http.StatusInternalServerError, msg, s.Logger)
	}
	id := chronograf.DashboardID(idParam)

	_, err = s.DashboardsStore.Get(ctx, id)
	if err != nil {
		Error(w, http.StatusNotFound, fmt.Sprintf("ID %s not found", id), s.Logger)
		return
	}

	var req *chronograf.Dashboard
	if err := json.NewDecoder(r.Body).Decode(req); err != nil {
		invalidJSON(w, s.Logger)
		return
	}
	req.ID = id

	if err := s.DashboardsStore.Update(ctx, req); err != nil {
		msg := fmt.Sprintf("Error updating dashboard ID %s: %v", id, err)
		Error(w, http.StatusInternalServerError, msg, s.Logger)
		return
	}

	res := newDashboardResponse(*req)
	encodeJSON(w, http.StatusOK, res, s.Logger)
}
<|endoftext|>


<commit_before>package registry

import (
	"net/http"

	"github.com/cozy/cozy-stack/pkg/permissions"
	"github.com/cozy/cozy-stack/pkg/registry"
	"github.com/cozy/cozy-stack/web/middlewares"
	"github.com/cozy/echo"
)

type authType int

const (
	authed authType = iota
	perms
)

func proxyReq(auth authType, clientPermanentCache bool, proxyCacheControl registry.CacheControl) echo.HandlerFunc {
	return func(c echo.Context) error {
		i := middlewares.GetInstance(c)
		switch auth {
		case authed:
			if !middlewares.IsLoggedIn(c) {
				return echo.NewHTTPError(http.StatusForbidden)
			}
		case perms:
			pdoc, err := middlewares.GetPermission(c)
			if err != nil || pdoc.Type != permissions.TypeWebapp {
				return echo.NewHTTPError(http.StatusForbidden)
			}
		default:
			panic("unknown authType")
		}
		req := c.Request()
		proxyResp, err := registry.Proxy(req, i.Registries(), proxyCacheControl)
		if err != nil {
			return err
		}
		defer proxyResp.Body.Close()
		if clientPermanentCache {
			c.Response().Header().Set("Cache-Control", "max-age=31536000, immutable")
		}
		contentType := proxyResp.Header.Get("content-type")
		return c.Stream(proxyResp.StatusCode, contentType, proxyResp.Body)
	}
}

func proxyListReq(c echo.Context) error {
	i := middlewares.GetInstance(c)
	pdoc, err := middlewares.GetPermission(c)
	if err != nil || pdoc.Type != permissions.TypeWebapp {
		return echo.NewHTTPError(http.StatusForbidden)
	}
	req := c.Request()
	list, err := registry.ProxyList(req, i.Registries())
	if err != nil {
		return err
	}
	return c.JSON(http.StatusOK, list)
}

// Routes sets the routing for the registry
func Routes(router *echo.Group) {
	router.GET("", proxyListReq)
	router.GET("/", proxyListReq)
	router.GET("/:app", proxyReq(perms, false, registry.WithCache))
	router.GET("/:app/icon", proxyReq(authed, false, registry.NoCache))
	router.GET("/:app/partnership_icon", proxyReq(authed, false, registry.NoCache))
	router.GET("/:app/screenshots/*", proxyReq(authed, false, registry.NoCache))
	router.GET("/:app/:version/icon", proxyReq(authed, true, registry.NoCache))
	router.GET("/:app/:version/partnership_icon", proxyReq(authed, true, registry.NoCache))
	router.GET("/:app/:version/screenshots/*", proxyReq(authed, true, registry.NoCache))
	router.GET("/:app/:version", proxyReq(perms, true, registry.WithCache))
	router.GET("/:app/:channel/latest", proxyReq(perms, false, registry.WithCache))
}
<commit_msg>Gzip the responses for /registry (#1763)<commit_after>package registry

import (
	"net/http"

	"github.com/cozy/cozy-stack/pkg/permissions"
	"github.com/cozy/cozy-stack/pkg/registry"
	"github.com/cozy/cozy-stack/web/middlewares"
	"github.com/cozy/echo"
	"github.com/cozy/echo/middleware"
)

type authType int

const (
	authed authType = iota
	perms
)

func proxyReq(auth authType, clientPermanentCache bool, proxyCacheControl registry.CacheControl) echo.HandlerFunc {
	return func(c echo.Context) error {
		i := middlewares.GetInstance(c)
		switch auth {
		case authed:
			if !middlewares.IsLoggedIn(c) {
				return echo.NewHTTPError(http.StatusForbidden)
			}
		case perms:
			pdoc, err := middlewares.GetPermission(c)
			if err != nil || pdoc.Type != permissions.TypeWebapp {
				return echo.NewHTTPError(http.StatusForbidden)
			}
		default:
			panic("unknown authType")
		}
		req := c.Request()
		proxyResp, err := registry.Proxy(req, i.Registries(), proxyCacheControl)
		if err != nil {
			return err
		}
		defer proxyResp.Body.Close()
		if clientPermanentCache {
			c.Response().Header().Set("Cache-Control", "max-age=31536000, immutable")
		}
		contentType := proxyResp.Header.Get("content-type")
		return c.Stream(proxyResp.StatusCode, contentType, proxyResp.Body)
	}
}

func proxyListReq(c echo.Context) error {
	i := middlewares.GetInstance(c)
	pdoc, err := middlewares.GetPermission(c)
	if err != nil || pdoc.Type != permissions.TypeWebapp {
		return echo.NewHTTPError(http.StatusForbidden)
	}
	req := c.Request()
	list, err := registry.ProxyList(req, i.Registries())
	if err != nil {
		return err
	}
	return c.JSON(http.StatusOK, list)
}

// Routes sets the routing for the registry
func Routes(router *echo.Group) {
	gzip := middleware.Gzip()
	router.GET("", proxyListReq, gzip)
	router.GET("/", proxyListReq, gzip)
	router.GET("/:app", proxyReq(perms, false, registry.WithCache))
	router.GET("/:app/icon", proxyReq(authed, false, registry.NoCache))
	router.GET("/:app/partnership_icon", proxyReq(authed, false, registry.NoCache))
	router.GET("/:app/screenshots/*", proxyReq(authed, false, registry.NoCache))
	router.GET("/:app/:version/icon", proxyReq(authed, true, registry.NoCache))
	router.GET("/:app/:version/partnership_icon", proxyReq(authed, true, registry.NoCache))
	router.GET("/:app/:version/screenshots/*", proxyReq(authed, true, registry.NoCache))
	router.GET("/:app/:version", proxyReq(perms, true, registry.WithCache))
	router.GET("/:app/:channel/latest", proxyReq(perms, false, registry.WithCache))
}
<|endoftext|>


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

package gosym

import (
	"debug/elf"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"testing"
)

var pclinetestBinary string

func dotest() bool {
	// For now, only works on ELF platforms.
	if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
		return false
	}
	if pclinetestBinary != "" {
		return true
	}
	// This command builds pclinetest from pclinetest.asm;
	// the resulting binary looks like it was built from pclinetest.s,
	// but we have renamed it to keep it away from the go tool.
	pclinetestBinary = os.TempDir() + "/pclinetest"
	cmd := exec.Command("sh", "-c", "go tool 6a pclinetest.asm && go tool 6l -E main -o "+pclinetestBinary+" pclinetest.6")
	if err := cmd.Run(); err != nil {
		panic(err)
	}
	return true
}

func getTable(t *testing.T) *Table {
	f, tab := crack(os.Args[0], t)
	f.Close()
	return tab
}

func crack(file string, t *testing.T) (*elf.File, *Table) {
	// Open self
	f, err := elf.Open(file)
	if err != nil {
		t.Fatal(err)
	}
	return parse(file, f, t)
}

func parse(file string, f *elf.File, t *testing.T) (*elf.File, *Table) {
	symdat, err := f.Section(".gosymtab").Data()
	if err != nil {
		f.Close()
		t.Fatalf("reading %s gosymtab: %v", file, err)
	}
	pclndat, err := f.Section(".gopclntab").Data()
	if err != nil {
		f.Close()
		t.Fatalf("reading %s gopclntab: %v", file, err)
	}

	pcln := NewLineTable(pclndat, f.Section(".text").Addr)
	tab, err := NewTable(symdat, pcln)
	if err != nil {
		f.Close()
		t.Fatalf("parsing %s gosymtab: %v", file, err)
	}

	return f, tab
}

var goarch = os.Getenv("O")

func TestLineFromAline(t *testing.T) {
	if !dotest() {
		return
	}

	tab := getTable(t)

	// Find the sym package
	pkg := tab.LookupFunc("debug/gosym.TestLineFromAline").Obj
	if pkg == nil {
		t.Fatalf("nil pkg")
	}

	// Walk every absolute line and ensure that we hit every
	// source line monotonically
	lastline := make(map[string]int)
	final := -1
	for i := 0; i < 10000; i++ {
		path, line := pkg.lineFromAline(i)
		// Check for end of object
		if path == "" {
			if final == -1 {
				final = i - 1
			}
			continue
		} else if final != -1 {
			t.Fatalf("reached end of package at absolute line %d, but absolute line %d mapped to %s:%d", final, i, path, line)
		}
		// It's okay to see files multiple times (e.g., sys.a)
		if line == 1 {
			lastline[path] = 1
			continue
		}
		// Check that the is the next line in path
		ll, ok := lastline[path]
		if !ok {
			t.Errorf("file %s starts on line %d", path, line)
		} else if line != ll+1 {
			t.Errorf("expected next line of file %s to be %d, got %d", path, ll+1, line)
		}
		lastline[path] = line
	}
	if final == -1 {
		t.Errorf("never reached end of object")
	}
}

func TestLineAline(t *testing.T) {
	if !dotest() {
		return
	}

	tab := getTable(t)

	for _, o := range tab.Files {
		// A source file can appear multiple times in a
		// object.  alineFromLine will always return alines in
		// the first file, so track which lines we've seen.
		found := make(map[string]int)
		for i := 0; i < 1000; i++ {
			path, line := o.lineFromAline(i)
			if path == "" {
				break
			}

			// cgo files are full of 'Z' symbols, which we don't handle
			if len(path) > 4 && path[len(path)-4:] == ".cgo" {
				continue
			}

			if minline, ok := found[path]; path != "" && ok {
				if minline >= line {
					// We've already covered this file
					continue
				}
			}
			found[path] = line

			a, err := o.alineFromLine(path, line)
			if err != nil {
				t.Errorf("absolute line %d in object %s maps to %s:%d, but mapping that back gives error %s", i, o.Paths[0].Name, path, line, err)
			} else if a != i {
				t.Errorf("absolute line %d in object %s maps to %s:%d, which maps back to absolute line %d\n", i, o.Paths[0].Name, path, line, a)
			}
		}
	}
}

func TestPCLine(t *testing.T) {
	if !dotest() {
		return
	}

	f, tab := crack(pclinetestBinary, t)
	text := f.Section(".text")
	textdat, err := text.Data()
	if err != nil {
		t.Fatalf("reading .text: %v", err)
	}

	// Test PCToLine
	sym := tab.LookupFunc("linefrompc")
	wantLine := 0
	for pc := sym.Entry; pc < sym.End; pc++ {
		file, line, fn := tab.PCToLine(pc)
		off := pc - text.Addr // TODO(rsc): should not need off; bug in 8g
		wantLine += int(textdat[off])
		t.Logf("off is %d", off)
		if fn == nil {
			t.Errorf("failed to get line of PC %#x", pc)
		} else if !strings.HasSuffix(file, "pclinetest.s") {
			t.Errorf("expected %s (%s) at PC %#x, got %s (%s)", "pclinetest.s", sym.Name, pc, file, fn.Name)
		} else if line != wantLine || fn != sym {
			t.Errorf("expected :%d (%s) at PC %#x, got :%d (%s)", wantLine, sym.Name, pc, line, fn.Name)
		}
	}

	// Test LineToPC
	sym = tab.LookupFunc("pcfromline")
	lookupline := -1
	wantLine = 0
	off := uint64(0) // TODO(rsc): should not need off; bug in 8g
	for pc := sym.Value; pc < sym.End; pc += 2 + uint64(textdat[off]) {
		file, line, fn := tab.PCToLine(pc)
		off = pc - text.Addr
		wantLine += int(textdat[off])
		if line != wantLine {
			t.Errorf("expected line %d at PC %#x in pcfromline, got %d", wantLine, pc, line)
			off = pc + 1 - text.Addr
			continue
		}
		if lookupline == -1 {
			lookupline = line
		}
		for ; lookupline <= line; lookupline++ {
			pc2, fn2, err := tab.LineToPC(file, lookupline)
			if lookupline != line {
				// Should be nothing on this line
				if err == nil {
					t.Errorf("expected no PC at line %d, got %#x (%s)", lookupline, pc2, fn2.Name)
				}
			} else if err != nil {
				t.Errorf("failed to get PC of line %d: %s", lookupline, err)
			} else if pc != pc2 {
				t.Errorf("expected PC %#x (%s) at line %d, got PC %#x (%s)", pc, fn.Name, line, pc2, fn2.Name)
			}
		}
		off = pc + 1 - text.Addr
	}
}
<commit_msg>debug/gosym: dump 6a/6l output to process stdout/stderr so we can see failures.<commit_after>// Copyright 2009 The Go Authors.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package gosym

import (
	"debug/elf"
	"os"
	"os/exec"
	"runtime"
	"strings"
	"testing"
)

var pclinetestBinary string

func dotest() bool {
	// For now, only works on ELF platforms.
	if runtime.GOOS != "linux" || runtime.GOARCH != "amd64" {
		return false
	}
	if pclinetestBinary != "" {
		return true
	}
	// This command builds pclinetest from pclinetest.asm;
	// the resulting binary looks like it was built from pclinetest.s,
	// but we have renamed it to keep it away from the go tool.
	pclinetestBinary = os.TempDir() + "/pclinetest"
	cmd := exec.Command("sh", "-c", "go tool 6a pclinetest.asm && go tool 6l -E main -o "+pclinetestBinary+" pclinetest.6")
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	if err := cmd.Run(); err != nil {
		panic(err)
	}
	return true
}

func getTable(t *testing.T) *Table {
	f, tab := crack(os.Args[0], t)
	f.Close()
	return tab
}

func crack(file string, t *testing.T) (*elf.File, *Table) {
	// Open self
	f, err := elf.Open(file)
	if err != nil {
		t.Fatal(err)
	}
	return parse(file, f, t)
}

func parse(file string, f *elf.File, t *testing.T) (*elf.File, *Table) {
	symdat, err := f.Section(".gosymtab").Data()
	if err != nil {
		f.Close()
		t.Fatalf("reading %s gosymtab: %v", file, err)
	}
	pclndat, err := f.Section(".gopclntab").Data()
	if err != nil {
		f.Close()
		t.Fatalf("reading %s gopclntab: %v", file, err)
	}

	pcln := NewLineTable(pclndat, f.Section(".text").Addr)
	tab, err := NewTable(symdat, pcln)
	if err != nil {
		f.Close()
		t.Fatalf("parsing %s gosymtab: %v", file, err)
	}

	return f, tab
}

var goarch = os.Getenv("O")

func TestLineFromAline(t *testing.T) {
	if !dotest() {
		return
	}

	tab := getTable(t)

	// Find the sym package
	pkg := tab.LookupFunc("debug/gosym.TestLineFromAline").Obj
	if pkg == nil {
		t.Fatalf("nil pkg")
	}

	// Walk every absolute line and ensure that we hit every
	// source line monotonically
	lastline := make(map[string]int)
	final := -1
	for i := 0; i < 10000; i++ {
		path, line := pkg.lineFromAline(i)
		// Check for end of object
		if path == "" {
			if final == -1 {
				final = i - 1
			}
			continue
		} else if final != -1 {
			t.Fatalf("reached end of package at absolute line %d, but absolute line %d mapped to %s:%d", final, i, path, line)
		}
		// It's okay to see files multiple times (e.g., sys.a)
		if line == 1 {
			lastline[path] = 1
			continue
		}
		// Check that the is the next line in path
		ll, ok := lastline[path]
		if !ok {
			t.Errorf("file %s starts on line %d", path, line)
		} else if line != ll+1 {
			t.Errorf("expected next line of file %s to be %d, got %d", path, ll+1, line)
		}
		lastline[path] = line
	}
	if final == -1 {
		t.Errorf("never reached end of object")
	}
}

func TestLineAline(t *testing.T) {
	if !dotest() {
		return
	}

	tab := getTable(t)

	for _, o := range tab.Files {
		// A source file can appear multiple times in a
		// object.  alineFromLine will always return alines in
		// the first file, so track which lines we've seen.
		found := make(map[string]int)
		for i := 0; i < 1000; i++ {
			path, line := o.lineFromAline(i)
			if path == "" {
				break
			}

			// cgo files are full of 'Z' symbols, which we don't handle
			if len(path) > 4 && path[len(path)-4:] == ".cgo" {
				continue
			}

			if minline, ok := found[path]; path != "" && ok {
				if minline >= line {
					// We've already covered this file
					continue
				}
			}
			found[path] = line

			a, err := o.alineFromLine(path, line)
			if err != nil {
				t.Errorf("absolute line %d in object %s maps to %s:%d, but mapping that back gives error %s", i, o.Paths[0].Name, path, line, err)
			} else if a != i {
				t.Errorf("absolute line %d in object %s maps to %s:%d, which maps back to absolute line %d\n", i, o.Paths[0].Name, path, line, a)
			}
		}
	}
}

func TestPCLine(t *testing.T) {
	if !dotest() {
		return
	}

	f, tab := crack(pclinetestBinary, t)
	text := f.Section(".text")
	textdat, err := text.Data()
	if err != nil {
		t.Fatalf("reading .text: %v", err)
	}

	// Test PCToLine
	sym := tab.LookupFunc("linefrompc")
	wantLine := 0
	for pc := sym.Entry; pc < sym.End; pc++ {
		file, line, fn := tab.PCToLine(pc)
		off := pc - text.Addr // TODO(rsc): should not need off; bug in 8g
		wantLine += int(textdat[off])
		t.Logf("off is %d", off)
		if fn == nil {
			t.Errorf("failed to get line of PC %#x", pc)
		} else if !strings.HasSuffix(file, "pclinetest.s") {
			t.Errorf("expected %s (%s) at PC %#x, got %s (%s)", "pclinetest.s", sym.Name, pc, file, fn.Name)
		} else if line != wantLine || fn != sym {
			t.Errorf("expected :%d (%s) at PC %#x, got :%d (%s)", wantLine, sym.Name, pc, line, fn.Name)
		}
	}

	// Test LineToPC
	sym = tab.LookupFunc("pcfromline")
	lookupline := -1
	wantLine = 0
	off := uint64(0) // TODO(rsc): should not need off; bug in 8g
	for pc := sym.Value; pc < sym.End; pc += 2 + uint64(textdat[off]) {
		file, line, fn := tab.PCToLine(pc)
		off = pc - text.Addr
		wantLine += int(textdat[off])
		if line != wantLine {
			t.Errorf("expected line %d at PC %#x in pcfromline, got %d", wantLine, pc, line)
			off = pc + 1 - text.Addr
			continue
		}
		if lookupline == -1 {
			lookupline = line
		}
		for ; lookupline <= line; lookupline++ {
			pc2, fn2, err := tab.LineToPC(file, lookupline)
			if lookupline != line {
				// Should be nothing on this line
				if err == nil {
					t.Errorf("expected no PC at line %d, got %#x (%s)", lookupline, pc2, fn2.Name)
				}
			} else if err != nil {
				t.Errorf("failed to get PC of line %d: %s", lookupline, err)
			} else if pc != pc2 {
				t.Errorf("expected PC %#x (%s) at line %d, got PC %#x (%s)", pc, fn.Name, line, pc2, fn2.Name)
			}
		}
		off = pc + 1 - text.Addr
	}
}
<|endoftext|>


<commit_before>package server

import (
	"fmt"

	"go.pachyderm.com/pachyderm/src/pkg/deploy"
	"golang.org/x/net/context"

	"go.pedge.io/google-protobuf"
	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/api/unversioned"
	client "k8s.io/kubernetes/pkg/client/unversioned"
)

var (
	emptyInstance = &google_protobuf.Empty{}
	pfsdImage     = "gcr.io/pachyderm-dev-us1/pfsd:3f63846"
)

type apiServer struct {
	client *client.Client
}

func newAPIServer(client *client.Client) APIServer {
	return &apiServer{client}
}

func (a *apiServer) CreateCluster(ctx context.Context, request *deploy.CreateClusterRequest) (*google_protobuf.Empty, error) {
	_, err := a.client.ReplicationControllers(api.NamespaceDefault).Create(
		pfsReplicationController(
			request.Cluster.Name,
			request.Nodes,
			request.Shards,
			request.Replicas,
		),
	)
	if err != nil {
		return nil, err
	}
	return emptyInstance, nil
}

func (a *apiServer) UpdateCluster(ctx context.Context, request *deploy.UpdateClusterRequest) (*google_protobuf.Empty, error) {
	return emptyInstance, nil
}

func (a *apiServer) InspectCluster(ctx context.Context, request *deploy.InspectClusterRequest) (*deploy.ClusterInfo, error) {
	return nil, nil
}

func (a *apiServer) ListCluster(ctx context.Context, request *deploy.ListClusterRequest) (*deploy.ClusterInfos, error) {
	return nil, nil
}

func (a *apiServer) DeleteCluster(ctx context.Context, request *deploy.DeleteClusterRequest) (*google_protobuf.Empty, error) {
	return emptyInstance, nil
}

func pfsReplicationController(name string, nodes uint64, shards uint64, replicas uint64) *api.ReplicationController {
	app := fmt.Sprintf("pfsd-%s", name)
	return &api.ReplicationController{
		unversioned.TypeMeta{
			Kind:       "ReplicationController",
			APIVersion: "v1",
		},
		api.ObjectMeta{
			Name: fmt.Sprintf("pfsd-rc-%s", name),
			Labels: map[string]string{
				"app": app,
			},
		},
		api.ReplicationControllerSpec{
			Replicas: int(nodes),
			Selector: map[string]string{
				"app": app,
			},
			Template: &api.PodTemplateSpec{
				api.ObjectMeta{
					Name: fmt.Sprintf("pfsd-%s", name),
					Labels: map[string]string{
						"app": app,
					},
				},
				api.PodSpec{
					Containers: []api.Container{
						{
							Name:  "pfsd",
							Image: pfsdImage,
							Env: []api.EnvVar{
								{
									Name:  "PFS_DRIVER_ROOT",
									Value: "/pfs/btrfs",
								},
							},
							Ports: []api.ContainerPort{
								{
									ContainerPort: 650,
									Name:          "api-grpc-port",
								},
								{
									ContainerPort: 750,
									Name:          "api-http-port",
								},
								{
									ContainerPort: 1050,
									Name:          "trace-port",
								},
							},
							VolumeMounts: []api.VolumeMount{
								{
									Name:      "pfs-disk",
									MountPath: "/pfs/btrfs",
								},
							},
						},
					},
					Volumes: []api.Volume{
						{
							"pfs-disk",
							api.VolumeSource{
								GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{
									PDName: "pch-pfs",
									FSType: "btrfs",
								},
							},
						},
					},
				},
			},
		},
		api.ReplicationControllerStatus{},
	}
}
<commit_msg>Update pfsd image.<commit_after>package server

import (
	"fmt"

	"go.pachyderm.com/pachyderm/src/pkg/deploy"
	"golang.org/x/net/context"

	"go.pedge.io/google-protobuf"
	"k8s.io/kubernetes/pkg/api"
	"k8s.io/kubernetes/pkg/api/unversioned"
	client "k8s.io/kubernetes/pkg/client/unversioned"
)

var (
	emptyInstance = &google_protobuf.Empty{}
	pfsdImage     = "pachyderm/pfsd"
)

type apiServer struct {
	client *client.Client
}

func newAPIServer(client *client.Client) APIServer {
	return &apiServer{client}
}

func (a *apiServer) CreateCluster(ctx context.Context, request *deploy.CreateClusterRequest) (*google_protobuf.Empty, error) {
	_, err := a.client.ReplicationControllers(api.NamespaceDefault).Create(
		pfsReplicationController(
			request.Cluster.Name,
			request.Nodes,
			request.Shards,
			request.Replicas,
		),
	)
	if err != nil {
		return nil, err
	}
	return emptyInstance, nil
}

func (a *apiServer) UpdateCluster(ctx context.Context, request *deploy.UpdateClusterRequest) (*google_protobuf.Empty, error) {
	return emptyInstance, nil
}

func (a *apiServer) InspectCluster(ctx context.Context, request *deploy.InspectClusterRequest) (*deploy.ClusterInfo, error) {
	return nil, nil
}

func (a *apiServer) ListCluster(ctx context.Context, request *deploy.ListClusterRequest) (*deploy.ClusterInfos, error) {
	return nil, nil
}

func (a *apiServer) DeleteCluster(ctx context.Context, request *deploy.DeleteClusterRequest) (*google_protobuf.Empty, error) {
	return emptyInstance, nil
}

func pfsReplicationController(name string, nodes uint64, shards uint64, replicas uint64) *api.ReplicationController {
	app := fmt.Sprintf("pfsd-%s", name)
	return &api.ReplicationController{
		unversioned.TypeMeta{
			Kind:       "ReplicationController",
			APIVersion: "v1",
		},
		api.ObjectMeta{
			Name: fmt.Sprintf("pfsd-rc-%s", name),
			Labels: map[string]string{
				"app": app,
			},
		},
		api.ReplicationControllerSpec{
			Replicas: int(nodes),
			Selector: map[string]string{
				"app": app,
			},
			Template: &api.PodTemplateSpec{
				api.ObjectMeta{
					Name: fmt.Sprintf("pfsd-%s", name),
					Labels: map[string]string{
						"app": app,
					},
				},
				api.PodSpec{
					Containers: []api.Container{
						{
							Name:  "pfsd",
							Image: pfsdImage,
							Env: []api.EnvVar{
								{
									Name:  "PFS_DRIVER_ROOT",
									Value: "/pfs/btrfs",
								},
							},
							Ports: []api.ContainerPort{
								{
									ContainerPort: 650,
									Name:          "api-grpc-port",
								},
								{
									ContainerPort: 750,
									Name:          "api-http-port",
								},
								{
									ContainerPort: 1050,
									Name:          "trace-port",
								},
							},
							VolumeMounts: []api.VolumeMount{
								{
									Name:      "pfs-disk",
									MountPath: "/pfs/btrfs",
								},
							},
						},
					},
					Volumes: []api.Volume{
						{
							"pfs-disk",
							api.VolumeSource{
								GCEPersistentDisk: &api.GCEPersistentDiskVolumeSource{
									PDName: "pch-pfs",
									FSType: "btrfs",
								},
							},
						},
					},
				},
			},
		},
		api.ReplicationControllerStatus{},
	}
}
<|endoftext|>


<commit_before>package amqp

import (
	"log"
	"sync"

	"time"

	"io/ioutil"

	"github.com/achilleasa/usrv-service-adapters"
	"github.com/achilleasa/usrv-service-adapters/dial"
	amqpDriver "github.com/streadway/amqp"
)

// Adapter is a singleton instance of a amqp service
var Adapter *Amqp = &Amqp{
	endpoint:      "localhost:55672",
	logger:        log.New(ioutil.Discard, "", log.LstdFlags),
	dialPolicy:    dial.ExpBackoff(10, time.Millisecond),
	closeNotifier: adapters.NewNotifier(),
}

type Amqp struct {

	// The amqp endpoint to connect to. Set manually by the user or discovered
	// by a configuration service (e.g. etcd)
	endpoint string

	// A logger for service events.
	logger *log.Logger

	// A mutex protecting dial attempts.
	sync.Mutex

	// The dial policy to use.
	dialPolicy dial.Policy

	// Connection status.
	connected bool

	// AMQP connection handle.
	conn *amqpDriver.Connection

	// A notifier for close events.
	closeNotifier *adapters.Notifier
}

// Connect to the service. If a dial policy has been specified,
// the service will keep trying to reconnect until a connection
// is established or the dial policy aborts the reconnection attempt.
func (s *Amqp) Dial() error {
	s.Lock()
	defer s.Unlock()

	// We are already connected
	if s.connected {
		return nil
	}

	var err error
	var wait time.Duration
	s.dialPolicy.ResetAttempts()
	wait, err = s.dialPolicy.NextRetry()
	s.logger.Printf("[AMQP] Connecting to endpoint %s\n", s.endpoint)
	for {
		s.conn, err = amqpDriver.Dial(s.endpoint)
		if err == nil {
			break
		}

		wait, err = s.dialPolicy.NextRetry()
		if err != nil {
			s.logger.Printf("[AMQP] Could not connect to endpoint %s after %d attempt(s)\n", s.endpoint, s.dialPolicy.CurAttempt())
			return dial.ErrTimeout
		}
		s.logger.Printf("[AMQP] Could not connect to endpoint %s; retrying in %v\n", s.endpoint, wait)
		<-time.After(wait)
	}

	s.connected = true
	s.dialPolicy.ResetAttempts()
	s.logger.Printf("[AMQP] Connected to endpoint %s\n", s.endpoint)

	// Start watchdog
	go s.watchdog()

	return nil
}

// Disconnect.
func (s *Amqp) Close() {
	s.Lock()
	defer s.Unlock()

	if !s.connected {
		return
	}

	// Close connection and notify any registered listeners
	s.conn.Close()
	s.closeNotifier.NotifyAll(adapters.ErrConnectionClosed)
	s.conn = nil
	s.connected = false
}

// Register a listener for receiving close notifications. The service adapter will emit an error and
// close the channel if the service is cleanly shut down or close the channel if the connection is reset.
func (s *Amqp) NotifyClose(c adapters.CloseListener) {
	s.closeNotifier.Add(c)
}

// Apply a list of options to the service.
func (s *Amqp) SetOptions(opts ...adapters.ServiceOption) error {
	for _, opt := range opts {
		if err := opt(s); err != nil {
			return err
		}
	}
	return nil
}

// Register a logger instance for service events.
func (s *Amqp) SetLogger(logger *log.Logger) {
	s.logger = logger
}

// Set a dial policy for this service.
func (s *Amqp) SetDialPolicy(policy dial.Policy) {
	s.dialPolicy = policy
}

// Set the service configuration. Changing the configuration settings for an already connected
// service will trigger a service shutdown. The service consumer is responsible for handing
// service close events and triggering a re-dial.
func (s *Amqp) Config(params map[string]string) error {
	s.Lock()
	defer s.Unlock()

	needsReset := false

	endpoint, exists := params["endpoint"]
	if exists {
		s.endpoint = endpoint
		needsReset = true
	}

	if needsReset {
		s.logger.Printf("[AMQP] Configuration changed; new settings: endpoint=%s\n", s.endpoint)
		if s.connected {
			s.conn.Close()
			s.closeNotifier.NotifyAll(nil)
			s.conn = nil
			s.connected = false
		}
	}

	return nil
}

// Allocate new amqp channel.
func (s *Amqp) NewChannel() (*amqpDriver.Channel, error) {
	s.Lock()
	defer s.Unlock()

	if !s.connected {
		return nil, adapters.ErrConnectionClosed
	}

	return s.conn.Channel()
}

// A worker that listens for service-related notifications or configuration changes.
func (s *Amqp) watchdog() {
	amqpClose := make(chan *amqpDriver.Error)
	s.conn.NotifyClose(amqpClose)

	select {
	case _, normalShutdown := <-amqpClose:
		if normalShutdown {
			s.closeNotifier.NotifyAll(adapters.ErrConnectionClosed)
			s.logger.Printf("[AMQP] Disconnected from endpoint %s\n", s.endpoint)
		} else {
			s.closeNotifier.NotifyAll(nil)
			s.logger.Printf("[AMQP] Lost connection to endpoint %s\n", s.endpoint)

		}
	}
}
<commit_msg>Reset AMQP connection state when connection is lost<commit_after>package amqp

import (
	"log"
	"sync"

	"time"

	"io/ioutil"

	"github.com/achilleasa/usrv-service-adapters"
	"github.com/achilleasa/usrv-service-adapters/dial"
	amqpDriver "github.com/streadway/amqp"
)

// Adapter is a singleton instance of a amqp service
var Adapter *Amqp = &Amqp{
	endpoint:      "localhost:55672",
	logger:        log.New(ioutil.Discard, "", log.LstdFlags),
	dialPolicy:    dial.ExpBackoff(10, time.Millisecond),
	closeNotifier: adapters.NewNotifier(),
}

type Amqp struct {

	// The amqp endpoint to connect to. Set manually by the user or discovered
	// by a configuration service (e.g. etcd)
	endpoint string

	// A logger for service events.
	logger *log.Logger

	// A mutex protecting dial attempts.
	sync.Mutex

	// The dial policy to use.
	dialPolicy dial.Policy

	// Connection status.
	connected bool

	// AMQP connection handle.
	conn *amqpDriver.Connection

	// A notifier for close events.
	closeNotifier *adapters.Notifier
}

// Connect to the service. If a dial policy has been specified,
// the service will keep trying to reconnect until a connection
// is established or the dial policy aborts the reconnection attempt.
func (s *Amqp) Dial() error {
	s.Lock()
	defer s.Unlock()

	// We are already connected
	if s.connected {
		return nil
	}

	var err error
	var wait time.Duration
	s.dialPolicy.ResetAttempts()
	wait, err = s.dialPolicy.NextRetry()
	s.logger.Printf("[AMQP] Connecting to endpoint %s\n", s.endpoint)
	for {
		s.conn, err = amqpDriver.Dial(s.endpoint)
		if err == nil {
			break
		}

		wait, err = s.dialPolicy.NextRetry()
		if err != nil {
			s.logger.Printf("[AMQP] Could not connect to endpoint %s after %d attempt(s)\n", s.endpoint, s.dialPolicy.CurAttempt())
			return dial.ErrTimeout
		}
		s.logger.Printf("[AMQP] Could not connect to endpoint %s; retrying in %v\n", s.endpoint, wait)
		<-time.After(wait)
	}

	s.connected = true
	s.dialPolicy.ResetAttempts()
	s.logger.Printf("[AMQP] Connected to endpoint %s\n", s.endpoint)

	// Start watchdog
	go s.watchdog()

	return nil
}

// Disconnect.
func (s *Amqp) Close() {
	s.Lock()
	defer s.Unlock()

	if !s.connected {
		return
	}

	// Close connection and notify any registered listeners
	s.conn.Close()
	s.closeNotifier.NotifyAll(adapters.ErrConnectionClosed)
	s.conn = nil
	s.connected = false
}

// Register a listener for receiving close notifications. The service adapter will emit an error and
// close the channel if the service is cleanly shut down or close the channel if the connection is reset.
func (s *Amqp) NotifyClose(c adapters.CloseListener) {
	s.closeNotifier.Add(c)
}

// Apply a list of options to the service.
func (s *Amqp) SetOptions(opts ...adapters.ServiceOption) error {
	for _, opt := range opts {
		if err := opt(s); err != nil {
			return err
		}
	}
	return nil
}

// Register a logger instance for service events.
func (s *Amqp) SetLogger(logger *log.Logger) {
	s.logger = logger
}

// Set a dial policy for this service.
func (s *Amqp) SetDialPolicy(policy dial.Policy) {
	s.dialPolicy = policy
}

// Set the service configuration. Changing the configuration settings for an already connected
// service will trigger a service shutdown. The service consumer is responsible for handing
// service close events and triggering a re-dial.
func (s *Amqp) Config(params map[string]string) error {
	s.Lock()
	defer s.Unlock()

	needsReset := false

	endpoint, exists := params["endpoint"]
	if exists {
		s.endpoint = endpoint
		needsReset = true
	}

	if needsReset {
		s.logger.Printf("[AMQP] Configuration changed; new settings: endpoint=%s\n", s.endpoint)
		if s.connected {
			s.conn.Close()
			s.closeNotifier.NotifyAll(nil)
			s.conn = nil
			s.connected = false
		}
	}

	return nil
}

// Allocate new amqp channel.
func (s *Amqp) NewChannel() (*amqpDriver.Channel, error) {
	s.Lock()
	defer s.Unlock()

	if !s.connected {
		return nil, adapters.ErrConnectionClosed
	}

	return s.conn.Channel()
}

// A worker that listens for service-related notifications or configuration changes.
func (s *Amqp) watchdog() {
	amqpClose := make(chan *amqpDriver.Error)
	s.conn.NotifyClose(amqpClose)

	select {
	case err := <-amqpClose:
		// Reset connection
		s.Lock()
		s.conn = nil
		s.connected = false
		s.Unlock()
		if err == nil {
			s.closeNotifier.NotifyAll(adapters.ErrConnectionClosed)
			s.logger.Printf("[AMQP] Disconnected from endpoint %s\n", s.endpoint)
		} else {
			s.closeNotifier.NotifyAll(nil)
			s.logger.Printf("[AMQP] Lost connection to endpoint %s\n", s.endpoint)
		}
	}
}
<|endoftext|>


<commit_before>package service

import (
	"fmt"
	"github.com/coreos/go-etcd/etcd"
	"path"
)

func Subscribe(service string) <-chan *etcd.Response {
	stop := make(chan bool)
	responses := make(chan *etcd.Response)
	go func() {
		_, err := client.Watch("/services/"+service, 0, true, responses, stop)
		if err != nil {
			logger.Println("fail to watch", service, err)
			close(responses)
			close(stop)
		}
	}()
	return responses
}

func SubscribeDown(service string) chan string {
	expirations := make(chan string)
	go func() {
		responses := Subscribe(service)
		for response := range responses {
			if response.Action == "expire" {
				expirations <- path.Base(response.Node.Key)
			}
		}
	}()
	return expirations
}

func SubscribeNew(service string) chan *Host {
	hosts := make(chan *Host)
	go func() {
		responses := Subscribe(service)
		for response := range responses {
			if response.Action == "create" {
				hosts <- buildHostFromNode(response.Node)
			}
		}
	}()
	return hosts
}
<commit_msg>forgot drop import 'fmt'<commit_after>package service

import (
	"github.com/coreos/go-etcd/etcd"
	"path"
)

func Subscribe(service string) <-chan *etcd.Response {
	stop := make(chan bool)
	responses := make(chan *etcd.Response)
	go func() {
		_, err := client.Watch("/services/"+service, 0, true, responses, stop)
		if err != nil {
			logger.Println("fail to watch", service, err)
			close(responses)
			close(stop)
		}
	}()
	return responses
}

func SubscribeDown(service string) chan string {
	expirations := make(chan string)
	go func() {
		responses := Subscribe(service)
		for response := range responses {
			if response.Action == "expire" {
				expirations <- path.Base(response.Node.Key)
			}
		}
	}()
	return expirations
}

func SubscribeNew(service string) chan *Host {
	hosts := make(chan *Host)
	go func() {
		responses := Subscribe(service)
		for response := range responses {
			if response.Action == "create" {
				hosts <- buildHostFromNode(response.Node)
			}
		}
	}()
	return hosts
}
<|endoftext|>


<commit_before>package user

import (
	"github.com/hackform/governor"
	"github.com/hackform/governor/service/user/model"
	"github.com/hackform/governor/service/user/session"
	"github.com/hackform/governor/service/user/token"
	"github.com/hackform/governor/util/uid"
	"github.com/labstack/echo"
	"github.com/sirupsen/logrus"
	"net/http"
	"strings"
	"time"
)

type (
	reqUserAuth struct {
		Username     string `json:"username"`
		Password     string `json:"password"`
		RefreshToken string `json:"refresh_token"`
	}

	reqExchangeToken struct {
		RefreshToken string `json:"refresh_token"`
	}

	resUserAuth struct {
		Valid        bool          `json:"valid"`
		AccessToken  string        `json:"access_token,omitempty"`
		RefreshToken string        `json:"refresh_token,omitempty"`
		Claims       *token.Claims `json:"claims,omitempty"`
		Username     string        `json:"username,omitempty"`
		FirstName    string        `json:"first_name,omitempty"`
		LastName     string        `json:"last_name,omitempty"`
	}
)

func (r *reqUserAuth) valid() *governor.Error {
	if err := hasUsername(r.Username); err != nil {
		return err
	}
	if err := hasPassword(r.Password); err != nil {
		return err
	}
	return nil
}

func (r *reqExchangeToken) valid() *governor.Error {
	if err := hasToken(r.RefreshToken); err != nil {
		return err
	}
	return nil
}

const (
	authenticationSubject = "authentication"
	refreshSubject        = "refresh"
)

var (
	userAuthGroup = ""
)

func (u *User) mountAuth(conf governor.Config, r *echo.Group, l *logrus.Logger) error {
	db := u.db.DB()
	ch := u.cache.Cache()
	mailer := u.mailer

	userAuthGroup = conf.BaseURL + "/u/auth"

	r.POST("/login", func(c echo.Context) error {
		ruser := &reqUserAuth{}
		if err := c.Bind(ruser); err != nil {
			return governor.NewErrorUser(moduleIDAuth, err.Error(), 0, http.StatusBadRequest)
		}
		if err := ruser.valid(); err != nil {
			return err
		}

		m, err := usermodel.GetByUsername(db, ruser.Username)
		if err != nil {
			if err.Code() == 2 {
				err.SetErrorUser()
			}
			return err
		}
		if m.ValidatePass(ruser.Password) {
			sessionID := ""
			// if session_id is provided, is in cache, and is valid, set it as the sessionID
			if ok, claims := u.tokenizer.GetClaims(ruser.RefreshToken); ok {
				if s := strings.Split(claims.Id, ":"); len(s) == 2 {
					if _, err := ch.Get(s[0]).Result(); err == nil {
						if id, err := uid.FromBase64(4, 8, 4, s[0]); err == nil {
							sessionID = id.Base64()
						}
					}
				}
			}

			var s *session.Session
			if sessionID == "" {
				// otherwise, create a new sessionID
				if s, err = session.New(m, c); err != nil {
					err.AddTrace(moduleIDAuth)
					return err
				}
			} else {
				if s, err = session.FromSessionID(sessionID, m, c); err != nil {
					err.AddTrace(moduleIDAuth)
					return err
				}
			}

			// generate an access token
			accessToken, claims, err := u.tokenizer.Generate(m, u.accessTime, authenticationSubject, "")
			if err != nil {
				err.AddTrace(moduleIDAuth)
				return err
			}
			// generate a refresh tokens with the sessionKey
			refreshToken, _, err := u.tokenizer.Generate(m, u.refreshTime, refreshSubject, s.SessionID+":"+s.SessionKey)
			if err != nil {
				err.AddTrace(moduleIDAuth)
				return err
			}

			// store the session in cache
			if isMember, err := ch.HExists(s.UserKey(), s.SessionID).Result(); err == nil {
				sessionGob, err := s.ToGob()
				if err != nil {
					err.AddTrace(moduleIDAuth)
					return err
				}
				if !isMember {
					if err := mailer.Send(m.Email, "New Login", "New login from "+s.IP+" with the useragent: "+s.UserAgent); err != nil {
						err.AddTrace(moduleIDAuth)
						return err
					}
				}
				if err := ch.HSet(s.UserKey(), s.SessionID, sessionGob).Err(); err != nil {
					return governor.NewError(moduleIDAuth, err.Error(), 0, http.StatusInternalServerError)
				}
			} else {
				return governor.NewError(moduleIDAuth, err.Error(), 0, http.StatusInternalServerError)
			}

			// set the session id and key into cache
			if err := ch.Set(s.SessionID, s.SessionKey, time.Duration(u.refreshTime*b1)).Err(); err != nil {
				return governor.NewError(moduleIDAuth, err.Error(), 0, http.StatusInternalServerError)
			}

			accessCookie := &http.Cookie{
				Name:     "access_token",
				Value:    accessToken,
				Path:     conf.BaseURL,
				Expires:  time.Now().Add(time.Duration(u.accessTime * b1)),
				HttpOnly: true,
			}
			refreshCookie := &http.Cookie{
				Name:     "refresh_token",
				Value:    refreshToken,
				Path:     userAuthGroup,
				Expires:  time.Now().Add(time.Duration(u.refreshTime * b1)),
				HttpOnly: true,
			}

			c.SetCookie(accessCookie)
			c.SetCookie(refreshCookie)

			return c.JSON(http.StatusOK, &resUserAuth{
				Valid:        true,
				AccessToken:  accessToken,
				RefreshToken: refreshToken,
				Claims:       claims,
				Username:     m.Username,
				FirstName:    m.FirstName,
				LastName:     m.LastName,
			})
		}

		return c.JSON(http.StatusUnauthorized, &resUserAuth{
			Valid: false,
		})
	})

	r.POST("/exchange", func(c echo.Context) error {
		ruser := &reqExchangeToken{}
		if err := c.Bind(ruser); err != nil {
			return governor.NewErrorUser(moduleIDAuth, err.Error(), 0, http.StatusBadRequest)
		}
		if err := ruser.valid(); err != nil {
			return err
		}

		sessionID := ""
		sessionKey := ""
		if ok, claims := u.tokenizer.GetClaims(ruser.RefreshToken); ok {
			if s := strings.Split(claims.Id, ":"); len(s) == 2 {
				if key, err := ch.Get(s[0]).Result(); err == nil {
					sessionID = s[0]
					sessionKey = key
				}
			}
		}

		if sessionID == "" {
			return governor.NewErrorUser(moduleIDAuth, "malformed refresh token", 0, http.StatusUnauthorized)
		}

		// check the refresh token
		validToken, claims := u.tokenizer.Validate(ruser.RefreshToken, refreshSubject, sessionID+":"+sessionKey)
		if !validToken {
			return c.JSON(http.StatusUnauthorized, &resUserAuth{
				Valid: false,
			})
		}

		// generate a new accessToken from the refreshToken claims
		accessToken, err := u.tokenizer.GenerateFromClaims(claims, u.accessTime, authenticationSubject, "")
		if err != nil {
			err.AddTrace(moduleIDAuth)
			return err
		}

		return c.JSON(http.StatusOK, &resUserAuth{
			Valid:       true,
			AccessToken: accessToken,
			Claims:      claims,
		})
	})

	r.POST("/refresh", func(c echo.Context) error {
		ruser := &reqExchangeToken{}
		if err := c.Bind(ruser); err != nil {
			return governor.NewErrorUser(moduleIDAuth, err.Error(), 0, http.StatusBadRequest)
		}
		if err := ruser.valid(); err != nil {
			return err
		}

		sessionID := ""
		sessionKey := ""
		if ok, claims := u.tokenizer.GetClaims(ruser.RefreshToken); ok {
			if s := strings.Split(claims.Id, ":"); len(s) == 2 {
				if key, err := ch.Get(s[0]).Result(); err == nil {
					sessionID = s[0]
					sessionKey = key
				}
			}
		}

		if sessionID == "" {
			return governor.NewErrorUser(moduleIDAuth, "malformed refresh token", 0, http.StatusUnauthorized)
		}

		// check the refresh token
		validToken, claims := u.tokenizer.Validate(ruser.RefreshToken, refreshSubject, sessionID+":"+sessionKey)
		if !validToken {
			return c.JSON(http.StatusUnauthorized, &resUserAuth{
				Valid: false,
			})
		}

		// create a new key for the session
		key, err := uid.NewU(0, 16)
		if err != nil {
			err.AddTrace(moduleIDAuth)
			return err
		}
		sessionKey = key.Base64()

		// generate a new refreshToken from the refreshToken claims
		refreshToken, err := u.tokenizer.GenerateFromClaims(claims, u.accessTime, refreshSubject, sessionID+":"+sessionKey)
		if err != nil {
			err.AddTrace(moduleIDAuth)
			return err
		}

		// set the session id and key into cache
		if err := ch.Set(sessionID, sessionKey, time.Duration(u.refreshTime*b1)).Err(); err != nil {
			return governor.NewError(moduleIDAuth, err.Error(), 0, http.StatusInternalServerError)
		}

		return c.JSON(http.StatusOK, &resUserAuth{
			Valid:        true,
			RefreshToken: refreshToken,
		})
	})

	if conf.IsDebug() {
		r.GET("/decode", func(c echo.Context) error {
			return c.JSON(http.StatusOK, resUserAuth{
				Valid:  true,
				Claims: c.Get("user").(*token.Claims),
			})
		}, u.gate.User())
	}

	return nil
}
<commit_msg>login route supports cookies<commit_after>package user

import (
	"github.com/hackform/governor"
	"github.com/hackform/governor/service/user/model"
	"github.com/hackform/governor/service/user/session"
	"github.com/hackform/governor/service/user/token"
	"github.com/hackform/governor/util/uid"
	"github.com/labstack/echo"
	"github.com/sirupsen/logrus"
	"net/http"
	"strings"
	"time"
)

type (
	reqUserAuth struct {
		Username     string `json:"username"`
		Password     string `json:"password"`
		RefreshToken string `json:"refresh_token"`
	}

	reqExchangeToken struct {
		RefreshToken string `json:"refresh_token"`
	}

	resUserAuth struct {
		Valid        bool          `json:"valid"`
		AccessToken  string        `json:"access_token,omitempty"`
		RefreshToken string        `json:"refresh_token,omitempty"`
		Claims       *token.Claims `json:"claims,omitempty"`
		Username     string        `json:"username,omitempty"`
		FirstName    string        `json:"first_name,omitempty"`
		LastName     string        `json:"last_name,omitempty"`
	}
)

func (r *reqUserAuth) valid() *governor.Error {
	if err := hasUsername(r.Username); err != nil {
		return err
	}
	if err := hasPassword(r.Password); err != nil {
		return err
	}
	return nil
}

func (r *reqExchangeToken) valid() *governor.Error {
	if err := hasToken(r.RefreshToken); err != nil {
		return err
	}
	return nil
}

const (
	authenticationSubject = "authentication"
	refreshSubject        = "refresh"
)

func (u *User) setAccessCookie(c echo.Context, conf governor.Config, accessToken string) {
	c.SetCookie(&http.Cookie{
		Name:     "access_token",
		Value:    accessToken,
		Path:     conf.BaseURL,
		Expires:  time.Now().Add(time.Duration(u.accessTime * b1)),
		HttpOnly: true,
	})
}

func (u *User) setRefreshCookie(c echo.Context, conf governor.Config, refreshToken string) {
	c.SetCookie(&http.Cookie{
		Name:     "refresh_token",
		Value:    refreshToken,
		Path:     conf.BaseURL + "/u/auth",
		Expires:  time.Now().Add(time.Duration(u.refreshTime * b1)),
		HttpOnly: true,
	})
}

func getAccessCookie(c echo.Context) (string, error) {
	cookie, err := c.Cookie("access_token")
	if err != nil {
		return "", err
	}
	return cookie.Value, nil
}

func getRefreshCookie(c echo.Context) (string, error) {
	cookie, err := c.Cookie("refresh_token")
	if err != nil {
		return "", err
	}
	return cookie.Value, nil
}

func (u *User) mountAuth(conf governor.Config, r *echo.Group, l *logrus.Logger) error {
	db := u.db.DB()
	ch := u.cache.Cache()
	mailer := u.mailer

	r.POST("/login", func(c echo.Context) error {
		ruser := &reqUserAuth{}
		if err := c.Bind(ruser); err != nil {
			return governor.NewErrorUser(moduleIDAuth, err.Error(), 0, http.StatusBadRequest)
		}
		if t, err := getRefreshCookie(c); err == nil {
			ruser.RefreshToken = t
		}
		if err := ruser.valid(); err != nil {
			return err
		}

		m, err := usermodel.GetByUsername(db, ruser.Username)
		if err != nil {
			if err.Code() == 2 {
				err.SetErrorUser()
			}
			return err
		}
		if m.ValidatePass(ruser.Password) {
			sessionID := ""
			// if session_id is provided, is in cache, and is valid, set it as the sessionID
			if ok, claims := u.tokenizer.GetClaims(ruser.RefreshToken); ok {
				if s := strings.Split(claims.Id, ":"); len(s) == 2 {
					if _, err := ch.Get(s[0]).Result(); err == nil {
						if id, err := uid.FromBase64(4, 8, 4, s[0]); err == nil {
							sessionID = id.Base64()
						}
					}
				}
			}

			var s *session.Session
			if sessionID == "" {
				// otherwise, create a new sessionID
				if s, err = session.New(m, c); err != nil {
					err.AddTrace(moduleIDAuth)
					return err
				}
			} else {
				if s, err = session.FromSessionID(sessionID, m, c); err != nil {
					err.AddTrace(moduleIDAuth)
					return err
				}
			}

			// generate an access token
			accessToken, claims, err := u.tokenizer.Generate(m, u.accessTime, authenticationSubject, "")
			if err != nil {
				err.AddTrace(moduleIDAuth)
				return err
			}
			// generate a refresh tokens with the sessionKey
			refreshToken, _, err := u.tokenizer.Generate(m, u.refreshTime, refreshSubject, s.SessionID+":"+s.SessionKey)
			if err != nil {
				err.AddTrace(moduleIDAuth)
				return err
			}

			// store the session in cache
			if isMember, err := ch.HExists(s.UserKey(), s.SessionID).Result(); err == nil {
				sessionGob, err := s.ToGob()
				if err != nil {
					err.AddTrace(moduleIDAuth)
					return err
				}
				if !isMember {
					if err := mailer.Send(m.Email, "New Login", "New login from "+s.IP+" with the useragent: "+s.UserAgent); err != nil {
						err.AddTrace(moduleIDAuth)
						return err
					}
				}
				if err := ch.HSet(s.UserKey(), s.SessionID, sessionGob).Err(); err != nil {
					return governor.NewError(moduleIDAuth, err.Error(), 0, http.StatusInternalServerError)
				}
			} else {
				return governor.NewError(moduleIDAuth, err.Error(), 0, http.StatusInternalServerError)
			}

			// set the session id and key into cache
			if err := ch.Set(s.SessionID, s.SessionKey, time.Duration(u.refreshTime*b1)).Err(); err != nil {
				return governor.NewError(moduleIDAuth, err.Error(), 0, http.StatusInternalServerError)
			}

			u.setAccessCookie(c, conf, accessToken)
			u.setRefreshCookie(c, conf, refreshToken)

			return c.JSON(http.StatusOK, &resUserAuth{
				Valid:        true,
				AccessToken:  accessToken,
				RefreshToken: refreshToken,
				Claims:       claims,
				Username:     m.Username,
				FirstName:    m.FirstName,
				LastName:     m.LastName,
			})
		}

		return c.JSON(http.StatusUnauthorized, &resUserAuth{
			Valid: false,
		})
	})

	r.POST("/exchange", func(c echo.Context) error {
		ruser := &reqExchangeToken{}
		if t, err := getRefreshCookie(c); err == nil {
			ruser.RefreshToken = t
		} else if err := c.Bind(ruser); err != nil {
			return governor.NewErrorUser(moduleIDAuth, err.Error(), 0, http.StatusBadRequest)
		}
		if err := ruser.valid(); err != nil {
			return err
		}

		sessionID := ""
		sessionKey := ""
		if ok, claims := u.tokenizer.GetClaims(ruser.RefreshToken); ok {
			if s := strings.Split(claims.Id, ":"); len(s) == 2 {
				if key, err := ch.Get(s[0]).Result(); err == nil {
					sessionID = s[0]
					sessionKey = key
				}
			}
		}

		if sessionID == "" {
			return governor.NewErrorUser(moduleIDAuth, "malformed refresh token", 0, http.StatusUnauthorized)
		}

		// check the refresh token
		validToken, claims := u.tokenizer.Validate(ruser.RefreshToken, refreshSubject, sessionID+":"+sessionKey)
		if !validToken {
			return c.JSON(http.StatusUnauthorized, &resUserAuth{
				Valid: false,
			})
		}

		// generate a new accessToken from the refreshToken claims
		accessToken, err := u.tokenizer.GenerateFromClaims(claims, u.accessTime, authenticationSubject, "")
		if err != nil {
			err.AddTrace(moduleIDAuth)
			return err
		}

		u.setAccessCookie(c, conf, accessToken)

		return c.JSON(http.StatusOK, &resUserAuth{
			Valid:       true,
			AccessToken: accessToken,
			Claims:      claims,
		})
	})

	r.POST("/refresh", func(c echo.Context) error {
		ruser := &reqExchangeToken{}
		if t, err := getRefreshCookie(c); err == nil {
			ruser.RefreshToken = t
		} else if err := c.Bind(ruser); err != nil {
			return governor.NewErrorUser(moduleIDAuth, err.Error(), 0, http.StatusBadRequest)
		}
		if err := ruser.valid(); err != nil {
			return err
		}

		sessionID := ""
		sessionKey := ""
		if ok, claims := u.tokenizer.GetClaims(ruser.RefreshToken); ok {
			if s := strings.Split(claims.Id, ":"); len(s) == 2 {
				if key, err := ch.Get(s[0]).Result(); err == nil {
					sessionID = s[0]
					sessionKey = key
				}
			}
		}

		if sessionID == "" {
			return governor.NewErrorUser(moduleIDAuth, "malformed refresh token", 0, http.StatusUnauthorized)
		}

		// check the refresh token
		validToken, claims := u.tokenizer.Validate(ruser.RefreshToken, refreshSubject, sessionID+":"+sessionKey)
		if !validToken {
			return c.JSON(http.StatusUnauthorized, &resUserAuth{
				Valid: false,
			})
		}

		// create a new key for the session
		key, err := uid.NewU(0, 16)
		if err != nil {
			err.AddTrace(moduleIDAuth)
			return err
		}
		sessionKey = key.Base64()

		// generate a new refreshToken from the refreshToken claims
		refreshToken, err := u.tokenizer.GenerateFromClaims(claims, u.accessTime, refreshSubject, sessionID+":"+sessionKey)
		if err != nil {
			err.AddTrace(moduleIDAuth)
			return err
		}

		// set the session id and key into cache
		if err := ch.Set(sessionID, sessionKey, time.Duration(u.refreshTime*b1)).Err(); err != nil {
			return governor.NewError(moduleIDAuth, err.Error(), 0, http.StatusInternalServerError)
		}

		u.setRefreshCookie(c, conf, refreshToken)

		return c.JSON(http.StatusOK, &resUserAuth{
			Valid:        true,
			RefreshToken: refreshToken,
		})
	})

	if conf.IsDebug() {
		r.GET("/decode", func(c echo.Context) error {
			return c.JSON(http.StatusOK, resUserAuth{
				Valid:  true,
				Claims: c.Get("user").(*token.Claims),
			})
		}, u.gate.User())
	}

	return nil
}
<|endoftext|>


<commit_before>package mangadownloader

import (
	"code.google.com/p/go-html-transform/css/selector"
	"errors"
	"net/url"
	"regexp"
)

const (
	serviceMangaWallDomain = "mangawall.com"
)

var (
	serviceMangaWallHtmlSelectorMangaName, _ = selector.Selector("meta[name=og:title]")

	serviceMangaWallRegexpIdentifyManga, _   = regexp.Compile("^/manga/[0-9a-z\\-]+/?$")
	serviceMangaWallRegexpIdentifyChapter, _ = regexp.Compile("^/manga/[0-9a-z\\-]+/.+$")
)

type MangaWallService struct {
	Md *MangaDownloader
}

func (service *MangaWallService) Supports(u *url.URL) bool {
	return u.Host == serviceMangaWallDomain
}

func (service *MangaWallService) Identify(u *url.URL) (interface{}, error) {
	if !service.Supports(u) {
		return nil, errors.New("Not supported")
	}

	if serviceMangaWallRegexpIdentifyChapter.MatchString(u.Path) {
		chapter := &Chapter{
			Url:     u,
			Service: service,
		}
		return chapter, nil
	}

	if serviceMangaWallRegexpIdentifyManga.MatchString(u.Path) {
		manga := &Manga{
			Url:     u,
			Service: service,
		}
		return manga, nil
	}

	return nil, errors.New("Unknown url")
}

func (service *MangaWallService) MangaName(manga *Manga) (string, error) {
	rootNode, err := service.Md.HttpGetHtml(manga.Url)
	if err != nil {
		return "", err
	}

	metaOgTitleNodes := serviceMangaWallHtmlSelectorMangaName.Find(rootNode)
	if len(metaOgTitleNodes) != 1 {
		return "", errors.New("Name node not found")
	}
	metaOgTitleNode := metaOgTitleNodes[0]
	name := htmlGetNodeAttribute(metaOgTitleNode, "content")

	return name, nil
}

func (service *MangaWallService) MangaChapters(manga *Manga) ([]*Chapter, error) {
	return nil, errors.New("Not implemented")
}

func (service *MangaWallService) ChapterName(chapter *Chapter) (string, error) {
	return "", errors.New("Not implemented")
}

func (service *MangaWallService) ChapterPages(chapter *Chapter) ([]*Page, error) {
	return nil, errors.New("Not implemented")
}

func (service *MangaWallService) PageImageUrl(page *Page) (*url.URL, error) {
	return nil, errors.New("Not implemented")
}

func (service *MangaWallService) String() string {
	return "MangaWallService"
}
<commit_msg>Add MangaChapters() in MangaWallService<commit_after>package mangadownloader

import (
	"code.google.com/p/go-html-transform/css/selector"
	"errors"
	"net/url"
	"regexp"
)

const (
	serviceMangaWallDomain = "mangawall.com"
)

var (
	serviceMangaWallUrlBase *url.URL

	serviceMangaWallHtmlSelectorMangaName, _     = selector.Selector("meta[name=og:title]")
	serviceMangaWallHtmlSelectorMangaChapters, _ = selector.Selector(".chapterlistfull a")

	serviceMangaWallRegexpIdentifyManga, _   = regexp.Compile("^/manga/[0-9a-z\\-]+/?$")
	serviceMangaWallRegexpIdentifyChapter, _ = regexp.Compile("^/manga/[0-9a-z\\-]+/.+$")
)

func init() {
	serviceMangaWallUrlBase = new(url.URL)
	serviceMangaWallUrlBase.Scheme = "http"
	serviceMangaWallUrlBase.Host = serviceMangaWallDomain
}

type MangaWallService struct {
	Md *MangaDownloader
}

func (service *MangaWallService) Supports(u *url.URL) bool {
	return u.Host == serviceMangaWallDomain
}

func (service *MangaWallService) Identify(u *url.URL) (interface{}, error) {
	if !service.Supports(u) {
		return nil, errors.New("Not supported")
	}

	if serviceMangaWallRegexpIdentifyChapter.MatchString(u.Path) {
		chapter := &Chapter{
			Url:     u,
			Service: service,
		}
		return chapter, nil
	}

	if serviceMangaWallRegexpIdentifyManga.MatchString(u.Path) {
		manga := &Manga{
			Url:     u,
			Service: service,
		}
		return manga, nil
	}

	return nil, errors.New("Unknown url")
}

func (service *MangaWallService) MangaName(manga *Manga) (string, error) {
	rootNode, err := service.Md.HttpGetHtml(manga.Url)
	if err != nil {
		return "", err
	}

	metaOgTitleNodes := serviceMangaWallHtmlSelectorMangaName.Find(rootNode)
	if len(metaOgTitleNodes) != 1 {
		return "", errors.New("Name node not found")
	}
	metaOgTitleNode := metaOgTitleNodes[0]
	name := htmlGetNodeAttribute(metaOgTitleNode, "content")

	return name, nil
}

func (service *MangaWallService) MangaChapters(manga *Manga) ([]*Chapter, error) {
	rootNode, err := service.Md.HttpGetHtml(manga.Url)
	if err != nil {
		return nil, err
	}

	linkNodes := serviceMangaWallHtmlSelectorMangaChapters.Find(rootNode)

	chapters := make([]*Chapter, 0, len(linkNodes))
	for _, linkNode := range linkNodes {
		chapterUrl := urlCopy(serviceMangaWallUrlBase)
		chapterUrl.Path = htmlGetNodeAttribute(linkNode, "href")
		chapter := &Chapter{
			Url:     chapterUrl,
			Service: service,
		}
		chapters = append(chapters, chapter)
	}

	return chapters, nil
}

func (service *MangaWallService) ChapterName(chapter *Chapter) (string, error) {
	return "", errors.New("Not implemented")
}

func (service *MangaWallService) ChapterPages(chapter *Chapter) ([]*Page, error) {
	return nil, errors.New("Not implemented")
}

func (service *MangaWallService) PageImageUrl(page *Page) (*url.URL, error) {
	return nil, errors.New("Not implemented")
}

func (service *MangaWallService) String() string {
	return "MangaWallService"
}
<|endoftext|>