<commit_before>package persist

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/rancher/machine/libmachine/host"
	"github.com/rancher/machine/libmachine/log"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	corev1types "k8s.io/client-go/kubernetes/typed/core/v1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

const machineConfigSecretKey = "extractedConfig"

type secretStore struct {
	Store
	SecretName, SecretNamespace string
	SecretClient                corev1types.SecretInterface
	secret                      *v1.Secret
}

func NewSecretStore(store Store, secretName, secretNamespace, kubeConfigPath string) (Store, error) {
	var config *rest.Config
	var err error
	if kubeConfigPath != "" {
		config, err = clientcmd.BuildConfigFromFlags("", kubeConfigPath)
	} else {
		config, err = rest.InClusterConfig()
	}
	if err != nil {
		return nil, err
	}

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, err
	}

	s := &secretStore{Store: store, SecretName: secretName, SecretNamespace: secretNamespace, SecretClient: clientset.CoreV1().Secrets(secretNamespace)}
	if err := s.extractConfig(); err != nil {
		return nil, err
	}

	return s, nil
}

func (s *secretStore) Remove(name string) error {
	if err := s.Store.Remove(name); err != nil {
		return fmt.Errorf("error removing directories for host %v: %v", name, err)
	}
	return s.saveSecret(name)
}

func (s *secretStore) Save(host *host.Host) error {
	if err := s.Store.Save(host); err != nil {
		return fmt.Errorf("error saving with file store: %v", err)
	}
	return s.saveSecret(host.Name)
}

func (s *secretStore) saveSecret(hostName string) error {
	// create the tar.gz file
	destFile := &bytes.Buffer{}

	fileWriter := gzip.NewWriter(destFile)
	tarfileWriter := tar.NewWriter(fileWriter)

	if err := s.addDirToArchive(tarfileWriter); err != nil {
		tarfileWriter.Close()
		fileWriter.Close()
		return err
	}

	tarfileWriter.Close()
	fileWriter.Close()

	secret, err := s.loadSecret()
	if err != nil {
		return fmt.Errorf("unable to load secret : %v", err)
	}

	if secret.Data == nil {
		secret.Data = make(map[string][]byte)
	}

	if bytes.Compare(secret.Data[machineConfigSecretKey], destFile.Bytes()) == 0 {
		return nil
	}

	secret.Data[machineConfigSecretKey] = destFile.Bytes()

	secret, err = s.SecretClient.Update(context.Background(), secret, metav1.UpdateOptions{})
	if err != nil {
		return fmt.Errorf("unable to update secret: %v", err)
	}

	return nil
}

func (s *secretStore) addDirToArchive(tarfileWriter *tar.Writer) error {
	baseDir := s.GetMachinesDir()

	return filepath.Walk(baseDir,
		func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}

			if path == baseDir || strings.HasSuffix(info.Name(), ".iso") ||
				strings.HasSuffix(info.Name(), ".tar.gz") ||
				strings.HasSuffix(info.Name(), ".vmdk") ||
				strings.HasSuffix(info.Name(), ".img") {
				return nil
			}

			header, err := tar.FileInfoHeader(info, info.Name())
			if err != nil {
				return err
			}

			header.Name = path

			if err := tarfileWriter.WriteHeader(header); err != nil {
				return err
			}

			if info.IsDir() {
				return nil
			}

			file, err := os.Open(path)
			if err != nil {
				return err
			}
			defer file.Close()

			_, err = io.Copy(tarfileWriter, file)
			return err
		})
}

func (s *secretStore) loadSecret() (*v1.Secret, error) {
	secret, err := s.SecretClient.Get(context.Background(), s.SecretName, metav1.GetOptions{})
	if err != nil {
		return nil, fmt.Errorf("error getting secret from kubernetes: %v", err)
	}

	return secret, nil
}

func (s *secretStore) extractConfig() error {
	secret, err := s.loadSecret()
	if err != nil {
		return fmt.Errorf("error getting secret from kubernetes: %v", err)
	}

	extractedConfig, ok := secret.Data[machineConfigSecretKey]
	if !ok {
		log.Infof("no data in %s", machineConfigSecretKey)
		return nil
	}

	gzipReader, err := gzip.NewReader(bytes.NewReader(extractedConfig))
	if err != nil {
		return err
	}
	tarReader := tar.NewReader(gzipReader)
	baseDir := s.GetMachinesDir()

	for {
		header, err := tarReader.Next()
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return fmt.Errorf("error reinitializing config (tarRead.Next). Config Dir: %v. Error: %v", baseDir, err)
		}

		filePath := header.Name
		log.Debugf("Extracting %v", filePath)

		info := header.FileInfo()
		if info.IsDir() {
			if err := os.MkdirAll(filePath, os.FileMode(header.Mode)); err != nil {
				return fmt.Errorf("error reinitializing config (Mkdirall). Config Dir: %v. Dir: %v. Error: %v", baseDir, info.Name(), err)
			}
			continue
		}

		file, err := os.OpenFile(filePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode())
		if err != nil {
			return fmt.Errorf("error reinitializing config (OpenFile). Config Dir: %v. File: %v. Error: %v", baseDir, info.Name(), err)
		}

		_, err = io.Copy(file, tarReader)
		file.Close()
		if err != nil {
			return fmt.Errorf("error reinitializing config (Copy). Config Dir: %v. File: %v. Error: %v", baseDir, info.Name(), err)
		}
	}
}
<commit_msg>Remove data log message in production<commit_after>package persist

import (
	"archive/tar"
	"bytes"
	"compress/gzip"
	"context"
	"fmt"
	"io"
	"os"
	"path/filepath"
	"strings"

	"github.com/rancher/machine/libmachine/host"
	"github.com/rancher/machine/libmachine/log"
	v1 "k8s.io/api/core/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/kubernetes"
	corev1types "k8s.io/client-go/kubernetes/typed/core/v1"
	"k8s.io/client-go/rest"
	"k8s.io/client-go/tools/clientcmd"
)

const machineConfigSecretKey = "extractedConfig"

type secretStore struct {
	Store
	SecretName, SecretNamespace string
	SecretClient                corev1types.SecretInterface
	secret                      *v1.Secret
}

func NewSecretStore(store Store, secretName, secretNamespace, kubeConfigPath string) (Store, error) {
	var config *rest.Config
	var err error
	if kubeConfigPath != "" {
		config, err = clientcmd.BuildConfigFromFlags("", kubeConfigPath)
	} else {
		config, err = rest.InClusterConfig()
	}
	if err != nil {
		return nil, err
	}

	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		return nil, err
	}

	s := &secretStore{Store: store, SecretName: secretName, SecretNamespace: secretNamespace, SecretClient: clientset.CoreV1().Secrets(secretNamespace)}
	if err := s.extractConfig(); err != nil {
		return nil, err
	}

	return s, nil
}

func (s *secretStore) Remove(name string) error {
	if err := s.Store.Remove(name); err != nil {
		return fmt.Errorf("error removing directories for host %v: %v", name, err)
	}
	return s.saveSecret(name)
}

func (s *secretStore) Save(host *host.Host) error {
	if err := s.Store.Save(host); err != nil {
		return fmt.Errorf("error saving with file store: %v", err)
	}
	return s.saveSecret(host.Name)
}

func (s *secretStore) saveSecret(hostName string) error {
	// create the tar.gz file
	destFile := &bytes.Buffer{}

	fileWriter := gzip.NewWriter(destFile)
	tarfileWriter := tar.NewWriter(fileWriter)

	if err := s.addDirToArchive(tarfileWriter); err != nil {
		tarfileWriter.Close()
		fileWriter.Close()
		return err
	}

	tarfileWriter.Close()
	fileWriter.Close()

	secret, err := s.loadSecret()
	if err != nil {
		return fmt.Errorf("unable to load secret : %v", err)
	}

	if secret.Data == nil {
		secret.Data = make(map[string][]byte)
	}

	if bytes.Compare(secret.Data[machineConfigSecretKey], destFile.Bytes()) == 0 {
		return nil
	}

	secret.Data[machineConfigSecretKey] = destFile.Bytes()

	secret, err = s.SecretClient.Update(context.Background(), secret, metav1.UpdateOptions{})
	if err != nil {
		return fmt.Errorf("unable to update secret: %v", err)
	}

	return nil
}

func (s *secretStore) addDirToArchive(tarfileWriter *tar.Writer) error {
	baseDir := s.GetMachinesDir()

	return filepath.Walk(baseDir,
		func(path string, info os.FileInfo, err error) error {
			if err != nil {
				return err
			}

			if path == baseDir || strings.HasSuffix(info.Name(), ".iso") ||
				strings.HasSuffix(info.Name(), ".tar.gz") ||
				strings.HasSuffix(info.Name(), ".vmdk") ||
				strings.HasSuffix(info.Name(), ".img") {
				return nil
			}

			header, err := tar.FileInfoHeader(info, info.Name())
			if err != nil {
				return err
			}

			header.Name = path

			if err := tarfileWriter.WriteHeader(header); err != nil {
				return err
			}

			if info.IsDir() {
				return nil
			}

			file, err := os.Open(path)
			if err != nil {
				return err
			}
			defer file.Close()

			_, err = io.Copy(tarfileWriter, file)
			return err
		})
}

func (s *secretStore) loadSecret() (*v1.Secret, error) {
	secret, err := s.SecretClient.Get(context.Background(), s.SecretName, metav1.GetOptions{})
	if err != nil {
		return nil, fmt.Errorf("error getting secret from kubernetes: %v", err)
	}

	return secret, nil
}

func (s *secretStore) extractConfig() error {
	secret, err := s.loadSecret()
	if err != nil {
		return fmt.Errorf("error getting secret from kubernetes: %v", err)
	}

	extractedConfig, ok := secret.Data[machineConfigSecretKey]
	if !ok {
		log.Debugf("no data in %s", machineConfigSecretKey)
		return nil
	}

	gzipReader, err := gzip.NewReader(bytes.NewReader(extractedConfig))
	if err != nil {
		return err
	}
	tarReader := tar.NewReader(gzipReader)
	baseDir := s.GetMachinesDir()

	for {
		header, err := tarReader.Next()
		if err != nil {
			if err == io.EOF {
				return nil
			}
			return fmt.Errorf("error reinitializing config (tarRead.Next). Config Dir: %v. Error: %v", baseDir, err)
		}

		filePath := header.Name
		log.Debugf("Extracting %v", filePath)

		info := header.FileInfo()
		if info.IsDir() {
			if err := os.MkdirAll(filePath, os.FileMode(header.Mode)); err != nil {
				return fmt.Errorf("error reinitializing config (Mkdirall). Config Dir: %v. Dir: %v. Error: %v", baseDir, info.Name(), err)
			}
			continue
		}

		file, err := os.OpenFile(filePath, os.O_CREATE|os.O_TRUNC|os.O_WRONLY, info.Mode())
		if err != nil {
			return fmt.Errorf("error reinitializing config (OpenFile). Config Dir: %v. File: %v. Error: %v", baseDir, info.Name(), err)
		}

		_, err = io.Copy(file, tarReader)
		file.Close()
		if err != nil {
			return fmt.Errorf("error reinitializing config (Copy). Config Dir: %v. File: %v. Error: %v", baseDir, info.Name(), err)
		}
	}
}
<|endoftext|>


<commit_before>package libnetwork

import (
	"net"
	"testing"

	"github.com/docker/libnetwork/resolvconf"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestCleanupServiceDiscovery(t *testing.T) {
	c, err := New()
	require.NoError(t, err)
	defer c.Stop()

	n1, err := c.NewNetwork("bridge", "net1", "", nil)
	require.NoError(t, err)
	defer n1.Delete()

	n2, err := c.NewNetwork("bridge", "net2", "", nil)
	require.NoError(t, err)
	defer n2.Delete()

	n1.(*network).addSvcRecords("N1ep1", "service_test", "serviceID1", net.ParseIP("192.168.0.1"), net.IP{}, true, "test")
	n1.(*network).addSvcRecords("N2ep2", "service_test", "serviceID2", net.ParseIP("192.168.0.2"), net.IP{}, true, "test")

	n2.(*network).addSvcRecords("N2ep1", "service_test", "serviceID1", net.ParseIP("192.168.1.1"), net.IP{}, true, "test")
	n2.(*network).addSvcRecords("N2ep2", "service_test", "serviceID2", net.ParseIP("192.168.1.2"), net.IP{}, true, "test")

	if len(c.(*controller).svcRecords) != 2 {
		t.Fatalf("Service record not added correctly:%v", c.(*controller).svcRecords)
	}

	// cleanup net1
	c.(*controller).cleanupServiceDiscovery(n1.ID())

	if len(c.(*controller).svcRecords) != 1 {
		t.Fatalf("Service record not cleaned correctly:%v", c.(*controller).svcRecords)
	}

	c.(*controller).cleanupServiceDiscovery("")

	if len(c.(*controller).svcRecords) != 0 {
		t.Fatalf("Service record not cleaned correctly:%v", c.(*controller).svcRecords)
	}
}

func TestDNSOptions(t *testing.T) {
	c, err := New()
	require.NoError(t, err)

	sb, err := c.(*controller).NewSandbox("cnt1", nil)
	require.NoError(t, err)
	defer sb.Delete()
	sb.(*sandbox).startResolver(false)

	err = sb.(*sandbox).setupDNS()
	require.NoError(t, err)
	err = sb.(*sandbox).rebuildDNS()
	require.NoError(t, err)
	currRC, err := resolvconf.GetSpecific(sb.(*sandbox).config.resolvConfPath)
	require.NoError(t, err)
	dnsOptionsList := resolvconf.GetOptions(currRC.Content)
	assert.Equal(t, 1, len(dnsOptionsList), "There should be only 1 option instead:", dnsOptionsList)
	assert.Equal(t, "ndots:0", dnsOptionsList[0], "The option must be ndots:0 instead:", dnsOptionsList[0])

	sb.(*sandbox).config.dnsOptionsList = []string{"ndots:5"}
	err = sb.(*sandbox).setupDNS()
	require.NoError(t, err)
	currRC, err = resolvconf.GetSpecific(sb.(*sandbox).config.resolvConfPath)
	require.NoError(t, err)
	dnsOptionsList = resolvconf.GetOptions(currRC.Content)
	assert.Equal(t, 1, len(dnsOptionsList), "There should be only 1 option instead:", dnsOptionsList)
	assert.Equal(t, "ndots:5", dnsOptionsList[0], "The option must be ndots:5 instead:", dnsOptionsList[0])

	err = sb.(*sandbox).rebuildDNS()
	require.NoError(t, err)
	currRC, err = resolvconf.GetSpecific(sb.(*sandbox).config.resolvConfPath)
	require.NoError(t, err)
	dnsOptionsList = resolvconf.GetOptions(currRC.Content)
	assert.Equal(t, 1, len(dnsOptionsList), "There should be only 1 option instead:", dnsOptionsList)
	assert.Equal(t, "ndots:5", dnsOptionsList[0], "The option must be ndots:5 instead:", dnsOptionsList[0])
}
<commit_msg>Remove redundant and faulty assert messages<commit_after>package libnetwork

import (
	"net"
	"testing"

	"github.com/docker/libnetwork/resolvconf"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

func TestCleanupServiceDiscovery(t *testing.T) {
	c, err := New()
	require.NoError(t, err)
	defer c.Stop()

	n1, err := c.NewNetwork("bridge", "net1", "", nil)
	require.NoError(t, err)
	defer n1.Delete()

	n2, err := c.NewNetwork("bridge", "net2", "", nil)
	require.NoError(t, err)
	defer n2.Delete()

	n1.(*network).addSvcRecords("N1ep1", "service_test", "serviceID1", net.ParseIP("192.168.0.1"), net.IP{}, true, "test")
	n1.(*network).addSvcRecords("N2ep2", "service_test", "serviceID2", net.ParseIP("192.168.0.2"), net.IP{}, true, "test")

	n2.(*network).addSvcRecords("N2ep1", "service_test", "serviceID1", net.ParseIP("192.168.1.1"), net.IP{}, true, "test")
	n2.(*network).addSvcRecords("N2ep2", "service_test", "serviceID2", net.ParseIP("192.168.1.2"), net.IP{}, true, "test")

	if len(c.(*controller).svcRecords) != 2 {
		t.Fatalf("Service record not added correctly:%v", c.(*controller).svcRecords)
	}

	// cleanup net1
	c.(*controller).cleanupServiceDiscovery(n1.ID())

	if len(c.(*controller).svcRecords) != 1 {
		t.Fatalf("Service record not cleaned correctly:%v", c.(*controller).svcRecords)
	}

	c.(*controller).cleanupServiceDiscovery("")

	if len(c.(*controller).svcRecords) != 0 {
		t.Fatalf("Service record not cleaned correctly:%v", c.(*controller).svcRecords)
	}
}

func TestDNSOptions(t *testing.T) {
	c, err := New()
	require.NoError(t, err)

	sb, err := c.(*controller).NewSandbox("cnt1", nil)
	require.NoError(t, err)
	defer sb.Delete()
	sb.(*sandbox).startResolver(false)

	err = sb.(*sandbox).setupDNS()
	require.NoError(t, err)
	err = sb.(*sandbox).rebuildDNS()
	require.NoError(t, err)
	currRC, err := resolvconf.GetSpecific(sb.(*sandbox).config.resolvConfPath)
	require.NoError(t, err)
	dnsOptionsList := resolvconf.GetOptions(currRC.Content)
	assert.Equal(t, 1, len(dnsOptionsList))
	assert.Equal(t, "ndots:0", dnsOptionsList[0])

	sb.(*sandbox).config.dnsOptionsList = []string{"ndots:5"}
	err = sb.(*sandbox).setupDNS()
	require.NoError(t, err)
	currRC, err = resolvconf.GetSpecific(sb.(*sandbox).config.resolvConfPath)
	require.NoError(t, err)
	dnsOptionsList = resolvconf.GetOptions(currRC.Content)
	assert.Equal(t, 1, len(dnsOptionsList))
	assert.Equal(t, "ndots:5", dnsOptionsList[0])

	err = sb.(*sandbox).rebuildDNS()
	require.NoError(t, err)
	currRC, err = resolvconf.GetSpecific(sb.(*sandbox).config.resolvConfPath)
	require.NoError(t, err)
	dnsOptionsList = resolvconf.GetOptions(currRC.Content)
	assert.Equal(t, 1, len(dnsOptionsList))
	assert.Equal(t, "ndots:5", dnsOptionsList[0])
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"github.com/bborbe/backup/constants"
	"github.com/bborbe/backup/date"
	"github.com/golang/glog"
	"io/ioutil"
	"os"
	"time"
)

type host struct {
	Active      bool   `json:"active"`
	User        string `json:"user"`
	Host        string `json:"host"`
	Port        int    `json:"port"`
	Directory   string `json:"dir"`
	ExcludeFrom string `json:"exclude_from"`
}

func (h *host) Backup(targetDirectory targetDirectory) error {
	glog.V(2).Infof("create backup of %s on target: %s", h.Host, targetDirectory)

	if err := h.createCurrentDirectory(targetDirectory); err != nil {
		glog.V(2).Infof("create current failed: %v", err)
		return err
	}

	found, err := h.todayHasAlreadyBackup(targetDirectory)
	if err != nil {
		glog.V(2).Infof("search for existing backups failed: %v", err)
		return err
	}
	if found {
		glog.V(2).Infof("skip backup => already exists")
		return nil
	}

	if err := h.createToDirectory(targetDirectory); err != nil {
		glog.V(2).Infof("create target directory failed: %v", err)
		return err
	}

	if err := h.rsync(targetDirectory); err != nil {
		glog.V(2).Infof("rsync failed: %v", err)
		return err
	}

	backupDate := time.Now()

	if err := h.renameIncompleteToDate(targetDirectory, backupDate); err != nil {
		glog.V(2).Infof("rename incomplete to date failed: %v", err)
		return err
	}

	if err := h.deleteCurrentSymlink(targetDirectory); err != nil {
		glog.V(2).Infof("delete current symlink failed: %v", err)
		return err
	}

	if err := h.createCurrentSymlink(targetDirectory, backupDate); err != nil {
		glog.V(2).Infof("create new current symlink failed: %v", err)
		return err
	}

	if err := h.deleteEmpty(targetDirectory); err != nil {
		glog.V(2).Infof("delete empty dir failed: %v", err)
		return err
	}

	glog.V(2).Infof("backup completed")
	return nil
}

func (h *host) rsync(targetDirectory targetDirectory) error {
	return runRsync(
		"-azP",
		"-e",
		fmt.Sprintf("ssh -p %d", h.Port),
		"--delete",
		"--delete-excluded",
		fmt.Sprintf("--port=%d", h.Port),
		fmt.Sprintf("--exclude-from=%s", h.ExcludeFrom),
		fmt.Sprintf("--link-dest=%s", h.linkDest(targetDirectory)),
		h.from(),
		h.to(targetDirectory),
	)
}

func (h *host) renameIncompleteToDate(targetDirectory targetDirectory, date time.Time) error {
	glog.V(2).Infof("rename incomplete on target: %s", targetDirectory)
	return os.Rename(h.incomplete(targetDirectory), h.date(targetDirectory, date))
}

func (h *host) deleteCurrentSymlink(targetDirectory targetDirectory) error {
	glog.V(2).Infof("delete current symlink on target: %s", targetDirectory)
	return os.Remove(h.current(targetDirectory))
}

func (h *host) createCurrentSymlink(targetDirectory targetDirectory, date time.Time) error {
	glog.V(2).Infof("create current symlink on target: %s", targetDirectory)
	if err := os.Symlink(h.date(targetDirectory, date), h.current(targetDirectory)); err != nil {
		glog.V(2).Infof("create symlink to current failed: %v", err)
		return err
	}
	return nil
}

func (h *host) deleteEmpty(targetDirectory targetDirectory) error {
	glog.V(2).Infof("delete empty directory on target: %s", targetDirectory)
	os.RemoveAll(h.empty(targetDirectory))
	return nil
}

func (h *host) createCurrentDirectory(targetDirectory targetDirectory) error {
	glog.V(2).Infof("create current directory on target: %s", targetDirectory)
	_, err := os.Stat(h.current(targetDirectory))
	if os.IsNotExist(err) {
		glog.V(2).Infof("current not existing")
		if err := os.MkdirAll(h.empty(targetDirectory)+h.Directory, 0700); err != nil {
			glog.V(2).Infof("create empty directory failed: %v", err)
			return err
		}
		if err := os.Symlink(h.empty(targetDirectory), h.current(targetDirectory)); err != nil {
			glog.V(2).Infof("create symlink from empty to current failed: %v", err)
			return err
		}
	}
	glog.V(2).Infof("create current completed")
	return nil
}

func (h *host) todayHasAlreadyBackup(targetDirectory targetDirectory) (bool, error) {
	glog.V(2).Infof("search if backup already exists on target: %s", targetDirectory)
	hostDirectory := fmt.Sprintf("%s/%s", targetDirectory, h.Host)
	dirs, err := ioutil.ReadDir(hostDirectory)
	if err != nil {
		glog.V(2).Infof("read directory %s failed: %v", targetDirectory, err)
		return false, err
	}
	for _, dir := range dirs {
		timeOfDir, err := time.Parse(constants.DATEFORMAT, dir.Name())
		if err != nil {
			glog.V(2).Infof("parse date of dir failed")
			continue
		}
		if date.DayEqual(timeOfDir, time.Now()) {
			glog.V(2).Infof("found backup for today")
			return true, nil
		}
	}
	glog.V(2).Infof("found no backup for today")
	return false, nil
}

func (h *host) createToDirectory(targetDirectory targetDirectory) error {
	glog.V(2).Infof("create to directory on target: %s", targetDirectory)
	return os.MkdirAll(h.to(targetDirectory), 0700)
}

func (h *host) from() string {
	return fmt.Sprintf("%s@%s:%s", h.User, h.Host, h.Directory)
}

func (h *host) to(targetDirectory targetDirectory) string {
	return fmt.Sprintf("%s/%s/incomplete%s", targetDirectory, h.Host, h.Directory)
}

func (h *host) incomplete(targetDirectory targetDirectory) string {
	return fmt.Sprintf("%s/%s/incomplete", targetDirectory, h.Host)
}

func (h *host) current(targetDirectory targetDirectory) string {
	return fmt.Sprintf("%s/%s/current", targetDirectory, h.Host)
}

func (h *host) empty(targetDirectory targetDirectory) string {
	return fmt.Sprintf("%s/%s/empty", targetDirectory, h.Host)
}

func (h *host) date(targetDirectory targetDirectory, date time.Time) string {
	return fmt.Sprintf("%s/%s/%s", targetDirectory, h.Host, date.Format(constants.DATEFORMAT))
}

func (h *host) linkDest(targetDirectory targetDirectory) string {
	return fmt.Sprintf("%s/%s/current%s", targetDirectory, h.Host, h.Directory)
}

func (h *host) Validate() error {
	glog.V(2).Infof("validate host: %s", h.Host)
	if len(h.User) == 0 {
		return fmt.Errorf("user invalid")
	}
	if len(h.Host) == 0 {
		return fmt.Errorf("host invalid")
	}
	if h.Port <= 0 {
		return fmt.Errorf("port invalid")
	}
	if len(h.Directory) == 0 {
		return fmt.Errorf("directory invalid")
	}
	if h.Directory[len(h.Directory)-1:] != "/" {
		return fmt.Errorf("directory not ending with '/'")
	}
	return nil
}
<commit_msg>disable ssh strict host check<commit_after>package main

import (
	"fmt"
	"github.com/bborbe/backup/constants"
	"github.com/bborbe/backup/date"
	"github.com/golang/glog"
	"io/ioutil"
	"os"
	"time"
)

type host struct {
	Active      bool   `json:"active"`
	User        string `json:"user"`
	Host        string `json:"host"`
	Port        int    `json:"port"`
	Directory   string `json:"dir"`
	ExcludeFrom string `json:"exclude_from"`
}

func (h *host) Backup(targetDirectory targetDirectory) error {
	glog.V(2).Infof("create backup of %s on target: %s", h.Host, targetDirectory)

	if err := h.createCurrentDirectory(targetDirectory); err != nil {
		glog.V(2).Infof("create current failed: %v", err)
		return err
	}

	found, err := h.todayHasAlreadyBackup(targetDirectory)
	if err != nil {
		glog.V(2).Infof("search for existing backups failed: %v", err)
		return err
	}
	if found {
		glog.V(2).Infof("skip backup => already exists")
		return nil
	}

	if err := h.createToDirectory(targetDirectory); err != nil {
		glog.V(2).Infof("create target directory failed: %v", err)
		return err
	}

	if err := h.rsync(targetDirectory); err != nil {
		glog.V(2).Infof("rsync failed: %v", err)
		return err
	}

	backupDate := time.Now()

	if err := h.renameIncompleteToDate(targetDirectory, backupDate); err != nil {
		glog.V(2).Infof("rename incomplete to date failed: %v", err)
		return err
	}

	if err := h.deleteCurrentSymlink(targetDirectory); err != nil {
		glog.V(2).Infof("delete current symlink failed: %v", err)
		return err
	}

	if err := h.createCurrentSymlink(targetDirectory, backupDate); err != nil {
		glog.V(2).Infof("create new current symlink failed: %v", err)
		return err
	}

	if err := h.deleteEmpty(targetDirectory); err != nil {
		glog.V(2).Infof("delete empty dir failed: %v", err)
		return err
	}

	glog.V(2).Infof("backup completed")
	return nil
}

func (h *host) rsync(targetDirectory targetDirectory) error {
	return runRsync(
		"-azP",
		"-e",
		fmt.Sprintf("ssh -o StrictHostKeyChecking=no -p %d", h.Port),
		"--delete",
		"--delete-excluded",
		fmt.Sprintf("--port=%d", h.Port),
		fmt.Sprintf("--exclude-from=%s", h.ExcludeFrom),
		fmt.Sprintf("--link-dest=%s", h.linkDest(targetDirectory)),
		h.from(),
		h.to(targetDirectory),
	)
}

func (h *host) renameIncompleteToDate(targetDirectory targetDirectory, date time.Time) error {
	glog.V(2).Infof("rename incomplete on target: %s", targetDirectory)
	return os.Rename(h.incomplete(targetDirectory), h.date(targetDirectory, date))
}

func (h *host) deleteCurrentSymlink(targetDirectory targetDirectory) error {
	glog.V(2).Infof("delete current symlink on target: %s", targetDirectory)
	return os.Remove(h.current(targetDirectory))
}

func (h *host) createCurrentSymlink(targetDirectory targetDirectory, date time.Time) error {
	glog.V(2).Infof("create current symlink on target: %s", targetDirectory)
	if err := os.Symlink(h.date(targetDirectory, date), h.current(targetDirectory)); err != nil {
		glog.V(2).Infof("create symlink to current failed: %v", err)
		return err
	}
	return nil
}

func (h *host) deleteEmpty(targetDirectory targetDirectory) error {
	glog.V(2).Infof("delete empty directory on target: %s", targetDirectory)
	os.RemoveAll(h.empty(targetDirectory))
	return nil
}

func (h *host) createCurrentDirectory(targetDirectory targetDirectory) error {
	glog.V(2).Infof("create current directory on target: %s", targetDirectory)
	_, err := os.Stat(h.current(targetDirectory))
	if os.IsNotExist(err) {
		glog.V(2).Infof("current not existing")
		if err := os.MkdirAll(h.empty(targetDirectory)+h.Directory, 0700); err != nil {
			glog.V(2).Infof("create empty directory failed: %v", err)
			return err
		}
		if err := os.Symlink(h.empty(targetDirectory), h.current(targetDirectory)); err != nil {
			glog.V(2).Infof("create symlink from empty to current failed: %v", err)
			return err
		}
	}
	glog.V(2).Infof("create current completed")
	return nil
}

func (h *host) todayHasAlreadyBackup(targetDirectory targetDirectory) (bool, error) {
	glog.V(2).Infof("search if backup already exists on target: %s", targetDirectory)
	hostDirectory := fmt.Sprintf("%s/%s", targetDirectory, h.Host)
	dirs, err := ioutil.ReadDir(hostDirectory)
	if err != nil {
		glog.V(2).Infof("read directory %s failed: %v", targetDirectory, err)
		return false, err
	}
	for _, dir := range dirs {
		timeOfDir, err := time.Parse(constants.DATEFORMAT, dir.Name())
		if err != nil {
			glog.V(2).Infof("parse date of dir failed")
			continue
		}
		if date.DayEqual(timeOfDir, time.Now()) {
			glog.V(2).Infof("found backup for today")
			return true, nil
		}
	}
	glog.V(2).Infof("found no backup for today")
	return false, nil
}

func (h *host) createToDirectory(targetDirectory targetDirectory) error {
	glog.V(2).Infof("create to directory on target: %s", targetDirectory)
	return os.MkdirAll(h.to(targetDirectory), 0700)
}

func (h *host) from() string {
	return fmt.Sprintf("%s@%s:%s", h.User, h.Host, h.Directory)
}

func (h *host) to(targetDirectory targetDirectory) string {
	return fmt.Sprintf("%s/%s/incomplete%s", targetDirectory, h.Host, h.Directory)
}

func (h *host) incomplete(targetDirectory targetDirectory) string {
	return fmt.Sprintf("%s/%s/incomplete", targetDirectory, h.Host)
}

func (h *host) current(targetDirectory targetDirectory) string {
	return fmt.Sprintf("%s/%s/current", targetDirectory, h.Host)
}

func (h *host) empty(targetDirectory targetDirectory) string {
	return fmt.Sprintf("%s/%s/empty", targetDirectory, h.Host)
}

func (h *host) date(targetDirectory targetDirectory, date time.Time) string {
	return fmt.Sprintf("%s/%s/%s", targetDirectory, h.Host, date.Format(constants.DATEFORMAT))
}

func (h *host) linkDest(targetDirectory targetDirectory) string {
	return fmt.Sprintf("%s/%s/current%s", targetDirectory, h.Host, h.Directory)
}

func (h *host) Validate() error {
	glog.V(2).Infof("validate host: %s", h.Host)
	if len(h.User) == 0 {
		return fmt.Errorf("user invalid")
	}
	if len(h.Host) == 0 {
		return fmt.Errorf("host invalid")
	}
	if h.Port <= 0 {
		return fmt.Errorf("port invalid")
	}
	if len(h.Directory) == 0 {
		return fmt.Errorf("directory invalid")
	}
	if h.Directory[len(h.Directory)-1:] != "/" {
		return fmt.Errorf("directory not ending with '/'")
	}
	return nil
}
<|endoftext|>


<commit_before>package buffer

import (
	"fmt"
	"time"

	. "project.go/obj"
)

func Manager() (chan<- Message, chan<- Message, <-chan Message) {
	push_channel := make(chan Message)
	pop_channel := make(chan Message)

	resend_channel := make(chan Message)
	pop_success_channel := make(chan Message)
	//clear_channel := make(chan bool)

	buffer_map := make(map[int]chan<- Message)

	go func() {
		for {
			select {
			case msg := <-push_channel:
				buffer_map[msg.Hash()] = worker(msg, resend_channel, pop_success_channel)
			case msg := <-pop_channel:
				fmt.Println("Pop:", msg)
				buffer_map[msg.Hash()] <- msg
			case msg := <-pop_success_channel:
				delete(buffer_map, msg.Hash())
			}
		}
	}()
	return push_channel, pop_channel, resend_channel
}

func worker(msg Message, resend_channel, pop_success_channel chan<- Message) chan<- Message {
	pop_worker_channel := make(chan Message)
	go func() {
		for {
			select {
			case <-time.After(2 * time.Second):
				resend_channel <- msg
			case <-pop_worker_channel:
				pop_success_channel <- msg
				close(pop_worker_channel)
				return
			}
		}
	}()
	return pop_worker_channel
}
<commit_msg>Clear channel for buffer.<commit_after>package buffer

import (
	//"fmt"
	"time"

	. "project.go/obj"
)

func Manager() (chan<- Message, chan<- Message, chan<- struct{}, <-chan Message) {
	push_channel := make(chan Message)
	pop_channel := make(chan Message)
	clear_channel := make(chan struct{})

	resend_channel := make(chan Message)
	pop_success_channel := make(chan Message)

	buffer_map := make(map[int]chan<- struct{})

	go func() {
		for {
			select {
			case msg := <-push_channel:
				buffer_map[msg.Hash()] = worker(msg, resend_channel, pop_success_channel)
			case msg := <-pop_channel:
				buffer_map[msg.Hash()] <- struct{}{}
			case msg := <-pop_success_channel:
				delete(buffer_map, msg.Hash())
			case <-clear_channel:
				for _, value := range buffer_map {
					value <-struct{}{};
				}
			}
		}
	}()
	return push_channel, pop_channel, clear_channel, resend_channel
}

func worker(msg Message, resend_channel, pop_success_channel chan<- Message) chan<- struct{} {
	pop_worker_channel := make(chan struct{})
	go func() {
		for {
			select {
			case <-time.After(2 * time.Second):
				resend_channel <- msg
			case <-pop_worker_channel:
				pop_success_channel <- msg
				close(pop_worker_channel)
				return
			}
		}
	}()
	return pop_worker_channel
}
<|endoftext|>


<commit_before>package source

import (
	"github.com/pkg/errors"
	"github.com/dpb587/metalink/repository"
	"github.com/dpb587/metalink/repository/filter"
)

func FilterInMemory(files []repository.RepositoryMetalink, filter filter.Filter) ([]repository.RepositoryMetalink, error) {
	results := []repository.RepositoryMetalink{}

	for _, meta4 := range files {
		matched, err := filter.IsTrue(meta4)
		if err != nil {
			return nil, errors.Wrap(err, "Matching metalink")
		} else if !matched {
			continue
		}

		results = append(results, meta4)
	}

	return results, nil
}
<commit_msg>Include meta4 path in filtering errors<commit_after>package source

import (
	"github.com/dpb587/metalink/repository"
	"github.com/dpb587/metalink/repository/filter"
	"github.com/pkg/errors"
)

func FilterInMemory(files []repository.RepositoryMetalink, filter filter.Filter) ([]repository.RepositoryMetalink, error) {
	results := []repository.RepositoryMetalink{}

	for _, meta4 := range files {
		matched, err := filter.IsTrue(meta4)
		if err != nil {
			return nil, errors.Wrapf(err, "Matching metalink %s", meta4.Reference.Path)
		} else if !matched {
			continue
		}

		results = append(results, meta4)
	}

	return results, nil
}
<|endoftext|>


<commit_before>package main

import (
	"os/exec"
	"strings"
	"encoding/gob"
	"fmt"
	"bufio"
	"io"
	"log"
	"net"
	"os"

	"github.com/flynn/go-discover/discover"
	"github.com/flynn/lorne/types"
	"github.com/titanous/go-dockerclient"
	"github.com/flynn/sampi/types"
	sc "github.com/flynn/sampi/client"
	lc "github.com/flynn/lorne/client"
)

// WARNING: assumes one host at the moment (firstHost will always be the same)

var sd *discover.Client
var sched *sc.Client
var host *lc.Client
var hostid string

func init() {
	var err error
	sd, err = discover.NewClient()
	if err != nil {
		log.Fatal(err)
	}

	sched, err = sc.New()
	if err != nil {
		log.Fatal(err)
	}

	hostid = findHost()
	host, err = lc.New(hostid)
	if err != nil {
		log.Fatal(err)
	}
}

func main() {
	root := "/var/lib/demo/apps"
	hostname := shell("curl -s icanhazip.com")

	set, _ := sd.Services("shelf")
	addrs := set.OnlineAddrs()
	if len(addrs) < 1 {
		panic("Shelf is not discoverable")
	}
	shelfHost := addrs[0]

	app := os.Args[2]
	os.MkdirAll(root+"/"+app, 0755)

	fmt.Printf("-----> Building %s on %s ...\n", app, hostname)

	scheduleAndAttach(app + ".build", docker.Config{
		Image:        "flynn/slugbuilder",
		Cmd:          []string{"http://" + shelfHost + "/" + app + ".tgz"},
		Tty:          false,
		AttachStdin:  true,
		AttachStdout: true,
		AttachStderr: true,
		OpenStdin:    true,
		StdinOnce:    true,
	})

	fmt.Printf("-----> Deploying %s ...\n", app)

	jobid := app + ".web"

	stopIfExists(jobid)
	scheduleWithTcpPort(jobid, docker.Config{
		Image:        "flynn/slugrunner",
		Cmd:          []string{"start web"},
		Tty:          false,
		AttachStdin:  false,
		AttachStdout: false,
		AttachStderr: false,
		OpenStdin:    false,
		StdinOnce:    false,
		Env: []string{
			"SLUG_URL=http://" + shelfHost + "/" + app + ".tgz",
		},
	})

	fmt.Printf("=====> Application deployed!\n")
	//fmt.Printf("       %s\n", getUrl(jobid))
	fmt.Println("")

}

func shell(cmdline string) string {
        out, err := exec.Command("bash", "-c", cmdline).Output()
        if err != nil {
                panic(err)
        }
        return strings.Trim(string(out), " \n")
}

func stopIfExists(jobid string) {
	_, err := host.GetJob(jobid)
	if err != nil {
		return
	}
	if err := host.StopJob(jobid); err != nil {
		log.Fatal(err)
	}
}

func scheduleWithTcpPort(jobid string, config docker.Config) {

	schedReq := &sampi.ScheduleReq{
		Incremental: true,
		HostJobs:    map[string][]*sampi.Job{hostid: {{ID: jobid, Config: &config, TCPPorts: 1}}},
	}
	if _, err := sched.Schedule(schedReq); err != nil {
		log.Fatal(err)
	}
}

/*func getUrl(jobid string) string {
	if job, err := host.GetJob(jobid); err != nil {
		log.Fatal(err)
	}
}*/

func findHost() string {
	state, err := sched.State()
	if err != nil {
		log.Fatal(err)
	}

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

func scheduleAndAttach(jobid string, config docker.Config) {

	services, err := sd.Services("flynn-lorne-attach." + hostid)
	if err != nil {
		log.Fatal(err)
	}
	conn, err := net.Dial("tcp", services.OnlineAddrs()[0])
	if err != nil {
		log.Fatal(err)
	}
	err = gob.NewEncoder(conn).Encode(&lorne.AttachReq{
		JobID: jobid,
		Flags: lorne.AttachFlagStdout | lorne.AttachFlagStderr | lorne.AttachFlagStdin | lorne.AttachFlagStream,
	})
	if err != nil {
		log.Fatal(err)
	}
	attachState := make([]byte, 1)
	if _, err := conn.Read(attachState); err != nil {
		log.Fatal(err)
	}
	switch attachState[0] {
	case lorne.AttachError:
		log.Fatal("attach error")
	}

	schedReq := &sampi.ScheduleReq{
		Incremental: true,
		HostJobs:    map[string][]*sampi.Job{hostid: {{ID: jobid, Config: &config}}},
	}
	if _, err := sched.Schedule(schedReq); err != nil {
		log.Fatal(err)
	}

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

	go func() {
		io.Copy(conn, os.Stdin)
		conn.(*net.TCPConn).CloseWrite()
	}()
	scanner := bufio.NewScanner(conn)
	for scanner.Scan() {
		fmt.Fprintln(os.Stdout, scanner.Text()[8:])
	}
	conn.Close()
}
<commit_msg>don't error<commit_after>package main

import (
	"os/exec"
	"strings"
	"encoding/gob"
	"fmt"
	"bufio"
	"io"
	"log"
	"net"
	"os"

	"github.com/flynn/go-discover/discover"
	"github.com/flynn/lorne/types"
	"github.com/titanous/go-dockerclient"
	"github.com/flynn/sampi/types"
	sc "github.com/flynn/sampi/client"
	lc "github.com/flynn/lorne/client"
)

// WARNING: assumes one host at the moment (firstHost will always be the same)

var sd *discover.Client
var sched *sc.Client
var host *lc.Client
var hostid string

func init() {
	var err error
	sd, err = discover.NewClient()
	if err != nil {
		log.Fatal(err)
	}

	sched, err = sc.New()
	if err != nil {
		log.Fatal(err)
	}

	hostid = findHost()
	host, err = lc.New(hostid)
	if err != nil {
		log.Fatal(err)
	}
}

func main() {
	root := "/var/lib/demo/apps"
	hostname := shell("curl -s icanhazip.com")

	set, _ := sd.Services("shelf")
	addrs := set.OnlineAddrs()
	if len(addrs) < 1 {
		panic("Shelf is not discoverable")
	}
	shelfHost := addrs[0]

	app := os.Args[2]
	os.MkdirAll(root+"/"+app, 0755)

	fmt.Printf("-----> Building %s on %s ...\n", app, hostname)

	scheduleAndAttach(app + ".build", docker.Config{
		Image:        "flynn/slugbuilder",
		Cmd:          []string{"http://" + shelfHost + "/" + app + ".tgz"},
		Tty:          false,
		AttachStdin:  true,
		AttachStdout: true,
		AttachStderr: true,
		OpenStdin:    true,
		StdinOnce:    true,
	})

	fmt.Printf("-----> Deploying %s ...\n", app)

	jobid := app + ".web"

	stopIfExists(jobid)
	scheduleWithTcpPort(jobid, docker.Config{
		Image:        "flynn/slugrunner",
		Cmd:          []string{"start web"},
		Tty:          false,
		AttachStdin:  false,
		AttachStdout: false,
		AttachStderr: false,
		OpenStdin:    false,
		StdinOnce:    false,
		Env: []string{
			"SLUG_URL=http://" + shelfHost + "/" + app + ".tgz",
		},
	})

	fmt.Printf("=====> Application deployed!\n")
	//fmt.Printf("       %s\n", getUrl(jobid))
	fmt.Println("")

}

func shell(cmdline string) string {
        out, err := exec.Command("bash", "-c", cmdline).Output()
        if err != nil {
                panic(err)
        }
        return strings.Trim(string(out), " \n")
}

func stopIfExists(jobid string) {
	_, err := host.GetJob(jobid)
	if err != nil {
		return
	}
	if err := host.StopJob(jobid); err != nil {
		return
	}
}

func scheduleWithTcpPort(jobid string, config docker.Config) {

	schedReq := &sampi.ScheduleReq{
		Incremental: true,
		HostJobs:    map[string][]*sampi.Job{hostid: {{ID: jobid, Config: &config, TCPPorts: 1}}},
	}
	if _, err := sched.Schedule(schedReq); err != nil {
		log.Fatal(err)
	}
}

/*func getUrl(jobid string) string {
	if job, err := host.GetJob(jobid); err != nil {
		log.Fatal(err)
	}
}*/

func findHost() string {
	state, err := sched.State()
	if err != nil {
		log.Fatal(err)
	}

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

func scheduleAndAttach(jobid string, config docker.Config) {

	services, err := sd.Services("flynn-lorne-attach." + hostid)
	if err != nil {
		log.Fatal(err)
	}
	conn, err := net.Dial("tcp", services.OnlineAddrs()[0])
	if err != nil {
		log.Fatal(err)
	}
	err = gob.NewEncoder(conn).Encode(&lorne.AttachReq{
		JobID: jobid,
		Flags: lorne.AttachFlagStdout | lorne.AttachFlagStderr | lorne.AttachFlagStdin | lorne.AttachFlagStream,
	})
	if err != nil {
		log.Fatal(err)
	}
	attachState := make([]byte, 1)
	if _, err := conn.Read(attachState); err != nil {
		log.Fatal(err)
	}
	switch attachState[0] {
	case lorne.AttachError:
		log.Fatal("attach error")
	}

	schedReq := &sampi.ScheduleReq{
		Incremental: true,
		HostJobs:    map[string][]*sampi.Job{hostid: {{ID: jobid, Config: &config}}},
	}
	if _, err := sched.Schedule(schedReq); err != nil {
		log.Fatal(err)
	}

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

	go func() {
		io.Copy(conn, os.Stdin)
		conn.(*net.TCPConn).CloseWrite()
	}()
	scanner := bufio.NewScanner(conn)
	for scanner.Scan() {
		fmt.Fprintln(os.Stdout, scanner.Text()[8:])
	}
	conn.Close()
}
<|endoftext|>


<commit_before>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package openstack

import (
	"context"
	"fmt"
	"regexp"

	"github.com/golang/glog"
	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"

	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/kubernetes/pkg/cloudprovider"
)

// Instances encapsulates an implementation of Instances for OpenStack.
type Instances struct {
	compute *gophercloud.ServiceClient
	opts    MetadataOpts
}

// Instances returns an implementation of Instances for OpenStack.
func (os *OpenStack) Instances() (cloudprovider.Instances, bool) {
	glog.V(4).Info("openstack.Instances() called")

	compute, err := os.NewComputeV2()
	if err != nil {
		return nil, false
	}

	glog.V(4).Info("Claiming to support Instances")

	return &Instances{
		compute: compute,
		opts:    os.metadataOpts,
	}, true
}

// CurrentNodeName implements Instances.CurrentNodeName
// Note this is *not* necessarily the same as hostname.
func (i *Instances) CurrentNodeName(ctx context.Context, hostname string) (types.NodeName, error) {
	md, err := getMetadata(i.opts.SearchOrder)
	if err != nil {
		return "", err
	}
	return types.NodeName(md.Hostname), nil
}

// AddSSHKeyToAllInstances is not implemented for OpenStack
func (i *Instances) AddSSHKeyToAllInstances(ctx context.Context, user string, keyData []byte) error {
	return cloudprovider.NotImplemented
}

// NodeAddresses implements Instances.NodeAddresses
func (i *Instances) NodeAddresses(ctx context.Context, name types.NodeName) ([]v1.NodeAddress, error) {
	glog.V(4).Infof("NodeAddresses(%v) called", name)

	addrs, err := getAddressesByName(i.compute, name)
	if err != nil {
		return nil, err
	}

	glog.V(4).Infof("NodeAddresses(%v) => %v", name, addrs)
	return addrs, nil
}

// NodeAddressesByProviderID returns the node addresses of an instances with the specified unique providerID
// This method will not be called from the node that is requesting this ID. i.e. metadata service
// and other local methods cannot be used here
func (i *Instances) NodeAddressesByProviderID(ctx context.Context, providerID string) ([]v1.NodeAddress, error) {
	instanceID, err := instanceIDFromProviderID(providerID)

	if err != nil {
		return []v1.NodeAddress{}, err
	}

	server, err := servers.Get(i.compute, instanceID).Extract()

	if err != nil {
		return []v1.NodeAddress{}, err
	}

	addresses, err := nodeAddresses(server)
	if err != nil {
		return []v1.NodeAddress{}, err
	}

	return addresses, nil
}

// ExternalID returns the cloud provider ID of the specified instance (deprecated).
func (i *Instances) ExternalID(ctx context.Context, name types.NodeName) (string, error) {
	srv, err := getServerByName(i.compute, name)
	if err != nil {
		if err == ErrNotFound {
			return "", cloudprovider.InstanceNotFound
		}
		return "", err
	}
	return srv.ID, nil
}

// InstanceExistsByProviderID returns true if the instance with the given provider id still exists and is running.
// If false is returned with no error, the instance will be immediately deleted by the cloud controller manager.
func (i *Instances) InstanceExistsByProviderID(ctx context.Context, providerID string) (bool, error) {
	instanceID, err := instanceIDFromProviderID(providerID)
	if err != nil {
		return false, err
	}

	server, err := servers.Get(i.compute, instanceID).Extract()
	if err != nil {
		if isNotFound(err) {
			return false, nil
		}
		return false, err
	}

	if server.Status != "ACTIVE" {
		glog.Warningf("the instance %s is not active", instanceID)
		return false, nil
	}

	return true, nil
}

// InstanceID returns the kubelet's cloud provider ID.
func (os *OpenStack) InstanceID() (string, error) {
	if len(os.localInstanceID) == 0 {
		id, err := readInstanceID(os.metadataOpts.SearchOrder)
		if err != nil {
			return "", err
		}
		os.localInstanceID = id
	}
	return os.localInstanceID, nil
}

// InstanceID returns the cloud provider ID of the specified instance.
func (i *Instances) InstanceID(ctx context.Context, name types.NodeName) (string, error) {
	srv, err := getServerByName(i.compute, name)
	if err != nil {
		if err == ErrNotFound {
			return "", cloudprovider.InstanceNotFound
		}
		return "", err
	}
	// In the future it is possible to also return an endpoint as:
	// <endpoint>/<instanceid>
	return "/" + srv.ID, nil
}

// InstanceTypeByProviderID returns the cloudprovider instance type of the node with the specified unique providerID
// This method will not be called from the node that is requesting this ID. i.e. metadata service
// and other local methods cannot be used here
func (i *Instances) InstanceTypeByProviderID(ctx context.Context, providerID string) (string, error) {
	instanceID, err := instanceIDFromProviderID(providerID)

	if err != nil {
		return "", err
	}

	server, err := servers.Get(i.compute, instanceID).Extract()

	if err != nil {
		return "", err
	}

	return srvInstanceType(server)
}

// InstanceType returns the type of the specified instance.
func (i *Instances) InstanceType(ctx context.Context, name types.NodeName) (string, error) {
	srv, err := getServerByName(i.compute, name)

	if err != nil {
		return "", err
	}

	return srvInstanceType(srv)
}

func srvInstanceType(srv *servers.Server) (string, error) {
	keys := []string{"name", "id", "original_name"}
	for _, key := range keys {
		val, found := srv.Flavor[key]
		if found {
			flavor, ok := val.(string)
			if ok {
				return flavor, nil
			}
		}
	}
	return "", fmt.Errorf("flavor name/id not found")
}

// instanceIDFromProviderID splits a provider's id and return instanceID.
// A providerID is build out of '${ProviderName}:///${instance-id}'which contains ':///'.
// See cloudprovider.GetInstanceProviderID and Instances.InstanceID.
func instanceIDFromProviderID(providerID string) (instanceID string, err error) {
	// If Instances.InstanceID or cloudprovider.GetInstanceProviderID is changed, the regexp should be changed too.
	var providerIDRegexp = regexp.MustCompile(`^` + ProviderName + `:///([^/]+)$`)

	matches := providerIDRegexp.FindStringSubmatch(providerID)
	if len(matches) != 2 {
		return "", fmt.Errorf("ProviderID \"%s\" didn't match expected format \"openstack:///InstanceID\"", providerID)
	}
	return matches[1], nil
}
<commit_msg>Split out the hostname when default dhcp_domain is used in nova.conf<commit_after>/*
Copyright 2016 The Kubernetes Authors.

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

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

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

package openstack

import (
	"context"
	"fmt"
	"regexp"
	"strings"

	"github.com/golang/glog"
	"github.com/gophercloud/gophercloud"
	"github.com/gophercloud/gophercloud/openstack/compute/v2/servers"

	"k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/types"
	"k8s.io/kubernetes/pkg/cloudprovider"
)

// Instances encapsulates an implementation of Instances for OpenStack.
type Instances struct {
	compute *gophercloud.ServiceClient
	opts    MetadataOpts
}

// Instances returns an implementation of Instances for OpenStack.
func (os *OpenStack) Instances() (cloudprovider.Instances, bool) {
	glog.V(4).Info("openstack.Instances() called")

	compute, err := os.NewComputeV2()
	if err != nil {
		return nil, false
	}

	glog.V(4).Info("Claiming to support Instances")

	return &Instances{
		compute: compute,
		opts:    os.metadataOpts,
	}, true
}

// CurrentNodeName implements Instances.CurrentNodeName
// Note this is *not* necessarily the same as hostname.
func (i *Instances) CurrentNodeName(ctx context.Context, hostname string) (types.NodeName, error) {
	md, err := getMetadata(i.opts.SearchOrder)
	if err != nil {
		return "", err
	}
	return types.NodeName(strings.Split(md.Hostname, ".")[0]), nil
}

// AddSSHKeyToAllInstances is not implemented for OpenStack
func (i *Instances) AddSSHKeyToAllInstances(ctx context.Context, user string, keyData []byte) error {
	return cloudprovider.NotImplemented
}

// NodeAddresses implements Instances.NodeAddresses
func (i *Instances) NodeAddresses(ctx context.Context, name types.NodeName) ([]v1.NodeAddress, error) {
	glog.V(4).Infof("NodeAddresses(%v) called", name)

	addrs, err := getAddressesByName(i.compute, name)
	if err != nil {
		return nil, err
	}

	glog.V(4).Infof("NodeAddresses(%v) => %v", name, addrs)
	return addrs, nil
}

// NodeAddressesByProviderID returns the node addresses of an instances with the specified unique providerID
// This method will not be called from the node that is requesting this ID. i.e. metadata service
// and other local methods cannot be used here
func (i *Instances) NodeAddressesByProviderID(ctx context.Context, providerID string) ([]v1.NodeAddress, error) {
	instanceID, err := instanceIDFromProviderID(providerID)

	if err != nil {
		return []v1.NodeAddress{}, err
	}

	server, err := servers.Get(i.compute, instanceID).Extract()

	if err != nil {
		return []v1.NodeAddress{}, err
	}

	addresses, err := nodeAddresses(server)
	if err != nil {
		return []v1.NodeAddress{}, err
	}

	return addresses, nil
}

// ExternalID returns the cloud provider ID of the specified instance (deprecated).
func (i *Instances) ExternalID(ctx context.Context, name types.NodeName) (string, error) {
	srv, err := getServerByName(i.compute, name)
	if err != nil {
		if err == ErrNotFound {
			return "", cloudprovider.InstanceNotFound
		}
		return "", err
	}
	return srv.ID, nil
}

// InstanceExistsByProviderID returns true if the instance with the given provider id still exists and is running.
// If false is returned with no error, the instance will be immediately deleted by the cloud controller manager.
func (i *Instances) InstanceExistsByProviderID(ctx context.Context, providerID string) (bool, error) {
	instanceID, err := instanceIDFromProviderID(providerID)
	if err != nil {
		return false, err
	}

	server, err := servers.Get(i.compute, instanceID).Extract()
	if err != nil {
		if isNotFound(err) {
			return false, nil
		}
		return false, err
	}

	if server.Status != "ACTIVE" {
		glog.Warningf("the instance %s is not active", instanceID)
		return false, nil
	}

	return true, nil
}

// InstanceID returns the kubelet's cloud provider ID.
func (os *OpenStack) InstanceID() (string, error) {
	if len(os.localInstanceID) == 0 {
		id, err := readInstanceID(os.metadataOpts.SearchOrder)
		if err != nil {
			return "", err
		}
		os.localInstanceID = id
	}
	return os.localInstanceID, nil
}

// InstanceID returns the cloud provider ID of the specified instance.
func (i *Instances) InstanceID(ctx context.Context, name types.NodeName) (string, error) {
	srv, err := getServerByName(i.compute, name)
	if err != nil {
		if err == ErrNotFound {
			return "", cloudprovider.InstanceNotFound
		}
		return "", err
	}
	// In the future it is possible to also return an endpoint as:
	// <endpoint>/<instanceid>
	return "/" + srv.ID, nil
}

// InstanceTypeByProviderID returns the cloudprovider instance type of the node with the specified unique providerID
// This method will not be called from the node that is requesting this ID. i.e. metadata service
// and other local methods cannot be used here
func (i *Instances) InstanceTypeByProviderID(ctx context.Context, providerID string) (string, error) {
	instanceID, err := instanceIDFromProviderID(providerID)

	if err != nil {
		return "", err
	}

	server, err := servers.Get(i.compute, instanceID).Extract()

	if err != nil {
		return "", err
	}

	return srvInstanceType(server)
}

// InstanceType returns the type of the specified instance.
func (i *Instances) InstanceType(ctx context.Context, name types.NodeName) (string, error) {
	srv, err := getServerByName(i.compute, name)

	if err != nil {
		return "", err
	}

	return srvInstanceType(srv)
}

func srvInstanceType(srv *servers.Server) (string, error) {
	keys := []string{"name", "id", "original_name"}
	for _, key := range keys {
		val, found := srv.Flavor[key]
		if found {
			flavor, ok := val.(string)
			if ok {
				return flavor, nil
			}
		}
	}
	return "", fmt.Errorf("flavor name/id not found")
}

// instanceIDFromProviderID splits a provider's id and return instanceID.
// A providerID is build out of '${ProviderName}:///${instance-id}'which contains ':///'.
// See cloudprovider.GetInstanceProviderID and Instances.InstanceID.
func instanceIDFromProviderID(providerID string) (instanceID string, err error) {
	// If Instances.InstanceID or cloudprovider.GetInstanceProviderID is changed, the regexp should be changed too.
	var providerIDRegexp = regexp.MustCompile(`^` + ProviderName + `:///([^/]+)$`)

	matches := providerIDRegexp.FindStringSubmatch(providerID)
	if len(matches) != 2 {
		return "", fmt.Errorf("ProviderID \"%s\" didn't match expected format \"openstack:///InstanceID\"", providerID)
	}
	return matches[1], nil
}
<|endoftext|>


<commit_before>package mvm

import (
	"github.com/mafik/mvm/matrix"
	"github.com/mafik/mvm/ui"
	"github.com/mafik/mvm/vec2"
)

type FramePart interface {
	MyFrame() *Frame
}

func (f *Frame) ContentSize() ui.Box {
	return ui.Box{-f.size.Y / 2, f.size.X / 2, f.size.Y / 2, -f.size.X / 2}
}

func (f *Frame) ContentLeft() float64   { return 0 }
func (f *Frame) ContentTop() float64    { return 0 }
func (f *Frame) ContentBottom() float64 { return f.ContentHeight() }
func (f *Frame) ContentRight() float64  { return f.ContentWidth() }
func (f *Frame) ContentWidth() float64  { return f.size.X }
func (f *Frame) ContentHeight() float64 { return f.size.Y }

func (f *Frame) TitleLeft() float64   { return f.ContentLeft() }
func (f *Frame) TitleTop() float64    { return f.TitleBottom() - buttonHeight }
func (f *Frame) TitleBottom() float64 { return f.ContentTop() }
func (f *Frame) TitleRight(m ui.TextMeasurer) float64 {
	return f.TitleLeft() + f.TitleWidth(m)
}
func (f *Frame) TitleWidth(m ui.TextMeasurer) float64 {
	return ButtonSize(m.MeasureText(f.Title()))
}
func (f *Frame) TitleHeight() float64 { return buttonHeight }

func (f *Frame) PayloadLeft(m ui.TextMeasurer) float64 { return f.TitleRight(m) }
func (f *Frame) PayloadTop() float64                   { return f.TitleTop() }
func (f *Frame) PayloadBottom() float64                { return f.TitleBottom() }
func (f *Frame) PayloadRight(s *Shell, m ui.TextMeasurer) float64 {
	return f.PayloadLeft(m) + f.PayloadWidth(s, m)
}
func (f *Frame) PayloadWidth(s *Shell, m ui.TextMeasurer) float64 {
	return ButtonSize(m.MeasureText(s.object.Name()))
}
func (f *Frame) PayloadHeight() float64 { return f.TitleHeight() }

func (f *Frame) ParamCenter(i int) vec2.Vec2 {
	y := 0.0
	if f.ShowWindow {
		y = f.size.Y
	}
	return vec2.Vec2{
		X: f.ContentLeft() + param_r,
		Y: y + float64(i)*(param_r*2+margin) + margin + param_r,
	}
}

func CircleClicked(pos vec2.Vec2, touch vec2.Vec2) bool {
	return Dist(pos, touch) < param_r
}

func ParamOffset(i int) float64 {
	return param_r + margin + float64(i)*(param_r*2+margin)
}

// Frame Title

type FrameTitle struct {
	Frame          *Frame
	Shell          *Shell
	BlueprintShell *Shell
}

func (f FrameTitle) Draw(ctx *ui.Context2D) {
	box := f.Size(ctx)
	ctx.FillStyle(f.Frame.blueprint.DarkColor())
	ctx.BeginPath()
	ctx.Rect2(box)
	ctx.Fill()
	ctx.FillStyle("#fff")
	ctx.FillText(f.Frame.Title(), box.Left+margin, box.Bottom-textMargin)
}
func (f FrameTitle) Options(pos vec2.Vec2) []ui.Option {
	options := []ui.Option{
		FrameDragging{f.Frame, vec2.Vec2{0, 0}},
		Schedule{f.Frame, f.Shell},
		DeleteFrame{f.Frame},
		CopyFrame{f.Frame, f.Shell},
		CloneFrame{f.Frame, f.Shell},
		ToggleParameter{f.Frame},
		TogglePublic{f.Frame},
		ToggleShowWindow{f.Frame},
		AddParameter{f.Frame},
		Raise{f.Frame},
		Lower{f.Frame, f.BlueprintShell},
	}
	if f.Shell != nil {
		options = append(options, ClearFrame{f.Frame, f.Shell})
	} else {
		options = append(options, NewBlueprint{f.Frame, f.BlueprintShell})
	}
	return options
}
func (f FrameTitle) Size(m ui.TextMeasurer) ui.Box {
	frame := f.Frame
	return ui.Box{frame.TitleTop(), frame.TitleRight(m), frame.TitleBottom(), frame.TitleLeft()}
}
func (f FrameTitle) GetText() string  { return f.Frame.name }
func (f FrameTitle) SetText(s string) { f.Frame.name = s }
func (f FrameTitle) MyFrame() *Frame  { return f.Frame }

type FrameElementPointer struct {
	Frame   *Frame
	Index   int
	Machine *Shell
}

func (self FrameElementPointer) Zip() ElementPack {
	machine := self.Machine.object.(*Machine)
	shell := machine.shells[self.Frame]
	return self.Frame.ZipElements(shell)[self.Index]
}
func (self FrameElementPointer) Member() Member {
	return self.Zip().Member
}
func (self FrameElementPointer) Param() Member {
	return self.Zip().Param
}
func (self FrameElementPointer) PositionInFrame() vec2.Vec2 {
	return vec2.Vec2{0, ParamOffset(self.Index)}
}
func (self FrameElementPointer) IsMember() bool {
	return self.Member() != nil
}
func (self FrameElementPointer) IsDefined() bool {
	return self.Member() != nil || self.Param() != nil
}
func (self FrameElementPointer) FrameElement() *FrameElement {
	if self.Index < len(self.Frame.elems) {
		return self.Frame.elems[self.Index]
	}
	return nil
}
func (self FrameElementPointer) MakeFrameElement() *FrameElement {
	if el := self.FrameElement(); el != nil {
		return el
	}
	return self.Frame.GetElement(self.Name())
}
func (self FrameElementPointer) Name() string {
	pack := self.Zip()
	if pack.FrameElement != nil {
		return pack.FrameElement.Name
	}
	if pack.Member != nil {
		return pack.Member.Name()
	}
	return pack.Param.Name()
}

type FrameElementCircle struct {
	FrameElementPointer
}

func (p FrameElementCircle) Draw(ctx *ui.Context2D) {
	ctx.BeginPath()
	if p.IsMember() {
		ctx.Rect2(ui.Box{-param_r, param_r, param_r, -param_r}.Grow(-1))
	} else {
		ctx.Circle(vec2.Vec2{0, 0}, param_r)
	}
	if p.IsDefined() {
		ctx.FillStyle("#fff")
		ctx.Fill()
	}
	if p.FrameElement() != nil {
		ctx.LineWidth(2)
		ctx.StrokeStyle("#000")
		ctx.Stroke()
	}
}
func (p FrameElementCircle) Options(pos vec2.Vec2) []ui.Option {
	return []ui.Option{ParameterDragging{p.FrameElementPointer}}
}
func (p FrameElementCircle) Size(ui.TextMeasurer) ui.Box {
	return ui.Box{-param_r, param_r, param_r, -param_r}
}

type FrameElementWidget struct {
	FrameElementPointer
}

func (p FrameElementWidget) Children() []interface{} {
	return []interface{}{FrameElementCircle{p.FrameElementPointer}}
}
func (p FrameElementWidget) Draw(ctx *ui.Context2D) {
	ctx.FillStyle("#000")
	ctx.FillText(p.GetText(), param_r+margin, -3)
}
func (p FrameElementWidget) Options(vec2.Vec2) (opts []ui.Option) {
	if el := p.FrameElement(); el != nil {
		opts = append(opts, DeleteParameter{el})
	}
	return
}
func (p FrameElementWidget) Transform(ui.TextMeasurer) matrix.Matrix {
	return matrix.Translate(p.PositionInFrame())
}
func (p FrameElementWidget) Size(measurer ui.TextMeasurer) ui.Box {
	return ui.Box{-param_r, param_r + margin + measurer.MeasureText(p.GetText()), param_r, -param_r}.Grow(margin / 2)
}
func (p FrameElementWidget) GetText() string {
	return p.Name()
}
func (p FrameElementWidget) SetText(newName string) {
	p.MakeFrameElement().Name = newName
}

type FrameElementList struct {
	Frame *Frame
	Shell *Shell
}

func (p FrameElementList) Draw(ctx *ui.Context2D) {
	/*
		params := p.Frame.Parameters(p.Shell)
		n := len(params)
		if n > 0 {
			ctx.LineWidth(2)
			ctx.BeginPath()
			ctx.MoveTo2(vec2.Vec2{0, 0})
			ctx.LineTo2(vec2.Vec2{0, ParamOffset(n - 1)})
			ctx.Stroke()
		}
	*/
}
func (p FrameElementList) Options(vec2.Vec2) []ui.Option { return nil }
func (p FrameElementList) Transform(m ui.TextMeasurer) matrix.Matrix {
	y := 0.
	if p.Frame.ShowWindow {
		y = p.Frame.size.Y
	}
	return matrix.Translate(vec2.Vec2{param_r, y})
}
func (p FrameElementList) Children() (children []interface{}) {
	zip := p.Frame.ZipElements(p.Shell)
	for i, _ := range zip {
		children = append(children, FrameElementWidget{FrameElementPointer{p.Frame, i, p.Shell.parent}})
	}
	return children
}

// Frame Payload

type FramePayload struct {
	frame *Frame
	shell *Shell
}

func (fp FramePayload) Draw(ctx *ui.Context2D) {
	box := fp.Size(ctx)
	ctx.FillStyle("#fff")
	ctx.BeginPath()
	ctx.Rect2(box)
	ctx.Fill()
	ctx.FillStyle(fp.frame.blueprint.DarkColor())
	ctx.FillText(fp.shell.object.Name(), box.Left+margin, box.Bottom-textMargin)
}
func (fp FramePayload) Size(m ui.TextMeasurer) ui.Box {
	return ui.Box{fp.frame.PayloadTop(),
		fp.frame.PayloadRight(fp.shell, m),
		fp.frame.PayloadBottom(),
		fp.frame.PayloadLeft(m)}
}
func (fp FramePayload) Options(pos vec2.Vec2) []ui.Option {
	return []ui.Option{FrameDragging{fp.frame, vec2.Vec2{0, 0}}, Enter{fp.shell}}
}

type FrameBlueprintPayload struct {
	FramePayload
	Blueprint *Blueprint
}

func (fbp FrameBlueprintPayload) GetText() string     { return fbp.Blueprint.name }
func (fbp FrameBlueprintPayload) SetText(text string) { fbp.Blueprint.name = text }

// FrameWindow

type FrameWindow struct {
	Frame *Frame
	Shell *Shell
}

func (ft FrameWindow) Children() []interface{} {
	s := ft.Shell
	if s == nil {
		return nil
	}
	if graphic, ok := s.object.(GraphicObject); ok {
		w := graphic.MakeWidget(s)
		if w != nil {
			return []interface{}{w}
		}
	}
	return nil
}
func (ft FrameWindow) Size(m ui.TextMeasurer) ui.Box {
	return ft.Frame.ContentSize()
}
func (ft FrameWindow) Draw(ctx *ui.Context2D) {
	ctx.BeginPath()
	ctx.Rect2(ft.Size(ctx).Grow(-2))
	ctx.Save()
	ctx.Clip()
}
func (ft FrameWindow) PostDraw(ctx *ui.Context2D) {
	ctx.Restore()
	ctx.BeginPath()
	ctx.Rect2(ft.Size(ctx).Grow(-1))
	ctx.LineWidth(2)
	ctx.StrokeStyle(ft.Frame.blueprint.DarkColor())
	ctx.Stroke()
}
func (ft FrameWindow) Transform(ui.TextMeasurer) matrix.Matrix {
	return matrix.Translate(vec2.Scale(ft.Frame.size, 0.5))
}
func (ft FrameWindow) MyFrame() *Frame { return ft.Frame }

// Frame Scaffolding

type FrameWidget struct {
	Frame          *Frame
	Shell          *Shell
	BlueprintShell *Shell
}

func (w FrameWidget) Draw(ctx *ui.Context2D) {
	shell := w.Shell
	size := w.Frame.size

	// Indicators
	if shell != nil && shell.execute {
		ctx.FillStyle("#f00")
		ctx.BeginPath()
		ctx.Rect2(ui.Box{-5, -5, size.X + 10, size.Y + 10})
		ctx.Fill()
	}
	if shell != nil && shell.running {
		ctx.Hourglass("#f00")
	}
}

func (w FrameWidget) Options(p vec2.Vec2) []ui.Option {
	size := w.Frame.size
	box := ui.Box{0, size.X, size.Y, 0}
	if box.Contains(p) {
		return []ui.Option{StartFrameDragging(p, w.Frame), DeleteFrame{w.Frame}}
	}
	return nil
}

func (w FrameWidget) Children() []interface{} {
	widgets := make([]interface{}, 0, 5)
	if w.Shell != nil {
		blueprint, ok := w.Shell.object.(*Blueprint)
		if ok {
			widgets = append(widgets, FrameBlueprintPayload{
				FramePayload: FramePayload{w.Frame, w.Shell},
				Blueprint:    blueprint,
			})
		} else {
			widgets = append(widgets, FramePayload{w.Frame, w.Shell})
		}
	}
	if w.Frame.ShowWindow {
		widgets = append(widgets, FrameWindow{w.Frame, w.Shell})
	}
	widgets = append(widgets, FrameTitle{w.Frame, w.Shell, w.BlueprintShell})
	widgets = append(widgets, FrameElementList{w.Frame, w.Shell})
	return widgets
}

func (w FrameWidget) Transform(ui.TextMeasurer) matrix.Matrix {
	return matrix.Translate(w.Frame.pos)
}
<commit_msg>Fixed running indicator<commit_after>package mvm

import (
	"github.com/mafik/mvm/matrix"
	"github.com/mafik/mvm/ui"
	"github.com/mafik/mvm/vec2"
)

type FramePart interface {
	MyFrame() *Frame
}

func (f *Frame) ContentSize() ui.Box {
	return ui.Box{-f.size.Y / 2, f.size.X / 2, f.size.Y / 2, -f.size.X / 2}
}

func (f *Frame) ContentLeft() float64   { return 0 }
func (f *Frame) ContentTop() float64    { return 0 }
func (f *Frame) ContentBottom() float64 { return f.ContentHeight() }
func (f *Frame) ContentRight() float64  { return f.ContentWidth() }
func (f *Frame) ContentWidth() float64  { return f.size.X }
func (f *Frame) ContentHeight() float64 { return f.size.Y }

func (f *Frame) TitleLeft() float64   { return f.ContentLeft() }
func (f *Frame) TitleTop() float64    { return f.TitleBottom() - buttonHeight }
func (f *Frame) TitleBottom() float64 { return f.ContentTop() }
func (f *Frame) TitleRight(m ui.TextMeasurer) float64 {
	return f.TitleLeft() + f.TitleWidth(m)
}
func (f *Frame) TitleWidth(m ui.TextMeasurer) float64 {
	return ButtonSize(m.MeasureText(f.Title()))
}
func (f *Frame) TitleHeight() float64 { return buttonHeight }

func (f *Frame) PayloadLeft(m ui.TextMeasurer) float64 { return f.TitleRight(m) }
func (f *Frame) PayloadTop() float64                   { return f.TitleTop() }
func (f *Frame) PayloadBottom() float64                { return f.TitleBottom() }
func (f *Frame) PayloadRight(s *Shell, m ui.TextMeasurer) float64 {
	return f.PayloadLeft(m) + f.PayloadWidth(s, m)
}
func (f *Frame) PayloadWidth(s *Shell, m ui.TextMeasurer) float64 {
	return ButtonSize(m.MeasureText(s.object.Name()))
}
func (f *Frame) PayloadHeight() float64 { return f.TitleHeight() }

func (f *Frame) ParamCenter(i int) vec2.Vec2 {
	y := 0.0
	if f.ShowWindow {
		y = f.size.Y
	}
	return vec2.Vec2{
		X: f.ContentLeft() + param_r,
		Y: y + float64(i)*(param_r*2+margin) + margin + param_r,
	}
}

func CircleClicked(pos vec2.Vec2, touch vec2.Vec2) bool {
	return Dist(pos, touch) < param_r
}

func ParamOffset(i int) float64 {
	return param_r + margin + float64(i)*(param_r*2+margin)
}

// Frame Title

type FrameTitle struct {
	Frame          *Frame
	Shell          *Shell
	BlueprintShell *Shell
}

func (f FrameTitle) Draw(ctx *ui.Context2D) {
	box := f.Size(ctx)
	ctx.FillStyle(f.Frame.blueprint.DarkColor())
	ctx.BeginPath()
	ctx.Rect2(box)
	ctx.Fill()
	ctx.FillStyle("#fff")
	ctx.FillText(f.Frame.Title(), box.Left+margin, box.Bottom-textMargin)
}
func (f FrameTitle) Options(pos vec2.Vec2) []ui.Option {
	options := []ui.Option{
		FrameDragging{f.Frame, vec2.Vec2{0, 0}},
		Schedule{f.Frame, f.Shell},
		DeleteFrame{f.Frame},
		CopyFrame{f.Frame, f.Shell},
		CloneFrame{f.Frame, f.Shell},
		ToggleParameter{f.Frame},
		TogglePublic{f.Frame},
		ToggleShowWindow{f.Frame},
		AddParameter{f.Frame},
		Raise{f.Frame},
		Lower{f.Frame, f.BlueprintShell},
	}
	if f.Shell != nil {
		options = append(options, ClearFrame{f.Frame, f.Shell})
	} else {
		options = append(options, NewBlueprint{f.Frame, f.BlueprintShell})
	}
	return options
}
func (f FrameTitle) Size(m ui.TextMeasurer) ui.Box {
	frame := f.Frame
	return ui.Box{frame.TitleTop(), frame.TitleRight(m), frame.TitleBottom(), frame.TitleLeft()}
}
func (f FrameTitle) GetText() string  { return f.Frame.name }
func (f FrameTitle) SetText(s string) { f.Frame.name = s }
func (f FrameTitle) MyFrame() *Frame  { return f.Frame }

type FrameElementPointer struct {
	Frame   *Frame
	Index   int
	Machine *Shell
}

func (self FrameElementPointer) Zip() ElementPack {
	machine := self.Machine.object.(*Machine)
	shell := machine.shells[self.Frame]
	return self.Frame.ZipElements(shell)[self.Index]
}
func (self FrameElementPointer) Member() Member {
	return self.Zip().Member
}
func (self FrameElementPointer) Param() Member {
	return self.Zip().Param
}
func (self FrameElementPointer) PositionInFrame() vec2.Vec2 {
	return vec2.Vec2{0, ParamOffset(self.Index)}
}
func (self FrameElementPointer) IsMember() bool {
	return self.Member() != nil
}
func (self FrameElementPointer) IsDefined() bool {
	return self.Member() != nil || self.Param() != nil
}
func (self FrameElementPointer) FrameElement() *FrameElement {
	if self.Index < len(self.Frame.elems) {
		return self.Frame.elems[self.Index]
	}
	return nil
}
func (self FrameElementPointer) MakeFrameElement() *FrameElement {
	if el := self.FrameElement(); el != nil {
		return el
	}
	return self.Frame.GetElement(self.Name())
}
func (self FrameElementPointer) Name() string {
	pack := self.Zip()
	if pack.FrameElement != nil {
		return pack.FrameElement.Name
	}
	if pack.Member != nil {
		return pack.Member.Name()
	}
	return pack.Param.Name()
}

type FrameElementCircle struct {
	FrameElementPointer
}

func (p FrameElementCircle) Draw(ctx *ui.Context2D) {
	ctx.BeginPath()
	if p.IsMember() {
		ctx.Rect2(ui.Box{-param_r, param_r, param_r, -param_r}.Grow(-1))
	} else {
		ctx.Circle(vec2.Vec2{0, 0}, param_r)
	}
	if p.IsDefined() {
		ctx.FillStyle("#fff")
		ctx.Fill()
	}
	if p.FrameElement() != nil {
		ctx.LineWidth(2)
		ctx.StrokeStyle("#000")
		ctx.Stroke()
	}
}
func (p FrameElementCircle) Options(pos vec2.Vec2) []ui.Option {
	return []ui.Option{ParameterDragging{p.FrameElementPointer}}
}
func (p FrameElementCircle) Size(ui.TextMeasurer) ui.Box {
	return ui.Box{-param_r, param_r, param_r, -param_r}
}

type FrameElementWidget struct {
	FrameElementPointer
}

func (p FrameElementWidget) Children() []interface{} {
	return []interface{}{FrameElementCircle{p.FrameElementPointer}}
}
func (p FrameElementWidget) Draw(ctx *ui.Context2D) {
	ctx.FillStyle("#000")
	ctx.FillText(p.GetText(), param_r+margin, -3)
}
func (p FrameElementWidget) Options(vec2.Vec2) (opts []ui.Option) {
	if el := p.FrameElement(); el != nil {
		opts = append(opts, DeleteParameter{el})
	}
	return
}
func (p FrameElementWidget) Transform(ui.TextMeasurer) matrix.Matrix {
	return matrix.Translate(p.PositionInFrame())
}
func (p FrameElementWidget) Size(measurer ui.TextMeasurer) ui.Box {
	return ui.Box{-param_r, param_r + margin + measurer.MeasureText(p.GetText()), param_r, -param_r}.Grow(margin / 2)
}
func (p FrameElementWidget) GetText() string {
	return p.Name()
}
func (p FrameElementWidget) SetText(newName string) {
	p.MakeFrameElement().Name = newName
}

type FrameElementList struct {
	Frame *Frame
	Shell *Shell
}

func (p FrameElementList) Draw(ctx *ui.Context2D) {
	/*
		params := p.Frame.Parameters(p.Shell)
		n := len(params)
		if n > 0 {
			ctx.LineWidth(2)
			ctx.BeginPath()
			ctx.MoveTo2(vec2.Vec2{0, 0})
			ctx.LineTo2(vec2.Vec2{0, ParamOffset(n - 1)})
			ctx.Stroke()
		}
	*/
}
func (p FrameElementList) Options(vec2.Vec2) []ui.Option { return nil }
func (p FrameElementList) Transform(m ui.TextMeasurer) matrix.Matrix {
	y := 0.
	if p.Frame.ShowWindow {
		y = p.Frame.size.Y
	}
	return matrix.Translate(vec2.Vec2{param_r, y})
}
func (p FrameElementList) Children() (children []interface{}) {
	zip := p.Frame.ZipElements(p.Shell)
	for i, _ := range zip {
		children = append(children, FrameElementWidget{FrameElementPointer{p.Frame, i, p.Shell.parent}})
	}
	return children
}

// Frame Payload

type FramePayload struct {
	frame *Frame
	shell *Shell
}

func (fp FramePayload) Draw(ctx *ui.Context2D) {
	box := fp.Size(ctx)
	ctx.FillStyle("#fff")
	ctx.BeginPath()
	ctx.Rect2(box)
	ctx.Fill()
	ctx.FillStyle(fp.frame.blueprint.DarkColor())
	ctx.FillText(fp.shell.object.Name(), box.Left+margin, box.Bottom-textMargin)
}
func (fp FramePayload) Size(m ui.TextMeasurer) ui.Box {
	return ui.Box{fp.frame.PayloadTop(),
		fp.frame.PayloadRight(fp.shell, m),
		fp.frame.PayloadBottom(),
		fp.frame.PayloadLeft(m)}
}
func (fp FramePayload) Options(pos vec2.Vec2) []ui.Option {
	return []ui.Option{FrameDragging{fp.frame, vec2.Vec2{0, 0}}, Enter{fp.shell}}
}

type FrameBlueprintPayload struct {
	FramePayload
	Blueprint *Blueprint
}

func (fbp FrameBlueprintPayload) GetText() string     { return fbp.Blueprint.name }
func (fbp FrameBlueprintPayload) SetText(text string) { fbp.Blueprint.name = text }

// FrameWindow

type FrameWindow struct {
	Frame *Frame
	Shell *Shell
}

func (ft FrameWindow) Children() []interface{} {
	s := ft.Shell
	if s == nil {
		return nil
	}
	if graphic, ok := s.object.(GraphicObject); ok {
		w := graphic.MakeWidget(s)
		if w != nil {
			return []interface{}{w}
		}
	}
	return nil
}
func (ft FrameWindow) Size(m ui.TextMeasurer) ui.Box {
	return ft.Frame.ContentSize()
}
func (ft FrameWindow) Draw(ctx *ui.Context2D) {
	ctx.BeginPath()
	ctx.Rect2(ft.Size(ctx).Grow(-2))
	ctx.Save()
	ctx.Clip()
}
func (ft FrameWindow) PostDraw(ctx *ui.Context2D) {
	ctx.Restore()
	ctx.BeginPath()
	ctx.Rect2(ft.Size(ctx).Grow(-1))
	ctx.LineWidth(2)
	ctx.StrokeStyle(ft.Frame.blueprint.DarkColor())
	ctx.Stroke()
}
func (ft FrameWindow) Transform(ui.TextMeasurer) matrix.Matrix {
	return matrix.Translate(vec2.Scale(ft.Frame.size, 0.5))
}
func (ft FrameWindow) MyFrame() *Frame { return ft.Frame }

// Frame Scaffolding

type FrameWidget struct {
	Frame          *Frame
	Shell          *Shell
	BlueprintShell *Shell
}

func (w FrameWidget) Draw(ctx *ui.Context2D) {
	shell := w.Shell
	f := w.Frame

	// Indicators
	if shell != nil && shell.execute {
		ctx.FillStyle("#f00")
		ctx.BeginPath()
		ctx.Rect2(ui.Box{f.TitleTop() - 5, f.PayloadRight(shell, ctx) + 5, f.TitleBottom() + 5, f.TitleLeft() - 5})
		ctx.Fill()
	}
	if shell != nil && shell.running {
		ctx.Save()
		ctx.Translate(-4, -5)
		ctx.Hourglass("#f00")
		ctx.Restore()
	}
}

func (w FrameWidget) Options(p vec2.Vec2) []ui.Option {
	size := w.Frame.size
	box := ui.Box{0, size.X, size.Y, 0}
	if box.Contains(p) {
		return []ui.Option{StartFrameDragging(p, w.Frame), DeleteFrame{w.Frame}}
	}
	return nil
}

func (w FrameWidget) Children() []interface{} {
	widgets := make([]interface{}, 0, 5)
	if w.Shell != nil {
		blueprint, ok := w.Shell.object.(*Blueprint)
		if ok {
			widgets = append(widgets, FrameBlueprintPayload{
				FramePayload: FramePayload{w.Frame, w.Shell},
				Blueprint:    blueprint,
			})
		} else {
			widgets = append(widgets, FramePayload{w.Frame, w.Shell})
		}
	}
	if w.Frame.ShowWindow {
		widgets = append(widgets, FrameWindow{w.Frame, w.Shell})
	}
	widgets = append(widgets, FrameTitle{w.Frame, w.Shell, w.BlueprintShell})
	widgets = append(widgets, FrameElementList{w.Frame, w.Shell})
	return widgets
}

func (w FrameWidget) Transform(ui.TextMeasurer) matrix.Matrix {
	return matrix.Translate(w.Frame.pos)
}
<|endoftext|>


<commit_before>package frank

import (
	"code.google.com/p/go.net/html"
	"code.google.com/p/go.net/html/atom"
	"errors"
	irc "github.com/fluffle/goirc/client"
	"io"
	"log"
	"net"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"time"
)

// how many URLs can the cache store
const cacheSize = 100

// how many hours an entry should be considered valid
const cacheValidHours = 24

// how many kilo bytes should be considered when looking for the title
// tag.
const httpReadKByte = 100

// abort HTTP requests if it takes longer than X seconds. Not sure, it’s
// definitely magic involved. Must be larger than 5.
const httpGetDeadline = 10

// don’t repost the same title within this period
const noRepostWithinSeconds = 30

// matches all whitespace and zero bytes. Additionally, all Unicode
// characters of class Cf (format chars, e.g. right-to-left) and Cc
// (control chars) are matched.
var whitespaceRegex = regexp.MustCompile(`[\s\0\p{Cf}\p{Cc}]+`)

var ignoreDomainsRegex = regexp.MustCompile(`^http://p\.nnev\.de`)

var twitterDomainRegex = regexp.MustCompile(`(?i)^https?://(?:[a-z0-9]\.)?twitter.com`)
var twitterPicsRegex = regexp.MustCompile(`(?i)(?:\b|^)pic\.twitter\.com/[a-z0-9]+(?:\b|$)`)

var noSpoilerRegex = regexp.MustCompile(`(?i)(don't|no|kein|nicht) spoiler`)

func UriFind(conn *irc.Conn, line *irc.Line) {
	defer func() {
		if r := recover(); r != nil {
			log.Printf("MEGA-WTF:pkg: %v", r)
		}
	}()

	msg := line.Args[1]

	if noSpoilerRegex.MatchString(msg) {
		log.Printf("not spoilering this line: %s", msg)
		return
	}

	urls := extract(msg)

	for _, url := range urls {
		if url == "" {
			continue
		}

		if title := cacheGetTitleByUrl(url); title != "" {
			log.Printf("using cache for URL: %s", url)
			postTitle(conn, line, title, "Cache Info")
			continue
		}

		go func(url string) {
			if ignoreDomainsRegex.MatchString(url) {
				log.Printf("ignoring this URL: %s", url)
				return
			}

			log.Printf("testing URL: %s", url)
			title, _, err := TitleGet(url)
			if err != nil {
				//postTitle(conn, line, err.Error(), "Error")
			} else if title != "" {
				postTitle(conn, line, title, "")
				cacheAdd(url, title)
			}
		}(url)
	}
}

// regexing ////////////////////////////////////////////////////////////

func extract(msg string) []string {
	results := make([]string, 0)
	for idx := strings.Index(msg, "http"); idx > -1; idx = strings.Index(msg, "http") {
		url := msg[idx:]
		if !strings.HasPrefix(url, "http://") &&
			!strings.HasPrefix(url, "https://") {
			msg = msg[idx+len("http"):]
			continue
		}

		// End on commas, but only if they are followed by a space.
		// spiegel.de URLs have commas in them, that would be a
		// false positive otherwise.
		if end := strings.Index(url, ", "); end > -1 {
			url = url[:end]
		}

		// End on closing paren, but only if there is an opening
		// paren before the URL (should fix most false-positives).
		if end := strings.Index(url, ")"); idx > 0 && msg[idx-1] == '(' && end > -1 {
			url = url[:end]
		}

		// Whitespace always ends a URL.
		if end := strings.IndexAny(url, " \t"); end > -1 {
			url = url[:end]
		}

		results = append(results, url)
		msg = msg[idx+len(url):]
	}
	return results
}

// http/html stuff /////////////////////////////////////////////////////

func TitleGet(url string) (string, string, error) {
	// via http://www.reddit.com/r/golang/comments/10awvj/timeout_on_httpget/c6bz49s
	c := http.Client{
		Transport: &http.Transport{
			Dial: func(netw, addr string) (net.Conn, error) {
				deadline := time.Now().Add(time.Second * httpGetDeadline)
				c, err := net.DialTimeout(netw, addr, time.Second*(httpGetDeadline-5))
				if err != nil {
					return nil, err
				}
				c.SetDeadline(deadline)
				return c, nil
			},
		},
	}

	r, err := c.Get(url)
	if err != nil {
		log.Printf("WTF: could not resolve %s: %s\n", url, err)
		return "", url, err
	}
	defer r.Body.Close()

	lastUrl := r.Request.URL.String()

	// TODO: r.Body → utf8?
	title, tweet := titleParseHtml(io.LimitReader(r.Body, 1024*httpReadKByte))

	if r.StatusCode != 200 {
		return "", lastUrl, errors.New("[" + strconv.Itoa(r.StatusCode) + "] " + title)
	}

	if tweet != "" && twitterDomainRegex.MatchString(lastUrl) {
		title = tweet
	}

	log.Printf("Title for URL %s: %s\n", url, title)

	return title, lastUrl, nil
}

// parses the incoming HTML fragment and tries to extract text from
// suitable tags. Currently this is the page’s title tag and tweets
// when the HTML-code is similar enough to twitter.com. Returns
// title and tweet.
func titleParseHtml(r io.Reader) (string, string) {
	doc, err := html.Parse(r)
	if err != nil {
		log.Printf("WTF: html parser blew up: %s\n", err)
		return "", ""
	}

	title := ""
	tweetText := ""
	tweetUser := ""
	tweetPicUrl := ""

	var f func(*html.Node)
	f = func(n *html.Node) {
		if title == "" && n.Type == html.ElementNode && n.DataAtom == atom.Title {
			title = extractText(n)
			return
		}

		if tweetText == "" && hasClass(n, "tweet-text") {
			tweetText = extractText(n)
			return
		}

		if tweetUser == "" && hasClass(n, "js-user-profile-link") {
			tweetUser = extractText(n)
			return
		}

		isMedia := hasClass(n, "media") || hasClass(n, "media-thumbnail")
		if tweetPicUrl == "" && isMedia && !hasClass(n, "profile-picture") {
			attrVal := getAttr(n, "data-url")
			if attrVal != "" {
				tweetPicUrl = attrVal
				return
			}
		}

		// recurse down
		for c := n.FirstChild; c != nil; c = c.NextSibling {
			f(c)
		}

	}
	f(doc)

	// cleanup
	tweet := ""
	if tweetText != "" {
		tweetText = twitterPicsRegex.ReplaceAllString(tweetText, "")
		tweetUser = strings.Replace(tweetUser, "@", "(@", 1) + "): "
		tweet = tweetUser + tweetText + " " + tweetPicUrl
		tweet = clean(tweet)
	}

	return strings.TrimSpace(title), strings.TrimSpace(tweet)
}

func extractText(n *html.Node) string {
	text := ""
	for c := n.FirstChild; c != nil; c = c.NextSibling {
		if c.Type == html.TextNode {
			text += c.Data
		} else {
			text += extractText(c)
		}
	}
	return clean(text)
}

func hasClass(n *html.Node, class string) bool {
	if n.Type != html.ElementNode {
		return false
	}

	class = " " + strings.TrimSpace(class) + " "
	if strings.Contains(" "+getAttr(n, "class")+" ", class) {
		return true
	}
	return false
}

func getAttr(n *html.Node, findAttr string) string {
	for _, attr := range n.Attr {
		if attr.Key == findAttr {
			return attr.Val
		}
	}
	return ""
}

// Cache ///////////////////////////////////////////////////////////////
type Cache struct {
	url   string
	title string
	date  time.Time
}

var cache = [cacheSize]Cache{}
var cacheIndex = 0

func cacheAdd(url string, title string) {
	if len(cache) == cacheIndex {
		cacheIndex = 0
	}
	cache[cacheIndex] = Cache{url, title, time.Now()}
	cacheIndex += 1
}

func cacheGetTitleByUrl(url string) string {
	for _, cc := range cache {
		if cc.url == url && time.Since(cc.date).Hours() <= cacheValidHours {
			return cc.title
		}
	}
	return ""
}

func cacheGetSecondsToLastPost(title string) int {
	var secondsAgo = int(^uint(0) >> 1)
	for _, cc := range cache {
		var a = int(time.Since(cc.date).Seconds())
		if cc.title == title && a < secondsAgo {
			secondsAgo = a
		}
	}
	return secondsAgo
}

// util ////////////////////////////////////////////////////////////////

func postTitle(conn *irc.Conn, line *irc.Line, title string, prefix string) {
	tgt := line.Args[0]

	secondsAgo := cacheGetSecondsToLastPost(title)
	if secondsAgo <= noRepostWithinSeconds {
		log.Printf("Skipping, because posted %d seconds ago (“%s”)", secondsAgo, title)
		return
	}

	log.Printf("nick=%s, target=%s, title=%s", line.Nick, tgt, title)
	// if target is our current nick, it was a private message.
	// Answer the users in this case.
	if tgt == conn.Me().Nick {
		tgt = line.Nick
	}
	if prefix == "" {
		prefix = "Link Info"
	} else {
		prefix = clean(prefix)
	}
	title = clean(title)
	// the IRC spec states that notice should be used instead of msg
	// and that bots should not react to notice at all. However, no
	// real world bot adheres to this. Furthermore, people who can’t
	// configure their client to not highlight them on notices will
	// complain.
	conn.Privmsg(tgt, "["+prefix+"] "+title)
}

func clean(text string) string {
	text = whitespaceRegex.ReplaceAllString(text, " ")
	return strings.TrimSpace(text)
}
<commit_msg>style nit<commit_after>package frank

import (
	"code.google.com/p/go.net/html"
	"code.google.com/p/go.net/html/atom"
	"errors"
	irc "github.com/fluffle/goirc/client"
	"io"
	"log"
	"net"
	"net/http"
	"regexp"
	"strconv"
	"strings"
	"time"
)

// how many URLs can the cache store
const cacheSize = 100

// how many hours an entry should be considered valid
const cacheValidHours = 24

// how many kilo bytes should be considered when looking for the title
// tag.
const httpReadKByte = 100

// abort HTTP requests if it takes longer than X seconds. Not sure, it’s
// definitely magic involved. Must be larger than 5.
const httpGetDeadline = 10

// don’t repost the same title within this period
const noRepostWithinSeconds = 30

// matches all whitespace and zero bytes. Additionally, all Unicode
// characters of class Cf (format chars, e.g. right-to-left) and Cc
// (control chars) are matched.
var whitespaceRegex = regexp.MustCompile(`[\s\0\p{Cf}\p{Cc}]+`)

var ignoreDomainsRegex = regexp.MustCompile(`^http://p\.nnev\.de`)

var twitterDomainRegex = regexp.MustCompile(`(?i)^https?://(?:[a-z0-9]\.)?twitter.com`)
var twitterPicsRegex = regexp.MustCompile(`(?i)(?:\b|^)pic\.twitter\.com/[a-z0-9]+(?:\b|$)`)

var noSpoilerRegex = regexp.MustCompile(`(?i)(don't|no|kein|nicht) spoiler`)

func UriFind(conn *irc.Conn, line *irc.Line) {
	defer func() {
		if r := recover(); r != nil {
			log.Printf("MEGA-WTF:pkg: %v", r)
		}
	}()

	msg := line.Args[1]

	if noSpoilerRegex.MatchString(msg) {
		log.Printf("not spoilering this line: %s", msg)
		return
	}

	urls := extract(msg)

	for _, url := range urls {
		if url == "" {
			continue
		}

		if title := cacheGetTitleByUrl(url); title != "" {
			log.Printf("using cache for URL: %s", url)
			postTitle(conn, line, title, "Cache Info")
			continue
		}

		go func(url string) {
			if ignoreDomainsRegex.MatchString(url) {
				log.Printf("ignoring this URL: %s", url)
				return
			}

			log.Printf("testing URL: %s", url)
			title, _, err := TitleGet(url)
			if err != nil {
				//postTitle(conn, line, err.Error(), "Error")
			} else if title != "" {
				postTitle(conn, line, title, "")
				cacheAdd(url, title)
			}
		}(url)
	}
}

// regexing ////////////////////////////////////////////////////////////

func extract(msg string) []string {
	results := make([]string, 0)
	for idx := strings.Index(msg, "http"); idx > -1; idx = strings.Index(msg, "http") {
		url := msg[idx:]
		if !strings.HasPrefix(url, "http://") &&
			!strings.HasPrefix(url, "https://") {
			msg = msg[idx+len("http"):]
			continue
		}

		// End on commas, but only if they are followed by a space.
		// spiegel.de URLs have commas in them, that would be a
		// false positive otherwise.
		if end := strings.Index(url, ", "); end > -1 {
			url = url[:end]
		}

		// End on closing paren, but only if there is an opening
		// paren before the URL (should fix most false-positives).
		if end := strings.Index(url, ")"); idx > 0 && msg[idx-1] == '(' && end > -1 {
			url = url[:end]
		}

		// Whitespace always ends a URL.
		if end := strings.IndexAny(url, " \t"); end > -1 {
			url = url[:end]
		}

		results = append(results, url)
		msg = msg[idx+len(url):]
	}
	return results
}

// http/html stuff /////////////////////////////////////////////////////

func TitleGet(url string) (string, string, error) {
	// via http://www.reddit.com/r/golang/comments/10awvj/timeout_on_httpget/c6bz49s
	c := http.Client{
		Transport: &http.Transport{
			Dial: func(netw, addr string) (net.Conn, error) {
				deadline := time.Now().Add(time.Second * httpGetDeadline)
				c, err := net.DialTimeout(netw, addr, time.Second*(httpGetDeadline-5))
				if err != nil {
					return nil, err
				}
				c.SetDeadline(deadline)
				return c, nil
			},
		},
	}

	r, err := c.Get(url)
	if err != nil {
		log.Printf("WTF: could not resolve %s: %s\n", url, err)
		return "", url, err
	}
	defer r.Body.Close()

	lastUrl := r.Request.URL.String()

	// TODO: r.Body → utf8?
	title, tweet := titleParseHtml(io.LimitReader(r.Body, 1024*httpReadKByte))

	if r.StatusCode != 200 {
		return "", lastUrl, errors.New("[" + strconv.Itoa(r.StatusCode) + "] " + title)
	}

	if tweet != "" && twitterDomainRegex.MatchString(lastUrl) {
		title = tweet
	}

	log.Printf("Title for URL %s: %s\n", url, title)

	return title, lastUrl, nil
}

// parses the incoming HTML fragment and tries to extract text from
// suitable tags. Currently this is the page’s title tag and tweets
// when the HTML-code is similar enough to twitter.com. Returns
// title and tweet.
func titleParseHtml(r io.Reader) (string, string) {
	doc, err := html.Parse(r)
	if err != nil {
		log.Printf("WTF: html parser blew up: %s\n", err)
		return "", ""
	}

	title := ""
	tweetText := ""
	tweetUser := ""
	tweetPicUrl := ""

	var f func(*html.Node)
	f = func(n *html.Node) {
		if title == "" && n.Type == html.ElementNode && n.DataAtom == atom.Title {
			title = extractText(n)
			return
		}

		if tweetText == "" && hasClass(n, "tweet-text") {
			tweetText = extractText(n)
			return
		}

		if tweetUser == "" && hasClass(n, "js-user-profile-link") {
			tweetUser = extractText(n)
			return
		}

		isMedia := hasClass(n, "media") || hasClass(n, "media-thumbnail")
		if tweetPicUrl == "" && isMedia && !hasClass(n, "profile-picture") {
			attrVal := getAttr(n, "data-url")
			if attrVal != "" {
				tweetPicUrl = attrVal
				return
			}
		}

		// recurse down
		for c := n.FirstChild; c != nil; c = c.NextSibling {
			f(c)
		}

	}
	f(doc)

	// cleanup
	tweet := ""
	if tweetText != "" {
		tweetText = twitterPicsRegex.ReplaceAllString(tweetText, "")
		tweetUser = strings.Replace(tweetUser, "@", "(@", 1) + "): "
		tweet = tweetUser + tweetText + " " + tweetPicUrl
		tweet = clean(tweet)
	}

	return strings.TrimSpace(title), strings.TrimSpace(tweet)
}

func extractText(n *html.Node) string {
	text := ""
	for c := n.FirstChild; c != nil; c = c.NextSibling {
		if c.Type == html.TextNode {
			text += c.Data
		} else {
			text += extractText(c)
		}
	}
	return clean(text)
}

func hasClass(n *html.Node, class string) bool {
	if n.Type != html.ElementNode {
		return false
	}

	class = " " + strings.TrimSpace(class) + " "
	if strings.Contains(" "+getAttr(n, "class")+" ", class) {
		return true
	}
	return false
}

func getAttr(n *html.Node, findAttr string) string {
	for _, attr := range n.Attr {
		if attr.Key == findAttr {
			return attr.Val
		}
	}
	return ""
}

// Cache ///////////////////////////////////////////////////////////////
type Cache struct {
	url   string
	title string
	date  time.Time
}

var cache = [cacheSize]Cache{}
var cacheIndex = 0

func cacheAdd(url string, title string) {
	if len(cache) == cacheIndex {
		cacheIndex = 0
	}
	cache[cacheIndex] = Cache{url, title, time.Now()}
	cacheIndex += 1
}

func cacheGetTitleByUrl(url string) string {
	for _, cc := range cache {
		if cc.url == url && time.Since(cc.date).Hours() <= cacheValidHours {
			return cc.title
		}
	}
	return ""
}

func cacheGetSecondsToLastPost(title string) int {
	var secondsAgo = int(^uint(0) >> 1)
	for _, cc := range cache {
		var a = int(time.Since(cc.date).Seconds())
		if cc.title == title && a < secondsAgo {
			secondsAgo = a
		}
	}
	return secondsAgo
}

// util ////////////////////////////////////////////////////////////////

func postTitle(conn *irc.Conn, line *irc.Line, title string, prefix string) {
	tgt := line.Args[0]

	secondsAgo := cacheGetSecondsToLastPost(title)
	if secondsAgo <= noRepostWithinSeconds {
		log.Printf("Skipping, because posted %d seconds ago (“%s”)", secondsAgo, title)
		return
	}

	log.Printf("nick=%s, target=%s, title=%s", line.Nick, tgt, title)
	// if target is our current nick, it was a private message.
	// Answer the users in this case.
	if tgt == conn.Me().Nick {
		tgt = line.Nick
	}
	if prefix == "" {
		prefix = "Link Info"
	} else {
		prefix = clean(prefix)
	}
	title = clean(title)
	// the IRC spec states that notice should be used instead of msg
	// and that bots should not react to notice at all. However, no
	// real world bot adheres to this. Furthermore, people who can’t
	// configure their client to not highlight them on notices will
	// complain.
	conn.Privmsg(tgt, "["+prefix+"] "+title)
}

func clean(text string) string {
	text = whitespaceRegex.ReplaceAllString(text, " ")
	return strings.TrimSpace(text)
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"math/rand"
	"net/http"
	"net/http/httptest"
	"net/url"
	"reflect"
	"testing"
	"time"

	"github.com/boltdb/bolt"
)

// Client structure to be injected into functions to perform HTTP calls
type Client struct {
	HTTPClient *http.Client
}

func expect(t *testing.T, a interface{}, b interface{}) {
	if a != b {
		t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
	}
}

func refute(t *testing.T, a interface{}, b interface{}) {
	if a == b {
		t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
	}
}

var TestDB *bolt.DB

func testTools(code int, body string) (*httptest.Server, *DBClient) {

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(code)
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprintln(w, body)
	}))

	tr := &http.Transport{
		Proxy: func(req *http.Request) (*url.URL, error) {
			return url.Parse(server.URL)
		},
	}
	// creating random buckets for everyone!
	bucket := GetRandomName(10)
	cache := Cache{db: TestDB, requestsBucket: bucket}

	// preparing client
	dbClient := &DBClient{
		http:  &http.Client{Transport: tr},
		cache: cache,
	}
	return server, dbClient
}

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
	letterIdxBits = 6                    // 6 bits to represent a letter index
	letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
	letterIdxMax  = 63 / letterIdxBits   // # of letter indices fitting in 63 bits
)

var src = rand.NewSource(time.Now().UnixNano())

func GetRandomName(n int) []byte {
	b := make([]byte, n)
	for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
		if remain == 0 {
			cache, remain = src.Int63(), letterIdxMax
		}
		if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
			b[i] = letterBytes[idx]
			i--
		}
		cache >>= letterIdxBits
		remain--
	}

	return b
}

func setup() {
	db := getDB("test.db")
	TestDB = db
}

// teardown does some cleanup after tests
func teardown() {
	// TODO: delete test.db file here
}
<commit_msg>tidying things up, removing test database after test<commit_after>package main

import (
	"fmt"
	"math/rand"
	"net/http"
	"net/http/httptest"
	"net/url"
	"os"
	"reflect"
	"testing"
	"time"

	"github.com/boltdb/bolt"
)

const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
const (
	letterIdxBits = 6                    // 6 bits to represent a letter index
	letterIdxMask = 1<<letterIdxBits - 1 // All 1-bits, as many as letterIdxBits
	letterIdxMax  = 63 / letterIdxBits   // # of letter indices fitting in 63 bits
)

const testingDatabaseName = "test.db" // very original

// Client structure to be injected into functions to perform HTTP calls
type Client struct {
	HTTPClient *http.Client
}

func expect(t *testing.T, a interface{}, b interface{}) {
	if a != b {
		t.Errorf("Expected %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
	}
}

func refute(t *testing.T, a interface{}, b interface{}) {
	if a == b {
		t.Errorf("Did not expect %v (type %v) - Got %v (type %v)", b, reflect.TypeOf(b), a, reflect.TypeOf(a))
	}
}

var TestDB *bolt.DB

func testTools(code int, body string) (*httptest.Server, *DBClient) {

	server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.WriteHeader(code)
		w.Header().Set("Content-Type", "application/json")
		fmt.Fprintln(w, body)
	}))

	tr := &http.Transport{
		Proxy: func(req *http.Request) (*url.URL, error) {
			return url.Parse(server.URL)
		},
	}
	// creating random buckets for everyone!
	bucket := GetRandomName(10)
	cache := Cache{db: TestDB, requestsBucket: bucket}

	// preparing client
	dbClient := &DBClient{
		http:  &http.Client{Transport: tr},
		cache: cache,
	}
	return server, dbClient
}

var src = rand.NewSource(time.Now().UnixNano())

func GetRandomName(n int) []byte {
	b := make([]byte, n)
	for i, cache, remain := n-1, src.Int63(), letterIdxMax; i >= 0; {
		if remain == 0 {
			cache, remain = src.Int63(), letterIdxMax
		}
		if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
			b[i] = letterBytes[idx]
			i--
		}
		cache >>= letterIdxBits
		remain--
	}

	return b
}

func setup() {
	db := getDB(testingDatabaseName)
	TestDB = db
}

// teardown does some cleanup after tests
func teardown() {
	os.Remove(testingDatabaseName)
}
<|endoftext|>