<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"time"

	"context"

	"gopkg.in/yaml.v2"

	"github.com/lxc/lxd/lxd/backup"
	"github.com/lxc/lxd/lxd/cluster"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/instance"
	"github.com/lxc/lxd/lxd/instance/instancetype"
	"github.com/lxc/lxd/lxd/operations"
	"github.com/lxc/lxd/lxd/project"
	"github.com/lxc/lxd/lxd/state"
	storagePools "github.com/lxc/lxd/lxd/storage"
	storageDrivers "github.com/lxc/lxd/lxd/storage/drivers"
	"github.com/lxc/lxd/lxd/task"
	"github.com/lxc/lxd/shared"
	log "github.com/lxc/lxd/shared/log15"
	"github.com/lxc/lxd/shared/logger"
	"github.com/pkg/errors"
)

// Create a new backup.
func backupCreate(s *state.State, args db.InstanceBackupArgs, sourceInst instance.Instance) error {
	// Create the database entry.
	err := s.Cluster.ContainerBackupCreate(args)
	if err != nil {
		if err == db.ErrAlreadyDefined {
			return fmt.Errorf("backup '%s' already exists", args.Name)
		}

		return errors.Wrap(err, "Insert backup info into database")
	}

	revert := true
	defer func() {
		if !revert {
			return
		}
		s.Cluster.ContainerBackupRemove(args.Name)
	}()

	// Get the backup struct.
	b, err := instance.BackupLoadByName(s, sourceInst.Project(), args.Name)
	if err != nil {
		return errors.Wrap(err, "Load backup object")
	}

	b.SetCompressionAlgorithm(args.CompressionAlgorithm)

	// Create a temporary path for the backup.
	tmpPath, err := ioutil.TempDir(shared.VarPath("backups"), "lxd_backup_")
	if err != nil {
		return err
	}
	defer os.RemoveAll(tmpPath)

	// Check if we can load new storage layer for pool driver type.
	pool, err := storagePools.GetPoolByInstance(s, sourceInst)
	if err != storageDrivers.ErrUnknownDriver && err != storageDrivers.ErrNotImplemented {
		if err != nil {
			return errors.Wrap(err, "Load instance storage pool")
		}

		err = pool.BackupInstance(sourceInst, tmpPath, b.OptimizedStorage(), !b.InstanceOnly(), nil)
		if err != nil {
			return errors.Wrap(err, "Backup create")
		}
	} else if sourceInst.Type() == instancetype.Container {
		ourStart, err := sourceInst.StorageStart()
		if err != nil {
			return err
		}
		if ourStart {
			defer sourceInst.StorageStop()
		}

		ct := sourceInst.(*containerLXC)
		err = ct.Storage().ContainerBackupCreate(tmpPath, *b, sourceInst)
		if err != nil {
			return errors.Wrap(err, "Backup create")
		}
	} else {
		return fmt.Errorf("Instance type not supported")
	}

	// Pack the backup.
	err = backupCreateTarball(s, tmpPath, *b, sourceInst)
	if err != nil {
		return err
	}

	revert = false
	return nil
}

func backupCreateTarball(s *state.State, path string, b backup.Backup, c instance.Instance) error {
	// Create the index
	poolName, err := c.StoragePool()
	if err != nil {
		return err
	}

	if c.Type() != instancetype.Container {
		return fmt.Errorf("Instance type must be container")
	}

	indexFile := backup.Info{
		Name:       c.Name(),
		Privileged: c.IsPrivileged(),
		Pool:       poolName,
		Snapshots:  []string{},
	}

	pool, err := storagePools.GetPoolByInstance(s, c)
	if err != storageDrivers.ErrUnknownDriver && err != storageDrivers.ErrNotImplemented && err != db.ErrNoSuchObject {
		if err != nil {
			return err
		}

		info := pool.Driver().Info()
		indexFile.Backend = info.Name
	} else {
		ct := c.(*containerLXC)
		indexFile.Backend = ct.Storage().GetStorageTypeName()
	}

	if !b.InstanceOnly() {
		snaps, err := c.Snapshots()
		if err != nil {
			return err
		}

		for _, snap := range snaps {
			_, snapName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name())
			indexFile.Snapshots = append(indexFile.Snapshots, snapName)
		}
	}

	data, err := yaml.Marshal(&indexFile)
	if err != nil {
		return err
	}

	file, err := os.Create(filepath.Join(path, "index.yaml"))
	if err != nil {
		return err
	}

	_, err = file.Write(data)
	file.Close()
	if err != nil {
		return err
	}

	// Create the target path if needed
	backupsPath := shared.VarPath("backups", project.Prefix(c.Project(), c.Name()))
	if !shared.PathExists(backupsPath) {
		err := os.MkdirAll(backupsPath, 0700)
		if err != nil {
			return err
		}
	}

	// Create the tarball
	backupPath := shared.VarPath("backups", project.Prefix(c.Project(), b.Name()))
	success := false
	defer func() {
		if success {
			return
		}

		os.RemoveAll(backupPath)
	}()

	args := []string{"-cf", backupPath, "--numeric-owner", "--xattrs", "-C", path, "--transform", "s,^./,backup/,", "."}
	_, err = shared.RunCommand("tar", args...)
	if err != nil {
		return err
	}

	err = os.RemoveAll(path)
	if err != nil {
		return err
	}

	var compress string

	if b.CompressionAlgorithm() != "" {
		compress = b.CompressionAlgorithm()
	} else {
		compress, err = cluster.ConfigGetString(s.Cluster, "backups.compression_algorithm")
		if err != nil {
			return err
		}
	}

	if compress != "none" {
		infile, err := os.Open(backupPath)
		if err != nil {
			return err
		}
		defer infile.Close()

		compressed, err := os.Create(backupPath + ".compressed")
		if err != nil {
			return err
		}
		compressedName := compressed.Name()

		defer compressed.Close()
		defer os.Remove(compressedName)

		err = compressFile(compress, infile, compressed)
		if err != nil {
			return err
		}

		err = os.Remove(backupPath)
		if err != nil {
			return err
		}

		err = os.Rename(compressedName, backupPath)
		if err != nil {
			return err
		}
	}

	// Set permissions
	err = os.Chmod(backupPath, 0600)
	if err != nil {
		return err
	}

	success = true
	return nil
}

func pruneExpiredContainerBackupsTask(d *Daemon) (task.Func, task.Schedule) {
	f := func(ctx context.Context) {
		opRun := func(op *operations.Operation) error {
			return pruneExpiredContainerBackups(ctx, d)
		}

		op, err := operations.OperationCreate(d.State(), "", operations.OperationClassTask, db.OperationBackupsExpire, nil, nil, opRun, nil, nil)
		if err != nil {
			logger.Error("Failed to start expired backups operation", log.Ctx{"err": err})
			return
		}

		logger.Info("Pruning expired container backups")
		_, err = op.Run()
		if err != nil {
			logger.Error("Failed to expire backups", log.Ctx{"err": err})
		}
		logger.Info("Done pruning expired container backups")
	}

	f(context.Background())

	first := true
	schedule := func() (time.Duration, error) {
		interval := time.Hour

		if first {
			first = false
			return interval, task.ErrSkip
		}

		return interval, nil
	}

	return f, schedule
}

func pruneExpiredContainerBackups(ctx context.Context, d *Daemon) error {
	// Get the list of expired backups.
	backups, err := d.cluster.ContainerBackupsGetExpired()
	if err != nil {
		return errors.Wrap(err, "Unable to retrieve the list of expired container backups")
	}

	for _, b := range backups {
		inst, err := instance.LoadByID(d.State(), b.InstanceID)
		if err != nil {
			return errors.Wrapf(err, "Error deleting container backup %s", b.Name)
		}

		err = backup.DoBackupDelete(d.State(), inst.Project(), b.Name, inst.Name())
		if err != nil {
			return errors.Wrapf(err, "Error deleting container backup %s", b.Name)
		}
	}

	return nil
}
<commit_msg>lxd/backup: Have tar not transform symlink targets<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"time"

	"context"

	"gopkg.in/yaml.v2"

	"github.com/lxc/lxd/lxd/backup"
	"github.com/lxc/lxd/lxd/cluster"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/instance"
	"github.com/lxc/lxd/lxd/instance/instancetype"
	"github.com/lxc/lxd/lxd/operations"
	"github.com/lxc/lxd/lxd/project"
	"github.com/lxc/lxd/lxd/state"
	storagePools "github.com/lxc/lxd/lxd/storage"
	storageDrivers "github.com/lxc/lxd/lxd/storage/drivers"
	"github.com/lxc/lxd/lxd/task"
	"github.com/lxc/lxd/shared"
	log "github.com/lxc/lxd/shared/log15"
	"github.com/lxc/lxd/shared/logger"
	"github.com/pkg/errors"
)

// Create a new backup.
func backupCreate(s *state.State, args db.InstanceBackupArgs, sourceInst instance.Instance) error {
	// Create the database entry.
	err := s.Cluster.ContainerBackupCreate(args)
	if err != nil {
		if err == db.ErrAlreadyDefined {
			return fmt.Errorf("backup '%s' already exists", args.Name)
		}

		return errors.Wrap(err, "Insert backup info into database")
	}

	revert := true
	defer func() {
		if !revert {
			return
		}
		s.Cluster.ContainerBackupRemove(args.Name)
	}()

	// Get the backup struct.
	b, err := instance.BackupLoadByName(s, sourceInst.Project(), args.Name)
	if err != nil {
		return errors.Wrap(err, "Load backup object")
	}

	b.SetCompressionAlgorithm(args.CompressionAlgorithm)

	// Create a temporary path for the backup.
	tmpPath, err := ioutil.TempDir(shared.VarPath("backups"), "lxd_backup_")
	if err != nil {
		return err
	}
	defer os.RemoveAll(tmpPath)

	// Check if we can load new storage layer for pool driver type.
	pool, err := storagePools.GetPoolByInstance(s, sourceInst)
	if err != storageDrivers.ErrUnknownDriver && err != storageDrivers.ErrNotImplemented {
		if err != nil {
			return errors.Wrap(err, "Load instance storage pool")
		}

		err = pool.BackupInstance(sourceInst, tmpPath, b.OptimizedStorage(), !b.InstanceOnly(), nil)
		if err != nil {
			return errors.Wrap(err, "Backup create")
		}
	} else if sourceInst.Type() == instancetype.Container {
		ourStart, err := sourceInst.StorageStart()
		if err != nil {
			return err
		}
		if ourStart {
			defer sourceInst.StorageStop()
		}

		ct := sourceInst.(*containerLXC)
		err = ct.Storage().ContainerBackupCreate(tmpPath, *b, sourceInst)
		if err != nil {
			return errors.Wrap(err, "Backup create")
		}
	} else {
		return fmt.Errorf("Instance type not supported")
	}

	// Pack the backup.
	err = backupCreateTarball(s, tmpPath, *b, sourceInst)
	if err != nil {
		return err
	}

	revert = false
	return nil
}

func backupCreateTarball(s *state.State, path string, b backup.Backup, c instance.Instance) error {
	// Create the index
	poolName, err := c.StoragePool()
	if err != nil {
		return err
	}

	if c.Type() != instancetype.Container {
		return fmt.Errorf("Instance type must be container")
	}

	indexFile := backup.Info{
		Name:       c.Name(),
		Privileged: c.IsPrivileged(),
		Pool:       poolName,
		Snapshots:  []string{},
	}

	pool, err := storagePools.GetPoolByInstance(s, c)
	if err != storageDrivers.ErrUnknownDriver && err != storageDrivers.ErrNotImplemented && err != db.ErrNoSuchObject {
		if err != nil {
			return err
		}

		info := pool.Driver().Info()
		indexFile.Backend = info.Name
	} else {
		ct := c.(*containerLXC)
		indexFile.Backend = ct.Storage().GetStorageTypeName()
	}

	if !b.InstanceOnly() {
		snaps, err := c.Snapshots()
		if err != nil {
			return err
		}

		for _, snap := range snaps {
			_, snapName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name())
			indexFile.Snapshots = append(indexFile.Snapshots, snapName)
		}
	}

	data, err := yaml.Marshal(&indexFile)
	if err != nil {
		return err
	}

	file, err := os.Create(filepath.Join(path, "index.yaml"))
	if err != nil {
		return err
	}

	_, err = file.Write(data)
	file.Close()
	if err != nil {
		return err
	}

	// Create the target path if needed
	backupsPath := shared.VarPath("backups", project.Prefix(c.Project(), c.Name()))
	if !shared.PathExists(backupsPath) {
		err := os.MkdirAll(backupsPath, 0700)
		if err != nil {
			return err
		}
	}

	// Create the tarball
	backupPath := shared.VarPath("backups", project.Prefix(c.Project(), b.Name()))
	success := false
	defer func() {
		if success {
			return
		}

		os.RemoveAll(backupPath)
	}()

	args := []string{"-cf", backupPath, "--numeric-owner", "--xattrs", "-C", path, "--transform", "s,^./,backup/,S", "."}
	_, err = shared.RunCommand("tar", args...)
	if err != nil {
		return err
	}

	err = os.RemoveAll(path)
	if err != nil {
		return err
	}

	var compress string

	if b.CompressionAlgorithm() != "" {
		compress = b.CompressionAlgorithm()
	} else {
		compress, err = cluster.ConfigGetString(s.Cluster, "backups.compression_algorithm")
		if err != nil {
			return err
		}
	}

	if compress != "none" {
		infile, err := os.Open(backupPath)
		if err != nil {
			return err
		}
		defer infile.Close()

		compressed, err := os.Create(backupPath + ".compressed")
		if err != nil {
			return err
		}
		compressedName := compressed.Name()

		defer compressed.Close()
		defer os.Remove(compressedName)

		err = compressFile(compress, infile, compressed)
		if err != nil {
			return err
		}

		err = os.Remove(backupPath)
		if err != nil {
			return err
		}

		err = os.Rename(compressedName, backupPath)
		if err != nil {
			return err
		}
	}

	// Set permissions
	err = os.Chmod(backupPath, 0600)
	if err != nil {
		return err
	}

	success = true
	return nil
}

func pruneExpiredContainerBackupsTask(d *Daemon) (task.Func, task.Schedule) {
	f := func(ctx context.Context) {
		opRun := func(op *operations.Operation) error {
			return pruneExpiredContainerBackups(ctx, d)
		}

		op, err := operations.OperationCreate(d.State(), "", operations.OperationClassTask, db.OperationBackupsExpire, nil, nil, opRun, nil, nil)
		if err != nil {
			logger.Error("Failed to start expired backups operation", log.Ctx{"err": err})
			return
		}

		logger.Info("Pruning expired container backups")
		_, err = op.Run()
		if err != nil {
			logger.Error("Failed to expire backups", log.Ctx{"err": err})
		}
		logger.Info("Done pruning expired container backups")
	}

	f(context.Background())

	first := true
	schedule := func() (time.Duration, error) {
		interval := time.Hour

		if first {
			first = false
			return interval, task.ErrSkip
		}

		return interval, nil
	}

	return f, schedule
}

func pruneExpiredContainerBackups(ctx context.Context, d *Daemon) error {
	// Get the list of expired backups.
	backups, err := d.cluster.ContainerBackupsGetExpired()
	if err != nil {
		return errors.Wrap(err, "Unable to retrieve the list of expired container backups")
	}

	for _, b := range backups {
		inst, err := instance.LoadByID(d.State(), b.InstanceID)
		if err != nil {
			return errors.Wrapf(err, "Error deleting container backup %s", b.Name)
		}

		err = backup.DoBackupDelete(d.State(), inst.Project(), b.Name, inst.Name())
		if err != nil {
			return errors.Wrapf(err, "Error deleting container backup %s", b.Name)
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"time"

	"context"

	"gopkg.in/yaml.v2"

	"github.com/lxc/lxd/lxd/backup"
	"github.com/lxc/lxd/lxd/cluster"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/instance"
	"github.com/lxc/lxd/lxd/instance/instancetype"
	"github.com/lxc/lxd/lxd/operations"
	"github.com/lxc/lxd/lxd/project"
	"github.com/lxc/lxd/lxd/state"
	storagePools "github.com/lxc/lxd/lxd/storage"
	storageDrivers "github.com/lxc/lxd/lxd/storage/drivers"
	"github.com/lxc/lxd/lxd/task"
	"github.com/lxc/lxd/shared"
	log "github.com/lxc/lxd/shared/log15"
	"github.com/lxc/lxd/shared/logger"
	"github.com/pkg/errors"
)

// Create a new backup.
func backupCreate(s *state.State, args db.InstanceBackupArgs, sourceInst instance.Instance) error {
	// Create the database entry.
	err := s.Cluster.ContainerBackupCreate(args)
	if err != nil {
		if err == db.ErrAlreadyDefined {
			return fmt.Errorf("backup '%s' already exists", args.Name)
		}

		return errors.Wrap(err, "Insert backup info into database")
	}

	revert := true
	defer func() {
		if !revert {
			return
		}
		s.Cluster.ContainerBackupRemove(args.Name)
	}()

	// Get the backup struct.
	b, err := backup.LoadByName(s, sourceInst.Project(), args.Name)
	if err != nil {
		return errors.Wrap(err, "Load backup object")
	}

	b.SetCompressionAlgorithm(args.CompressionAlgorithm)

	// Create a temporary path for the backup.
	tmpPath, err := ioutil.TempDir(shared.VarPath("backups"), "lxd_backup_")
	if err != nil {
		return err
	}
	defer os.RemoveAll(tmpPath)

	pool, err := storagePools.GetPoolByInstance(s, sourceInst)
	if err != storageDrivers.ErrUnknownDriver && err != storageDrivers.ErrNotImplemented {
		if err != nil {
			return errors.Wrap(err, "Load instance storage pool")
		}

		err = pool.BackupInstance(sourceInst, tmpPath, b.OptimizedStorage(), !b.InstanceOnly(), nil)
		if err != nil {
			return errors.Wrap(err, "Backup create")
		}
	} else if sourceInst.Type() == instancetype.Container {
		ourStart, err := sourceInst.StorageStart()
		if err != nil {
			return err
		}
		if ourStart {
			defer sourceInst.StorageStop()
		}

		ct := sourceInst.(*containerLXC)
		err = ct.Storage().ContainerBackupCreate(tmpPath, *b, sourceInst)
		if err != nil {
			return errors.Wrap(err, "Backup create")
		}
	} else {
		return fmt.Errorf("Instance type not supported")
	}

	// Pack the backup.
	err = backupCreateTarball(s, tmpPath, *b, sourceInst)
	if err != nil {
		return err
	}

	revert = false
	return nil
}

func backupCreateTarball(s *state.State, path string, b backup.Backup, c instance.Instance) error {
	// Create the index
	pool, err := c.StoragePool()
	if err != nil {
		return err
	}

	if c.Type() != instancetype.Container {
		return fmt.Errorf("Instance type must be container")
	}

	ct := c.(*containerLXC)

	indexFile := backup.Info{
		Name:       c.Name(),
		Backend:    ct.Storage().GetStorageTypeName(),
		Privileged: c.IsPrivileged(),
		Pool:       pool,
		Snapshots:  []string{},
	}

	if !b.InstanceOnly() {
		snaps, err := c.Snapshots()
		if err != nil {
			return err
		}

		for _, snap := range snaps {
			_, snapName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name())
			indexFile.Snapshots = append(indexFile.Snapshots, snapName)
		}
	}

	data, err := yaml.Marshal(&indexFile)
	if err != nil {
		return err
	}

	file, err := os.Create(filepath.Join(path, "index.yaml"))
	if err != nil {
		return err
	}

	_, err = file.Write(data)
	file.Close()
	if err != nil {
		return err
	}

	// Create the target path if needed
	backupsPath := shared.VarPath("backups", project.Prefix(c.Project(), c.Name()))
	if !shared.PathExists(backupsPath) {
		err := os.MkdirAll(backupsPath, 0700)
		if err != nil {
			return err
		}
	}

	// Create the tarball
	backupPath := shared.VarPath("backups", project.Prefix(c.Project(), b.Name()))
	success := false
	defer func() {
		if success {
			return
		}

		os.RemoveAll(backupPath)
	}()

	args := []string{"-cf", backupPath, "--numeric-owner", "--xattrs", "-C", path, "--transform", "s,^./,backup/,", "."}
	_, err = shared.RunCommand("tar", args...)
	if err != nil {
		return err
	}

	err = os.RemoveAll(path)
	if err != nil {
		return err
	}

	var compress string

	if b.CompressionAlgorithm() != "" {
		compress = b.CompressionAlgorithm()
	} else {
		compress, err = cluster.ConfigGetString(s.Cluster, "backups.compression_algorithm")
		if err != nil {
			return err
		}
	}

	if compress != "none" {
		infile, err := os.Open(backupPath)
		if err != nil {
			return err
		}
		defer infile.Close()

		compressed, err := os.Create(backupPath + ".compressed")
		if err != nil {
			return err
		}
		compressedName := compressed.Name()

		defer compressed.Close()
		defer os.Remove(compressedName)

		err = compressFile(compress, infile, compressed)
		if err != nil {
			return err
		}

		err = os.Remove(backupPath)
		if err != nil {
			return err
		}

		err = os.Rename(compressedName, backupPath)
		if err != nil {
			return err
		}
	}

	// Set permissions
	err = os.Chmod(backupPath, 0600)
	if err != nil {
		return err
	}

	success = true
	return nil
}

func pruneExpiredContainerBackupsTask(d *Daemon) (task.Func, task.Schedule) {
	f := func(ctx context.Context) {
		opRun := func(op *operations.Operation) error {
			return pruneExpiredContainerBackups(ctx, d)
		}

		op, err := operations.OperationCreate(d.State(), "", operations.OperationClassTask, db.OperationBackupsExpire, nil, nil, opRun, nil, nil)
		if err != nil {
			logger.Error("Failed to start expired backups operation", log.Ctx{"err": err})
			return
		}

		logger.Info("Pruning expired container backups")
		_, err = op.Run()
		if err != nil {
			logger.Error("Failed to expire backups", log.Ctx{"err": err})
		}
		logger.Info("Done pruning expired container backups")
	}

	f(context.Background())

	first := true
	schedule := func() (time.Duration, error) {
		interval := time.Hour

		if first {
			first = false
			return interval, task.ErrSkip
		}

		return interval, nil
	}

	return f, schedule
}

func pruneExpiredContainerBackups(ctx context.Context, d *Daemon) error {
	// Get the list of expired backups.
	backups, err := d.cluster.ContainerBackupsGetExpired()
	if err != nil {
		return errors.Wrap(err, "Unable to retrieve the list of expired container backups")
	}

	for _, b := range backups {
		inst, err := instanceLoadById(d.State(), b.InstanceID)
		if err != nil {
			return errors.Wrapf(err, "Error deleting container backup %s", b.Name)
		}

		err = backup.DoBackupDelete(d.State(), inst.Project(), b.Name, inst.Name())
		if err != nil {
			return errors.Wrapf(err, "Error deleting container backup %s", b.Name)
		}
	}

	return nil
}
<commit_msg>lxd/backup: Comment consistency<commit_after>package main

import (
	"fmt"
	"io/ioutil"
	"os"
	"path/filepath"
	"time"

	"context"

	"gopkg.in/yaml.v2"

	"github.com/lxc/lxd/lxd/backup"
	"github.com/lxc/lxd/lxd/cluster"
	"github.com/lxc/lxd/lxd/db"
	"github.com/lxc/lxd/lxd/instance"
	"github.com/lxc/lxd/lxd/instance/instancetype"
	"github.com/lxc/lxd/lxd/operations"
	"github.com/lxc/lxd/lxd/project"
	"github.com/lxc/lxd/lxd/state"
	storagePools "github.com/lxc/lxd/lxd/storage"
	storageDrivers "github.com/lxc/lxd/lxd/storage/drivers"
	"github.com/lxc/lxd/lxd/task"
	"github.com/lxc/lxd/shared"
	log "github.com/lxc/lxd/shared/log15"
	"github.com/lxc/lxd/shared/logger"
	"github.com/pkg/errors"
)

// Create a new backup.
func backupCreate(s *state.State, args db.InstanceBackupArgs, sourceInst instance.Instance) error {
	// Create the database entry.
	err := s.Cluster.ContainerBackupCreate(args)
	if err != nil {
		if err == db.ErrAlreadyDefined {
			return fmt.Errorf("backup '%s' already exists", args.Name)
		}

		return errors.Wrap(err, "Insert backup info into database")
	}

	revert := true
	defer func() {
		if !revert {
			return
		}
		s.Cluster.ContainerBackupRemove(args.Name)
	}()

	// Get the backup struct.
	b, err := backup.LoadByName(s, sourceInst.Project(), args.Name)
	if err != nil {
		return errors.Wrap(err, "Load backup object")
	}

	b.SetCompressionAlgorithm(args.CompressionAlgorithm)

	// Create a temporary path for the backup.
	tmpPath, err := ioutil.TempDir(shared.VarPath("backups"), "lxd_backup_")
	if err != nil {
		return err
	}
	defer os.RemoveAll(tmpPath)

	// Check if we can load new storage layer for pool driver type.
	pool, err := storagePools.GetPoolByInstance(s, sourceInst)
	if err != storageDrivers.ErrUnknownDriver && err != storageDrivers.ErrNotImplemented {
		if err != nil {
			return errors.Wrap(err, "Load instance storage pool")
		}

		err = pool.BackupInstance(sourceInst, tmpPath, b.OptimizedStorage(), !b.InstanceOnly(), nil)
		if err != nil {
			return errors.Wrap(err, "Backup create")
		}
	} else if sourceInst.Type() == instancetype.Container {
		ourStart, err := sourceInst.StorageStart()
		if err != nil {
			return err
		}
		if ourStart {
			defer sourceInst.StorageStop()
		}

		ct := sourceInst.(*containerLXC)
		err = ct.Storage().ContainerBackupCreate(tmpPath, *b, sourceInst)
		if err != nil {
			return errors.Wrap(err, "Backup create")
		}
	} else {
		return fmt.Errorf("Instance type not supported")
	}

	// Pack the backup.
	err = backupCreateTarball(s, tmpPath, *b, sourceInst)
	if err != nil {
		return err
	}

	revert = false
	return nil
}

func backupCreateTarball(s *state.State, path string, b backup.Backup, c instance.Instance) error {
	// Create the index
	pool, err := c.StoragePool()
	if err != nil {
		return err
	}

	if c.Type() != instancetype.Container {
		return fmt.Errorf("Instance type must be container")
	}

	ct := c.(*containerLXC)

	indexFile := backup.Info{
		Name:       c.Name(),
		Backend:    ct.Storage().GetStorageTypeName(),
		Privileged: c.IsPrivileged(),
		Pool:       pool,
		Snapshots:  []string{},
	}

	if !b.InstanceOnly() {
		snaps, err := c.Snapshots()
		if err != nil {
			return err
		}

		for _, snap := range snaps {
			_, snapName, _ := shared.InstanceGetParentAndSnapshotName(snap.Name())
			indexFile.Snapshots = append(indexFile.Snapshots, snapName)
		}
	}

	data, err := yaml.Marshal(&indexFile)
	if err != nil {
		return err
	}

	file, err := os.Create(filepath.Join(path, "index.yaml"))
	if err != nil {
		return err
	}

	_, err = file.Write(data)
	file.Close()
	if err != nil {
		return err
	}

	// Create the target path if needed
	backupsPath := shared.VarPath("backups", project.Prefix(c.Project(), c.Name()))
	if !shared.PathExists(backupsPath) {
		err := os.MkdirAll(backupsPath, 0700)
		if err != nil {
			return err
		}
	}

	// Create the tarball
	backupPath := shared.VarPath("backups", project.Prefix(c.Project(), b.Name()))
	success := false
	defer func() {
		if success {
			return
		}

		os.RemoveAll(backupPath)
	}()

	args := []string{"-cf", backupPath, "--numeric-owner", "--xattrs", "-C", path, "--transform", "s,^./,backup/,", "."}
	_, err = shared.RunCommand("tar", args...)
	if err != nil {
		return err
	}

	err = os.RemoveAll(path)
	if err != nil {
		return err
	}

	var compress string

	if b.CompressionAlgorithm() != "" {
		compress = b.CompressionAlgorithm()
	} else {
		compress, err = cluster.ConfigGetString(s.Cluster, "backups.compression_algorithm")
		if err != nil {
			return err
		}
	}

	if compress != "none" {
		infile, err := os.Open(backupPath)
		if err != nil {
			return err
		}
		defer infile.Close()

		compressed, err := os.Create(backupPath + ".compressed")
		if err != nil {
			return err
		}
		compressedName := compressed.Name()

		defer compressed.Close()
		defer os.Remove(compressedName)

		err = compressFile(compress, infile, compressed)
		if err != nil {
			return err
		}

		err = os.Remove(backupPath)
		if err != nil {
			return err
		}

		err = os.Rename(compressedName, backupPath)
		if err != nil {
			return err
		}
	}

	// Set permissions
	err = os.Chmod(backupPath, 0600)
	if err != nil {
		return err
	}

	success = true
	return nil
}

func pruneExpiredContainerBackupsTask(d *Daemon) (task.Func, task.Schedule) {
	f := func(ctx context.Context) {
		opRun := func(op *operations.Operation) error {
			return pruneExpiredContainerBackups(ctx, d)
		}

		op, err := operations.OperationCreate(d.State(), "", operations.OperationClassTask, db.OperationBackupsExpire, nil, nil, opRun, nil, nil)
		if err != nil {
			logger.Error("Failed to start expired backups operation", log.Ctx{"err": err})
			return
		}

		logger.Info("Pruning expired container backups")
		_, err = op.Run()
		if err != nil {
			logger.Error("Failed to expire backups", log.Ctx{"err": err})
		}
		logger.Info("Done pruning expired container backups")
	}

	f(context.Background())

	first := true
	schedule := func() (time.Duration, error) {
		interval := time.Hour

		if first {
			first = false
			return interval, task.ErrSkip
		}

		return interval, nil
	}

	return f, schedule
}

func pruneExpiredContainerBackups(ctx context.Context, d *Daemon) error {
	// Get the list of expired backups.
	backups, err := d.cluster.ContainerBackupsGetExpired()
	if err != nil {
		return errors.Wrap(err, "Unable to retrieve the list of expired container backups")
	}

	for _, b := range backups {
		inst, err := instanceLoadById(d.State(), b.InstanceID)
		if err != nil {
			return errors.Wrapf(err, "Error deleting container backup %s", b.Name)
		}

		err = backup.DoBackupDelete(d.State(), inst.Project(), b.Name, inst.Name())
		if err != nil {
			return errors.Wrapf(err, "Error deleting container backup %s", b.Name)
		}
	}

	return nil
}
<|endoftext|>


<commit_before>package sarama

import (
	"encoding/binary"
	"math"
)

type realDecoder struct {
	raw   []byte
	off   int
	stack []pushDecoder
}

// primitives

func (rd *realDecoder) getInt8() (int8, error) {
	if rd.remaining() < 1 {
		rd.off = len(rd.raw)
		return -1, InsufficientData
	}
	tmp := int8(rd.raw[rd.off])
	rd.off += binary.Size(tmp)
	return tmp, nil
}

func (rd *realDecoder) getInt16() (int16, error) {
	if rd.remaining() < 2 {
		rd.off = len(rd.raw)
		return -1, InsufficientData
	}
	tmp := int16(binary.BigEndian.Uint16(rd.raw[rd.off:]))
	rd.off += binary.Size(tmp)
	return tmp, nil
}

func (rd *realDecoder) getInt32() (int32, error) {
	if rd.remaining() < 4 {
		rd.off = len(rd.raw)
		return -1, InsufficientData
	}
	tmp := int32(binary.BigEndian.Uint32(rd.raw[rd.off:]))
	rd.off += binary.Size(tmp)
	return tmp, nil
}

func (rd *realDecoder) getInt64() (int64, error) {
	if rd.remaining() < 8 {
		rd.off = len(rd.raw)
		return -1, InsufficientData
	}
	tmp := int64(binary.BigEndian.Uint64(rd.raw[rd.off:]))
	rd.off += binary.Size(tmp)
	return tmp, nil
}

func (rd *realDecoder) getArrayLength() (int, error) {
	if rd.remaining() < 4 {
		rd.off = len(rd.raw)
		return -1, InsufficientData
	}
	tmp := int(binary.BigEndian.Uint32(rd.raw[rd.off:]))
	rd.off += 4
	if tmp > rd.remaining() {
		rd.off = len(rd.raw)
		return -1, InsufficientData
	} else if tmp > 2*math.MaxUint16 {
		return -1, DecodingError
	}
	return tmp, nil
}

// collections

func (rd *realDecoder) getBytes() ([]byte, error) {
	tmp, err := rd.getInt32()

	if err != nil {
		return nil, err
	}

	n := int(tmp)

	switch {
	case n < -1:
		return nil, DecodingError
	case n == -1:
		return nil, nil
	case n == 0:
		return make([]byte, 0), nil
	case n > rd.remaining():
		rd.off = len(rd.raw)
		return nil, InsufficientData
	}

	tmpStr := rd.raw[rd.off : rd.off+n]
	rd.off += n
	return tmpStr, nil
}

func (rd *realDecoder) getString() (string, error) {
	tmp, err := rd.getInt16()

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

	n := int(tmp)

	switch {
	case n < -1:
		return "", DecodingError
	case n == -1:
		return "", nil
	case n == 0:
		return "", nil
	case n > rd.remaining():
		rd.off = len(rd.raw)
		return "", InsufficientData
	}

	tmpStr := string(rd.raw[rd.off : rd.off+n])
	rd.off += n
	return tmpStr, nil
}

func (rd *realDecoder) getInt32Array() ([]int32, error) {
	if rd.remaining() < 4 {
		rd.off = len(rd.raw)
		return nil, InsufficientData
	}
	n := int(binary.BigEndian.Uint32(rd.raw[rd.off:]))
	rd.off += 4

	if rd.remaining() < 4*n {
		rd.off = len(rd.raw)
		return nil, InsufficientData
	}

	if n <= 0 {
		return nil, nil
	}

	ret := make([]int32, n)
	for i := range ret {
		ret[i] = int32(binary.BigEndian.Uint32(rd.raw[rd.off:]))
		rd.off += binary.Size(ret[i])
	}
	return ret, nil
}

func (rd *realDecoder) getInt64Array() ([]int64, error) {
	if rd.remaining() < 4 {
		rd.off = len(rd.raw)
		return nil, InsufficientData
	}
	n := int(binary.BigEndian.Uint32(rd.raw[rd.off:]))
	rd.off += 4

	if rd.remaining() < 8*n {
		rd.off = len(rd.raw)
		return nil, InsufficientData
	}

	if n <= 0 {
		return nil, nil
	}

	ret := make([]int64, n)
	for i := range ret {
		ret[i] = int64(binary.BigEndian.Uint64(rd.raw[rd.off:]))
		rd.off += binary.Size(ret[i])
	}
	return ret, nil
}

// subsets

func (rd *realDecoder) remaining() int {
	return len(rd.raw) - rd.off
}

func (rd *realDecoder) getSubset(length int) (packetDecoder, error) {
	if length > rd.remaining() {
		rd.off = len(rd.raw)
		return nil, InsufficientData
	}

	start := rd.off
	rd.off += length
	return &realDecoder{raw: rd.raw[start:rd.off]}, nil
}

// stacks

func (rd *realDecoder) push(in pushDecoder) error {
	in.saveOffset(rd.off)

	reserve := in.reserveLength()
	if rd.remaining() < reserve {
		rd.off = len(rd.raw)
		return InsufficientData
	}

	rd.stack = append(rd.stack, in)

	rd.off += reserve

	return nil
}

func (rd *realDecoder) pop() error {
	// this is go's ugly pop pattern (the inverse of append)
	in := rd.stack[len(rd.stack)-1]
	rd.stack = rd.stack[:len(rd.stack)-1]

	return in.check(rd.off, rd.raw)
}
<commit_msg>Return a decoding error for negative array lengths<commit_after>package sarama

import (
	"encoding/binary"
	"math"
)

type realDecoder struct {
	raw   []byte
	off   int
	stack []pushDecoder
}

// primitives

func (rd *realDecoder) getInt8() (int8, error) {
	if rd.remaining() < 1 {
		rd.off = len(rd.raw)
		return -1, InsufficientData
	}
	tmp := int8(rd.raw[rd.off])
	rd.off += binary.Size(tmp)
	return tmp, nil
}

func (rd *realDecoder) getInt16() (int16, error) {
	if rd.remaining() < 2 {
		rd.off = len(rd.raw)
		return -1, InsufficientData
	}
	tmp := int16(binary.BigEndian.Uint16(rd.raw[rd.off:]))
	rd.off += binary.Size(tmp)
	return tmp, nil
}

func (rd *realDecoder) getInt32() (int32, error) {
	if rd.remaining() < 4 {
		rd.off = len(rd.raw)
		return -1, InsufficientData
	}
	tmp := int32(binary.BigEndian.Uint32(rd.raw[rd.off:]))
	rd.off += binary.Size(tmp)
	return tmp, nil
}

func (rd *realDecoder) getInt64() (int64, error) {
	if rd.remaining() < 8 {
		rd.off = len(rd.raw)
		return -1, InsufficientData
	}
	tmp := int64(binary.BigEndian.Uint64(rd.raw[rd.off:]))
	rd.off += binary.Size(tmp)
	return tmp, nil
}

func (rd *realDecoder) getArrayLength() (int, error) {
	if rd.remaining() < 4 {
		rd.off = len(rd.raw)
		return -1, InsufficientData
	}
	tmp := int(binary.BigEndian.Uint32(rd.raw[rd.off:]))
	rd.off += 4
	if tmp > rd.remaining() {
		rd.off = len(rd.raw)
		return -1, InsufficientData
	} else if tmp > 2*math.MaxUint16 {
		return -1, DecodingError
	}
	return tmp, nil
}

// collections

func (rd *realDecoder) getBytes() ([]byte, error) {
	tmp, err := rd.getInt32()

	if err != nil {
		return nil, err
	}

	n := int(tmp)

	switch {
	case n < -1:
		return nil, DecodingError
	case n == -1:
		return nil, nil
	case n == 0:
		return make([]byte, 0), nil
	case n > rd.remaining():
		rd.off = len(rd.raw)
		return nil, InsufficientData
	}

	tmpStr := rd.raw[rd.off : rd.off+n]
	rd.off += n
	return tmpStr, nil
}

func (rd *realDecoder) getString() (string, error) {
	tmp, err := rd.getInt16()

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

	n := int(tmp)

	switch {
	case n < -1:
		return "", DecodingError
	case n == -1:
		return "", nil
	case n == 0:
		return "", nil
	case n > rd.remaining():
		rd.off = len(rd.raw)
		return "", InsufficientData
	}

	tmpStr := string(rd.raw[rd.off : rd.off+n])
	rd.off += n
	return tmpStr, nil
}

func (rd *realDecoder) getInt32Array() ([]int32, error) {
	if rd.remaining() < 4 {
		rd.off = len(rd.raw)
		return nil, InsufficientData
	}
	n := int(binary.BigEndian.Uint32(rd.raw[rd.off:]))
	rd.off += 4

	if rd.remaining() < 4*n {
		rd.off = len(rd.raw)
		return nil, InsufficientData
	}

	if n == 0 {
		return nil, nil
	}

	if n < 0 {
		return nil, DecodingError
	}

	ret := make([]int32, n)
	for i := range ret {
		ret[i] = int32(binary.BigEndian.Uint32(rd.raw[rd.off:]))
		rd.off += binary.Size(ret[i])
	}
	return ret, nil
}

func (rd *realDecoder) getInt64Array() ([]int64, error) {
	if rd.remaining() < 4 {
		rd.off = len(rd.raw)
		return nil, InsufficientData
	}
	n := int(binary.BigEndian.Uint32(rd.raw[rd.off:]))
	rd.off += 4

	if rd.remaining() < 8*n {
		rd.off = len(rd.raw)
		return nil, InsufficientData
	}

	if n == 0 {
		return nil, nil
	}

	if n < 0 {
		return nil, DecodingError
	}

	ret := make([]int64, n)
	for i := range ret {
		ret[i] = int64(binary.BigEndian.Uint64(rd.raw[rd.off:]))
		rd.off += binary.Size(ret[i])
	}
	return ret, nil
}

// subsets

func (rd *realDecoder) remaining() int {
	return len(rd.raw) - rd.off
}

func (rd *realDecoder) getSubset(length int) (packetDecoder, error) {
	if length > rd.remaining() {
		rd.off = len(rd.raw)
		return nil, InsufficientData
	}

	start := rd.off
	rd.off += length
	return &realDecoder{raw: rd.raw[start:rd.off]}, nil
}

// stacks

func (rd *realDecoder) push(in pushDecoder) error {
	in.saveOffset(rd.off)

	reserve := in.reserveLength()
	if rd.remaining() < reserve {
		rd.off = len(rd.raw)
		return InsufficientData
	}

	rd.stack = append(rd.stack, in)

	rd.off += reserve

	return nil
}

func (rd *realDecoder) pop() error {
	// this is go's ugly pop pattern (the inverse of append)
	in := rd.stack[len(rd.stack)-1]
	rd.stack = rd.stack[:len(rd.stack)-1]

	return in.check(rd.off, rd.raw)
}
<|endoftext|>


<commit_before>package ircclient

import (
	"strings"
	"log"
)

type IRCMessage struct {
	Source   string
	Target   string
	Command  string
	Args     []string
	Complete string
}

type IRCCommand struct {
	IRCMessage
	Args []string
}

func ParseCommand(msg *IRCMessage, trigger byte) *IRCCommand {
	if msg.Command != "PRIVMSG" || len(msg.Args) == 0 || msg.Args[0][0] != trigger {
		return nil
	}
	toParse := msg.Args[0]
	ret := &IRCCommand{*msg, make([]string, 0)}
	for i, last, matchP := 1, 0, false; i < len(toParse); i++ {
		// Match "longer strings in parenthesis"
		if toParse[i] == '"' && (toParse[i-1] == ' ' || toParse[i-1] == '\t' || matchP) {
			if !matchP {
				last = i
				matchP = true
			} else if toParse[i-1] == '\\' {
				// escaped parenthesis
			} else {
				ret.Args = append(ret.Args, toParse[last+1:i])
				matchP = false
				// Skip whitespace
				i++
				for i < len(toParse) && (toParse[i] == ' ' || toParse[i] == '\t') {
					i++
				}
				last = i
			}
		// Match texts seperated by whitespace
		} else if !matchP && (toParse[i] == ' ' || toParse[i] == '\t') {
			log.Println(toParse[last:i])
			ret.Args = append(ret.Args, toParse[last:i])
			for i < len(toParse) && (toParse[i] == ' ' || toParse[i] == '\t') {
				i++
			}
			last = i
			i--
		}
	}
	return ret
}

func ParseServerLine(line string) *IRCMessage {
	im := &IRCMessage{"", "", "", make([]string, 0), line}

	if len(line) == 0 || strings.Trim(line, " \t\n\r") == "" {
		log.Println("ParseIrcLine: empty line")
		return im
	}

	// source and target
	if line[0] == ':' {
		parts := strings.SplitN(line[1:], " ", 4) // 4: src cmd target rest
		im.Source = parts[0]
		im.Command = parts[1]
		im.Target = parts[2]
		// cut them off
		if len(parts) > 3 {
			line = parts[3]
		} else {
			line = ""
		}
	} else {
		parts := strings.SplitN(line, " ", 2) // cmd, rest
		im.Command = parts[0]
		if len(parts) > 1 {
			line = parts[1]
		} else {
			line = ""
		}
	}

	args := strings.SplitN(line, ":", 2)
	for _, a := range strings.Split(args[0], " ") {
		if a != "" {
			im.Args = append(im.Args, a)
		}
	}
	if len(args) > 1 {
		im.Args = append(im.Args, args[1])
	}

	//log.Printf("im: %#v\n", im)
	return im
}
<commit_msg>Some modifications to IRCMessage<commit_after>package ircclient

import (
	"strings"
	"log"
)

type IRCMessage struct {
	Source   string
	Target   string
	Command  string
	Args     []string
	Complete string
}

type IRCCommand struct {
	Source  string
	Command string
	Args    []string
}

func ParseCommand(msg *IRCMessage, trigger byte) *IRCCommand {
	if msg.Command != "PRIVMSG" || len(msg.Args) == 0 || msg.Args[0][0] != trigger {
		return nil
	}
	toParse := msg.Args[0]
	ret := &IRCCommand{"", "", make([]string, 0)}
	for i, last, matchP := 1, 0, false; i < len(toParse); i++ {
		// Match "longer strings in parenthesis"
		if toParse[i] == '"' && (toParse[i-1] == ' ' || toParse[i-1] == '\t' || matchP) {
			if !matchP {
				last = i
				matchP = true
			} else if toParse[i-1] == '\\' {
				// escaped parenthesis
			} else {
				ret.Args = append(ret.Args, toParse[last+1:i])
				matchP = false
				// Skip whitespace
				i++
				for i < len(toParse) && (toParse[i] == ' ' || toParse[i] == '\t') {
					i++
				}
				last = i
			}
			// Match texts seperated by whitespace
		} else if !matchP && (toParse[i] == ' ' || toParse[i] == '\t') {
			log.Println(toParse[last:i])
			ret.Args = append(ret.Args, toParse[last:i])
			for i < len(toParse) && (toParse[i] == ' ' || toParse[i] == '\t') {
				i++
			}
			last = i
			i--
		}
	}
	ret.Command = ret.Args[0][1 : len(ret.Args[0])-1] // Strip off trigger
	ret.Source = msg.Source
	ret.Args = ret.Args[1 : len(ret.Args)-1]
	return ret
}

func ParseServerLine(line string) *IRCMessage {
	im := &IRCMessage{"", "", "", make([]string, 0), line}

	if len(line) == 0 || strings.Trim(line, " \t\n\r") == "" {
		log.Println("ParseIrcLine: empty line")
		return im
	}

	// source and target
	if line[0] == ':' {
		parts := strings.SplitN(line[1:], " ", 4) // 4: src cmd target rest
		im.Source = parts[0]
		im.Command = parts[1]
		im.Target = parts[2]
		// cut them off
		if len(parts) > 3 {
			line = parts[3]
		} else {
			line = ""
		}
	} else {
		parts := strings.SplitN(line, " ", 2) // cmd, rest
		im.Command = parts[0]
		if len(parts) > 1 {
			line = parts[1]
		} else {
			line = ""
		}
	}

	args := strings.SplitN(line, ":", 2)
	for _, a := range strings.Split(args[0], " ") {
		if a != "" {
			im.Args = append(im.Args, a)
		}
	}
	if len(args) > 1 {
		im.Args = append(im.Args, args[1])
	}

	//log.Printf("im: %#v\n", im)
	return im
}
<|endoftext|>


<commit_before>// Copyright 2011, 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package charm_test

import (
	"encoding/json"
	"fmt"
	"strings"

	"labix.org/v2/mgo/bson"
	gc "launchpad.net/gocheck"

	"launchpad.net/juju-core/charm"
)

type URLSuite struct{}

var _ = gc.Suite(&URLSuite{})

var urlTests = []struct {
	s, err string
	url    *charm.URL
}{
	{"cs:~user/series/name", "", &charm.URL{charm.Reference{"cs", "user", "name", -1}, "series"}},
	{"cs:~user/series/name-0", "", &charm.URL{charm.Reference{"cs", "user", "name", 0}, "series"}},
	{"cs:series/name", "", &charm.URL{charm.Reference{"cs", "", "name", -1}, "series"}},
	{"cs:series/name-42", "", &charm.URL{charm.Reference{"cs", "", "name", 42}, "series"}},
	{"local:series/name-1", "", &charm.URL{charm.Reference{"local", "", "name", 1}, "series"}},
	{"local:series/name", "", &charm.URL{charm.Reference{"local", "", "name", -1}, "series"}},
	{"local:series/n0-0n-n0", "", &charm.URL{charm.Reference{"local", "", "n0-0n-n0", -1}, "series"}},
	{"cs:~user/name", "", &charm.URL{charm.Reference{"cs", "user", "name", -1}, ""}},
	{"cs:name", "", &charm.URL{charm.Reference{"cs", "", "name", -1}, ""}},
	{"local:name", "", &charm.URL{charm.Reference{"local", "", "name", -1}, ""}},

	{"bs:~user/series/name-1", "charm URL has invalid schema: .*", nil},
	{"cs:~1/series/name-1", "charm URL has invalid user name: .*", nil},
	{"cs:~user", "charm URL without charm name: .*", nil},
	{"cs:~user/1/name-1", "charm URL has invalid series: .*", nil},
	{"cs:~user/series/name-1-2", "charm URL has invalid charm name: .*", nil},
	{"cs:~user/series/name-1-name-2", "charm URL has invalid charm name: .*", nil},
	{"cs:~user/series/name--name-2", "charm URL has invalid charm name: .*", nil},
	{"cs:~user/series/huh/name-1", "charm URL has invalid form: .*", nil},
	{"cs:/name", "charm URL has invalid series: .*", nil},
	{"local:~user/series/name", "local charm URL with user name: .*", nil},
	{"local:~user/name", "local charm URL with user name: .*", nil},
}

func (s *URLSuite) TestParseURL(c *gc.C) {
	for i, t := range urlTests {
		c.Logf("test %d", i)
		url, uerr := charm.ParseURL(t.s)
		ref, series, rerr := charm.ParseReference(t.s)
		comment := gc.Commentf("ParseURL(%q)", t.s)
		if t.url != nil && t.url.Series == "" {
			if t.err != "" {
				// Expected error should match
				c.Assert(rerr, gc.NotNil, comment)
				c.Check(rerr.Error(), gc.Matches, t.err, comment)
			} else {
				// Expected charm reference should match
				c.Check(ref, gc.DeepEquals, t.url.Reference, comment)
				c.Check(t.url.Reference.String(), gc.Equals, t.s)
			}
			if rerr != nil {
				// If ParseReference has an error, ParseURL should share it
				c.Check(uerr.Error(), gc.Equals, rerr.Error(), comment)
			} else {
				// Otherwise, ParseURL with an empty series should error unresolved.
				c.Check(uerr.Error(), gc.Equals, charm.ErrUnresolvedUrl.Error(), comment)
			}
		} else {
			if t.err != "" {
				c.Assert(uerr, gc.NotNil, comment)
				c.Check(uerr.Error(), gc.Matches, t.err, comment)
				c.Check(uerr.Error(), gc.Equals, rerr.Error(), comment)
			} else {
				c.Check(url.Series, gc.Equals, series, comment)
				c.Check(url, gc.DeepEquals, t.url, comment)
				c.Check(t.url.String(), gc.Equals, t.s)
			}
		}
	}
}

var inferTests = []struct {
	vague, exact string
}{
	{"foo", "cs:defseries/foo"},
	{"foo-1", "cs:defseries/foo-1"},
	{"n0-n0-n0", "cs:defseries/n0-n0-n0"},
	{"cs:foo", "cs:defseries/foo"},
	{"local:foo", "local:defseries/foo"},
	{"series/foo", "cs:series/foo"},
	{"cs:series/foo", "cs:series/foo"},
	{"local:series/foo", "local:series/foo"},
	{"cs:~user/foo", "cs:~user/defseries/foo"},
	{"cs:~user/series/foo", "cs:~user/series/foo"},
	{"local:~user/series/foo", "local:~user/series/foo"},
	{"bs:foo", "bs:defseries/foo"},
	{"cs:~1/foo", "cs:~1/defseries/foo"},
	{"cs:foo-1-2", "cs:defseries/foo-1-2"},
}

func (s *URLSuite) TestInferURL(c *gc.C) {
	for i, t := range inferTests {
		c.Logf("test %d", i)
		comment := gc.Commentf("InferURL(%q, %q)", t.vague, "defseries")
		inferred, ierr := charm.InferURL(t.vague, "defseries")
		parsed, perr := charm.ParseURL(t.exact)
		if perr == nil {
			c.Check(inferred, gc.DeepEquals, parsed, comment)
			c.Check(ierr, gc.IsNil)
		} else {
			expect := perr.Error()
			if t.vague != t.exact {
				if colIdx := strings.Index(expect, ":"); colIdx > 0 {
					expect = expect[:colIdx]
				}
			}
			c.Check(ierr.Error(), gc.Matches, expect+".*", comment)
		}
	}
	u, err := charm.InferURL("~blah", "defseries")
	c.Assert(u, gc.IsNil)
	c.Assert(err, gc.ErrorMatches, "charm URL without charm name: .*")
}

var inferNoDefaultSeriesTests = []struct {
	vague, exact string
	resolved     bool
}{
	{"foo", "", false},
	{"foo-1", "", false},
	{"cs:foo", "", false},
	{"cs:~user/foo", "", false},
	{"series/foo", "cs:series/foo", true},
	{"cs:series/foo", "cs:series/foo", true},
	{"cs:~user/series/foo", "cs:~user/series/foo", true},
}

func (s *URLSuite) TestInferURLNoDefaultSeries(c *gc.C) {
	for _, t := range inferNoDefaultSeriesTests {
		inferred, err := charm.InferURL(t.vague, "")
		if t.exact == "" {
			c.Assert(err, gc.ErrorMatches, fmt.Sprintf("cannot infer charm URL for %q: no series provided", t.vague))
		} else {
			parsed, err := charm.ParseURL(t.exact)
			c.Assert(err, gc.IsNil)
			c.Assert(inferred, gc.DeepEquals, parsed, gc.Commentf(`InferURL(%q, "")`, t.vague))
		}
	}
}

func (s *URLSuite) TestParseUnresolved(c *gc.C) {
	for _, t := range inferNoDefaultSeriesTests {
		if t.resolved {
			url, err := charm.ParseURL(t.vague)
			c.Assert(err, gc.IsNil)
			c.Assert(url.Series, gc.Not(gc.Equals), "")
		} else {
			_, series, err := charm.ParseReference(t.vague)
			c.Assert(err, gc.IsNil)
			c.Assert(series, gc.Equals, "")
			_, err = charm.ParseURL(t.vague)
			c.Assert(err, gc.NotNil)
			c.Assert(err, gc.Equals, charm.ErrUnresolvedUrl)
		}
	}
}

var validTests = []struct {
	valid  func(string) bool
	string string
	expect bool
}{
	{charm.IsValidUser, "", false},
	{charm.IsValidUser, "bob", true},
	{charm.IsValidUser, "Bob", false},
	{charm.IsValidUser, "bOB", true},
	{charm.IsValidUser, "b^b", false},
	{charm.IsValidUser, "bob1", true},
	{charm.IsValidUser, "bob-1", true},
	{charm.IsValidUser, "bob+1", true},
	{charm.IsValidUser, "bob.1", true},
	{charm.IsValidUser, "1bob", true},
	{charm.IsValidUser, "1-bob", true},
	{charm.IsValidUser, "1+bob", true},
	{charm.IsValidUser, "1.bob", true},
	{charm.IsValidUser, "jim.bob+99-1.", true},

	{charm.IsValidName, "", false},
	{charm.IsValidName, "wordpress", true},
	{charm.IsValidName, "Wordpress", false},
	{charm.IsValidName, "word-press", true},
	{charm.IsValidName, "word press", false},
	{charm.IsValidName, "word^press", false},
	{charm.IsValidName, "-wordpress", false},
	{charm.IsValidName, "wordpress-", false},
	{charm.IsValidName, "wordpress2", true},
	{charm.IsValidName, "wordpress-2", false},
	{charm.IsValidName, "word2-press2", true},

	{charm.IsValidSeries, "", false},
	{charm.IsValidSeries, "precise", true},
	{charm.IsValidSeries, "Precise", false},
	{charm.IsValidSeries, "pre cise", false},
	{charm.IsValidSeries, "pre-cise", false},
	{charm.IsValidSeries, "pre^cise", false},
	{charm.IsValidSeries, "prec1se", true},
	{charm.IsValidSeries, "-precise", false},
	{charm.IsValidSeries, "precise-", false},
	{charm.IsValidSeries, "precise-1", false},
	{charm.IsValidSeries, "precise1", true},
	{charm.IsValidSeries, "pre-c1se", false},
}

func (s *URLSuite) TestValidCheckers(c *gc.C) {
	for i, t := range validTests {
		c.Logf("test %d: %s", i, t.string)
		c.Assert(t.valid(t.string), gc.Equals, t.expect, gc.Commentf("%s", t.string))
	}
}

func (s *URLSuite) TestMustParseURL(c *gc.C) {
	url := charm.MustParseURL("cs:series/name")
	c.Assert(url, gc.DeepEquals,
		&charm.URL{Reference: charm.Reference{"cs", "", "name", -1}, Series: "series"})
	f := func() { charm.MustParseURL("local:@@/name") }
	c.Assert(f, gc.PanicMatches, "charm URL has invalid series: .*")
	f = func() { charm.MustParseURL("cs:~user") }
	c.Assert(f, gc.PanicMatches, "charm URL without charm name: .*")
	f = func() { charm.MustParseURL("cs:~user") }
	c.Assert(f, gc.PanicMatches, "charm URL without charm name: .*")
	f = func() { charm.MustParseURL("cs:name") }
	c.Assert(f, gc.PanicMatches, "charm url series is not resolved")
}

func (s *URLSuite) TestWithRevision(c *gc.C) {
	url := charm.MustParseURL("cs:series/name")
	other := url.WithRevision(1)
	c.Assert(url, gc.DeepEquals, &charm.URL{charm.Reference{"cs", "", "name", -1}, "series"})
	c.Assert(other, gc.DeepEquals, &charm.URL{charm.Reference{"cs", "", "name", 1}, "series"})

	// Should always copy. The opposite behavior is error prone.
	c.Assert(other.WithRevision(1), gc.Not(gc.Equals), other)
	c.Assert(other.WithRevision(1), gc.DeepEquals, other)
}

var codecs = []struct {
	Marshal   func(interface{}) ([]byte, error)
	Unmarshal func([]byte, interface{}) error
}{{
	Marshal:   bson.Marshal,
	Unmarshal: bson.Unmarshal,
}, {
	Marshal:   json.Marshal,
	Unmarshal: json.Unmarshal,
}}

func (s *URLSuite) TestURLCodecs(c *gc.C) {
	for i, codec := range codecs {
		c.Logf("codec %d", i)
		type doc struct {
			URL *charm.URL
		}
		url := charm.MustParseURL("cs:series/name")
		data, err := codec.Marshal(doc{url})
		c.Assert(err, gc.IsNil)
		var v doc
		err = codec.Unmarshal(data, &v)
		c.Assert(v.URL, gc.DeepEquals, url)

		data, err = codec.Marshal(doc{})
		c.Assert(err, gc.IsNil)
		err = codec.Unmarshal(data, &v)
		c.Assert(err, gc.IsNil)
		c.Assert(v.URL, gc.IsNil)
	}
}

func (s *URLSuite) TestReferenceJSON(c *gc.C) {
	ref, _, err := charm.ParseReference("cs:series/name")
	c.Assert(err, gc.IsNil)
	data, err := json.Marshal(&ref)
	c.Assert(err, gc.IsNil)
	c.Check(string(data), gc.Equals, `"cs:name"`)

	var parsed charm.Reference
	err = json.Unmarshal(data, &parsed)
	c.Assert(err, gc.IsNil)
	c.Check(parsed, gc.DeepEquals, ref)
}

type QuoteSuite struct{}

var _ = gc.Suite(&QuoteSuite{})

func (s *QuoteSuite) TestUnmodified(c *gc.C) {
	// Check that a string containing only valid
	// chars stays unmodified.
	in := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-"
	out := charm.Quote(in)
	c.Assert(out, gc.Equals, in)
}

func (s *QuoteSuite) TestQuote(c *gc.C) {
	// Check that invalid chars are translated correctly.
	in := "hello_there/how'are~you-today.sir"
	out := charm.Quote(in)
	c.Assert(out, gc.Equals, "hello_5f_there_2f_how_27_are_7e_you-today.sir")
}
<commit_msg>Add coverage for unmarshalling JSON gibberish.<commit_after>// Copyright 2011, 2012, 2013 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package charm_test

import (
	"encoding/json"
	"fmt"
	"strings"

	"labix.org/v2/mgo/bson"
	gc "launchpad.net/gocheck"

	"launchpad.net/juju-core/charm"
)

type URLSuite struct{}

var _ = gc.Suite(&URLSuite{})

var urlTests = []struct {
	s, err string
	url    *charm.URL
}{
	{"cs:~user/series/name", "", &charm.URL{charm.Reference{"cs", "user", "name", -1}, "series"}},
	{"cs:~user/series/name-0", "", &charm.URL{charm.Reference{"cs", "user", "name", 0}, "series"}},
	{"cs:series/name", "", &charm.URL{charm.Reference{"cs", "", "name", -1}, "series"}},
	{"cs:series/name-42", "", &charm.URL{charm.Reference{"cs", "", "name", 42}, "series"}},
	{"local:series/name-1", "", &charm.URL{charm.Reference{"local", "", "name", 1}, "series"}},
	{"local:series/name", "", &charm.URL{charm.Reference{"local", "", "name", -1}, "series"}},
	{"local:series/n0-0n-n0", "", &charm.URL{charm.Reference{"local", "", "n0-0n-n0", -1}, "series"}},
	{"cs:~user/name", "", &charm.URL{charm.Reference{"cs", "user", "name", -1}, ""}},
	{"cs:name", "", &charm.URL{charm.Reference{"cs", "", "name", -1}, ""}},
	{"local:name", "", &charm.URL{charm.Reference{"local", "", "name", -1}, ""}},

	{"bs:~user/series/name-1", "charm URL has invalid schema: .*", nil},
	{"cs:~1/series/name-1", "charm URL has invalid user name: .*", nil},
	{"cs:~user", "charm URL without charm name: .*", nil},
	{"cs:~user/1/name-1", "charm URL has invalid series: .*", nil},
	{"cs:~user/series/name-1-2", "charm URL has invalid charm name: .*", nil},
	{"cs:~user/series/name-1-name-2", "charm URL has invalid charm name: .*", nil},
	{"cs:~user/series/name--name-2", "charm URL has invalid charm name: .*", nil},
	{"cs:~user/series/huh/name-1", "charm URL has invalid form: .*", nil},
	{"cs:/name", "charm URL has invalid series: .*", nil},
	{"local:~user/series/name", "local charm URL with user name: .*", nil},
	{"local:~user/name", "local charm URL with user name: .*", nil},
}

func (s *URLSuite) TestParseURL(c *gc.C) {
	for i, t := range urlTests {
		c.Logf("test %d", i)
		url, uerr := charm.ParseURL(t.s)
		ref, series, rerr := charm.ParseReference(t.s)
		comment := gc.Commentf("ParseURL(%q)", t.s)
		if t.url != nil && t.url.Series == "" {
			if t.err != "" {
				// Expected error should match
				c.Assert(rerr, gc.NotNil, comment)
				c.Check(rerr.Error(), gc.Matches, t.err, comment)
			} else {
				// Expected charm reference should match
				c.Check(ref, gc.DeepEquals, t.url.Reference, comment)
				c.Check(t.url.Reference.String(), gc.Equals, t.s)
			}
			if rerr != nil {
				// If ParseReference has an error, ParseURL should share it
				c.Check(uerr.Error(), gc.Equals, rerr.Error(), comment)
			} else {
				// Otherwise, ParseURL with an empty series should error unresolved.
				c.Check(uerr.Error(), gc.Equals, charm.ErrUnresolvedUrl.Error(), comment)
			}
		} else {
			if t.err != "" {
				c.Assert(uerr, gc.NotNil, comment)
				c.Check(uerr.Error(), gc.Matches, t.err, comment)
				c.Check(uerr.Error(), gc.Equals, rerr.Error(), comment)
			} else {
				c.Check(url.Series, gc.Equals, series, comment)
				c.Check(url, gc.DeepEquals, t.url, comment)
				c.Check(t.url.String(), gc.Equals, t.s)
			}
		}
	}
}

var inferTests = []struct {
	vague, exact string
}{
	{"foo", "cs:defseries/foo"},
	{"foo-1", "cs:defseries/foo-1"},
	{"n0-n0-n0", "cs:defseries/n0-n0-n0"},
	{"cs:foo", "cs:defseries/foo"},
	{"local:foo", "local:defseries/foo"},
	{"series/foo", "cs:series/foo"},
	{"cs:series/foo", "cs:series/foo"},
	{"local:series/foo", "local:series/foo"},
	{"cs:~user/foo", "cs:~user/defseries/foo"},
	{"cs:~user/series/foo", "cs:~user/series/foo"},
	{"local:~user/series/foo", "local:~user/series/foo"},
	{"bs:foo", "bs:defseries/foo"},
	{"cs:~1/foo", "cs:~1/defseries/foo"},
	{"cs:foo-1-2", "cs:defseries/foo-1-2"},
}

func (s *URLSuite) TestInferURL(c *gc.C) {
	for i, t := range inferTests {
		c.Logf("test %d", i)
		comment := gc.Commentf("InferURL(%q, %q)", t.vague, "defseries")
		inferred, ierr := charm.InferURL(t.vague, "defseries")
		parsed, perr := charm.ParseURL(t.exact)
		if perr == nil {
			c.Check(inferred, gc.DeepEquals, parsed, comment)
			c.Check(ierr, gc.IsNil)
		} else {
			expect := perr.Error()
			if t.vague != t.exact {
				if colIdx := strings.Index(expect, ":"); colIdx > 0 {
					expect = expect[:colIdx]
				}
			}
			c.Check(ierr.Error(), gc.Matches, expect+".*", comment)
		}
	}
	u, err := charm.InferURL("~blah", "defseries")
	c.Assert(u, gc.IsNil)
	c.Assert(err, gc.ErrorMatches, "charm URL without charm name: .*")
}

var inferNoDefaultSeriesTests = []struct {
	vague, exact string
	resolved     bool
}{
	{"foo", "", false},
	{"foo-1", "", false},
	{"cs:foo", "", false},
	{"cs:~user/foo", "", false},
	{"series/foo", "cs:series/foo", true},
	{"cs:series/foo", "cs:series/foo", true},
	{"cs:~user/series/foo", "cs:~user/series/foo", true},
}

func (s *URLSuite) TestInferURLNoDefaultSeries(c *gc.C) {
	for _, t := range inferNoDefaultSeriesTests {
		inferred, err := charm.InferURL(t.vague, "")
		if t.exact == "" {
			c.Assert(err, gc.ErrorMatches, fmt.Sprintf("cannot infer charm URL for %q: no series provided", t.vague))
		} else {
			parsed, err := charm.ParseURL(t.exact)
			c.Assert(err, gc.IsNil)
			c.Assert(inferred, gc.DeepEquals, parsed, gc.Commentf(`InferURL(%q, "")`, t.vague))
		}
	}
}

func (s *URLSuite) TestParseUnresolved(c *gc.C) {
	for _, t := range inferNoDefaultSeriesTests {
		if t.resolved {
			url, err := charm.ParseURL(t.vague)
			c.Assert(err, gc.IsNil)
			c.Assert(url.Series, gc.Not(gc.Equals), "")
		} else {
			_, series, err := charm.ParseReference(t.vague)
			c.Assert(err, gc.IsNil)
			c.Assert(series, gc.Equals, "")
			_, err = charm.ParseURL(t.vague)
			c.Assert(err, gc.NotNil)
			c.Assert(err, gc.Equals, charm.ErrUnresolvedUrl)
		}
	}
}

var validTests = []struct {
	valid  func(string) bool
	string string
	expect bool
}{
	{charm.IsValidUser, "", false},
	{charm.IsValidUser, "bob", true},
	{charm.IsValidUser, "Bob", false},
	{charm.IsValidUser, "bOB", true},
	{charm.IsValidUser, "b^b", false},
	{charm.IsValidUser, "bob1", true},
	{charm.IsValidUser, "bob-1", true},
	{charm.IsValidUser, "bob+1", true},
	{charm.IsValidUser, "bob.1", true},
	{charm.IsValidUser, "1bob", true},
	{charm.IsValidUser, "1-bob", true},
	{charm.IsValidUser, "1+bob", true},
	{charm.IsValidUser, "1.bob", true},
	{charm.IsValidUser, "jim.bob+99-1.", true},

	{charm.IsValidName, "", false},
	{charm.IsValidName, "wordpress", true},
	{charm.IsValidName, "Wordpress", false},
	{charm.IsValidName, "word-press", true},
	{charm.IsValidName, "word press", false},
	{charm.IsValidName, "word^press", false},
	{charm.IsValidName, "-wordpress", false},
	{charm.IsValidName, "wordpress-", false},
	{charm.IsValidName, "wordpress2", true},
	{charm.IsValidName, "wordpress-2", false},
	{charm.IsValidName, "word2-press2", true},

	{charm.IsValidSeries, "", false},
	{charm.IsValidSeries, "precise", true},
	{charm.IsValidSeries, "Precise", false},
	{charm.IsValidSeries, "pre cise", false},
	{charm.IsValidSeries, "pre-cise", false},
	{charm.IsValidSeries, "pre^cise", false},
	{charm.IsValidSeries, "prec1se", true},
	{charm.IsValidSeries, "-precise", false},
	{charm.IsValidSeries, "precise-", false},
	{charm.IsValidSeries, "precise-1", false},
	{charm.IsValidSeries, "precise1", true},
	{charm.IsValidSeries, "pre-c1se", false},
}

func (s *URLSuite) TestValidCheckers(c *gc.C) {
	for i, t := range validTests {
		c.Logf("test %d: %s", i, t.string)
		c.Assert(t.valid(t.string), gc.Equals, t.expect, gc.Commentf("%s", t.string))
	}
}

func (s *URLSuite) TestMustParseURL(c *gc.C) {
	url := charm.MustParseURL("cs:series/name")
	c.Assert(url, gc.DeepEquals,
		&charm.URL{Reference: charm.Reference{"cs", "", "name", -1}, Series: "series"})
	f := func() { charm.MustParseURL("local:@@/name") }
	c.Assert(f, gc.PanicMatches, "charm URL has invalid series: .*")
	f = func() { charm.MustParseURL("cs:~user") }
	c.Assert(f, gc.PanicMatches, "charm URL without charm name: .*")
	f = func() { charm.MustParseURL("cs:~user") }
	c.Assert(f, gc.PanicMatches, "charm URL without charm name: .*")
	f = func() { charm.MustParseURL("cs:name") }
	c.Assert(f, gc.PanicMatches, "charm url series is not resolved")
}

func (s *URLSuite) TestWithRevision(c *gc.C) {
	url := charm.MustParseURL("cs:series/name")
	other := url.WithRevision(1)
	c.Assert(url, gc.DeepEquals, &charm.URL{charm.Reference{"cs", "", "name", -1}, "series"})
	c.Assert(other, gc.DeepEquals, &charm.URL{charm.Reference{"cs", "", "name", 1}, "series"})

	// Should always copy. The opposite behavior is error prone.
	c.Assert(other.WithRevision(1), gc.Not(gc.Equals), other)
	c.Assert(other.WithRevision(1), gc.DeepEquals, other)
}

var codecs = []struct {
	Marshal   func(interface{}) ([]byte, error)
	Unmarshal func([]byte, interface{}) error
}{{
	Marshal:   bson.Marshal,
	Unmarshal: bson.Unmarshal,
}, {
	Marshal:   json.Marshal,
	Unmarshal: json.Unmarshal,
}}

func (s *URLSuite) TestURLCodecs(c *gc.C) {
	for i, codec := range codecs {
		c.Logf("codec %d", i)
		type doc struct {
			URL *charm.URL
		}
		url := charm.MustParseURL("cs:series/name")
		data, err := codec.Marshal(doc{url})
		c.Assert(err, gc.IsNil)
		var v doc
		err = codec.Unmarshal(data, &v)
		c.Assert(v.URL, gc.DeepEquals, url)

		data, err = codec.Marshal(doc{})
		c.Assert(err, gc.IsNil)
		err = codec.Unmarshal(data, &v)
		c.Assert(err, gc.IsNil)
		c.Assert(v.URL, gc.IsNil)
	}
}

func (s *URLSuite) TestReferenceJSON(c *gc.C) {
	ref, _, err := charm.ParseReference("cs:series/name")
	c.Assert(err, gc.IsNil)
	data, err := json.Marshal(&ref)
	c.Assert(err, gc.IsNil)
	c.Check(string(data), gc.Equals, `"cs:name"`)

	var parsed charm.Reference
	err = json.Unmarshal(data, &parsed)
	c.Assert(err, gc.IsNil)
	c.Check(parsed, gc.DeepEquals, ref)

	// unmarshalling json gibberish
	err = json.Unmarshal([]byte(":{"), &parsed)
	c.Check(err, gc.NotNil)
}

type QuoteSuite struct{}

var _ = gc.Suite(&QuoteSuite{})

func (s *QuoteSuite) TestUnmodified(c *gc.C) {
	// Check that a string containing only valid
	// chars stays unmodified.
	in := "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-"
	out := charm.Quote(in)
	c.Assert(out, gc.Equals, in)
}

func (s *QuoteSuite) TestQuote(c *gc.C) {
	// Check that invalid chars are translated correctly.
	in := "hello_there/how'are~you-today.sir"
	out := charm.Quote(in)
	c.Assert(out, gc.Equals, "hello_5f_there_2f_how_27_are_7e_you-today.sir")
}
<|endoftext|>


<commit_before>package main

// Originally adapted from https://github.com/awslabs/service-discovery-ecs-dns
// This service is meant to run as a singleton instance in concert with redecs_agent
// running on AWS ECS instances. This service will check Redis regularly for active
// service updates and rewrite the A records for services to only contain recently
// active ip addresses.

import (
	"errors"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/route53"
	"gopkg.in/redis.v5"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
)

const checkInterval = 1 * time.Minute // how often to check Redis
const defaultTTL = 300                // seconds
const fetchLast = 300                 // seconds

var DNSName = "servicediscovery.local"

func logErrorAndFail(err error) {
	if err != nil {
		// logrus calls os.exit(1)
		log.Fatal(err)
	}
}

func logErrorNoFatal(err error) {
	if err != nil {
		log.Error(err)
	}
}

type config struct {
	HostedZoneId string
	RedisHost    string
}

var configuration config

func getDNSHostedZoneId() (string, error) {
	r53 := route53.New(session.New())
	params := &route53.ListHostedZonesByNameInput{
		DNSName: aws.String(DNSName),
	}

	zones, err := r53.ListHostedZonesByName(params)

	if err == nil && len(zones.HostedZones) > 0 {
		return aws.StringValue(zones.HostedZones[0].Id), nil
	}

	return "", err
}

// Modify (or create) the A record for this serviceName, adding the private IP of the host.
func modifyDNSRecord(serviceName string, ips []string) error {
	var err error
	r53 := route53.New(session.New())

	aValues := make([]*route53.ResourceRecord, 0)
	// Put all the IPs in one A record
	for _, ip := range ips {
		aValues = append(aValues, &route53.ResourceRecord{Value: aws.String(ip)})
	}

	// This API call creates a new DNS record for this service
	params := &route53.ChangeResourceRecordSetsInput{
		ChangeBatch: &route53.ChangeBatch{
			Changes: []*route53.Change{
				{
					Action: aws.String(route53.ChangeActionUpsert),
					ResourceRecordSet: &route53.ResourceRecordSet{
						Name: aws.String(serviceName + "." + DNSName),
						// It creates an A record with the name of the service
						Type:            aws.String(route53.RRTypeA),
						ResourceRecords: aValues,
						TTL:             aws.Int64(defaultTTL),
					},
				},
			},
			Comment: aws.String("Service Discovery Created Record"),
		},
		HostedZoneId: aws.String(configuration.HostedZoneId),
	}
	_, err = r53.ChangeResourceRecordSets(params)
	logErrorNoFatal(err)
	log.Debug(fmt.Sprintf("Record %s.%s updated with %d records.", serviceName, DNSName, len(ips)))
	return err
}

var redisClient *redis.Client

func processServicePings(servicePings []string) {
	var serviceMap = make(map[string][]string)

	for _, servicePing := range servicePings {
		pingChunks := strings.Split(servicePing, "_")
		serviceName := pingChunks[0]
		serviceIp := pingChunks[1]
		serviceMap[serviceName] = append(serviceMap[serviceName], serviceIp)
	}

	for serviceName, serviceIps := range serviceMap {
		modifyDNSRecord(serviceName, serviceIps)
	}
}

func fetchActiveServices() {
	var servicePings []string
	var sum int
	var err error
	log.Debug("Fetching active services ...")

	// Fetch last "fetchLast" seconds of service pings
	now := time.Now().Unix()
	epoch := strconv.FormatInt(now - fetchLast, 10) // seconds (string)

	// Try several times with exponential backoff.
	sum = 1
	for {
		servicePings, err = redisClient.ZRangeByScore("redecs:service_pings", redis.ZRangeBy{Min:epoch, Max:"+inf"}).Result()

		if err == nil {
			break
		}
		if sum > 8 {
			// Bail out if this is failing.
			logErrorAndFail(err)
		}
		time.Sleep(time.Duration(sum) * time.Second)
		sum += 2
	}

	processServicePings(servicePings)

	log.Debug("Done fetching active services.")
}

func main() {
	var err error
	var sum int
	var zoneId string

	if len(os.Args) != 3 {
		err = errors.New(fmt.Sprintf("Usage: %s [domain] [Redis host]\n", os.Args[0]))
		logErrorAndFail(err)
	}

	DNSName = os.Args[1]
	configuration.RedisHost = os.Args[2]

	sum = 1
	for {
		// We try to get the Hosted Zone Id using exponential backoff
		zoneId, err = getDNSHostedZoneId()
		if err == nil {
			break
		}
		if sum > 8 {
			logErrorAndFail(err)
		}
		time.Sleep(time.Duration(sum) * time.Second)
		sum += 2
	}
	configuration.HostedZoneId = zoneId

	sum = 1
	for {
		// We try to get the Redis connection using exponential backoff
		redisClient = redis.NewClient(&redis.Options{
			Addr:       configuration.RedisHost + ":6379",
			Password:   "", // none
			DB:         0,  // default DB
			MaxRetries: 3,
		})
		// Check the connection.
		err := redisClient.Ping().Err()

		if err == nil {
			break
		}
		if sum > 8 {
			logErrorAndFail(err)
		}
		time.Sleep(time.Duration(sum) * time.Second)
		sum += 2
	}

	http.HandleFunc("/healthcheck", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintf(w, "OK")
	})

	log.Fatal(http.ListenAndServe(":8080", nil))

	// check regularly, specified by checkInterval
	ticker := time.NewTicker(checkInterval)

	for {
		// TODO: Add Redis Pub/Sub
		fetchActiveServices()
		<-ticker.C
	}
}
<commit_msg>Make http healthcheck async<commit_after>package main

// Originally adapted from https://github.com/awslabs/service-discovery-ecs-dns
// This service is meant to run as a singleton instance in concert with redecs_agent
// running on AWS ECS instances. This service will check Redis regularly for active
// service updates and rewrite the A records for services to only contain recently
// active ip addresses.

import (
	"errors"
	"fmt"
	log "github.com/Sirupsen/logrus"
	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/route53"
	"gopkg.in/redis.v5"
	"net/http"
	"os"
	"strconv"
	"strings"
	"time"
)

const checkInterval = 1 * time.Minute // how often to check Redis
const defaultTTL = 300                // seconds
const fetchLast = 300                 // seconds

var DNSName = "servicediscovery.local"

func logErrorAndFail(err error) {
	if err != nil {
		// logrus calls os.exit(1)
		log.Fatal(err)
	}
}

func logErrorNoFatal(err error) {
	if err != nil {
		log.Error(err)
	}
}

type config struct {
	HostedZoneId string
	RedisHost    string
}

var configuration config

func getDNSHostedZoneId() (string, error) {
	r53 := route53.New(session.New())
	params := &route53.ListHostedZonesByNameInput{
		DNSName: aws.String(DNSName),
	}

	zones, err := r53.ListHostedZonesByName(params)

	if err == nil && len(zones.HostedZones) > 0 {
		return aws.StringValue(zones.HostedZones[0].Id), nil
	}

	return "", err
}

// Modify (or create) the A record for this serviceName, adding the private IP of the host.
func modifyDNSRecord(serviceName string, ips []string) error {
	var err error
	r53 := route53.New(session.New())

	aValues := make([]*route53.ResourceRecord, 0)
	// Put all the IPs in one A record
	for _, ip := range ips {
		aValues = append(aValues, &route53.ResourceRecord{Value: aws.String(ip)})
	}

	// This API call creates a new DNS record for this service
	params := &route53.ChangeResourceRecordSetsInput{
		ChangeBatch: &route53.ChangeBatch{
			Changes: []*route53.Change{
				{
					Action: aws.String(route53.ChangeActionUpsert),
					ResourceRecordSet: &route53.ResourceRecordSet{
						Name: aws.String(serviceName + "." + DNSName),
						// It creates an A record with the name of the service
						Type:            aws.String(route53.RRTypeA),
						ResourceRecords: aValues,
						TTL:             aws.Int64(defaultTTL),
					},
				},
			},
			Comment: aws.String("Service Discovery Created Record"),
		},
		HostedZoneId: aws.String(configuration.HostedZoneId),
	}
	_, err = r53.ChangeResourceRecordSets(params)
	logErrorNoFatal(err)
	log.Debug(fmt.Sprintf("Record %s.%s updated with %d records.", serviceName, DNSName, len(ips)))
	return err
}

var redisClient *redis.Client

func processServicePings(servicePings []string) {
	var serviceMap = make(map[string][]string)

	for _, servicePing := range servicePings {
		pingChunks := strings.Split(servicePing, "_")
		serviceName := pingChunks[0]
		serviceIp := pingChunks[1]
		serviceMap[serviceName] = append(serviceMap[serviceName], serviceIp)
	}

	for serviceName, serviceIps := range serviceMap {
		modifyDNSRecord(serviceName, serviceIps)
	}
}

func fetchActiveServices() {
	var servicePings []string
	var sum int
	var err error
	log.Debug("Fetching active services ...")

	// Fetch last "fetchLast" seconds of service pings
	now := time.Now().Unix()
	epoch := strconv.FormatInt(now - fetchLast, 10) // seconds (string)

	// Try several times with exponential backoff.
	sum = 1
	for {
		servicePings, err = redisClient.ZRangeByScore("redecs:service_pings", redis.ZRangeBy{Min:epoch, Max:"+inf"}).Result()

		if err == nil {
			break
		}
		if sum > 8 {
			// Bail out if this is failing.
			logErrorAndFail(err)
		}
		time.Sleep(time.Duration(sum) * time.Second)
		sum += 2
	}

	processServicePings(servicePings)

	log.Debug("Done fetching active services.")
}

func main() {
	var err error
	var sum int
	var zoneId string

	if len(os.Args) != 3 {
		err = errors.New(fmt.Sprintf("Usage: %s [domain] [Redis host]\n", os.Args[0]))
		logErrorAndFail(err)
	}

	DNSName = os.Args[1]
	configuration.RedisHost = os.Args[2]

	sum = 1
	for {
		// We try to get the Hosted Zone Id using exponential backoff
		zoneId, err = getDNSHostedZoneId()
		if err == nil {
			break
		}
		if sum > 8 {
			logErrorAndFail(err)
		}
		time.Sleep(time.Duration(sum) * time.Second)
		sum += 2
	}
	configuration.HostedZoneId = zoneId

	sum = 1
	for {
		// We try to get the Redis connection using exponential backoff
		redisClient = redis.NewClient(&redis.Options{
			Addr:       configuration.RedisHost + ":6379",
			Password:   "", // none
			DB:         0,  // default DB
			MaxRetries: 3,
		})
		// Check the connection.
		err := redisClient.Ping().Err()

		if err == nil {
			break
		}
		if sum > 8 {
			logErrorAndFail(err)
		}
		time.Sleep(time.Duration(sum) * time.Second)
		sum += 2
	}

	go func() {
		http.HandleFunc("/healthcheck", func(w http.ResponseWriter, r *http.Request) {
			fmt.Fprintf(w, "OK")
		})

		log.Fatal(http.ListenAndServe(":8080", nil))
	}()

	// check regularly, specified by checkInterval
	ticker := time.NewTicker(checkInterval)

	for {
		// TODO: Add Redis Pub/Sub
		fetchActiveServices()
		<-ticker.C
	}
}
<|endoftext|>


<commit_before>package reddit

import "testing"

func TestSubmit(t *testing.T) {

	session, err := NewLoginSession(
		"Algorhythmical",
		"Parkfield.123",
		"tester",
	)
	if err != nil {
		t.Error(err)
	}

	subreddit, err := session.AboutSubreddit("mybottester")
	if err != nil {
		t.Error(err)
	}

	needsCaptcha, err := session.NeedsCaptcha()
	if err != nil {
		t.Error(err)
	}

	t.Log(needsCaptcha)

	if needsCaptcha {
		iden, err := session.NewCaptchaIden()
		if err != nil {
			t.Error(err)
		}

		_, err = session.CaptchaImage(iden)
		if err != nil {
			t.Error(err)
		}

		err = session.Submit(NewTextSubmission(subreddit.Name, "CAPTCHA TESTING TEXT", "TEST TEXT", true, &Captcha{iden, "test"}))
		if err != nil {
			t.Error(err)
		}

		err = session.Submit(NewLinkSubmission(subreddit.Name, "CAPTCHA TESTING LINK", "https://github.com/jzelinskie/reddit", true, &Captcha{iden, "test"}))
		if err != nil {
			t.Error(err)
		}

	} else {

		err = session.Submit(NewTextSubmission(subreddit.Name, "TESTING TEXT", "TEST TEXT", true, &Captcha{}))
		if err != nil {
			t.Error(err)
		}

		err = session.Submit(NewLinkSubmission(subreddit.Name, "TESTING LINK", "https://github.com/jzelinskie/reddit", true, &Captcha{}))
		if err != nil {
			t.Error(err)
		}
	}

}
<commit_msg>Update reddit_test.go<commit_after>package reddit

import "testing"

func TestSubmit(t *testing.T) {

	session, err := NewLoginSession(
		"redditgolang",
		"apitest11",
		"tester",
	)
	if err != nil {
		t.Error(err)
	}

	subreddit, err := session.AboutSubreddit("mybottester")
	if err != nil {
		t.Error(err)
	}

	needsCaptcha, err := session.NeedsCaptcha()
	if err != nil {
		t.Error(err)
	}

	t.Log(needsCaptcha)

	if needsCaptcha {
		iden, err := session.NewCaptchaIden()
		if err != nil {
			t.Error(err)
		}

		_, err = session.CaptchaImage(iden)
		if err != nil {
			t.Error(err)
		}

		err = session.Submit(NewTextSubmission(subreddit.Name, "CAPTCHA TESTING TEXT", "TEST TEXT", true, &Captcha{iden, "test"}))
		if err != nil {
			t.Error(err)
		}

		err = session.Submit(NewLinkSubmission(subreddit.Name, "CAPTCHA TESTING LINK", "https://github.com/jzelinskie/reddit", true, &Captcha{iden, "test"}))
		if err != nil {
			t.Error(err)
		}

	} else {

		err = session.Submit(NewTextSubmission(subreddit.Name, "TESTING TEXT", "TEST TEXT", true, &Captcha{}))
		if err != nil {
			t.Error(err)
		}

		err = session.Submit(NewLinkSubmission(subreddit.Name, "TESTING LINK", "https://github.com/jzelinskie/reddit", true, &Captcha{}))
		if err != nil {
			t.Error(err)
		}
	}

}
<|endoftext|>


<commit_before>package main

import (
	"encoding/json"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strconv"
	"strings"
)

var root = "/tmp/dpr"

func init() {
	log.SetFlags(0)
}

type Resource struct {
	*http.Request
}

func (r *Resource) localPath() string {
	normalizedPath := root + r.Request.URL.Path
	if strings.HasSuffix(normalizedPath, "/") {
		normalizedPath += "index"
	}
	return normalizedPath
}

func (r *Resource) localHeadersPath() string {
	return r.localPath() + ".headers"
}

func (r *Resource) store() error {
	if e := r.storeHeaders(); e != nil {
		return e
	}
	if e := r.storeBody(); e != nil {
		return e
	}
	return nil
}

func (r *Resource) storeBody() error {
	f, e := r.createFile(r.localPath())
	if e != nil {
		return e
	}
	defer f.Close()
	i, e := io.Copy(f, r.Body)
	if e != nil {
		return e
	}
	log.Printf("stored %d byte", i)
	return nil
}

func (r *Resource) createFile(p string) (io.WriteCloser, error) {
	if e := os.MkdirAll(path.Dir(p), 0755); e != nil {
		return nil, e
	}
	log.Printf("creating file %s", p)
	return os.Create(p)
}

func (r *Resource) storeHeaders() error {
	f, e := r.createFile(r.localHeadersPath())
	if e != nil {
		return e
	}
	defer f.Close()
	return json.NewEncoder(f).Encode(r.Header)
}

func (r *Resource) Write(w http.ResponseWriter) (int64, error) {
	log.Println("sending " + r.localPath())
	f, e := os.Open(r.localPath())
	if e != nil {
		return 0, e
	}
	defer f.Close()
	stat, e := f.Stat()
	if e != nil {
		return 0, e
	}
	w.Header().Set("Content-Length", strconv.FormatInt(stat.Size(), 10))
	if strings.HasSuffix(r.Request.URL.String(), "/json") {
		stat, e := os.Stat(path.Dir(r.localPath()) + "/layer")
		if e == nil {
			w.Header().Add("X-Docker-Size", strconv.FormatInt(stat.Size(), 10))
		}
		w.Header().Set("Content-Type", "application/json")
	}
	w.WriteHeader(200)
	i, e := io.Copy(w, f)
	log.Printf("sent %d byte", i)
	return i, e
}

func (r *Resource) Exists() bool {
	return fileExists(r.localPath())
}

func fileExists(path string) bool {
	_, err := os.Stat(path)
	return err == nil || !os.IsNotExist(err)
}

func writeTags(p string, w http.ResponseWriter) {
	files, e := filepath.Glob(p + "/*")
	if e != nil {
		log.Println("ERROR: " + e.Error())
		return
	}
	tags := map[string]string{}
	for _, f := range files {
		if strings.HasSuffix(f, ".headers") {
			continue
		}
		b, e := ioutil.ReadFile(f)
		if e != nil {
			continue
		}
		tags[path.Base(f)] = strings.Replace(string(b), `"`, "", -1)
	}
	if e := json.NewEncoder(w).Encode(tags); e != nil {
		log.Println("ERROR: " + e.Error())
	}
}

func ServeHTTP(w http.ResponseWriter, r *http.Request) {
	log.Println(r.Method + " http://" + r.Host + r.URL.String())
	defer r.Body.Close()

	normalizedPath := root + r.URL.Path
	if strings.HasSuffix(normalizedPath, "/") {
		normalizedPath += "index"
	}

	s := &Resource{r}
	w.Header().Set("X-Docker-Registry-Version", "0.0.1")
	w.Header().Add("X-Docker-Endpoints", r.Host)
	switch r.Method {
	case "PUT":
		e := s.store()
		if e != nil {
			log.Println(e.Error())
			http.Error(w, e.Error(), 500)
			return
		}
		w.Header().Add("WWW-Authenticate", `Token signature=123abc,repository="dynport/test",access=write`)
		w.Header().Add("X-Docker-Token", "token")
		w.Header().Add("Content-Type", "application/json")
		if strings.HasSuffix(r.URL.String(), "/images") {
			w.WriteHeader(204)
		} else {
			w.WriteHeader(200)
		}
		return
	case "GET":
		if strings.HasSuffix(r.URL.String(), "/tags") {
			writeTags(s.localPath(), w)
		} else if strings.HasSuffix(r.URL.String(), "/ancestry") {
			p := root + path.Dir(r.URL.String())
			list := []string{path.Base(p)}
			for {
				img, e := loadImage(p + "/json")
				if e != nil {
					log.Print(e.Error())
					break
				}
				if img.Parent == "" {
					break
				}
				list = append(list, img.Parent)
				log.Println(list)
				p = path.Dir(p) + "/" + img.Parent
			}
			w.Header().Set("Content-Type", "application/json")
			if e := json.NewEncoder(w).Encode(list); e != nil {
				log.Print("ERROR: " + e.Error())
			}
		} else if s.Exists() {
			_, e := s.Write(w)
			if e != nil {
				log.Print("ERROR: " + e.Error())
			}
			return
		}
	}
	w.WriteHeader(404)
}

func loadImage(p string) (*Image, error) {
	f, e := os.Open(p)
	if e != nil {
		return nil, e
	}
	defer f.Close()
	i := &Image{}
	e = json.NewDecoder(f).Decode(i)
	return i, e
}

func main() {
	http.HandleFunc("/", ServeHTTP)
	e := http.ListenAndServe(":8088", nil)
	if e != nil {
		log.Fatal(e.Error())
	}
}
<commit_msg>print address before starting<commit_after>package main

import (
	"encoding/json"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"path"
	"path/filepath"
	"strconv"
	"strings"
)

var root = "/tmp/dpr"

func init() {
	log.SetFlags(0)
}

type Resource struct {
	*http.Request
}

func (r *Resource) localPath() string {
	normalizedPath := root + r.Request.URL.Path
	if strings.HasSuffix(normalizedPath, "/") {
		normalizedPath += "index"
	}
	return normalizedPath
}

func (r *Resource) localHeadersPath() string {
	return r.localPath() + ".headers"
}

func (r *Resource) store() error {
	if e := r.storeHeaders(); e != nil {
		return e
	}
	if e := r.storeBody(); e != nil {
		return e
	}
	return nil
}

func (r *Resource) storeBody() error {
	f, e := r.createFile(r.localPath())
	if e != nil {
		return e
	}
	defer f.Close()
	i, e := io.Copy(f, r.Body)
	if e != nil {
		return e
	}
	log.Printf("stored %d byte", i)
	return nil
}

func (r *Resource) createFile(p string) (io.WriteCloser, error) {
	if e := os.MkdirAll(path.Dir(p), 0755); e != nil {
		return nil, e
	}
	log.Printf("creating file %s", p)
	return os.Create(p)
}

func (r *Resource) storeHeaders() error {
	f, e := r.createFile(r.localHeadersPath())
	if e != nil {
		return e
	}
	defer f.Close()
	return json.NewEncoder(f).Encode(r.Header)
}

func (r *Resource) Write(w http.ResponseWriter) (int64, error) {
	log.Println("sending " + r.localPath())
	f, e := os.Open(r.localPath())
	if e != nil {
		return 0, e
	}
	defer f.Close()
	stat, e := f.Stat()
	if e != nil {
		return 0, e
	}
	w.Header().Set("Content-Length", strconv.FormatInt(stat.Size(), 10))
	if strings.HasSuffix(r.Request.URL.String(), "/json") {
		stat, e := os.Stat(path.Dir(r.localPath()) + "/layer")
		if e == nil {
			w.Header().Add("X-Docker-Size", strconv.FormatInt(stat.Size(), 10))
		}
		w.Header().Set("Content-Type", "application/json")
	}
	w.WriteHeader(200)
	i, e := io.Copy(w, f)
	log.Printf("sent %d byte", i)
	return i, e
}

func (r *Resource) Exists() bool {
	return fileExists(r.localPath())
}

func fileExists(path string) bool {
	_, err := os.Stat(path)
	return err == nil || !os.IsNotExist(err)
}

func writeTags(p string, w http.ResponseWriter) {
	files, e := filepath.Glob(p + "/*")
	if e != nil {
		log.Println("ERROR: " + e.Error())
		return
	}
	tags := map[string]string{}
	for _, f := range files {
		if strings.HasSuffix(f, ".headers") {
			continue
		}
		b, e := ioutil.ReadFile(f)
		if e != nil {
			continue
		}
		tags[path.Base(f)] = strings.Replace(string(b), `"`, "", -1)
	}
	if e := json.NewEncoder(w).Encode(tags); e != nil {
		log.Println("ERROR: " + e.Error())
	}
}

func ServeHTTP(w http.ResponseWriter, r *http.Request) {
	log.Println(r.Method + " http://" + r.Host + r.URL.String())
	defer r.Body.Close()

	normalizedPath := root + r.URL.Path
	if strings.HasSuffix(normalizedPath, "/") {
		normalizedPath += "index"
	}

	s := &Resource{r}
	w.Header().Set("X-Docker-Registry-Version", "0.0.1")
	w.Header().Add("X-Docker-Endpoints", r.Host)
	switch r.Method {
	case "PUT":
		e := s.store()
		if e != nil {
			log.Println(e.Error())
			http.Error(w, e.Error(), 500)
			return
		}
		w.Header().Add("WWW-Authenticate", `Token signature=123abc,repository="dynport/test",access=write`)
		w.Header().Add("X-Docker-Token", "token")
		w.Header().Add("Content-Type", "application/json")
		if strings.HasSuffix(r.URL.String(), "/images") {
			w.WriteHeader(204)
		} else {
			w.WriteHeader(200)
		}
		return
	case "GET":
		if strings.HasSuffix(r.URL.String(), "/tags") {
			writeTags(s.localPath(), w)
		} else if strings.HasSuffix(r.URL.String(), "/ancestry") {
			p := root + path.Dir(r.URL.String())
			list := []string{path.Base(p)}
			for {
				img, e := loadImage(p + "/json")
				if e != nil {
					log.Print(e.Error())
					break
				}
				if img.Parent == "" {
					break
				}
				list = append(list, img.Parent)
				log.Println(list)
				p = path.Dir(p) + "/" + img.Parent
			}
			w.Header().Set("Content-Type", "application/json")
			if e := json.NewEncoder(w).Encode(list); e != nil {
				log.Print("ERROR: " + e.Error())
			}
		} else if s.Exists() {
			_, e := s.Write(w)
			if e != nil {
				log.Print("ERROR: " + e.Error())
			}
			return
		}
	}
	w.WriteHeader(404)
}

func loadImage(p string) (*Image, error) {
	f, e := os.Open(p)
	if e != nil {
		return nil, e
	}
	defer f.Close()
	i := &Image{}
	e = json.NewDecoder(f).Decode(i)
	return i, e
}

func main() {
	http.HandleFunc("/", ServeHTTP)
	addr := ":8088"
	log.Printf("starting dpr on %s", addr)
	e := http.ListenAndServe(addr, nil)
	if e != nil {
		log.Fatal(e.Error())
	}
}
<|endoftext|>


<commit_before>// Problem 6.18

package chapter6

import (
	"fmt"
	"strings"
	"unicode"
)

func Encoding(s string) string {
	runes := []rune(s)

	var out string

	cnt := 1

	for i := 1; i < len(runes); i++ {
		if runes[i] != runes[i-1] {
			out += fmt.Sprintf("%d%c", cnt, runes[i-1])
			cnt = 1
		} else {
			cnt++
		}
	}

	out += fmt.Sprintf("%d%c", cnt, runes[len(runes)-1])

	return out
}

func Decoding(s string) string {
	runes := []rune(s)

	var out string

	cnt := 0

	for i := 0; i < len(runes); i++ {
		if unicode.IsDigit(runes[i]) {
			cnt = cnt*10 + int(runes[i]-'0')
		} else {
			out += strings.Repeat(string(runes[i]), cnt)
			cnt = 0
		}

	}

	return out
}
<commit_msg>Fix 6.18 - do not need []rune<commit_after>// Problem 6.18

package chapter6

import (
	"fmt"
	"strings"
	"unicode"
)

func Encoding(s string) string {
	runes := []rune(s)

	var out string

	cnt := 1

	for i := 1; i < len(runes); i++ {
		if runes[i] != runes[i-1] {
			out += fmt.Sprintf("%d%c", cnt, runes[i-1])
			cnt = 1
		} else {
			cnt++
		}
	}

	out += fmt.Sprintf("%d%c", cnt, runes[len(runes)-1])

	return out
}

func Decoding(s string) string {
	var out string

	cnt := 0

	for _, r := range s {
		if unicode.IsDigit(r) {
			cnt = cnt*10 + int(r-'0')
		} else {
			out += strings.Repeat(string(r), cnt)
			cnt = 0
		}
	}

	return out
}
<|endoftext|>


<commit_before>// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.

package guestbook

import (
	"html/template"
	"net/http"
	"time"

	"google.golang.org/appengine"
	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/user"
)

var initTime time.Time

type Greeting struct {
	Author  string
	Content string
	Date    time.Time
}

func init() {
	http.HandleFunc("/", handleMainPage)
	http.HandleFunc("/sign", handleSign)
}

// guestbookKey returns the key used for all guestbook entries.
func guestbookKey(c appengine.Context) *datastore.Key {
	// The string "default_guestbook" here could be varied to have multiple guestbooks.
	return datastore.NewKey(c, "Guestbook", "default_guestbook", 0, nil)
}

var tpl = template.Must(template.ParseGlob("templates/*.html"))

func handleMainPage(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		http.Error(w, "GET requests only", http.StatusMethodNotAllowed)
		return
	}
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}

	c := appengine.NewContext(r)
	tic := time.Now()
	q := datastore.NewQuery("Greeting").Ancestor(guestbookKey(c)).Order("-Date").Limit(10)
	var gg []*Greeting
	_, err := q.GetAll(c, &gg)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		c.Errorf("GetAll: %v", err)
		return
	}
	c.Infof("Datastore lookup took %s", time.Since(tic).String())
	c.Infof("Rendering %d greetings", len(gg))

	var email, logout, login string
	if u := user.Current(c); u != nil {
		logout, _ = user.LogoutURL(c, "/")
		email = u.Email
	} else {
		login, _ = user.LoginURL(c, "/")
	}
	data := struct {
		Greetings            []*Greeting
		Login, Logout, Email string
	}{
		Greetings: gg,
		Login:     login,
		Logout:    logout,
		Email:     email,
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	if err := tpl.ExecuteTemplate(w, "guestbook.html", data); err != nil {
		c.Errorf("%v", err)
	}
}

func handleSign(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "POST requests only", http.StatusMethodNotAllowed)
		return
	}
	c := appengine.NewContext(r)
	if err := r.ParseForm(); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	g := &Greeting{
		Content: r.FormValue("content"),
		Date:    time.Now(),
	}
	if u := user.Current(c); u != nil {
		g.Author = u.String()
	}
	key := datastore.NewIncompleteKey(c, "Greeting", guestbookKey(c))
	if _, err := datastore.Put(c, key, g); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	// Redirect with 303 which causes the subsequent request to use GET.
	http.Redirect(w, r, "/", http.StatusSeeOther)
}
<commit_msg>Tighten up guestbook demo.<commit_after>// Copyright 2011 Google Inc. All rights reserved.
// Use of this source code is governed by the Apache 2.0
// license that can be found in the LICENSE file.

package guestbook

import (
	"html/template"
	"net/http"
	"time"

	"google.golang.org/appengine"
	"google.golang.org/appengine/datastore"
	"google.golang.org/appengine/user"
)

var initTime time.Time

type Greeting struct {
	Author  string
	Content string
	Date    time.Time
}

func init() {
	http.HandleFunc("/", handleMainPage)
	http.HandleFunc("/sign", handleSign)
}

// guestbookKey returns the key used for all guestbook entries.
func guestbookKey(c appengine.Context) *datastore.Key {
	// The string "default_guestbook" here could be varied to have multiple guestbooks.
	return datastore.NewKey(c, "Guestbook", "default_guestbook", 0, nil)
}

var tpl = template.Must(template.ParseGlob("templates/*.html"))

func handleMainPage(w http.ResponseWriter, r *http.Request) {
	if r.Method != "GET" {
		http.Error(w, "GET requests only", http.StatusMethodNotAllowed)
		return
	}
	if r.URL.Path != "/" {
		http.NotFound(w, r)
		return
	}

	c := appengine.NewContext(r)
	tic := time.Now()
	q := datastore.NewQuery("Greeting").Ancestor(guestbookKey(c)).Order("-Date").Limit(10)
	var gg []*Greeting
	if _, err := q.GetAll(c, &gg); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		c.Errorf("GetAll: %v", err)
		return
	}
	c.Infof("Datastore lookup took %s", time.Since(tic).String())
	c.Infof("Rendering %d greetings", len(gg))

	var email, logout, login string
	if u := user.Current(c); u != nil {
		logout, _ = user.LogoutURL(c, "/")
		email = u.Email
	} else {
		login, _ = user.LoginURL(c, "/")
	}
	data := struct {
		Greetings            []*Greeting
		Login, Logout, Email string
	}{
		Greetings: gg,
		Login:     login,
		Logout:    logout,
		Email:     email,
	}
	w.Header().Set("Content-Type", "text/html; charset=utf-8")
	if err := tpl.ExecuteTemplate(w, "guestbook.html", data); err != nil {
		c.Errorf("%v", err)
	}
}

func handleSign(w http.ResponseWriter, r *http.Request) {
	if r.Method != "POST" {
		http.Error(w, "POST requests only", http.StatusMethodNotAllowed)
		return
	}
	c := appengine.NewContext(r)
	g := &Greeting{
		Content: r.FormValue("content"),
		Date:    time.Now(),
	}
	if u := user.Current(c); u != nil {
		g.Author = u.String()
	}
	key := datastore.NewIncompleteKey(c, "Greeting", guestbookKey(c))
	if _, err := datastore.Put(c, key, g); err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}
	// Redirect with 303 which causes the subsequent request to use GET.
	http.Redirect(w, r, "/", http.StatusSeeOther)
}
<|endoftext|>