<commit_before>package gqt_test

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

	"github.com/cloudfoundry-incubator/garden"
	"github.com/cloudfoundry-incubator/guardian/gqt/runner"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/ginkgo/extensions/table"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
	"github.com/onsi/gomega/types"
)

var _ = Describe("Run", func() {
	var client *runner.RunningGarden

	AfterEach(func() {
		// Ensure we don't leak process.json files
		matches, err := filepath.Glob(fmt.Sprintf("%s/guardianprocess*", client.Tmpdir))
		Expect(err).ToNot(HaveOccurred())
		Expect(len(matches)).To(Equal(0))

		Expect(client.DestroyAndStop()).To(Succeed())
	})

	DescribeTable("running a process",
		func(spec garden.ProcessSpec, matchers ...func(actual interface{})) {
			client = startGarden()
			container, err := client.Create(garden.ContainerSpec{
				RootFSPath: runner.RootFSPath,
			})
			Expect(err).NotTo(HaveOccurred())

			out := gbytes.NewBuffer()
			proc, err := container.Run(
				spec,
				garden.ProcessIO{
					Stdout: io.MultiWriter(GinkgoWriter, out),
					Stderr: io.MultiWriter(GinkgoWriter, out),
				})
			Expect(err).NotTo(HaveOccurred())

			exitCode, err := proc.Wait()
			Expect(err).NotTo(HaveOccurred())

			for _, m := range matchers {
				m(&process{exitCode, out})
			}
		},

		Entry("with an absolute path",
			spec("/bin/sh", "-c", "echo hello; exit 12"),
			should(gbytes.Say("hello"), gexec.Exit(12)),
		),

		Entry("with a path to be found in a regular user's path",
			spec("sh", "-c", "echo potato; exit 24"),
			should(gbytes.Say("potato"), gexec.Exit(24)),
		),

		Entry("with a path that doesn't exist",
			spec("potato"),
			shouldNot(gexec.Exit(0)),
		),
	)

	Describe("security", func() {
		Describe("rlimits", func() {
			It("sets requested rlimits, even if they are increased above current limit", func() {
				var old syscall.Rlimit
				Expect(syscall.Getrlimit(syscall.RLIMIT_NOFILE, &old)).To(Succeed())

				Expect(syscall.Setrlimit(syscall.RLIMIT_NOFILE, &syscall.Rlimit{
					Max: 100000,
					Cur: 100000,
				})).To(Succeed())

				defer syscall.Setrlimit(syscall.RLIMIT_NOFILE, &old)

				client = startGarden()
				container, err := client.Create(garden.ContainerSpec{
					RootFSPath: runner.RootFSPath,
					Privileged: false,
				})
				Expect(err).NotTo(HaveOccurred())

				limit := uint64(100001)
				stdout := gbytes.NewBuffer()
				process, err := container.Run(garden.ProcessSpec{
					User: "root",
					Path: "/bin/sh",
					Args: []string{"-c", "ulimit -a"},
					Limits: garden.ResourceLimits{
						Nofile: &limit,
					},
				}, garden.ProcessIO{
					Stdout: stdout,
					Stderr: GinkgoWriter,
				})
				Expect(err).ToNot(HaveOccurred())

				Expect(process.Wait()).To(Equal(0))
				Expect(stdout).To(gbytes.Say("file descriptors\\W+100001"))
			})
		})

		Describe("symlinks", func() {
			var (
				target, rootfs string
			)

			BeforeEach(func() {
				var err error
				rootfs, err = ioutil.TempDir("", "symlinks")
				Expect(err).NotTo(HaveOccurred())

				Expect(os.Mkdir(filepath.Join(rootfs, "tmp"), 0777)).To(Succeed())

				target, err = ioutil.TempDir("", "symlinkstarget")
				Expect(err).NotTo(HaveOccurred())

				Expect(os.Symlink(target, path.Join(rootfs, "symlink"))).To(Succeed())
			})

			It("does not follow symlinks into the host when creating cwd", func() {
				client = startGarden()
				container, err := client.Create(garden.ContainerSpec{RootFSPath: rootfs})
				Expect(err).NotTo(HaveOccurred())

				process, err := container.Run(garden.ProcessSpec{
					Path: "echo",
					Args: []string{"hello"},
					Dir:  "/symlink/foo/bar",
				}, garden.ProcessIO{Stdout: GinkgoWriter, Stderr: GinkgoWriter})
				Expect(err).NotTo(HaveOccurred())

				Expect(process.Wait()).ToNot(Equal(0)) // `echo` wont exist in the fake rootfs. This is fine.
				Expect(path.Join(target, "foo")).NotTo(BeADirectory())
			})
		})
	})

	Context("when container is privileged", func() {
		It("can run a process as a particular user", func() {
			client = startGarden()
			container, err := client.Create(garden.ContainerSpec{
				RootFSPath: runner.RootFSPath,
				Privileged: true,
			})
			Expect(err).NotTo(HaveOccurred())

			out := gbytes.NewBuffer()
			proc, err := container.Run(
				garden.ProcessSpec{
					Path: "whoami",
					User: "alice",
				},
				garden.ProcessIO{
					Stdout: io.MultiWriter(GinkgoWriter, out),
					Stderr: io.MultiWriter(GinkgoWriter, out),
				})
			Expect(err).NotTo(HaveOccurred())

			exitCode, err := proc.Wait()
			Expect(err).NotTo(HaveOccurred())
			Expect(exitCode).To(Equal(0))

			Expect(out).To(gbytes.Say("alice"))
		})
	})

	Describe("PATH env variable", func() {
		var container garden.Container

		BeforeEach(func() {
			client = startGarden()
			var err error
			container, err = client.Create(garden.ContainerSpec{
				RootFSPath: runner.RootFSPath,
			})
			Expect(err).NotTo(HaveOccurred())
		})

		DescribeTable("contains the correct default values", func(user, path string) {
			out := gbytes.NewBuffer()
			proc, err := container.Run(
				garden.ProcessSpec{
					Path: "sh",
					Args: []string{"-c", "echo $PATH"},
					User: user,
				},
				garden.ProcessIO{
					Stdout: io.MultiWriter(GinkgoWriter, out),
					Stderr: io.MultiWriter(GinkgoWriter, out),
				})
			Expect(err).NotTo(HaveOccurred())

			exitCode, err := proc.Wait()
			Expect(err).NotTo(HaveOccurred())
			Expect(exitCode).To(Equal(0))

			Expect(out).To(gbytes.Say(path))
		},
			Entry("for a non-root user", "alice", `^/usr/local/bin:/usr/bin:/bin\n$`),
			Entry("for the root user", "root",
				`^/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n$`),
		)
	})

	Describe("user env variable", func() {
		var container garden.Container

		BeforeEach(func() {
			client = startGarden()
			var err error
			container, err = client.Create(garden.ContainerSpec{
				RootFSPath: runner.RootFSPath,
				Env:        []string{"USER=ppp", "HOME=/home/ppp"},
			})
			Expect(err).NotTo(HaveOccurred())
		})

		DescribeTable("contains the correct values", func(user string, env, paths []string) {
			out := gbytes.NewBuffer()
			proc, err := container.Run(
				garden.ProcessSpec{
					Path: "sh",
					Args: []string{"-c", "env"},
					User: user,
					Env:  env,
				},
				garden.ProcessIO{
					Stdout: io.MultiWriter(GinkgoWriter, out),
					Stderr: io.MultiWriter(GinkgoWriter, out),
				})
			Expect(err).NotTo(HaveOccurred())

			exitCode, err := proc.Wait()
			Expect(err).NotTo(HaveOccurred())
			Expect(exitCode).To(Equal(0))

			for _, path := range paths {
				Expect(out).To(gbytes.Say(path))
			}
		},
			Entry("for empty user", "", []string{}, []string{"USER=ppp", "HOME=/home/ppp"}),
			Entry("when we specify the env in processSpec", "alice", []string{"USER=alice", "HI=YO"}, []string{"USER=alice", "HOME=/home/ppp", "HI=YO"}),
		)
	})

	Describe("Signalling", func() {
		It("should forward SIGTERM to the process", func(done Done) {
			client = startGarden()

			container, err := client.Create(garden.ContainerSpec{
				RootFSPath: runner.RootFSPath,
			})
			Expect(err).NotTo(HaveOccurred())

			buffer := gbytes.NewBuffer()
			proc, err := container.Run(garden.ProcessSpec{
				Path: "sh",
				Args: []string{"-c", `
					trap 'exit 42' TERM

					while true; do
					  echo 'sleeping'
					  sleep 1
					done
				`},
			}, garden.ProcessIO{
				Stdout: buffer,
			})
			Expect(err).NotTo(HaveOccurred())

			Eventually(buffer).Should(gbytes.Say("sleeping"))

			err = proc.Signal(garden.SignalTerminate)
			Expect(err).NotTo(HaveOccurred())

			Expect(proc.Wait()).To(Equal(42))

			close(done)
		}, 20.0)
	})
})

func should(matchers ...types.GomegaMatcher) func(actual interface{}) {
	return func(actual interface{}) {
		for _, matcher := range matchers {
			Expect(actual).To(matcher)
		}
	}
}

func shouldNot(matchers ...types.GomegaMatcher) func(actual interface{}) {
	return func(actual interface{}) {
		for _, matcher := range matchers {
			Expect(actual).ToNot(matcher)
		}
	}
}

func spec(path string, args ...string) garden.ProcessSpec {
	return garden.ProcessSpec{
		Path: path,
		Args: args,
	}
}

type process struct {
	exitCode int
	buffer   *gbytes.Buffer
}

func (p *process) ExitCode() int {
	return p.exitCode
}

func (p *process) Buffer() *gbytes.Buffer {
	return p.buffer
}
<commit_msg>add integration tests for TTY support<commit_after>package gqt_test

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

	"github.com/cloudfoundry-incubator/garden"
	"github.com/cloudfoundry-incubator/guardian/gqt/runner"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/ginkgo/extensions/table"
	. "github.com/onsi/gomega"
	"github.com/onsi/gomega/gbytes"
	"github.com/onsi/gomega/gexec"
	"github.com/onsi/gomega/types"
)

var _ = Describe("Run", func() {
	var client *runner.RunningGarden

	AfterEach(func() {
		// Ensure we don't leak process.json files
		matches, err := filepath.Glob(fmt.Sprintf("%s/guardianprocess*", client.Tmpdir))
		Expect(err).ToNot(HaveOccurred())
		Expect(len(matches)).To(Equal(0))

		Expect(client.DestroyAndStop()).To(Succeed())
	})

	DescribeTable("running a process",
		func(spec garden.ProcessSpec, matchers ...func(actual interface{})) {
			client = startGarden()
			container, err := client.Create(garden.ContainerSpec{
				RootFSPath: runner.RootFSPath,
			})
			Expect(err).NotTo(HaveOccurred())

			out := gbytes.NewBuffer()
			proc, err := container.Run(
				spec,
				garden.ProcessIO{
					Stdout: io.MultiWriter(GinkgoWriter, out),
					Stderr: io.MultiWriter(GinkgoWriter, out),
				})
			Expect(err).NotTo(HaveOccurred())

			exitCode, err := proc.Wait()
			Expect(err).NotTo(HaveOccurred())

			for _, m := range matchers {
				m(&process{exitCode, out})
			}
		},

		Entry("with an absolute path",
			spec("/bin/sh", "-c", "echo hello; exit 12"),
			should(gbytes.Say("hello"), gexec.Exit(12)),
		),

		Entry("with a path to be found in a regular user's path",
			spec("sh", "-c", "echo potato; exit 24"),
			should(gbytes.Say("potato"), gexec.Exit(24)),
		),

		Entry("with a path that doesn't exist",
			spec("potato"),
			shouldNot(gexec.Exit(0)),
		),

		Entry("without a TTY",
			spec("test", "-t", "1"),
			should(gexec.Exit(1)),
		),

		Entry("with a TTY",
			ttySpec("test", "-t", "1"),
			should(gexec.Exit(0)),
		),
	)

	Describe("security", func() {
		Describe("rlimits", func() {
			It("sets requested rlimits, even if they are increased above current limit", func() {
				var old syscall.Rlimit
				Expect(syscall.Getrlimit(syscall.RLIMIT_NOFILE, &old)).To(Succeed())

				Expect(syscall.Setrlimit(syscall.RLIMIT_NOFILE, &syscall.Rlimit{
					Max: 100000,
					Cur: 100000,
				})).To(Succeed())

				defer syscall.Setrlimit(syscall.RLIMIT_NOFILE, &old)

				client = startGarden()
				container, err := client.Create(garden.ContainerSpec{
					RootFSPath: runner.RootFSPath,
					Privileged: false,
				})
				Expect(err).NotTo(HaveOccurred())

				limit := uint64(100001)
				stdout := gbytes.NewBuffer()
				process, err := container.Run(garden.ProcessSpec{
					User: "root",
					Path: "/bin/sh",
					Args: []string{"-c", "ulimit -a"},
					Limits: garden.ResourceLimits{
						Nofile: &limit,
					},
				}, garden.ProcessIO{
					Stdout: stdout,
					Stderr: GinkgoWriter,
				})
				Expect(err).ToNot(HaveOccurred())

				Expect(process.Wait()).To(Equal(0))
				Expect(stdout).To(gbytes.Say("file descriptors\\W+100001"))
			})
		})

		Describe("symlinks", func() {
			var (
				target, rootfs string
			)

			BeforeEach(func() {
				var err error
				rootfs, err = ioutil.TempDir("", "symlinks")
				Expect(err).NotTo(HaveOccurred())

				Expect(os.Mkdir(filepath.Join(rootfs, "tmp"), 0777)).To(Succeed())

				target, err = ioutil.TempDir("", "symlinkstarget")
				Expect(err).NotTo(HaveOccurred())

				Expect(os.Symlink(target, path.Join(rootfs, "symlink"))).To(Succeed())
			})

			It("does not follow symlinks into the host when creating cwd", func() {
				client = startGarden()
				container, err := client.Create(garden.ContainerSpec{RootFSPath: rootfs})
				Expect(err).NotTo(HaveOccurred())

				process, err := container.Run(garden.ProcessSpec{
					Path: "echo",
					Args: []string{"hello"},
					Dir:  "/symlink/foo/bar",
				}, garden.ProcessIO{Stdout: GinkgoWriter, Stderr: GinkgoWriter})
				Expect(err).NotTo(HaveOccurred())

				Expect(process.Wait()).ToNot(Equal(0)) // `echo` wont exist in the fake rootfs. This is fine.
				Expect(path.Join(target, "foo")).NotTo(BeADirectory())
			})
		})
	})

	Context("when container is privileged", func() {
		It("can run a process as a particular user", func() {
			client = startGarden()
			container, err := client.Create(garden.ContainerSpec{
				RootFSPath: runner.RootFSPath,
				Privileged: true,
			})
			Expect(err).NotTo(HaveOccurred())

			out := gbytes.NewBuffer()
			proc, err := container.Run(
				garden.ProcessSpec{
					Path: "whoami",
					User: "alice",
				},
				garden.ProcessIO{
					Stdout: io.MultiWriter(GinkgoWriter, out),
					Stderr: io.MultiWriter(GinkgoWriter, out),
				})
			Expect(err).NotTo(HaveOccurred())

			exitCode, err := proc.Wait()
			Expect(err).NotTo(HaveOccurred())
			Expect(exitCode).To(Equal(0))

			Expect(out).To(gbytes.Say("alice"))
		})
	})

	Describe("PATH env variable", func() {
		var container garden.Container

		BeforeEach(func() {
			client = startGarden()
			var err error
			container, err = client.Create(garden.ContainerSpec{
				RootFSPath: runner.RootFSPath,
			})
			Expect(err).NotTo(HaveOccurred())
		})

		DescribeTable("contains the correct default values", func(user, path string) {
			out := gbytes.NewBuffer()
			proc, err := container.Run(
				garden.ProcessSpec{
					Path: "sh",
					Args: []string{"-c", "echo $PATH"},
					User: user,
				},
				garden.ProcessIO{
					Stdout: io.MultiWriter(GinkgoWriter, out),
					Stderr: io.MultiWriter(GinkgoWriter, out),
				})
			Expect(err).NotTo(HaveOccurred())

			exitCode, err := proc.Wait()
			Expect(err).NotTo(HaveOccurred())
			Expect(exitCode).To(Equal(0))

			Expect(out).To(gbytes.Say(path))
		},
			Entry("for a non-root user", "alice", `^/usr/local/bin:/usr/bin:/bin\n$`),
			Entry("for the root user", "root",
				`^/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n$`),
		)
	})

	Describe("user env variable", func() {
		var container garden.Container

		BeforeEach(func() {
			client = startGarden()
			var err error
			container, err = client.Create(garden.ContainerSpec{
				RootFSPath: runner.RootFSPath,
				Env:        []string{"USER=ppp", "HOME=/home/ppp"},
			})
			Expect(err).NotTo(HaveOccurred())
		})

		DescribeTable("contains the correct values", func(user string, env, paths []string) {
			out := gbytes.NewBuffer()
			proc, err := container.Run(
				garden.ProcessSpec{
					Path: "sh",
					Args: []string{"-c", "env"},
					User: user,
					Env:  env,
				},
				garden.ProcessIO{
					Stdout: io.MultiWriter(GinkgoWriter, out),
					Stderr: io.MultiWriter(GinkgoWriter, out),
				})
			Expect(err).NotTo(HaveOccurred())

			exitCode, err := proc.Wait()
			Expect(err).NotTo(HaveOccurred())
			Expect(exitCode).To(Equal(0))

			for _, path := range paths {
				Expect(out).To(gbytes.Say(path))
			}
		},
			Entry("for empty user", "", []string{}, []string{"USER=ppp", "HOME=/home/ppp"}),
			Entry("when we specify the env in processSpec", "alice", []string{"USER=alice", "HI=YO"}, []string{"USER=alice", "HOME=/home/ppp", "HI=YO"}),
		)
	})

	Describe("Signalling", func() {
		It("should forward SIGTERM to the process", func(done Done) {
			client = startGarden()

			container, err := client.Create(garden.ContainerSpec{
				RootFSPath: runner.RootFSPath,
			})
			Expect(err).NotTo(HaveOccurred())

			buffer := gbytes.NewBuffer()
			proc, err := container.Run(garden.ProcessSpec{
				Path: "sh",
				Args: []string{"-c", `
					trap 'exit 42' TERM

					while true; do
					  echo 'sleeping'
					  sleep 1
					done
				`},
			}, garden.ProcessIO{
				Stdout: buffer,
			})
			Expect(err).NotTo(HaveOccurred())

			Eventually(buffer).Should(gbytes.Say("sleeping"))

			err = proc.Signal(garden.SignalTerminate)
			Expect(err).NotTo(HaveOccurred())

			Expect(proc.Wait()).To(Equal(42))

			close(done)
		}, 20.0)
	})
})

func should(matchers ...types.GomegaMatcher) func(actual interface{}) {
	return func(actual interface{}) {
		for _, matcher := range matchers {
			Expect(actual).To(matcher)
		}
	}
}

func shouldNot(matchers ...types.GomegaMatcher) func(actual interface{}) {
	return func(actual interface{}) {
		for _, matcher := range matchers {
			Expect(actual).ToNot(matcher)
		}
	}
}

func spec(path string, args ...string) garden.ProcessSpec {
	return garden.ProcessSpec{
		Path: path,
		Args: args,
	}
}

func ttySpec(path string, args ...string) garden.ProcessSpec {
	base := spec(path, args...)
	base.TTY = &garden.TTYSpec{}
	return base
}

type process struct {
	exitCode int
	buffer   *gbytes.Buffer
}

func (p *process) ExitCode() int {
	return p.exitCode
}

func (p *process) Buffer() *gbytes.Buffer {
	return p.buffer
}
<|endoftext|>


<commit_before>package gobuddyfs

import (
	"encoding/json"
	"math/rand"

	"bazil.org/fuse"
	"bazil.org/fuse/fs"
	"github.com/golang/glog"
)

// File implements both Node and Handle for the hello file.
type File struct {
	Block
	Size       uint64
	Blocks     []Block
	Root       *FSMeta      `json:"-"`
	BlockCache []*DataBlock `json:"-"`
}

func (file *File) Open(req *fuse.OpenRequest, res *fuse.OpenResponse, intr fs.Intr) (fs.Handle, fuse.Error) {
	if glog.V(2) {
		glog.Infoln("Open called")
	}
	return file, nil
}

func (file *File) getBlock(index int64) *DataBlock {
	glog.Infoln("GetBlock: ", index)
	if int(index) > len(file.BlockCache) {
		return nil
	}

	if file.BlockCache[index] == nil {
		var startBlock DataBlock
		startBlock.Block.Id = file.Blocks[index].Id
		err := startBlock.ReadBlock(&startBlock, *file.Root.Store)
		if err != nil {
			glog.Errorf("Error while reading data block: %q", err)
			return nil
		}

		file.BlockCache[index] = &startBlock
	}

	return file.BlockCache[index]
}

func (file *File) appendBlock(dblk *DataBlock) {
	if glog.V(2) {
		glog.Infoln("AppendBlock: ", len(file.BlockCache))
	}
	file.BlockCache = append(file.BlockCache, dblk)
}

func (file *File) Setattr(req *fuse.SetattrRequest, res *fuse.SetattrResponse, intr fs.Intr) fuse.Error {
	if glog.V(2) {
		glog.Infoln("Setattr called")
		glog.Infoln("Req: ", req)
	}

	valid := req.Valid
	if valid.Size() && req.Size != file.Size {
		// Resizing!
		if file.Size > req.Size {
			glog.Infoln("Shrinking")
			numBlocks := req.Size / BLOCK_SIZE

			if numBlocks < uint64(len(file.Blocks)) {
				blocksToDelete := file.Blocks[numBlocks:]
				file.Blocks = file.Blocks[:numBlocks]
				file.BlockCache = file.BlockCache[:numBlocks]

				for blk := range blocksToDelete {
					glog.Warningln("Removing ", blocksToDelete[blk].Id)
				}
			}
		} else {
			glog.Warningln("TODO: Expanding")
		}
		file.Size = req.Size

		err := file.WriteBlock(file, *file.Root.Store)
		if err != nil {
			return fuse.EIO
		}

		if glog.V(2) {
			glog.Infoln("Resizing file to size", file.Size)
		}
		res.Attr = file.Attr()
		return nil
	}

	res.Attr = file.Attr()
	if glog.V(2) {
		glog.Infoln("Finished Setattr")
	}
	// TODO: Not implemented.
	return nil
}

func (file *File) Write(req *fuse.WriteRequest, res *fuse.WriteResponse, intr fs.Intr) fuse.Error {
	dataBytes := len(req.Data)
	if glog.V(2) {
		glog.Infof("Writing %d byte(s) at offset %d", dataBytes, req.Offset)
	}
	for req.Offset+int64(dataBytes) >= int64(BLOCK_SIZE*len(file.Blocks)) {
		blk := Block{Id: rand.Int63()}
		dBlk := DataBlock{Block: blk, Data: []byte{}}
		dBlk.MarkDirty()

		file.Blocks = append(file.Blocks, blk)
		file.appendBlock(&dBlk)
		file.MarkDirty()
	}

	startBlockId := req.Offset / BLOCK_SIZE

	var startBlock *DataBlock = file.getBlock(startBlockId)

	if glog.V(2) {
		glog.Infof("Block content length: %d", len(startBlock.Data))
	}

	oldLen := len(startBlock.Data)
	bytesToAdd := min(BLOCK_SIZE-int(req.Offset%BLOCK_SIZE), dataBytes)
	data := append(startBlock.Data[:(req.Offset%BLOCK_SIZE)], req.Data[:bytesToAdd]...)
	if len(startBlock.Data) >= int(req.Offset%BLOCK_SIZE)+bytesToAdd {
		data = append(data, startBlock.Data[int(req.Offset%BLOCK_SIZE)+bytesToAdd:]...)
	}
	startBlock.Data = data

	if glog.V(2) {
		glog.Infof("Block content length after: %d", len(startBlock.Data))
	}

	startBlock.MarkDirty()

	if glog.V(2) {
		glog.Infoln("Successfully completed write operation")
	}

	res.Size = bytesToAdd
	file.Size = file.Size + uint64(len(startBlock.Data)) - uint64(oldLen)

	file.MarkDirty()
	return nil
}

func (file *File) Marshal() ([]byte, error) {
	return json.Marshal(file)
}

func (file File) Attr() fuse.Attr {
	if glog.V(2) {
		glog.Infoln("Attr called")
	}
	return fuse.Attr{Mode: 0444, Blocks: uint64(len(file.Blocks)), Size: file.Size}
}

func (file *File) Release(req *fuse.ReleaseRequest, intr fs.Intr) fuse.Error {
	if glog.V(2) {
		glog.Infoln("Release", file.Name)
	}
	return nil
}

func (file *File) Forget() {
	if glog.V(2) {
		glog.Infoln("FORGET", file.Name)
	}
}

func (file *File) Flush(req *fuse.FlushRequest, intr fs.Intr) fuse.Error {
	if glog.V(2) {
		glog.Infoln("FLUSH", file.Name, file.IsDirty())
	}
	for i := range file.Blocks {
		if file.Blocks[i].IsDirty() {
			err := file.Blocks[i].WriteBlock(file.getBlock(int64(i)), *file.Root.Store)
			if err != nil {
				glog.Warning("Unable to write block %s due to error: %s", file.Blocks[i].Id, err)
			}
		}
	}

	if file.IsDirty() {
		file.WriteBlock(file, *file.Root.Store)
	}
	return nil
}

func (file *File) Fsync(req *fuse.FsyncRequest, intr fs.Intr) fuse.Error {
	if glog.V(2) {
		glog.Infoln("FSYNC", file.Name, file.IsDirty())
	}
	return file.Flush(nil, intr)
}

func (file *File) Read(req *fuse.ReadRequest, res *fuse.ReadResponse, intr fs.Intr) fuse.Error {
	if glog.V(2) {
		glog.Infof("Reading %d byte(s) at offset %d", req.Size, req.Offset)
	}

	if req.Offset >= int64(file.Size) {
		res.Data = []byte{}
		return nil
	}

	res.Data = []byte{}

	startBlockId := req.Offset / BLOCK_SIZE

	var startBlock *DataBlock = file.getBlock(startBlockId)
	if startBlock == nil {
		glog.Error("Error while reading block")
		return fuse.EIO
	}

	beginReadByte := int(req.Offset % BLOCK_SIZE)
	endReadByte := min(len(startBlock.Data)-beginReadByte, req.Size)
	if glog.V(2) {
		glog.Infof("Block content length: %d", len(startBlock.Data))
		glog.Infof("Reading from %d to %d in block %d", beginReadByte, endReadByte+beginReadByte, startBlockId)
	}
	res.Data = startBlock.Data[beginReadByte : endReadByte+beginReadByte]

	return nil
}
<commit_msg>Check if data blocks are dirty, not the block metadata.<commit_after>package gobuddyfs

import (
	"encoding/json"
	"math/rand"

	"bazil.org/fuse"
	"bazil.org/fuse/fs"
	"github.com/golang/glog"
)

// File implements both Node and Handle for the hello file.
type File struct {
	Block
	Size       uint64
	Blocks     []Block
	Root       *FSMeta      `json:"-"`
	BlockCache []*DataBlock `json:"-"`
}

func (file *File) Open(req *fuse.OpenRequest, res *fuse.OpenResponse, intr fs.Intr) (fs.Handle, fuse.Error) {
	if glog.V(2) {
		glog.Infoln("Open called")
	}
	return file, nil
}

func (file *File) getBlock(index int64) *DataBlock {
	glog.Infoln("GetBlock: ", index)
	if int(index) > len(file.BlockCache) {
		return nil
	}

	if file.BlockCache[index] == nil {
		var startBlock DataBlock
		startBlock.Block.Id = file.Blocks[index].Id
		err := startBlock.ReadBlock(&startBlock, *file.Root.Store)
		if err != nil {
			glog.Errorf("Error while reading data block: %q", err)
			return nil
		}

		file.BlockCache[index] = &startBlock
	}

	return file.BlockCache[index]
}

func (file *File) appendBlock(dblk *DataBlock) {
	if glog.V(2) {
		glog.Infoln("AppendBlock: ", len(file.BlockCache))
	}
	file.BlockCache = append(file.BlockCache, dblk)
}

func (file *File) Setattr(req *fuse.SetattrRequest, res *fuse.SetattrResponse, intr fs.Intr) fuse.Error {
	if glog.V(2) {
		glog.Infoln("Setattr called")
		glog.Infoln("Req: ", req)
	}

	valid := req.Valid
	if valid.Size() && req.Size != file.Size {
		// Resizing!
		if file.Size > req.Size {
			glog.Infoln("Shrinking")
			numBlocks := req.Size / BLOCK_SIZE

			if numBlocks < uint64(len(file.Blocks)) {
				blocksToDelete := file.Blocks[numBlocks:]
				file.Blocks = file.Blocks[:numBlocks]
				file.BlockCache = file.BlockCache[:numBlocks]

				for blk := range blocksToDelete {
					glog.Warningln("Removing ", blocksToDelete[blk].Id)
				}
			}
		} else {
			glog.Warningln("TODO: Expanding")
		}
		file.Size = req.Size

		err := file.WriteBlock(file, *file.Root.Store)
		if err != nil {
			return fuse.EIO
		}

		if glog.V(2) {
			glog.Infoln("Resizing file to size", file.Size)
		}
		res.Attr = file.Attr()
		return nil
	}

	res.Attr = file.Attr()
	if glog.V(2) {
		glog.Infoln("Finished Setattr")
	}
	// TODO: Not implemented.
	return nil
}

func (file *File) Write(req *fuse.WriteRequest, res *fuse.WriteResponse, intr fs.Intr) fuse.Error {
	dataBytes := len(req.Data)
	if glog.V(2) {
		glog.Infof("Writing %d byte(s) at offset %d", dataBytes, req.Offset)
	}
	for req.Offset+int64(dataBytes) >= int64(BLOCK_SIZE*len(file.Blocks)) {
		blk := Block{Id: rand.Int63()}
		dBlk := DataBlock{Block: blk, Data: []byte{}}
		dBlk.MarkDirty()

		file.Blocks = append(file.Blocks, blk)
		file.appendBlock(&dBlk)
		file.MarkDirty()
	}

	startBlockId := req.Offset / BLOCK_SIZE

	var startBlock *DataBlock = file.getBlock(startBlockId)

	if glog.V(2) {
		glog.Infof("Block content length: %d", len(startBlock.Data))
	}

	oldLen := len(startBlock.Data)
	bytesToAdd := min(BLOCK_SIZE-int(req.Offset%BLOCK_SIZE), dataBytes)
	data := append(startBlock.Data[:(req.Offset%BLOCK_SIZE)], req.Data[:bytesToAdd]...)
	if len(startBlock.Data) >= int(req.Offset%BLOCK_SIZE)+bytesToAdd {
		data = append(data, startBlock.Data[int(req.Offset%BLOCK_SIZE)+bytesToAdd:]...)
	}
	startBlock.Data = data

	if glog.V(2) {
		glog.Infof("Block content length after: %d", len(startBlock.Data))
	}

	startBlock.MarkDirty()

	if glog.V(2) {
		glog.Infoln("Successfully completed write operation")
	}

	res.Size = bytesToAdd
	file.Size = file.Size + uint64(len(startBlock.Data)) - uint64(oldLen)

	file.MarkDirty()
	return nil
}

func (file *File) Marshal() ([]byte, error) {
	return json.Marshal(file)
}

func (file File) Attr() fuse.Attr {
	if glog.V(2) {
		glog.Infoln("Attr called")
	}
	return fuse.Attr{Mode: 0444, Blocks: uint64(len(file.Blocks)), Size: file.Size}
}

func (file *File) Release(req *fuse.ReleaseRequest, intr fs.Intr) fuse.Error {
	if glog.V(2) {
		glog.Infoln("Release", file.Name)
	}
	return nil
}

func (file *File) Forget() {
	if glog.V(2) {
		glog.Infoln("FORGET", file.Name)
	}
}

func (file *File) Flush(req *fuse.FlushRequest, intr fs.Intr) fuse.Error {
	if glog.V(2) {
		glog.Infoln("FLUSH", file.Name, file.IsDirty())
	}
	for i := range file.BlockCache {
		if file.BlockCache[i] != nil && file.BlockCache[i].IsDirty() {
			err := file.Blocks[i].WriteBlock(file.getBlock(int64(i)), *file.Root.Store)
			if err != nil {
				glog.Warning("Unable to write block %s due to error: %s", file.Blocks[i].Id, err)
			}
		}
	}

	if file.IsDirty() {
		file.WriteBlock(file, *file.Root.Store)
	}
	return nil
}

func (file *File) Fsync(req *fuse.FsyncRequest, intr fs.Intr) fuse.Error {
	if glog.V(2) {
		glog.Infoln("FSYNC", file.Name, file.IsDirty())
	}
	return file.Flush(nil, intr)
}

func (file *File) Read(req *fuse.ReadRequest, res *fuse.ReadResponse, intr fs.Intr) fuse.Error {
	if glog.V(2) {
		glog.Infof("Reading %d byte(s) at offset %d", req.Size, req.Offset)
	}

	if req.Offset >= int64(file.Size) {
		res.Data = []byte{}
		return nil
	}

	res.Data = []byte{}

	startBlockId := req.Offset / BLOCK_SIZE

	var startBlock *DataBlock = file.getBlock(startBlockId)
	if startBlock == nil {
		glog.Error("Error while reading block")
		return fuse.EIO
	}

	beginReadByte := int(req.Offset % BLOCK_SIZE)
	endReadByte := min(len(startBlock.Data)-beginReadByte, req.Size)
	if glog.V(2) {
		glog.Infof("Block content length: %d", len(startBlock.Data))
		glog.Infof("Reading from %d to %d in block %d", beginReadByte, endReadByte+beginReadByte, startBlockId)
	}
	res.Data = startBlock.Data[beginReadByte : endReadByte+beginReadByte]

	return nil
}
<|endoftext|>


<commit_before>package gannoy

import (
	"bytes"
	"encoding/binary"
	"math"
	"os"
	"syscall"
)

type File struct {
	tree       int
	dim        int
	K          int
	file       *os.File
	filename   string
	appendFile *os.File
	createChan chan createArgs
	locker     Locker
	nodeSize   int64
	offsetOfV  int64
}

func newFile(filename string, tree, dim, K int) *File {
	_, err := os.Stat(filename)
	if err != nil {
		f, _ := os.Create(filename)
		f.Close()
	}

	file, _ := os.OpenFile(filename, os.O_RDWR, 0)
	appendFile, _ := os.OpenFile(filename, os.O_RDWR|os.O_APPEND, 0)

	f := &File{
		tree:       tree,
		dim:        dim,
		K:          K,
		file:       file,
		filename:   filename,
		appendFile: appendFile,
		createChan: make(chan createArgs, 1),
		locker:     newLocker(),
		nodeSize: int64(1 + // free
			4 + // nDescendants
			4 + // key
			4*tree + // parents
			4*2 + // children
			8*dim), // v
		offsetOfV: int64(1 + // free
			4 + // nDescendants
			4 + // key
			4*tree + // parents
			4*2), // children
	}
	go f.creator()
	return f
}

func (f *File) Create(n Node) (int, error) {
	args := createArgs{node: n, result: make(chan createResult)}
	f.createChan <- args
	result := <-args.result
	return result.id, result.err
}

func (f *File) create(n Node) (int, error) {
	buf := &bytes.Buffer{}
	f.nodeToBuf(buf, n)
	id := f.nodeCount()
	_, err := f.appendFile.Write(buf.Bytes())
	return id, err
}

func (f *File) Find(id int) (Node, error) {
	node := Node{}
	node.id = id
	node.storage = f
	offset := f.offset(id)
	err := f.locker.ReadLock(f.file.Fd(), offset, f.nodeSize)
	if err != nil {
		return node, err
	}
	defer f.locker.UnLock(f.file.Fd(), offset, f.nodeSize)

	b := make([]byte, f.nodeSize)
	_, err = syscall.Pread(int(f.file.Fd()), b, offset)
	if err != nil {
		return node, err
	}

	buf := bytes.NewReader(b)

	var free bool
	binary.Read(buf, binary.BigEndian, &free)
	node.free = free

	var nDescendants int32
	binary.Read(buf, binary.BigEndian, &nDescendants)
	node.nDescendants = int(nDescendants)

	var key int32
	binary.Read(buf, binary.BigEndian, &key)
	node.key = int(key)

	parents := make([]int32, f.tree)
	binary.Read(buf, binary.BigEndian, &parents)
	nodeParents := make([]int, f.tree)
	for i, parent := range parents {
		nodeParents[i] = int(parent)
	}
	node.parents = nodeParents

	if node.nDescendants == 1 {
		// leaf node
		node.children = []int{0, 0} // skip children
		node.v = bytesToFloat64s(b[f.offsetOfV:])
	} else if node.nDescendants <= f.K {
		// bucket node
		children := make([]int32, nDescendants)
		binary.Read(buf, binary.BigEndian, &children)
		nodeChildren := make([]int, nDescendants)
		for i, child := range children {
			nodeChildren[i] = int(child)
		}
		node.children = nodeChildren
	} else {
		// other node
		children := make([]int32, 2)
		binary.Read(buf, binary.BigEndian, &children)
		nodeChildren := make([]int, 2)
		for i, child := range children {
			nodeChildren[i] = int(child)
		}
		node.children = nodeChildren
		node.v = bytesToFloat64s(b[f.offsetOfV:])
	}
	return node, nil
}

func (f *File) Update(n Node) error {
	buf := &bytes.Buffer{}
	f.nodeToBuf(buf, n)
	offset := f.offset(n.id)
	file, _ := os.OpenFile(f.filename, os.O_RDWR, 0)
	defer file.Close()

	err := f.locker.WriteLock(file.Fd(), offset, f.nodeSize)
	if err != nil {
		return err
	}
	defer f.locker.UnLock(file.Fd(), offset, f.nodeSize)

	_, err = syscall.Pwrite(int(file.Fd()), buf.Bytes(), offset)
	return err
}

func (f *File) UpdateParent(id, rootIndex, parent int) error {
	offset := f.offset(id) +
		int64(1+ // free
			4+ // nDescendants
			4+ // key
			4*rootIndex) // parents
	buf := &bytes.Buffer{}
	binary.Write(buf, binary.BigEndian, int32(parent))

	file, _ := os.OpenFile(f.filename, os.O_RDWR, 0)
	defer file.Close()

	err := f.locker.WriteLock(file.Fd(), offset, 4)
	if err != nil {
		return err
	}
	defer f.locker.UnLock(file.Fd(), offset, 4)

	_, err = syscall.Pwrite(int(file.Fd()), buf.Bytes(), offset)
	return err
}

func (f *File) Delete(n Node) error {
	n.free = true
	return f.Update(n)
}

func (f *File) Iterate(c chan Node) {
	count := f.nodeCount()
	// TODO: Use goroutine
	for i := 0; i < count; i++ {
		n, err := f.Find(i)
		if err != nil {
			close(c)
		}
		c <- n
	}
	close(c)
}

func (f File) offset(id int) int64 {
	return (int64(id) * f.nodeSize)
}

func (f File) nodeCount() int {
	stat, _ := f.file.Stat()
	size := stat.Size()
	return int(size / f.nodeSize)
}

func (f File) nodeToBuf(buf *bytes.Buffer, node Node) {
	// 1bytes free
	binary.Write(buf, binary.BigEndian, node.free)

	// 4bytes nDescendants
	binary.Write(buf, binary.BigEndian, int32(node.nDescendants))

	// 4bytes key
	binary.Write(buf, binary.BigEndian, int32(node.key))

	// 4bytes parents
	parents := make([]int32, len(node.parents))
	for i, parent := range node.parents {
		parents[i] = int32(parent)
	}
	binary.Write(buf, binary.BigEndian, parents)

	if node.isBucket() {
		// 4bytes children in K
		children := make([]int32, f.K)
		for i, child := range node.children {
			children[i] = int32(child)
		}
		binary.Write(buf, binary.BigEndian, children)

		// padding by zero
		remainingSize := ((2*4 + 8*f.dim) - (4 * f.K))
		binary.Write(buf, binary.BigEndian, make([]int32, remainingSize/4))
	} else {
		// 4bytes children in K
		children := make([]int32, 2)
		for i, child := range node.children {
			children[i] = int32(child)
		}
		binary.Write(buf, binary.BigEndian, children)

		// 8bytes v in f
		vec := make([]float64, f.dim)
		for i, v := range node.v {
			vec[i] = float64(v)
		}
		binary.Write(buf, binary.BigEndian, vec)
	}
}

type createArgs struct {
	node   Node
	result chan createResult
}

type createResult struct {
	id  int
	err error
}

func (f *File) creator() {
	for args := range f.createChan {
		id, err := f.create(args.node)
		args.result <- createResult{
			id:  id,
			err: err,
		}
	}
}

func (f File) size() int64 {
	info, _ := f.file.Stat()
	return info.Size()
}

func bytesToFloat64s(bytes []byte) []float64 {
	size := len(bytes) / 8
	floats := make([]float64, size)
	for i := 0; i < size; i++ {
		floats[i] = math.Float64frombits(binary.BigEndian.Uint64(bytes[0:8]))
		bytes = bytes[8:]
	}
	return floats
}
<commit_msg>Reduce allocate memory when file.Find.<commit_after>package gannoy

import (
	"bytes"
	"encoding/binary"
	"math"
	"os"
	"syscall"
)

type File struct {
	tree       int
	dim        int
	K          int
	file       *os.File
	filename   string
	appendFile *os.File
	createChan chan createArgs
	locker     Locker
	nodeSize   int64
	offsetOfV  int64
}

func newFile(filename string, tree, dim, K int) *File {
	_, err := os.Stat(filename)
	if err != nil {
		f, _ := os.Create(filename)
		f.Close()
	}

	file, _ := os.OpenFile(filename, os.O_RDWR, 0)
	appendFile, _ := os.OpenFile(filename, os.O_RDWR|os.O_APPEND, 0)

	f := &File{
		tree:       tree,
		dim:        dim,
		K:          K,
		file:       file,
		filename:   filename,
		appendFile: appendFile,
		createChan: make(chan createArgs, 1),
		locker:     newLocker(),
		nodeSize: int64(1 + // free
			4 + // nDescendants
			4 + // key
			4*tree + // parents
			4*2 + // children
			8*dim), // v
		offsetOfV: int64(1 + // free
			4 + // nDescendants
			4 + // key
			4*tree + // parents
			4*2), // children
	}
	go f.creator()
	return f
}

func (f *File) Create(n Node) (int, error) {
	args := createArgs{node: n, result: make(chan createResult)}
	f.createChan <- args
	result := <-args.result
	return result.id, result.err
}

func (f *File) create(n Node) (int, error) {
	buf := &bytes.Buffer{}
	f.nodeToBuf(buf, n)
	id := f.nodeCount()
	_, err := f.appendFile.Write(buf.Bytes())
	return id, err
}

func (f *File) Find(id int) (Node, error) {
	node := Node{}
	node.id = id
	node.storage = f
	offset := f.offset(id)
	err := f.locker.ReadLock(f.file.Fd(), offset, f.nodeSize)
	if err != nil {
		return node, err
	}
	defer f.locker.UnLock(f.file.Fd(), offset, f.nodeSize)

	b := make([]byte, f.nodeSize)
	_, err = syscall.Pread(int(f.file.Fd()), b, offset)
	if err != nil {
		return node, err
	}

	node.free = b[0] != 0
	node.nDescendants = int(int32(binary.BigEndian.Uint32(b[1:5])))
	node.key = int(int32(binary.BigEndian.Uint32(b[5:9])))

	node.parents = make([]int, f.tree)
	for i := 0; i < f.tree; i++ {
		node.parents[i] = int(int32(binary.BigEndian.Uint32(b[9+i*4 : 9+i*4+4])))
	}

	if node.nDescendants == 1 {
		// leaf node
		node.children = []int{0, 0} // skip children
		node.v = bytesToFloat64s(b[f.offsetOfV:])
	} else if node.nDescendants <= f.K {
		// bucket node
		node.children = make([]int, node.nDescendants)
		offsetOfChildren := int(f.offsetOfV - (4 * 2))
		for i := 0; i < node.nDescendants; i++ {
			node.children[i] = int(int32(binary.BigEndian.Uint32(b[offsetOfChildren+i*4 : offsetOfChildren+i*4+4])))
		}
	} else {
		// other node
		node.children = make([]int, 2)
		offsetOfChildren := int(f.offsetOfV - (4 * 2))
		for i := 0; i < 2; i++ {
			node.children[i] = int(int32(binary.BigEndian.Uint32(b[offsetOfChildren+i*4 : offsetOfChildren+i*4+4])))
		}
		node.v = bytesToFloat64s(b[f.offsetOfV:])
	}
	return node, nil
}

func (f *File) Update(n Node) error {
	buf := &bytes.Buffer{}
	f.nodeToBuf(buf, n)
	offset := f.offset(n.id)
	file, _ := os.OpenFile(f.filename, os.O_RDWR, 0)
	defer file.Close()

	err := f.locker.WriteLock(file.Fd(), offset, f.nodeSize)
	if err != nil {
		return err
	}
	defer f.locker.UnLock(file.Fd(), offset, f.nodeSize)

	_, err = syscall.Pwrite(int(file.Fd()), buf.Bytes(), offset)
	return err
}

func (f *File) UpdateParent(id, rootIndex, parent int) error {
	offset := f.offset(id) +
		int64(1+ // free
			4+ // nDescendants
			4+ // key
			4*rootIndex) // parents
	buf := &bytes.Buffer{}
	binary.Write(buf, binary.BigEndian, int32(parent))

	file, _ := os.OpenFile(f.filename, os.O_RDWR, 0)
	defer file.Close()

	err := f.locker.WriteLock(file.Fd(), offset, 4)
	if err != nil {
		return err
	}
	defer f.locker.UnLock(file.Fd(), offset, 4)

	_, err = syscall.Pwrite(int(file.Fd()), buf.Bytes(), offset)
	return err
}

func (f *File) Delete(n Node) error {
	n.free = true
	return f.Update(n)
}

func (f *File) Iterate(c chan Node) {
	count := f.nodeCount()
	// TODO: Use goroutine
	for i := 0; i < count; i++ {
		n, err := f.Find(i)
		if err != nil {
			close(c)
		}
		c <- n
	}
	close(c)
}

func (f File) offset(id int) int64 {
	return (int64(id) * f.nodeSize)
}

func (f File) nodeCount() int {
	stat, _ := f.file.Stat()
	size := stat.Size()
	return int(size / f.nodeSize)
}

func (f File) nodeToBuf(buf *bytes.Buffer, node Node) {
	// 1bytes free
	binary.Write(buf, binary.BigEndian, node.free)

	// 4bytes nDescendants
	binary.Write(buf, binary.BigEndian, int32(node.nDescendants))

	// 4bytes key
	binary.Write(buf, binary.BigEndian, int32(node.key))

	// 4bytes parents
	parents := make([]int32, len(node.parents))
	for i, parent := range node.parents {
		parents[i] = int32(parent)
	}
	binary.Write(buf, binary.BigEndian, parents)

	if node.isBucket() {
		// 4bytes children in K
		children := make([]int32, f.K)
		for i, child := range node.children {
			children[i] = int32(child)
		}
		binary.Write(buf, binary.BigEndian, children)

		// padding by zero
		remainingSize := ((2*4 + 8*f.dim) - (4 * f.K))
		binary.Write(buf, binary.BigEndian, make([]int32, remainingSize/4))
	} else {
		// 4bytes children in K
		children := make([]int32, 2)
		for i, child := range node.children {
			children[i] = int32(child)
		}
		binary.Write(buf, binary.BigEndian, children)

		// 8bytes v in f
		vec := make([]float64, f.dim)
		for i, v := range node.v {
			vec[i] = float64(v)
		}
		binary.Write(buf, binary.BigEndian, vec)
	}
}

type createArgs struct {
	node   Node
	result chan createResult
}

type createResult struct {
	id  int
	err error
}

func (f *File) creator() {
	for args := range f.createChan {
		id, err := f.create(args.node)
		args.result <- createResult{
			id:  id,
			err: err,
		}
	}
}

func (f File) size() int64 {
	info, _ := f.file.Stat()
	return info.Size()
}

func bytesToFloat64s(bytes []byte) []float64 {
	size := len(bytes) / 8
	floats := make([]float64, size)
	for i := 0; i < size; i++ {
		floats[i] = math.Float64frombits(binary.BigEndian.Uint64(bytes[0:8]))
		bytes = bytes[8:]
	}
	return floats
}
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2014 Kurt Jung (Gmail: kurt.w.jung)
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

package gofpdf

import (
	"regexp"
	"strings"
)

// HTMLBasicSegmentType defines a segment of literal text in which the current
// attributes do not vary, or an open tag or a close tag.
type HTMLBasicSegmentType struct {
	Cat  byte              // 'O' open tag, 'C' close tag, 'T' text
	Str  string            // Literal text unchanged, tags are lower case
	Attr map[string]string // Attribute keys are lower case
}

// HTMLBasicTokenize returns a list of HTML tags and literal elements. This is
// done with regular expressions, so the result is only marginally better than
// useless.
func HTMLBasicTokenize(htmlStr string) (list []HTMLBasicSegmentType) {
	// This routine is adapted from http://www.fpdf.org/
	list = make([]HTMLBasicSegmentType, 0, 16)
	htmlStr = strings.Replace(htmlStr, "\n", " ", -1)
	htmlStr = strings.Replace(htmlStr, "\r", "", -1)
	tagRe, _ := regexp.Compile(`(?U)<.*>`)
	attrRe, _ := regexp.Compile(`([^=]+)=["']?([^"']+)`)
	capList := tagRe.FindAllStringIndex(htmlStr, -1)
	if capList != nil {
		var seg HTMLBasicSegmentType
		var parts []string
		pos := 0
		for _, cap := range capList {
			if pos < cap[0] {
				seg.Cat = 'T'
				seg.Str = htmlStr[pos:cap[0]]
				seg.Attr = nil
				list = append(list, seg)
			}
			if htmlStr[cap[0]+1] == '/' {
				seg.Cat = 'C'
				seg.Str = strings.ToLower(htmlStr[cap[0]+2 : cap[1]-1])
				seg.Attr = nil
				list = append(list, seg)
			} else {
				// Extract attributes
				parts = strings.Split(htmlStr[cap[0]+1:cap[1]-1], " ")
				if len(parts) > 0 {
					for j, part := range parts {
						if j == 0 {
							seg.Cat = 'O'
							seg.Str = strings.ToLower(parts[0])
							seg.Attr = make(map[string]string)
						} else {
							attrList := attrRe.FindAllStringSubmatch(part, -1)
							if attrList != nil {
								for _, attr := range attrList {
									seg.Attr[strings.ToLower(attr[1])] = attr[2]
								}
							}
						}
					}
					list = append(list, seg)
				}
			}
			pos = cap[1]
		}
		if len(htmlStr) > pos {
			seg.Cat = 'T'
			seg.Str = htmlStr[pos:]
			seg.Attr = nil
			list = append(list, seg)
		}
	}
	return
}

// HTMLBasicType is used for rendering a very basic subset of HTML. It supports
// only hyperlinks and bold, italic and underscore attributes. In the Link
// structure, the ClrR, ClrG and ClrB fields (0 through 255) define the color
// of hyperlinks. The Bold, Italic and Underscore values define the hyperlink
// style.
type HTMLBasicType struct {
	pdf  *Fpdf
	Link struct {
		ClrR, ClrG, ClrB         int
		Bold, Italic, Underscore bool
	}
}

// HTMLBasicNew returns an instance that facilitates writing basic HTML in the
// specified PDF file.
func (f *Fpdf) HTMLBasicNew() (html HTMLBasicType) {
	html.pdf = f
	html.Link.ClrR, html.Link.ClrG, html.Link.ClrB = 0, 0, 128
	html.Link.Bold, html.Link.Italic, html.Link.Underscore = false, false, true
	return
}

// Write prints text from the current position using the currently selected
// font. The text can be encoded with a basic subset of HTML that includes
// hyperlinks and tags for italic (I), bold (B) and underscore (U) attributes.
// When the right margin is reached a line break occurs and text continues from
// the left margin. Upon method exit, the current position is left at the end
// of the text.
//
// lineHt indicates the line height in the unit of measure specified in New().
func (html *HTMLBasicType) Write(lineHt float64, htmlStr string) {
	var boldLvl, italicLvl, underscoreLvl, linkBold, linkItalic, linkUnderscore int
	var textR, textG, textB = html.pdf.GetTextColor()
	var hrefStr string
	if html.Link.Bold {
		linkBold = 1
	}
	if html.Link.Italic {
		linkItalic = 1
	}
	if html.Link.Underscore {
		linkUnderscore = 1
	}
	setStyle := func(boldAdj, italicAdj, underscoreAdj int) {
		styleStr := ""
		boldLvl += boldAdj
		if boldLvl > 0 {
			styleStr += "B"
		}
		italicLvl += italicAdj
		if italicLvl > 0 {
			styleStr += "I"
		}
		underscoreLvl += underscoreAdj
		if underscoreLvl > 0 {
			styleStr += "U"
		}
		html.pdf.SetFont("", styleStr, 0)
	}
	putLink := func(urlStr, txtStr string) {
		// Put a hyperlink
		html.pdf.SetTextColor(html.Link.ClrR, html.Link.ClrG, html.Link.ClrB)
		setStyle(linkBold, linkItalic, linkUnderscore)
		html.pdf.WriteLinkString(lineHt, txtStr, urlStr)
		setStyle(-linkBold, -linkItalic, -linkUnderscore)
		html.pdf.SetTextColor(textR, textG, textB)
	}
	list := HTMLBasicTokenize(htmlStr)
	var ok bool
	for _, el := range list {
		switch el.Cat {
		case 'T':
			if len(hrefStr) > 0 {
				putLink(hrefStr, el.Str)
				hrefStr = ""
			} else {
				html.pdf.Write(lineHt, el.Str)
			}
		case 'O':
			switch el.Str {
			case "b":
				setStyle(1, 0, 0)
			case "i":
				setStyle(0, 1, 0)
			case "u":
				setStyle(0, 0, 1)
			case "br":
				html.pdf.Ln(lineHt)
			case "a":
				hrefStr, ok = el.Attr["href"]
				if !ok {
					hrefStr = ""
				}
			}
		case 'C':
			switch el.Str {
			case "b":
				setStyle(-1, 0, 0)
			case "i":
				setStyle(0, -1, 0)
			case "u":
				setStyle(0, 0, -1)

			}
		}
	}
}
<commit_msg>Reference tutorial for writing basic HTML.<commit_after>/*
 * Copyright (c) 2014 Kurt Jung (Gmail: kurt.w.jung)
 *
 * Permission to use, copy, modify, and distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

package gofpdf

import (
	"regexp"
	"strings"
)

// HTMLBasicSegmentType defines a segment of literal text in which the current
// attributes do not vary, or an open tag or a close tag.
type HTMLBasicSegmentType struct {
	Cat  byte              // 'O' open tag, 'C' close tag, 'T' text
	Str  string            // Literal text unchanged, tags are lower case
	Attr map[string]string // Attribute keys are lower case
}

// HTMLBasicTokenize returns a list of HTML tags and literal elements. This is
// done with regular expressions, so the result is only marginally better than
// useless.
func HTMLBasicTokenize(htmlStr string) (list []HTMLBasicSegmentType) {
	// This routine is adapted from http://www.fpdf.org/
	list = make([]HTMLBasicSegmentType, 0, 16)
	htmlStr = strings.Replace(htmlStr, "\n", " ", -1)
	htmlStr = strings.Replace(htmlStr, "\r", "", -1)
	tagRe, _ := regexp.Compile(`(?U)<.*>`)
	attrRe, _ := regexp.Compile(`([^=]+)=["']?([^"']+)`)
	capList := tagRe.FindAllStringIndex(htmlStr, -1)
	if capList != nil {
		var seg HTMLBasicSegmentType
		var parts []string
		pos := 0
		for _, cap := range capList {
			if pos < cap[0] {
				seg.Cat = 'T'
				seg.Str = htmlStr[pos:cap[0]]
				seg.Attr = nil
				list = append(list, seg)
			}
			if htmlStr[cap[0]+1] == '/' {
				seg.Cat = 'C'
				seg.Str = strings.ToLower(htmlStr[cap[0]+2 : cap[1]-1])
				seg.Attr = nil
				list = append(list, seg)
			} else {
				// Extract attributes
				parts = strings.Split(htmlStr[cap[0]+1:cap[1]-1], " ")
				if len(parts) > 0 {
					for j, part := range parts {
						if j == 0 {
							seg.Cat = 'O'
							seg.Str = strings.ToLower(parts[0])
							seg.Attr = make(map[string]string)
						} else {
							attrList := attrRe.FindAllStringSubmatch(part, -1)
							if attrList != nil {
								for _, attr := range attrList {
									seg.Attr[strings.ToLower(attr[1])] = attr[2]
								}
							}
						}
					}
					list = append(list, seg)
				}
			}
			pos = cap[1]
		}
		if len(htmlStr) > pos {
			seg.Cat = 'T'
			seg.Str = htmlStr[pos:]
			seg.Attr = nil
			list = append(list, seg)
		}
	}
	return
}

// HTMLBasicType is used for rendering a very basic subset of HTML. It supports
// only hyperlinks and bold, italic and underscore attributes. In the Link
// structure, the ClrR, ClrG and ClrB fields (0 through 255) define the color
// of hyperlinks. The Bold, Italic and Underscore values define the hyperlink
// style.
type HTMLBasicType struct {
	pdf  *Fpdf
	Link struct {
		ClrR, ClrG, ClrB         int
		Bold, Italic, Underscore bool
	}
}

// HTMLBasicNew returns an instance that facilitates writing basic HTML in the
// specified PDF file.
//
// This function is demonstrated in tutorial 6.
func (f *Fpdf) HTMLBasicNew() (html HTMLBasicType) {
	html.pdf = f
	html.Link.ClrR, html.Link.ClrG, html.Link.ClrB = 0, 0, 128
	html.Link.Bold, html.Link.Italic, html.Link.Underscore = false, false, true
	return
}

// Write prints text from the current position using the currently selected
// font. See HTMLBasicNew() to create a receiver that is associated with the
// PDF document instance. The text can be encoded with a basic subset of HTML
// that includes hyperlinks and tags for italic (I), bold (B) and underscore
// (U) attributes. When the right margin is reached a line break occurs and
// text continues from the left margin. Upon method exit, the current position
// is left at the end of the text.
//
// lineHt indicates the line height in the unit of measure specified in New().
func (html *HTMLBasicType) Write(lineHt float64, htmlStr string) {
	var boldLvl, italicLvl, underscoreLvl, linkBold, linkItalic, linkUnderscore int
	var textR, textG, textB = html.pdf.GetTextColor()
	var hrefStr string
	if html.Link.Bold {
		linkBold = 1
	}
	if html.Link.Italic {
		linkItalic = 1
	}
	if html.Link.Underscore {
		linkUnderscore = 1
	}
	setStyle := func(boldAdj, italicAdj, underscoreAdj int) {
		styleStr := ""
		boldLvl += boldAdj
		if boldLvl > 0 {
			styleStr += "B"
		}
		italicLvl += italicAdj
		if italicLvl > 0 {
			styleStr += "I"
		}
		underscoreLvl += underscoreAdj
		if underscoreLvl > 0 {
			styleStr += "U"
		}
		html.pdf.SetFont("", styleStr, 0)
	}
	putLink := func(urlStr, txtStr string) {
		// Put a hyperlink
		html.pdf.SetTextColor(html.Link.ClrR, html.Link.ClrG, html.Link.ClrB)
		setStyle(linkBold, linkItalic, linkUnderscore)
		html.pdf.WriteLinkString(lineHt, txtStr, urlStr)
		setStyle(-linkBold, -linkItalic, -linkUnderscore)
		html.pdf.SetTextColor(textR, textG, textB)
	}
	list := HTMLBasicTokenize(htmlStr)
	var ok bool
	for _, el := range list {
		switch el.Cat {
		case 'T':
			if len(hrefStr) > 0 {
				putLink(hrefStr, el.Str)
				hrefStr = ""
			} else {
				html.pdf.Write(lineHt, el.Str)
			}
		case 'O':
			switch el.Str {
			case "b":
				setStyle(1, 0, 0)
			case "i":
				setStyle(0, 1, 0)
			case "u":
				setStyle(0, 0, 1)
			case "br":
				html.pdf.Ln(lineHt)
			case "a":
				hrefStr, ok = el.Attr["href"]
				if !ok {
					hrefStr = ""
				}
			}
		case 'C':
			switch el.Str {
			case "b":
				setStyle(-1, 0, 0)
			case "i":
				setStyle(0, -1, 0)
			case "u":
				setStyle(0, 0, -1)

			}
		}
	}
}
<|endoftext|>


<commit_before>package find

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

// Find finds recursively under a root directory those regular files whose base name matches the provided regular expression.  Files residing
// at a depth other than atDepth are ignored.  For a root R, this file F resides at depth 2: R/t/F, for some subdirectory t of R.
// If atDepth is -1, depth is not considered.  ignoreDirectories is an array of strings representing base directory names that are ignored in the search.
// For example, a directory name in .git will be ignored if ".git" is an element in ignoreDirectories.
func Find(root string, expression *regexp.Regexp, atDepth int, ignoreDirectories []string) ([]string, error) {
	if strings.HasSuffix(root, "/") {
		return nil, fmt.Errorf("Root must not end in /: %s\n", root)
	}

	checkDepth := atDepth != -1

	var files []string
	markFn := func(path string, info os.FileInfo, err error) error {

		normalizedPath := path[len(root):]

		parts := strings.Count(normalizedPath, "/")

		if err != nil {
			return err
		}

		for _, v := range ignoreDirectories {
			if strings.HasSuffix(normalizedPath, v) {
				return filepath.SkipDir
			}
		}

		if checkDepth && info.IsDir() && strings.Count(normalizedPath, "/") > parts {
			return filepath.SkipDir
		}

		if info.Mode().IsRegular() {
			fmt.Printf("testing %s\n", normalizedPath)
			ok := expression.MatchString(info.Name())
			if ok {
				if checkDepth {
					if atDepth == parts {
						files = append(files, path)
					}
				} else {
					files = append(files, path)
				}
			}
		}
		return nil
	}

	err := filepath.Walk(root, markFn)
	if err != nil {
		return nil, err
	}

	return files, nil
}
<commit_msg>remove logging<commit_after>package find

import (
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"
)

// Find finds recursively under a root directory those regular files whose base name matches the provided regular expression.  Files residing
// at a depth other than atDepth are ignored.  For a root R, this file F resides at depth 2: R/t/F, for some subdirectory t of R.
// If atDepth is -1, depth is not considered.  ignoreDirectories is an array of strings representing base directory names that are ignored in the search.
// For example, a directory name in .git will be ignored if ".git" is an element in ignoreDirectories.
func Find(root string, expression *regexp.Regexp, atDepth int, ignoreDirectories []string) ([]string, error) {
	if strings.HasSuffix(root, "/") {
		return nil, fmt.Errorf("Root must not end in /: %s\n", root)
	}

	checkDepth := atDepth != -1

	var files []string
	markFn := func(path string, info os.FileInfo, err error) error {

		normalizedPath := path[len(root):]

		parts := strings.Count(normalizedPath, "/")

		if err != nil {
			return err
		}

		for _, v := range ignoreDirectories {
			if strings.HasSuffix(normalizedPath, v) {
				return filepath.SkipDir
			}
		}

		if checkDepth && info.IsDir() && strings.Count(normalizedPath, "/") > parts {
			return filepath.SkipDir
		}

		if info.Mode().IsRegular() {
			ok := expression.MatchString(info.Name())
			if ok {
				if checkDepth {
					if atDepth == parts {
						files = append(files, path)
					}
				} else {
					files = append(files, path)
				}
			}
		}
		return nil
	}

	err := filepath.Walk(root, markFn)
	if err != nil {
		return nil, err
	}

	return files, nil
}
<|endoftext|>


<commit_before>// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package http

import (
	"encoding/json"
	"fmt"
	"net/http"
	"runtime/pprof"
	"time"

	_ "net/http/pprof"
)

type stats struct {
	Goroutine int   `json:"goroutine"`
	Thread    int   `json:"thread"`
	Block     int   `json:"block"`
	Timestamp int64 `json:"timestamp"`
}

func init() {
	// TODO(jbd): enable block profile.
	http.HandleFunc("/debug/pprofstats", func(w http.ResponseWriter, r *http.Request) {
		n := &stats{
			Goroutine: pprof.Lookup("goroutine").Count(),
			Thread:    pprof.Lookup("threadcreate").Count(),
			Block:     pprof.Lookup("block").Count(),
			Timestamp: time.Now().Unix(),
		}
		err := json.NewEncoder(w).Encode(n)
		if err != nil {
			w.WriteHeader(500)
			fmt.Fprint(w, err)
		}
	})
}
<commit_msg>export stats handler<commit_after>// Copyright 2014 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package http

import (
	"encoding/json"
	"fmt"
	"net/http"
	"runtime/pprof"
	"time"

	_ "net/http/pprof"
)

type stats struct {
	Goroutine int   `json:"goroutine"`
	Thread    int   `json:"thread"`
	Block     int   `json:"block"`
	Timestamp int64 `json:"timestamp"`
}

func init() {
	http.HandleFunc("/debug/pprofstats", Stats())
}

// Stats handler returns an http.HandlerFunc that returns stats
// about the number of current goroutines, threads, etc.
// Stats handler must be accessible through "/debug/pprofstats" route
// in order for gom to display the stats from the debugged program.
func Stats() http.HandlerFunc {
	// TODO(jbd): enable block profile.
	return func(w http.ResponseWriter, r *http.Request) {
		n := &stats{
			Goroutine: pprof.Lookup("goroutine").Count(),
			Thread:    pprof.Lookup("threadcreate").Count(),
			Block:     pprof.Lookup("block").Count(),
			Timestamp: time.Now().Unix(),
		}
		err := json.NewEncoder(w).Encode(n)
		if err != nil {
			w.WriteHeader(500)
			fmt.Fprint(w, err)
		}
	}
}
<|endoftext|>


<commit_before>package taggolib

import (
	"encoding/binary"
	"fmt"
	"io"
	"strconv"
	"strings"
	"time"

	"github.com/eaburns/bit"
)

const (
	// flacStreamInfo denotes a STREAMINFO metadata block
	flacStreamInfo = 0
	// flacVorbisComment denotes a VORBISCOMMENT metadata block
	flacVorbisComment = 4
)

var (
	// flacMagicNumber is the magic number used to identify a FLAC audio stream
	flacMagicNumber = []byte("fLaC")
)

// flacParser represents a FLAC audio metadata tag parser
type flacParser struct {
	encoder    string
	endPos     int64
	properties *flacStreamInfoBlock
	reader     io.ReadSeeker
	tags       map[string]string

	// Shared buffer stored as field to prevent unneeded allocations
	buffer []byte
}

// Album returns the Album tag for this stream
func (f flacParser) Album() string {
	return f.tags[tagAlbum]
}

// AlbumArtist returns the AlbumArtist tag for this stream
func (f flacParser) AlbumArtist() string {
	return f.tags[tagAlbumArtist]
}

// Artist returns the Artist tag for this stream
func (f flacParser) Artist() string {
	return f.tags[tagArtist]
}

// BitDepth returns the bits-per-sample of this stream
func (f flacParser) BitDepth() int {
	return int(f.properties.BitsPerSample)
}

// Bitrate calculates the audio bitrate for this stream
func (f flacParser) Bitrate() int {
	return int(((f.endPos * 8) / int64(f.Duration().Seconds())) / 1024)
}

// Channels returns the number of channels for this stream
func (f flacParser) Channels() int {
	return int(f.properties.ChannelCount)
}

// Checksum returns the checksum for this stream
func (f flacParser) Checksum() string {
	return f.properties.MD5Checksum
}

// Comment returns the Comment tag for this stream
func (f flacParser) Comment() string {
	return f.tags[tagComment]
}

// Date returns the Date tag for this stream
func (f flacParser) Date() string {
	return f.tags[tagDate]
}

// DiscNumber returns the DiscNumber tag for this stream
func (f flacParser) DiscNumber() int {
	disc, err := strconv.Atoi(f.tags[tagDiscNumber])
	if err != nil {
		return 0
	}

	return disc
}

// Duration returns the time duration for this stream
func (f flacParser) Duration() time.Duration {
	return time.Duration(int64(f.properties.SampleCount)/int64(f.SampleRate())) * time.Second
}

// Encoder returns the encoder for this stream
func (f flacParser) Encoder() string {
	return f.encoder
}

// Format returns the name of the FLAC format
func (f flacParser) Format() string {
	return "FLAC"
}

// Genre returns the Genre tag for this stream
func (f flacParser) Genre() string {
	return f.tags[tagGenre]
}

// SampleRate returns the sample rate in Hertz for this stream
func (f flacParser) SampleRate() int {
	return int(f.properties.SampleRate)
}

// Tag attempts to return the raw, unprocessed tag with the specified name for this stream
func (f flacParser) Tag(name string) string {
	return f.tags[strings.ToUpper(name)]
}

// Title returns the Title tag for this stream
func (f flacParser) Title() string {
	return f.tags[tagTitle]
}

// TrackNumber returns the TrackNumber tag for this stream
func (f flacParser) TrackNumber() int {
	track, err := strconv.Atoi(f.tags[tagTrackNumber])
	if err != nil {
		return 0
	}

	return track
}

// newFLACParser creates a parser for FLAC audio streams
func newFLACParser(reader io.ReadSeeker) (*flacParser, error) {
	// Create FLAC parser
	parser := &flacParser{
		buffer: make([]byte, 256),
		reader: reader,
	}

	// Begin parsing properties
	if err := parser.parseProperties(); err != nil {
		return nil, err
	}

	// Seek through the file and attempt to parse tags
	if err := parser.parseTags(); err != nil {
		return nil, err
	}

	// Seek to end of file to grab the final position, used to calculate bitrate
	n, err := parser.reader.Seek(0, 2)
	if err != nil {
		return nil, err
	}
	parser.endPos = n

	// Return parser
	return parser, nil
}

// flacMetadataHeader represents the header for a FLAC metadata block
type flacMetadataHeader struct {
	LastBlock   bool
	BlockType   uint8
	BlockLength uint32
}

// flacStreamInfoBlock represents the metadata from a FLAC STREAMINFO block
type flacStreamInfoBlock struct {
	SampleRate    uint16
	ChannelCount  uint8
	BitsPerSample uint16
	SampleCount   uint64
	MD5Checksum   string
}

// parseMetadataHeader retrieves metadata header information from a FLAC stream
func (f *flacParser) parseMetadataHeader() (*flacMetadataHeader, error) {
	// Create and use a bit reader to parse the following fields:
	//    1 - Last metadata block before audio (boolean)
	//    7 - Metadata block type (should be 0, for streaminfo)
	//   24 - Length of following metadata (in bytes)
	fields, err := bit.NewReader(f.reader).ReadFields(1, 7, 24)
	if err != nil {
		return nil, err
	}

	// Generate metadata header
	return &flacMetadataHeader{
		LastBlock:   fields[0] == 1,
		BlockType:   uint8(fields[1]),
		BlockLength: uint32(fields[2]),
	}, nil
}

// parseTags retrieves metadata tags from a FLAC VORBISCOMMENT block
func (f *flacParser) parseTags() error {
	// Continuously parse and seek through blocks until we discover the VORBISCOMMENT block
	for {
		header, err := f.parseMetadataHeader()
		if err != nil {
			return err
		}

		// Check for VORBISCOMMENT block, break so we can begin parsing tags
		if header.BlockType == flacVorbisComment {
			break
		}

		// If last block and no VORBISCOMMENT block found, no tags
		if header.LastBlock {
			return nil
		}

		// If nothing found and not last block, seek forward in stream
		if _, err := f.reader.Seek(int64(header.BlockLength), 1); err != nil {
			return err
		}
	}

	// Parse length fields
	var length uint32

	// Read vendor string length
	if err := binary.Read(f.reader, binary.LittleEndian, &length); err != nil {
		return err
	}

	// Read vendor string
	if _, err := f.reader.Read(f.buffer[:length]); err != nil {
		return err
	}
	f.encoder = string(f.buffer[:length])

	// Read comment length (new allocation so we can use it as loop counter)
	var commentLength uint32
	if err := binary.Read(f.reader, binary.LittleEndian, &commentLength); err != nil {
		return err
	}

	// Begin iterating tags, and building tag map
	tagMap := map[string]string{}
	for i := 0; i < int(commentLength); i++ {
		// Read tag string length
		if err := binary.Read(f.reader, binary.LittleEndian, &length); err != nil {
			return err
		}

		// Read tag string
		n, err := f.reader.Read(f.buffer[:length])
		if err != nil {
			return err
		}

		// Split tag name and data, store in map
		pair := strings.Split(string(f.buffer[:n]), "=")
		tagMap[strings.ToUpper(pair[0])] = pair[1]
	}

	// Store tags
	f.tags = tagMap
	return nil
}

// parseProperties retrieves stream properties from a FLAC STREAMINFO block
func (f *flacParser) parseProperties() error {
	// Read the metadata header for STREAMINFO block
	header, err := f.parseMetadataHeader()
	if err != nil {
		return err
	}

	// Ensure that the metadata block type is STREAMINFO
	if header.BlockType != flacStreamInfo {
		return TagError{
			Err:     errInvalidStream,
			Format:  f.Format(),
			Details: "first metadata block is not type STREAMINFO",
		}
	}

	// Ensure that STREAMINFO is not the last block
	if header.LastBlock {
		return TagError{
			Err:     errInvalidStream,
			Format:  f.Format(),
			Details: "STREAMINFO block is marked as last metadata block in stream",
		}
	}

	// Seek forward past frame information, to sample rate
	if _, err := f.reader.Seek(10, 1); err != nil {
		return err
	}

	// Create and use a bit reader to parse the following fields:
	//   20 - Sample rate
	//    3 - Channel count (+1)
	//    5 - Bits per sample (+1)
	//   36 - Sample count
	fields, err := bit.NewReader(f.reader).ReadFields(20, 3, 5, 36)
	if err != nil {
		return err
	}

	// Read the MD5 checksum of the stream
	if _, err := f.reader.Read(f.buffer[:16]); err != nil {
		return err
	}

	// Store properties
	f.properties = &flacStreamInfoBlock{
		SampleRate:    uint16(fields[0]),
		ChannelCount:  uint8(fields[1]) + 1,
		BitsPerSample: uint16(fields[2]) + 1,
		SampleCount:   uint64(fields[3]),
		MD5Checksum:   fmt.Sprintf("%x", f.buffer[:16]),
	}

	return nil
}
<commit_msg>FLAC: increase buffer size and check for division-by-zero to prevent panics<commit_after>package taggolib

import (
	"encoding/binary"
	"fmt"
	"io"
	"strconv"
	"strings"
	"time"

	"github.com/eaburns/bit"
)

const (
	// flacStreamInfo denotes a STREAMINFO metadata block
	flacStreamInfo = 0
	// flacVorbisComment denotes a VORBISCOMMENT metadata block
	flacVorbisComment = 4
)

var (
	// flacMagicNumber is the magic number used to identify a FLAC audio stream
	flacMagicNumber = []byte("fLaC")
)

// flacParser represents a FLAC audio metadata tag parser
type flacParser struct {
	encoder    string
	endPos     int64
	properties *flacStreamInfoBlock
	reader     io.ReadSeeker
	tags       map[string]string

	// Shared buffer stored as field to prevent unneeded allocations
	buffer []byte
}

// Album returns the Album tag for this stream
func (f flacParser) Album() string {
	return f.tags[tagAlbum]
}

// AlbumArtist returns the AlbumArtist tag for this stream
func (f flacParser) AlbumArtist() string {
	return f.tags[tagAlbumArtist]
}

// Artist returns the Artist tag for this stream
func (f flacParser) Artist() string {
	return f.tags[tagArtist]
}

// BitDepth returns the bits-per-sample of this stream
func (f flacParser) BitDepth() int {
	return int(f.properties.BitsPerSample)
}

// Bitrate calculates the audio bitrate for this stream
func (f flacParser) Bitrate() int {
	// Check for zero duration or end position, to prevent a division-by-zero panic
	seconds := f.Duration().Seconds()
	if f.endPos == 0 || seconds == 0 {
		return 0
	}

	return int(((f.endPos * 8) / int64(f.Duration().Seconds())) / 1024)
}

// Channels returns the number of channels for this stream
func (f flacParser) Channels() int {
	return int(f.properties.ChannelCount)
}

// Checksum returns the checksum for this stream
func (f flacParser) Checksum() string {
	return f.properties.MD5Checksum
}

// Comment returns the Comment tag for this stream
func (f flacParser) Comment() string {
	return f.tags[tagComment]
}

// Date returns the Date tag for this stream
func (f flacParser) Date() string {
	return f.tags[tagDate]
}

// DiscNumber returns the DiscNumber tag for this stream
func (f flacParser) DiscNumber() int {
	disc, err := strconv.Atoi(f.tags[tagDiscNumber])
	if err != nil {
		return 0
	}

	return disc
}

// Duration returns the time duration for this stream
func (f flacParser) Duration() time.Duration {
	return time.Duration(int64(f.properties.SampleCount)/int64(f.SampleRate())) * time.Second
}

// Encoder returns the encoder for this stream
func (f flacParser) Encoder() string {
	return f.encoder
}

// Format returns the name of the FLAC format
func (f flacParser) Format() string {
	return "FLAC"
}

// Genre returns the Genre tag for this stream
func (f flacParser) Genre() string {
	return f.tags[tagGenre]
}

// SampleRate returns the sample rate in Hertz for this stream
func (f flacParser) SampleRate() int {
	return int(f.properties.SampleRate)
}

// Tag attempts to return the raw, unprocessed tag with the specified name for this stream
func (f flacParser) Tag(name string) string {
	return f.tags[strings.ToUpper(name)]
}

// Title returns the Title tag for this stream
func (f flacParser) Title() string {
	return f.tags[tagTitle]
}

// TrackNumber returns the TrackNumber tag for this stream
func (f flacParser) TrackNumber() int {
	track, err := strconv.Atoi(f.tags[tagTrackNumber])
	if err != nil {
		return 0
	}

	return track
}

// newFLACParser creates a parser for FLAC audio streams
func newFLACParser(reader io.ReadSeeker) (*flacParser, error) {
	// Create FLAC parser
	parser := &flacParser{
		buffer: make([]byte, 2048),
		reader: reader,
	}

	// Begin parsing properties
	if err := parser.parseProperties(); err != nil {
		return nil, err
	}

	// Seek through the file and attempt to parse tags
	if err := parser.parseTags(); err != nil {
		return nil, err
	}

	// Seek to end of file to grab the final position, used to calculate bitrate
	n, err := parser.reader.Seek(0, 2)
	if err != nil {
		return nil, err
	}
	parser.endPos = n

	// Return parser
	return parser, nil
}

// flacMetadataHeader represents the header for a FLAC metadata block
type flacMetadataHeader struct {
	LastBlock   bool
	BlockType   uint8
	BlockLength uint32
}

// flacStreamInfoBlock represents the metadata from a FLAC STREAMINFO block
type flacStreamInfoBlock struct {
	SampleRate    uint16
	ChannelCount  uint8
	BitsPerSample uint16
	SampleCount   uint64
	MD5Checksum   string
}

// parseMetadataHeader retrieves metadata header information from a FLAC stream
func (f *flacParser) parseMetadataHeader() (*flacMetadataHeader, error) {
	// Create and use a bit reader to parse the following fields:
	//    1 - Last metadata block before audio (boolean)
	//    7 - Metadata block type (should be 0, for streaminfo)
	//   24 - Length of following metadata (in bytes)
	fields, err := bit.NewReader(f.reader).ReadFields(1, 7, 24)
	if err != nil {
		return nil, err
	}

	// Generate metadata header
	return &flacMetadataHeader{
		LastBlock:   fields[0] == 1,
		BlockType:   uint8(fields[1]),
		BlockLength: uint32(fields[2]),
	}, nil
}

// parseTags retrieves metadata tags from a FLAC VORBISCOMMENT block
func (f *flacParser) parseTags() error {
	// Continuously parse and seek through blocks until we discover the VORBISCOMMENT block
	for {
		header, err := f.parseMetadataHeader()
		if err != nil {
			return err
		}

		// Check for VORBISCOMMENT block, break so we can begin parsing tags
		if header.BlockType == flacVorbisComment {
			break
		}

		// If last block and no VORBISCOMMENT block found, no tags
		if header.LastBlock {
			return nil
		}

		// If nothing found and not last block, seek forward in stream
		if _, err := f.reader.Seek(int64(header.BlockLength), 1); err != nil {
			return err
		}
	}

	// Parse length fields
	var length uint32

	// Read vendor string length
	if err := binary.Read(f.reader, binary.LittleEndian, &length); err != nil {
		return err
	}

	// Read vendor string
	if _, err := f.reader.Read(f.buffer[:length]); err != nil {
		return err
	}
	f.encoder = string(f.buffer[:length])

	// Read comment length (new allocation so we can use it as loop counter)
	var commentLength uint32
	if err := binary.Read(f.reader, binary.LittleEndian, &commentLength); err != nil {
		return err
	}

	// Begin iterating tags, and building tag map
	tagMap := map[string]string{}
	for i := 0; i < int(commentLength); i++ {
		// Read tag string length
		if err := binary.Read(f.reader, binary.LittleEndian, &length); err != nil {
			return err
		}

		// Read tag string
		n, err := f.reader.Read(f.buffer[:length])
		if err != nil {
			return err
		}

		// Split tag name and data, store in map
		pair := strings.Split(string(f.buffer[:n]), "=")
		tagMap[strings.ToUpper(pair[0])] = pair[1]
	}

	// Store tags
	f.tags = tagMap
	return nil
}

// parseProperties retrieves stream properties from a FLAC STREAMINFO block
func (f *flacParser) parseProperties() error {
	// Read the metadata header for STREAMINFO block
	header, err := f.parseMetadataHeader()
	if err != nil {
		return err
	}

	// Ensure that the metadata block type is STREAMINFO
	if header.BlockType != flacStreamInfo {
		return TagError{
			Err:     errInvalidStream,
			Format:  f.Format(),
			Details: "first metadata block is not type STREAMINFO",
		}
	}

	// Ensure that STREAMINFO is not the last block
	if header.LastBlock {
		return TagError{
			Err:     errInvalidStream,
			Format:  f.Format(),
			Details: "STREAMINFO block is marked as last metadata block in stream",
		}
	}

	// Seek forward past frame information, to sample rate
	if _, err := f.reader.Seek(10, 1); err != nil {
		return err
	}

	// Create and use a bit reader to parse the following fields:
	//   20 - Sample rate
	//    3 - Channel count (+1)
	//    5 - Bits per sample (+1)
	//   36 - Sample count
	fields, err := bit.NewReader(f.reader).ReadFields(20, 3, 5, 36)
	if err != nil {
		return err
	}

	// Read the MD5 checksum of the stream
	if _, err := f.reader.Read(f.buffer[:16]); err != nil {
		return err
	}

	// Store properties
	f.properties = &flacStreamInfoBlock{
		SampleRate:    uint16(fields[0]),
		ChannelCount:  uint8(fields[1]) + 1,
		BitsPerSample: uint16(fields[2]) + 1,
		SampleCount:   uint64(fields[3]),
		MD5Checksum:   fmt.Sprintf("%x", f.buffer[:16]),
	}

	return nil
}
<|endoftext|>


<commit_before>package buffer

import (
	"errors"
	"regexp"
	"strings"
	"sync"

	"github.com/wx13/sith/file/cursor"
)

type Buffer struct {
	lines []Line
	mutex *sync.Mutex
}

func MakeBuffer(stringBuf []string) Buffer {
	lines := make([]Line, len(stringBuf))
	for row, str := range stringBuf {
		lines[row] = MakeLine(str)
	}
	return Buffer{
		lines: lines,
		mutex: &sync.Mutex{},
	}
}

func (buffer *Buffer) Lines() []Line {
	lines := buffer.DeepDup().lines
	return lines
}

// Dup creates a new buffer with the same lines.
func (buffer *Buffer) Dup() Buffer {
	buffer.mutex.Lock()
	linesCopy := make([]Line, len(buffer.lines))
	for row, line := range buffer.lines {
		linesCopy[row] = line
	}
	buffer.mutex.Unlock()
	return Buffer{
		lines: linesCopy,
		mutex: &sync.Mutex{},
	}
}

// DeepDup creates a new buffer with copies of the lines.
func (buffer *Buffer) DeepDup() Buffer {
	buffer.mutex.Lock()
	linesCopy := make([]Line, len(buffer.lines))
	for row, line := range buffer.lines {
		linesCopy[row] = line.Dup()
	}
	buffer.mutex.Unlock()
	return Buffer{
		lines: linesCopy,
		mutex: &sync.Mutex{},
	}
}

// Length returns the number of lines in the buffer.
func (buffer *Buffer) Length() int {
	if buffer.mutex == nil {
		return 0
	}
	buffer.mutex.Lock()
	n := len(buffer.lines)
	buffer.mutex.Unlock()
	return n
}

// ReplaceBuffer replaces the content (lines) with the
// content from another buffer.
func (buffer *Buffer) ReplaceBuffer(newBuffer Buffer) {

	newLen := newBuffer.Length()
	bufLen := buffer.Length()

	if newLen <= bufLen {
		buffer.mutex.Lock()
		buffer.lines = buffer.lines[:newLen]
		buffer.mutex.Unlock()
	}

	for k, line := range newBuffer.Lines() {
		if k >= bufLen {
			buffer.Append(line)
		} else {
			if buffer.GetRow(k).ToString() != line.ToString() {
				buffer.ReplaceLine(line, k)
			}
		}
	}

}

func (buffer *Buffer) Append(line ...Line) {
	buffer.mutex.Lock()
	buffer.lines = append(buffer.lines, line...)
	buffer.mutex.Unlock()
}

// MakeSplitBuffer creates a buffer from a long string by splitting
// the string at a certain length.
func MakeSplitBuffer(bigString string, lineLen int) Buffer {
	words := strings.Fields(bigString)
	lines := []Line{}
	lineStr := words[0]
	for _, word := range words[1:] {
		if lineLen > 0 && len(lineStr)+len(word) > lineLen {
			lines = append(lines, MakeLine(lineStr))
			lineStr = word
		} else {
			lineStr += " " + word
		}
	}
	lines = append(lines, MakeLine(lineStr))
	return Buffer{
		lines: lines,
		mutex: &sync.Mutex{},
	}
}

func (buffer *Buffer) InclSlice(row1, row2 int) *Buffer {
	if row2 >= buffer.Length() {
		row2 = buffer.Length() - 1
	}
	if row2 < 0 {
		row2 += buffer.Length()
	}
	buffer.mutex.Lock()
	lines := buffer.lines[row1 : row2+1]
	buffer.mutex.Unlock()
	return &Buffer{lines: lines, mutex: &sync.Mutex{}}
}

func (buffer *Buffer) RowSlice(row, startCol, endCol int) Line {
	buffer.mutex.Lock()
	line := buffer.lines[row].Slice(startCol, endCol)
	buffer.mutex.Unlock()
	return line
}

func (buffer *Buffer) StrSlab(row1, row2, col1, col2 int) []string {
	lines := buffer.Lines()[row1:row2]
	strs := make([]string, len(lines))
	for idx, line := range lines {
		strs[idx] = line.StrSlice(col1, col2)
	}
	return strs
}

// ToString concatenates the buffer into one long string.
func (buffer *Buffer) ToString(newline string) string {
	str := ""
	for _, line := range buffer.Lines() {
		str += line.ToString() + newline
	}
	return str[:len(str)-1]
}

func (buffer *Buffer) InsertAfter(row int, lines ...Line) {
	buffer.mutex.Lock()
	buffer.lines = append(buffer.lines[:row+1], append(lines, buffer.lines[row+1:]...)...)
	buffer.mutex.Unlock()
}

func (buffer *Buffer) DeleteRow(row int) {
	buffer.mutex.Lock()
	defer buffer.mutex.Unlock()
	if len(buffer.lines) == 1 {
		buffer.lines = []Line{MakeLine("")}
	} else if row == 0 {
		buffer.lines = buffer.lines[1:]
	} else if row < len(buffer.lines)-1 {
		buffer.lines = append(buffer.lines[:row], buffer.lines[row+1:]...)
	} else {
		buffer.lines = buffer.lines[:row]
	}
}

func (buffer *Buffer) ReplaceLine(line Line, row int) {
	buffer.mutex.Lock()
	buffer.lines[row] = line
	buffer.mutex.Unlock()
}

// ReplaceLines replaces the lines from minRow to maxRow with lines.
func (buffer *Buffer) ReplaceLines(lines []Line, minRow, maxRow int) {
	buffer.mutex.Lock()
	buffer.lines = append(buffer.lines[:minRow], append(lines, buffer.lines[maxRow+1:]...)...)
	buffer.mutex.Unlock()
}

// Search searches for a string within the buffer.
func (buffer *Buffer) Search(searchTerm string, cursor cursor.Cursor, loop bool) (int, int, error) {
	var col int
	col, _ = buffer.GetRow(cursor.Row()).Search(searchTerm, cursor.Col()+1, -1)
	if col >= 0 {
		return cursor.Row(), col, nil
	}
	for row := cursor.Row() + 1; row < buffer.Length(); row++ {
		col, _ = buffer.GetRow(row).Search(searchTerm, 0, -1)
		if col >= 0 {
			return row, col, nil
		}
	}
	if !loop {
		return cursor.Row(), cursor.Col(), errors.New("Not Found")
	}
	for row := 0; row < cursor.Row(); row++ {
		col, _ = buffer.GetRow(row).Search(searchTerm, 0, -1)
		if col >= 0 {
			return row, col, nil
		}
	}
	col, _ = buffer.GetRow(cursor.Row()).Search(searchTerm, 0, col)
	if col >= 0 {
		return cursor.Row(), col, nil
	}
	return cursor.Row(), cursor.Col(), errors.New("Not Found")
}

// Replace replaces occurrences of a string within a line.
func (buffer *Buffer) ReplaceWord(searchTerm, replaceTerm string, row, col int) {
	startCol, endCol := buffer.GetRow(row).Search(searchTerm, col, -1)
	strLine := buffer.GetRow(row).ToString()
	newStrLine := strLine[:startCol] + replaceTerm + strLine[endCol:]
	buffer.lines[row] = MakeLine(newStrLine)
}

func (buffer *Buffer) GetRow(row int) Line {
	buffer.mutex.Lock()
	line := buffer.lines[row]
	buffer.mutex.Unlock()
	return line
}

func (buffer *Buffer) SetRow(row int, line Line) error {
	if row >= buffer.Length() {
		return errors.New("index exceeds buffer length")
	}
	buffer.mutex.Lock()
	buffer.lines[row] = line
	buffer.mutex.Unlock()
	return nil
}

func (buffer *Buffer) RowLength(row int) int {
	return buffer.GetRow(row).Length()
}

func (buffer *Buffer) GetIndent() (string, bool) {
	spaceHisto := buffer.countLeadingSpacesAndTabs()
	tabCount := spaceHisto[0]
	nSpaces, spaceCount := buffer.scoreIndents(spaceHisto)
	clean := true
	if tabCount > 0 && spaceCount > 0 {
		clean = false
	}
	if tabCount >= spaceCount {
		return "\t", clean
	} else {
		return strings.Repeat(" ", nSpaces), clean
	}
}

func (buffer *Buffer) countLeadingSpacesAndTabs() []int {
	spaceHisto := make([]int, 33)
	re := regexp.MustCompile("^[ \t]*")
	for _, line := range buffer.Lines() {
		indentStr := re.FindString(line.ToString())
		nSpaces := strings.Count(indentStr, " ")
		nTabs := strings.Count(indentStr, "\t")
		if nSpaces > 0 && nSpaces <= 32 {
			spaceHisto[nSpaces]++
		}
		if nTabs > 0 {
			spaceHisto[0]++
		}
	}
	return spaceHisto
}

func (buffer *Buffer) scoreIndents(spaceHisto []int) (int, int) {
	count := 0
	nSpaces := 0
	for indentSize := 2; indentSize < 9; indentSize++ {
		score := 0
		for n := 1; n <= 4; n++ {
			score += spaceHisto[n*indentSize]
		}
		if score > count && spaceHisto[indentSize] > 0 {
			nSpaces = indentSize
			count = score
		}
	}
	return nSpaces, count
}

func (buffer *Buffer) Equals(buffer2 *Buffer) bool {
	if buffer.Length() != buffer2.Length() {
		return false
	}

	buffer.mutex.Lock()
	defer buffer.mutex.Unlock()
	buffer2.mutex.Lock()
	defer buffer2.mutex.Unlock()

	for idx := range buffer.lines {
		if buffer.lines[idx].ToString() != buffer2.lines[idx].ToString() {
			return false
		}
	}
	return true
}

func (buffer *Buffer) CompressPriorSpaces(row, col int) int {
	line := buffer.GetRow(row)
	line, col = line.CompressPriorSpaces(col)
	buffer.SetRow(row, line)
	return col
}

// BracketMatch looks for matching partner rune in a set of lines.
//
//   row, col         where to start the search from
//   end_row          last row to search
func (buffer *Buffer) BracketMatch(row, col, end_row int) (int, int, error) {

	// Get the rune under the cursor.
	current_line := buffer.GetRow(row)
	start := current_line.GetChar(col)

	// Get the partner rune:
	formap := map[rune]rune{'(': ')', '[': ']', '{': '}', '<': '>'}
	backmap := map[rune]rune{}
	for k, v := range formap {
		backmap[v] = k
	}
	dir := 1
	end, ok := formap[start]
	if !ok {
		end, ok = backmap[start]
		if !ok {
			return row, col, errors.New("not a bracket character")
		}
		dir = -1
	}

	// Start at one level, and move off the current char.
	count := 1
	col += dir
	if col < 0 {
		row += dir
	}
	for ; row >= 0 && row != end_row+dir && row < buffer.Length(); row += dir {
		line := buffer.GetRow(row)
		col, count = line.BracketMatch(start, end, col, dir, count)
		if count == 0 {
			return row, col, nil
		}
		// Reset column.
		col = (dir - 1) / 2
	}

	return row, col, errors.New("could not find bracket match")
}
<commit_msg>bugfix: check array bounds on GetRow()<commit_after>package buffer

import (
	"errors"
	"regexp"
	"strings"
	"sync"

	"github.com/wx13/sith/file/cursor"
)

type Buffer struct {
	lines []Line
	mutex *sync.Mutex
}

func MakeBuffer(stringBuf []string) Buffer {
	lines := make([]Line, len(stringBuf))
	for row, str := range stringBuf {
		lines[row] = MakeLine(str)
	}
	return Buffer{
		lines: lines,
		mutex: &sync.Mutex{},
	}
}

func (buffer *Buffer) Lines() []Line {
	lines := buffer.DeepDup().lines
	return lines
}

// Dup creates a new buffer with the same lines.
func (buffer *Buffer) Dup() Buffer {
	buffer.mutex.Lock()
	linesCopy := make([]Line, len(buffer.lines))
	for row, line := range buffer.lines {
		linesCopy[row] = line
	}
	buffer.mutex.Unlock()
	return Buffer{
		lines: linesCopy,
		mutex: &sync.Mutex{},
	}
}

// DeepDup creates a new buffer with copies of the lines.
func (buffer *Buffer) DeepDup() Buffer {
	buffer.mutex.Lock()
	linesCopy := make([]Line, len(buffer.lines))
	for row, line := range buffer.lines {
		linesCopy[row] = line.Dup()
	}
	buffer.mutex.Unlock()
	return Buffer{
		lines: linesCopy,
		mutex: &sync.Mutex{},
	}
}

// Length returns the number of lines in the buffer.
func (buffer *Buffer) Length() int {
	if buffer.mutex == nil {
		return 0
	}
	buffer.mutex.Lock()
	n := len(buffer.lines)
	buffer.mutex.Unlock()
	return n
}

// ReplaceBuffer replaces the content (lines) with the
// content from another buffer.
func (buffer *Buffer) ReplaceBuffer(newBuffer Buffer) {

	newLen := newBuffer.Length()
	bufLen := buffer.Length()

	if newLen <= bufLen {
		buffer.mutex.Lock()
		buffer.lines = buffer.lines[:newLen]
		buffer.mutex.Unlock()
	}

	for k, line := range newBuffer.Lines() {
		if k >= bufLen {
			buffer.Append(line)
		} else {
			if buffer.GetRow(k).ToString() != line.ToString() {
				buffer.ReplaceLine(line, k)
			}
		}
	}

}

func (buffer *Buffer) Append(line ...Line) {
	buffer.mutex.Lock()
	buffer.lines = append(buffer.lines, line...)
	buffer.mutex.Unlock()
}

// MakeSplitBuffer creates a buffer from a long string by splitting
// the string at a certain length.
func MakeSplitBuffer(bigString string, lineLen int) Buffer {
	words := strings.Fields(bigString)
	lines := []Line{}
	lineStr := words[0]
	for _, word := range words[1:] {
		if lineLen > 0 && len(lineStr)+len(word) > lineLen {
			lines = append(lines, MakeLine(lineStr))
			lineStr = word
		} else {
			lineStr += " " + word
		}
	}
	lines = append(lines, MakeLine(lineStr))
	return Buffer{
		lines: lines,
		mutex: &sync.Mutex{},
	}
}

func (buffer *Buffer) InclSlice(row1, row2 int) *Buffer {
	if row2 >= buffer.Length() {
		row2 = buffer.Length() - 1
	}
	if row2 < 0 {
		row2 += buffer.Length()
	}
	buffer.mutex.Lock()
	lines := buffer.lines[row1 : row2+1]
	buffer.mutex.Unlock()
	return &Buffer{lines: lines, mutex: &sync.Mutex{}}
}

func (buffer *Buffer) RowSlice(row, startCol, endCol int) Line {
	buffer.mutex.Lock()
	line := buffer.lines[row].Slice(startCol, endCol)
	buffer.mutex.Unlock()
	return line
}

func (buffer *Buffer) StrSlab(row1, row2, col1, col2 int) []string {
	lines := buffer.Lines()[row1:row2]
	strs := make([]string, len(lines))
	for idx, line := range lines {
		strs[idx] = line.StrSlice(col1, col2)
	}
	return strs
}

// ToString concatenates the buffer into one long string.
func (buffer *Buffer) ToString(newline string) string {
	str := ""
	for _, line := range buffer.Lines() {
		str += line.ToString() + newline
	}
	return str[:len(str)-1]
}

func (buffer *Buffer) InsertAfter(row int, lines ...Line) {
	buffer.mutex.Lock()
	buffer.lines = append(buffer.lines[:row+1], append(lines, buffer.lines[row+1:]...)...)
	buffer.mutex.Unlock()
}

func (buffer *Buffer) DeleteRow(row int) {
	buffer.mutex.Lock()
	defer buffer.mutex.Unlock()
	if len(buffer.lines) == 1 {
		buffer.lines = []Line{MakeLine("")}
	} else if row == 0 {
		buffer.lines = buffer.lines[1:]
	} else if row < len(buffer.lines)-1 {
		buffer.lines = append(buffer.lines[:row], buffer.lines[row+1:]...)
	} else {
		buffer.lines = buffer.lines[:row]
	}
}

func (buffer *Buffer) ReplaceLine(line Line, row int) {
	buffer.mutex.Lock()
	buffer.lines[row] = line
	buffer.mutex.Unlock()
}

// ReplaceLines replaces the lines from minRow to maxRow with lines.
func (buffer *Buffer) ReplaceLines(lines []Line, minRow, maxRow int) {
	buffer.mutex.Lock()
	buffer.lines = append(buffer.lines[:minRow], append(lines, buffer.lines[maxRow+1:]...)...)
	buffer.mutex.Unlock()
}

// Search searches for a string within the buffer.
func (buffer *Buffer) Search(searchTerm string, cursor cursor.Cursor, loop bool) (int, int, error) {
	var col int
	col, _ = buffer.GetRow(cursor.Row()).Search(searchTerm, cursor.Col()+1, -1)
	if col >= 0 {
		return cursor.Row(), col, nil
	}
	for row := cursor.Row() + 1; row < buffer.Length(); row++ {
		col, _ = buffer.GetRow(row).Search(searchTerm, 0, -1)
		if col >= 0 {
			return row, col, nil
		}
	}
	if !loop {
		return cursor.Row(), cursor.Col(), errors.New("Not Found")
	}
	for row := 0; row < cursor.Row(); row++ {
		col, _ = buffer.GetRow(row).Search(searchTerm, 0, -1)
		if col >= 0 {
			return row, col, nil
		}
	}
	col, _ = buffer.GetRow(cursor.Row()).Search(searchTerm, 0, col)
	if col >= 0 {
		return cursor.Row(), col, nil
	}
	return cursor.Row(), cursor.Col(), errors.New("Not Found")
}

// Replace replaces occurrences of a string within a line.
func (buffer *Buffer) ReplaceWord(searchTerm, replaceTerm string, row, col int) {
	startCol, endCol := buffer.GetRow(row).Search(searchTerm, col, -1)
	strLine := buffer.GetRow(row).ToString()
	newStrLine := strLine[:startCol] + replaceTerm + strLine[endCol:]
	buffer.lines[row] = MakeLine(newStrLine)
}

func (buffer *Buffer) GetRow(row int) Line {
	buffer.mutex.Lock()
	if row >= len(buffer.lines) {
		row = len(buffer.lines) - 1
	}
	line := buffer.lines[row]
	buffer.mutex.Unlock()
	return line
}

func (buffer *Buffer) SetRow(row int, line Line) error {
	if row >= buffer.Length() {
		return errors.New("index exceeds buffer length")
	}
	buffer.mutex.Lock()
	buffer.lines[row] = line
	buffer.mutex.Unlock()
	return nil
}

func (buffer *Buffer) RowLength(row int) int {
	return buffer.GetRow(row).Length()
}

func (buffer *Buffer) GetIndent() (string, bool) {
	spaceHisto := buffer.countLeadingSpacesAndTabs()
	tabCount := spaceHisto[0]
	nSpaces, spaceCount := buffer.scoreIndents(spaceHisto)
	clean := true
	if tabCount > 0 && spaceCount > 0 {
		clean = false
	}
	if tabCount >= spaceCount {
		return "\t", clean
	} else {
		return strings.Repeat(" ", nSpaces), clean
	}
}

func (buffer *Buffer) countLeadingSpacesAndTabs() []int {
	spaceHisto := make([]int, 33)
	re := regexp.MustCompile("^[ \t]*")
	for _, line := range buffer.Lines() {
		indentStr := re.FindString(line.ToString())
		nSpaces := strings.Count(indentStr, " ")
		nTabs := strings.Count(indentStr, "\t")
		if nSpaces > 0 && nSpaces <= 32 {
			spaceHisto[nSpaces]++
		}
		if nTabs > 0 {
			spaceHisto[0]++
		}
	}
	return spaceHisto
}

func (buffer *Buffer) scoreIndents(spaceHisto []int) (int, int) {
	count := 0
	nSpaces := 0
	for indentSize := 2; indentSize < 9; indentSize++ {
		score := 0
		for n := 1; n <= 4; n++ {
			score += spaceHisto[n*indentSize]
		}
		if score > count && spaceHisto[indentSize] > 0 {
			nSpaces = indentSize
			count = score
		}
	}
	return nSpaces, count
}

func (buffer *Buffer) Equals(buffer2 *Buffer) bool {
	if buffer.Length() != buffer2.Length() {
		return false
	}

	buffer.mutex.Lock()
	defer buffer.mutex.Unlock()
	buffer2.mutex.Lock()
	defer buffer2.mutex.Unlock()

	for idx := range buffer.lines {
		if buffer.lines[idx].ToString() != buffer2.lines[idx].ToString() {
			return false
		}
	}
	return true
}

func (buffer *Buffer) CompressPriorSpaces(row, col int) int {
	line := buffer.GetRow(row)
	line, col = line.CompressPriorSpaces(col)
	buffer.SetRow(row, line)
	return col
}

// BracketMatch looks for matching partner rune in a set of lines.
//
//   row, col         where to start the search from
//   end_row          last row to search
func (buffer *Buffer) BracketMatch(row, col, end_row int) (int, int, error) {

	// Get the rune under the cursor.
	current_line := buffer.GetRow(row)
	start := current_line.GetChar(col)

	// Get the partner rune:
	formap := map[rune]rune{'(': ')', '[': ']', '{': '}', '<': '>'}
	backmap := map[rune]rune{}
	for k, v := range formap {
		backmap[v] = k
	}
	dir := 1
	end, ok := formap[start]
	if !ok {
		end, ok = backmap[start]
		if !ok {
			return row, col, errors.New("not a bracket character")
		}
		dir = -1
	}

	// Start at one level, and move off the current char.
	count := 1
	col += dir
	if col < 0 {
		row += dir
	}
	for ; row >= 0 && row != end_row+dir && row < buffer.Length(); row += dir {
		line := buffer.GetRow(row)
		col, count = line.BracketMatch(start, end, col, dir, count)
		if count == 0 {
			return row, col, nil
		}
		// Reset column.
		col = (dir - 1) / 2
	}

	return row, col, errors.New("could not find bracket match")
}
<|endoftext|>