<commit_before>//
// store.go
//

package srnd

import (
  "github.com/majestrate/srndv2/src/nacl"
  "bufio"
  "bytes"
  "crypto/sha512"
  "errors"
  "io"
  "log"
  "mime"
  "mime/multipart"
  "net/mail"
  "os"
  "os/exec"
  "path/filepath"
  "strings"
)

type ArticleStore interface {
  MessageReader
  MessageWriter

  // get the filepath for an attachment
  AttachmentFilepath(fname string) string
  // get the filepath for an attachment's thumbnail
  ThumbnailFilepath(fname string) string
  // do we have this article?
  HasArticle(msgid string) bool
  // create a file for a message
  CreateFile(msgid string) io.WriteCloser
  // create a file for a temp message, returns nil if it's already open
  CreateTempFile(msgid string) io.WriteCloser
  // get the filename of a message
  GetFilename(msgid string) string
  // get the filename of a temp message
  GetTempFilename(msgid string) string
  // Get a message given its messageid
  GetMessage(msgid string) NNTPMessage
  // get a temp message given its messageid
  // temp message is deleted once read
  ReadTempMessage(msgid string) NNTPMessage
  // store a post
  StorePost(nntp NNTPMessage) error
  // get article headers only
  GetHeaders(msgid string) ArticleHeaders
  // get our temp directory for articles
  TempDir() string
  // get a list of all the attachments we have
  GetAllAttachments() ([]string, error)
  // generate a thumbnail
  GenerateThumbnail(fname string) error
}
type articleStore struct {
  directory string
  temp string
  attachments string
  thumbs string
  database Database
  convert_path string
  ffmpeg_path string
  sox_path string
}

func createArticleStore(config map[string]string, database Database) ArticleStore {
  store := articleStore{
    directory: config["store_dir"],
    temp: config["incoming_dir"],
    attachments: config["attachments_dir"],
    thumbs: config["thumbs_dir"],
    convert_path: config["convert_bin"],
    ffmpeg_path: config["ffmpegthumbnailer_bin"],
    sox_path: config["sox_bin"],
    database: database,
  }
  store.Init()
  return store
}

func (self articleStore) TempDir() string {
  return self.temp
}

// initialize article store
func (self articleStore) Init() {
  EnsureDir(self.directory)
  EnsureDir(self.temp)
  EnsureDir(self.attachments)
  EnsureDir(self.thumbs)
  if ! CheckFile(self.convert_path) {
    log.Fatal("cannot find executable for convert: ", self.convert_path, " not found")
  }
  if ! CheckFile(self.ffmpeg_path) {
    log.Fatal("connt find executable for ffmpegthumbnailer: ", self.ffmpeg_path, " not found")
  }
  if ! CheckFile(self.sox_path) {
    log.Fatal("connt find executable for sox: ", self.sox_path, " not found")
  }
}

func (self articleStore) isAudio(fname string) bool {
  for _, ext := range []string{".mp3", ".ogg", ".oga", ".opus", ".flac"} {
    if strings.HasPrefix(fname, ext) {
      return true
    }
  }
  return false
}

func (self articleStore) GenerateThumbnail(fname string) error {
  outfname := self.ThumbnailFilepath(fname)
  infname := self.AttachmentFilepath(fname)
  var cmd *exec.Cmd
  if strings.HasPrefix(strings.ToLower(fname), ".gif") {
    cmd = exec.Command(self.convert_path, "-thumbnail", "200", infname, outfname)
  } else if self.isAudio(fname) {
    cmd = exec.Command(self.sox_path, infname, "-n", "spectrogram", "-a", "-d", "0:30", "-r", "-p", "6", "-x", "200", "-y", "150", "-o", outfname)
  } else {
    cmd = exec.Command(self.ffmpeg_path, "-i", infname, "-o", outfname, "-s", "200")
  }
  exec_out, err := cmd.CombinedOutput()
  if err != nil {
    log.Println("error generating thumbnail", string(exec_out))
  }
  return err
}

func (self articleStore) GetAllAttachments() (names []string, err error) {
  var f *os.File
  f, err = os.Open(self.attachments)
  if err == nil {
    names, err = f.Readdirnames(0)
  }
  return
}

func (self articleStore) ReadMessage(r io.Reader) (NNTPMessage, error) {
  return read_message(r)
}

func (self articleStore) StorePost(nntp NNTPMessage) (err error) {

  f := self.CreateFile(nntp.MessageID())
  if f != nil {
    err = self.WriteMessage(nntp, f)
    f.Close()
  }

  nntp_inner := nntp.Signed()
  if nntp_inner == nil {
    // no inner article
    // store the data in the article
    self.database.RegisterArticle(nntp)
    for _, att := range nntp.Attachments() {
      // save attachments
      go self.saveAttachment(att)
    }
  } else {
    // record a tripcode
    self.database.RegisterSigned(nntp.MessageID(), nntp.Pubkey())
    // we have inner data
    // store the signed data
    self.database.RegisterArticle(nntp_inner)
    for _, att := range nntp_inner.Attachments() {
      go self.saveAttachment(att)
    }
  }
  return
}

// save an attachment
func (self articleStore) saveAttachment(att NNTPAttachment) {
  var err error
  var f io.WriteCloser
  fpath := att.Filepath()
  upload := self.AttachmentFilepath(fpath)
  thumb := self.ThumbnailFilepath(fpath)
  if CheckFile(upload) {
    log.Println("already have file", fpath)
    if ! CheckFile(thumb) && att.NeedsThumbnail() {
      log.Println("create thumbnail for", fpath)
      err = self.GenerateThumbnail(fpath)
      if err != nil {
        log.Println("failed to generate thumbnail", err)
      }
    }
    return
  }
  // save attachment
  log.Println("save attachment", att.Filename(), "to", upload)
  f, err = os.Create(upload)
  if err == nil {
    err = att.WriteTo(f)
    f.Close()
  }
  if err != nil {
    log.Println("did not save attachment", err)
    return
  }

  // generate thumbanils
  if att.NeedsThumbnail() {
    log.Println("create thumbnail for", fpath)
    err = self.GenerateThumbnail(fpath)
    if err != nil {
      log.Println("failed to generate thumbnail", err)
    }
  }
}

// eh this isn't really needed is it?
func (self articleStore) WriteMessage(nntp NNTPMessage, wr io.Writer) (err error) {
  return nntp.WriteTo(wr, "\n")
}

// get the filepath for an attachment
func (self articleStore) AttachmentFilepath(fname string) string {
  return filepath.Join(self.attachments, fname)
}

// get the filepath for a thumbanil
func (self articleStore) ThumbnailFilepath(fname string) string {
  // all thumbnails are jpegs now
  return filepath.Join(self.thumbs, fname + ".jpg")
}

// create a file for this article
func (self articleStore) CreateFile(messageID string) io.WriteCloser {
  fname := self.GetFilename(messageID)
  file, err := os.Create(fname)
  if err != nil {
    log.Println("cannot open file", fname)
    return nil
  }
  return file
}

// create a temp file for inboud articles
func (self articleStore) CreateTempFile(messageID string) io.WriteCloser {
  fname := self.GetTempFilename(messageID)
  if CheckFile(fname) {
    log.Println(fname, "already open")
    return nil
  }
  file, err := os.Create(fname)
  if err != nil {
    log.Println("cannot open file", fname)
    return nil
  }
  return file
}

// return true if we have an article
func (self articleStore) HasArticle(messageID string) bool {
  return CheckFile(self.GetFilename(messageID))
}

// get the filename for this article
func (self articleStore) GetFilename(messageID string) string {
  if ! ValidMessageID(messageID) {
    log.Println("!!! bug: tried to open invalid message", messageID, "!!!")
    return ""
  }
  return filepath.Join(self.directory, messageID)
}

// get the filename for this article
func (self articleStore) GetTempFilename(messageID string) string {
  if ! ValidMessageID(messageID) {
    log.Println("!!! bug: tried to open invalid temp message", messageID, "!!!")
    return ""
  }
  return filepath.Join(self.temp, messageID)
}

// loads temp message and deletes old article
func (self articleStore) ReadTempMessage(messageID string) NNTPMessage {
  fname := self.GetTempFilename(messageID)
  nntp := self.readfile(fname)
  DelFile(fname)
  return nntp
}

// read a file give filepath
func (self articleStore) readfile(fname string) NNTPMessage {

  file, err := os.Open(fname)
  if err != nil {
    log.Println("store cannot open file",fname)
    return nil
  }
  message, err := self.ReadMessage(file)
  file.Close()
  if err == nil {
    return message
  }

  log.Println("failed to load file", fname)
  return nil
}

// get the replies for a thread
func (self articleStore) GetThreadReplies(messageID string, last int) []NNTPMessage {
  var repls []NNTPMessage
  if self.database.ThreadHasReplies(messageID) {
    rpls := self.database.GetThreadReplies(messageID, last)
    if rpls == nil {
      return repls
    }
    for _, rpl := range rpls {
      msg := self.GetMessage(rpl)
      if msg == nil {
        log.Println("cannot get message", rpl)
      } else {
        repls = append(repls, msg)
      }
    }
  }
  return repls
}

// load an article
// return nil on failure
func (self articleStore) GetMessage(messageID string) NNTPMessage {
  return self.readfile(self.GetFilename(messageID))
}

// get article with headers only
func (self articleStore) GetHeaders(messageID string) ArticleHeaders {
  // TODO: don't load the entire body
  nntp := self.readfile(self.GetFilename(messageID))
  if nntp == nil {
    return nil
  }
  return nntp.Headers()
}

func read_message(r io.Reader) (NNTPMessage, error) {

  msg, err := mail.ReadMessage(r)
  var nntp nntpArticle

  if err == nil {
    nntp.headers = ArticleHeaders(msg.Header)
    content_type := nntp.ContentType()
    media_type, params, err := mime.ParseMediaType(content_type)
    if err != nil {
      log.Println("failed to parse media type", err, "for mime", content_type)
      return nil, err
    }
    boundary, ok := params["boundary"]
    if ok {
      partReader := multipart.NewReader(msg.Body, boundary)
      for {
        part, err := partReader.NextPart()
        if err == io.EOF {
          return nntp, nil
        } else if err == nil {
          hdr := part.Header
          // get content type of part
          part_type := hdr.Get("Content-Type")
          // parse content type
          media_type, _, err = mime.ParseMediaType(part_type)
          if err == nil {
            if media_type == "text/plain" {
              att := readAttachmentFromMimePart(part)
              if att != nil {
                nntp.message = att.(nntpAttachment)
                nntp.message.header.Set("Content-Type", part_type)
              }
            } else {
              // non plaintext gets added to attachments
              att := readAttachmentFromMimePart(part)
              if att != nil {
                nntp = nntp.Attach(att).(nntpArticle)
              }
            }
          } else {
            log.Println("part has no content type", err)
          }
          part.Close()
        } else {
          log.Println("failed to load part! ", err)
          return nil, err
        }
      }

    } else if media_type == "message/rfc822" {
      // tripcoded message
      sig := nntp.headers.Get("X-Signature-Ed25519-Sha512", "")
      pk := nntp.Pubkey()
      if pk == "" || sig == "" {
        log.Println("invalid sig or pubkey", sig, pk)
        return nil, errors.New("invalid headers")
      }
      log.Printf("got signed message from %s", pk)
      pk_bytes := unhex(pk)
      sig_bytes := unhex(sig)
      r := bufio.NewReader(msg.Body)
      crlf := []byte{13,10}
      for {
        line, err := r.ReadBytes('\n')
        if err == io.EOF {
          break
        }
        nntp.signedPart.body.Write(line[:len(line)-1])
        nntp.signedPart.body.Write(crlf)
      }
      if nntp.signedPart.body.Len() < 2 {
        log.Println("signed body is too small")
      } else {
        body := nntp.signedPart.body.Bytes()[:nntp.signedPart.body.Len()-2]
        body_hash := sha512.Sum512(body)
        log.Printf("hash=%s", hexify(body_hash[:]))
        if nacl.CryptoVerifyFucky(body_hash[:], sig_bytes, pk_bytes) {
          log.Println("signature is valid :^)")
          return nntp, nil
        } else {
          log.Println("!!!signature is invalid!!!")
        }
      }
    } else {
      // plaintext attachment
      var buff bytes.Buffer
      _, err = io.Copy(&buff, msg.Body)
      nntp.message = createPlaintextAttachment(buff.String())
      return nntp, err
    }
  } else {
    log.Println("failed to read message", err)
    return nil, err
  }
  return nntp, err
}

<commit_msg>fix thumbnailing<commit_after>//
// store.go
//

package srnd

import (
  "github.com/majestrate/srndv2/src/nacl"
  "bufio"
  "bytes"
  "crypto/sha512"
  "errors"
  "io"
  "log"
  "mime"
  "mime/multipart"
  "net/mail"
  "os"
  "os/exec"
  "path/filepath"
  "strings"
)

type ArticleStore interface {
  MessageReader
  MessageWriter

  // get the filepath for an attachment
  AttachmentFilepath(fname string) string
  // get the filepath for an attachment's thumbnail
  ThumbnailFilepath(fname string) string
  // do we have this article?
  HasArticle(msgid string) bool
  // create a file for a message
  CreateFile(msgid string) io.WriteCloser
  // create a file for a temp message, returns nil if it's already open
  CreateTempFile(msgid string) io.WriteCloser
  // get the filename of a message
  GetFilename(msgid string) string
  // get the filename of a temp message
  GetTempFilename(msgid string) string
  // Get a message given its messageid
  GetMessage(msgid string) NNTPMessage
  // get a temp message given its messageid
  // temp message is deleted once read
  ReadTempMessage(msgid string) NNTPMessage
  // store a post
  StorePost(nntp NNTPMessage) error
  // get article headers only
  GetHeaders(msgid string) ArticleHeaders
  // get our temp directory for articles
  TempDir() string
  // get a list of all the attachments we have
  GetAllAttachments() ([]string, error)
  // generate a thumbnail
  GenerateThumbnail(fname string) error
}
type articleStore struct {
  directory string
  temp string
  attachments string
  thumbs string
  database Database
  convert_path string
  ffmpeg_path string
  sox_path string
}

func createArticleStore(config map[string]string, database Database) ArticleStore {
  store := articleStore{
    directory: config["store_dir"],
    temp: config["incoming_dir"],
    attachments: config["attachments_dir"],
    thumbs: config["thumbs_dir"],
    convert_path: config["convert_bin"],
    ffmpeg_path: config["ffmpegthumbnailer_bin"],
    sox_path: config["sox_bin"],
    database: database,
  }
  store.Init()
  return store
}

func (self articleStore) TempDir() string {
  return self.temp
}

// initialize article store
func (self articleStore) Init() {
  EnsureDir(self.directory)
  EnsureDir(self.temp)
  EnsureDir(self.attachments)
  EnsureDir(self.thumbs)
  if ! CheckFile(self.convert_path) {
    log.Fatal("cannot find executable for convert: ", self.convert_path, " not found")
  }
  if ! CheckFile(self.ffmpeg_path) {
    log.Fatal("connt find executable for ffmpegthumbnailer: ", self.ffmpeg_path, " not found")
  }
  if ! CheckFile(self.sox_path) {
    log.Fatal("connt find executable for sox: ", self.sox_path, " not found")
  }
}

func (self articleStore) isAudio(fname string) bool {
  for _, ext := range []string{".mp3", ".ogg", ".oga", ".opus", ".flac"} {
    if strings.HasSuffix(strings.ToLower(fname), ext) {
      return true
    }
  }
  return false
}

func (self articleStore) GenerateThumbnail(fname string) error {
  outfname := self.ThumbnailFilepath(fname)
  infname := self.AttachmentFilepath(fname)
  var cmd *exec.Cmd
  if strings.HasSuffix(strings.ToLower(fname), ".gif") {
    cmd = exec.Command(self.convert_path, "-thumbnail", "200", infname, outfname)
  } else if self.isAudio(fname) {
    cmd = exec.Command(self.sox_path, infname, "-n", "spectrogram", "-a", "-d", "0:30", "-r", "-p", "6", "-x", "200", "-y", "150", "-o", outfname)
  } else {
    cmd = exec.Command(self.ffmpeg_path, "-i", infname, "-o", outfname, "-s", "200")
  }
  exec_out, err := cmd.CombinedOutput()
  if err != nil {
    log.Println("error generating thumbnail", string(exec_out))
  }
  return err
}

func (self articleStore) GetAllAttachments() (names []string, err error) {
  var f *os.File
  f, err = os.Open(self.attachments)
  if err == nil {
    names, err = f.Readdirnames(0)
  }
  return
}

func (self articleStore) ReadMessage(r io.Reader) (NNTPMessage, error) {
  return read_message(r)
}

func (self articleStore) StorePost(nntp NNTPMessage) (err error) {

  f := self.CreateFile(nntp.MessageID())
  if f != nil {
    err = self.WriteMessage(nntp, f)
    f.Close()
  }

  nntp_inner := nntp.Signed()
  if nntp_inner == nil {
    // no inner article
    // store the data in the article
    self.database.RegisterArticle(nntp)
    for _, att := range nntp.Attachments() {
      // save attachments
      go self.saveAttachment(att)
    }
  } else {
    // record a tripcode
    self.database.RegisterSigned(nntp.MessageID(), nntp.Pubkey())
    // we have inner data
    // store the signed data
    self.database.RegisterArticle(nntp_inner)
    for _, att := range nntp_inner.Attachments() {
      go self.saveAttachment(att)
    }
  }
  return
}

// save an attachment
func (self articleStore) saveAttachment(att NNTPAttachment) {
  var err error
  var f io.WriteCloser
  fpath := att.Filepath()
  upload := self.AttachmentFilepath(fpath)
  thumb := self.ThumbnailFilepath(fpath)
  if CheckFile(upload) {
    log.Println("already have file", fpath)
    if ! CheckFile(thumb) && att.NeedsThumbnail() {
      log.Println("create thumbnail for", fpath)
      err = self.GenerateThumbnail(fpath)
      if err != nil {
        log.Println("failed to generate thumbnail", err)
      }
    }
    return
  }
  // save attachment
  log.Println("save attachment", att.Filename(), "to", upload)
  f, err = os.Create(upload)
  if err == nil {
    err = att.WriteTo(f)
    f.Close()
  }
  if err != nil {
    log.Println("did not save attachment", err)
    return
  }

  // generate thumbanils
  if att.NeedsThumbnail() {
    log.Println("create thumbnail for", fpath)
    err = self.GenerateThumbnail(fpath)
    if err != nil {
      log.Println("failed to generate thumbnail", err)
    }
  }
}

// eh this isn't really needed is it?
func (self articleStore) WriteMessage(nntp NNTPMessage, wr io.Writer) (err error) {
  return nntp.WriteTo(wr, "\n")
}

// get the filepath for an attachment
func (self articleStore) AttachmentFilepath(fname string) string {
  return filepath.Join(self.attachments, fname)
}

// get the filepath for a thumbanil
func (self articleStore) ThumbnailFilepath(fname string) string {
  // all thumbnails are jpegs now
  return filepath.Join(self.thumbs, fname + ".jpg")
}

// create a file for this article
func (self articleStore) CreateFile(messageID string) io.WriteCloser {
  fname := self.GetFilename(messageID)
  file, err := os.Create(fname)
  if err != nil {
    log.Println("cannot open file", fname)
    return nil
  }
  return file
}

// create a temp file for inboud articles
func (self articleStore) CreateTempFile(messageID string) io.WriteCloser {
  fname := self.GetTempFilename(messageID)
  if CheckFile(fname) {
    log.Println(fname, "already open")
    return nil
  }
  file, err := os.Create(fname)
  if err != nil {
    log.Println("cannot open file", fname)
    return nil
  }
  return file
}

// return true if we have an article
func (self articleStore) HasArticle(messageID string) bool {
  return CheckFile(self.GetFilename(messageID))
}

// get the filename for this article
func (self articleStore) GetFilename(messageID string) string {
  if ! ValidMessageID(messageID) {
    log.Println("!!! bug: tried to open invalid message", messageID, "!!!")
    return ""
  }
  return filepath.Join(self.directory, messageID)
}

// get the filename for this article
func (self articleStore) GetTempFilename(messageID string) string {
  if ! ValidMessageID(messageID) {
    log.Println("!!! bug: tried to open invalid temp message", messageID, "!!!")
    return ""
  }
  return filepath.Join(self.temp, messageID)
}

// loads temp message and deletes old article
func (self articleStore) ReadTempMessage(messageID string) NNTPMessage {
  fname := self.GetTempFilename(messageID)
  nntp := self.readfile(fname)
  DelFile(fname)
  return nntp
}

// read a file give filepath
func (self articleStore) readfile(fname string) NNTPMessage {

  file, err := os.Open(fname)
  if err != nil {
    log.Println("store cannot open file",fname)
    return nil
  }
  message, err := self.ReadMessage(file)
  file.Close()
  if err == nil {
    return message
  }

  log.Println("failed to load file", fname)
  return nil
}

// get the replies for a thread
func (self articleStore) GetThreadReplies(messageID string, last int) []NNTPMessage {
  var repls []NNTPMessage
  if self.database.ThreadHasReplies(messageID) {
    rpls := self.database.GetThreadReplies(messageID, last)
    if rpls == nil {
      return repls
    }
    for _, rpl := range rpls {
      msg := self.GetMessage(rpl)
      if msg == nil {
        log.Println("cannot get message", rpl)
      } else {
        repls = append(repls, msg)
      }
    }
  }
  return repls
}

// load an article
// return nil on failure
func (self articleStore) GetMessage(messageID string) NNTPMessage {
  return self.readfile(self.GetFilename(messageID))
}

// get article with headers only
func (self articleStore) GetHeaders(messageID string) ArticleHeaders {
  // TODO: don't load the entire body
  nntp := self.readfile(self.GetFilename(messageID))
  if nntp == nil {
    return nil
  }
  return nntp.Headers()
}

func read_message(r io.Reader) (NNTPMessage, error) {

  msg, err := mail.ReadMessage(r)
  var nntp nntpArticle

  if err == nil {
    nntp.headers = ArticleHeaders(msg.Header)
    content_type := nntp.ContentType()
    media_type, params, err := mime.ParseMediaType(content_type)
    if err != nil {
      log.Println("failed to parse media type", err, "for mime", content_type)
      return nil, err
    }
    boundary, ok := params["boundary"]
    if ok {
      partReader := multipart.NewReader(msg.Body, boundary)
      for {
        part, err := partReader.NextPart()
        if err == io.EOF {
          return nntp, nil
        } else if err == nil {
          hdr := part.Header
          // get content type of part
          part_type := hdr.Get("Content-Type")
          // parse content type
          media_type, _, err = mime.ParseMediaType(part_type)
          if err == nil {
            if media_type == "text/plain" {
              att := readAttachmentFromMimePart(part)
              if att != nil {
                nntp.message = att.(nntpAttachment)
                nntp.message.header.Set("Content-Type", part_type)
              }
            } else {
              // non plaintext gets added to attachments
              att := readAttachmentFromMimePart(part)
              if att != nil {
                nntp = nntp.Attach(att).(nntpArticle)
              }
            }
          } else {
            log.Println("part has no content type", err)
          }
          part.Close()
        } else {
          log.Println("failed to load part! ", err)
          return nil, err
        }
      }

    } else if media_type == "message/rfc822" {
      // tripcoded message
      sig := nntp.headers.Get("X-Signature-Ed25519-Sha512", "")
      pk := nntp.Pubkey()
      if pk == "" || sig == "" {
        log.Println("invalid sig or pubkey", sig, pk)
        return nil, errors.New("invalid headers")
      }
      log.Printf("got signed message from %s", pk)
      pk_bytes := unhex(pk)
      sig_bytes := unhex(sig)
      r := bufio.NewReader(msg.Body)
      crlf := []byte{13,10}
      for {
        line, err := r.ReadBytes('\n')
        if err == io.EOF {
          break
        }
        nntp.signedPart.body.Write(line[:len(line)-1])
        nntp.signedPart.body.Write(crlf)
      }
      if nntp.signedPart.body.Len() < 2 {
        log.Println("signed body is too small")
      } else {
        body := nntp.signedPart.body.Bytes()[:nntp.signedPart.body.Len()-2]
        body_hash := sha512.Sum512(body)
        log.Printf("hash=%s", hexify(body_hash[:]))
        if nacl.CryptoVerifyFucky(body_hash[:], sig_bytes, pk_bytes) {
          log.Println("signature is valid :^)")
          return nntp, nil
        } else {
          log.Println("!!!signature is invalid!!!")
        }
      }
    } else {
      // plaintext attachment
      var buff bytes.Buffer
      _, err = io.Copy(&buff, msg.Body)
      nntp.message = createPlaintextAttachment(buff.String())
      return nntp, err
    }
  } else {
    log.Println("failed to read message", err)
    return nil, err
  }
  return nntp, err
}

<|endoftext|>


<commit_before>package edgerouter

import (
	"context"
	"errors"
	"fmt"
	"net"
	"time"

	"github.com/golang/glog"
)

type SerialTcpSeeker struct {
	Period  string
	Port    int
	TimeOut string
}

type Device interface {
	DeviceID() string
	DeviceType() string
}

type SerialTcpSeekHandler interface {
	PacketSend() []*BytesMessage
	SeekReceived([]byte, Device) (handled_length int, shouldStartNew bool)
}

func (u *SerialTcpSeeker) Run(ctx context.Context, handler interface{}) (context.Context, error) {
	var err error
	var d, to time.Duration
	if d, err = time.ParseDuration(u.Period); err != nil {
		return ctx, err
	} else {
		fmt.Printf("tcp send package by (%s) period\n", d)
	}
	if to, err = time.ParseDuration(u.TimeOut); err != nil {
		return ctx, err
	}
	if uh, ok := handler.(SerialTcpSeekHandler); ok {
		go u.handleTcpSeek(ctx, d, to, uh)
		return ctx, err
	}
	return ctx, errors.New("the plugin is not a tcp serial seek handler with PacketSend and SeekReceived function")
}

type Seeking struct {
	conn       *net.TCPConn
	cha        chan bool
	device     Device
	to         time.Duration
	handler    SerialTcpSeekHandler
	toReminder *time.Timer
}

func (s *Seeking) PacketReceived(bts []byte, conn *net.TCPConn) int {
	_, shouldStartNew := s.handler.SeekReceived(bts, s.device)
	if shouldStartNew {
		s.cha <- true
	}
	return len(bts)
}

func (u *SerialTcpSeeker) handleTcpSeek(ctx context.Context, d, to time.Duration, handler SerialTcpSeekHandler) {
	var seekings = make(map[string]*Seeking)
	for {
		select {
		case <-time.After(d):
			msgs := handler.PacketSend()
			for _, msg := range msgs {
				var err error
				var addr *net.TCPAddr
				var seeking *Seeking
				if addr, err = net.ResolveTCPAddr("tcp", msg.To); err == nil {
					var ok bool
					if seeking, ok = seekings[addr.String()]; !ok {
						var localPort *net.TCPAddr
						if u.Port != 0 {
							localPort, _ = net.ResolveTCPAddr("tcp", fmt.Sprintf(":%d", u.Port))
						}
						var conn *net.TCPConn
						conn, err = net.DialTCP("tcp", localPort, addr)
						seeking = &Seeking{
							conn:    conn,
							cha:     make(chan bool, 1),
							to:      to,
							handler: handler,
						}
						go handleTcpConn(conn, seeking)
						seekings[addr.String()] = seeking
						seeking.cha <- true
					}
					<-seeking.cha
					if seeking.toReminder != nil {
						seeking.toReminder.Stop()
					}
					seeking.device = msg.For
					glog.Info("start for", msg.For)
					_, err = seeking.conn.Write(msg.Message)
					seeking.toReminder = time.AfterFunc(seeking.to, func() {
						glog.Infoln("timeout")
						seeking.cha <- false
					})
				}
				if err != nil {
					fmt.Println(err)
				}
			}
		}
	}
}
<commit_msg>fix some bug of chan<commit_after>package edgerouter

import (
	"context"
	"errors"
	"fmt"
	"net"
	"time"

	"github.com/golang/glog"
)

type SerialTcpSeeker struct {
	Period  string
	Port    int
	TimeOut string
}

type Device interface {
	DeviceID() string
	DeviceType() string
}

type SerialTcpSeekHandler interface {
	PacketSend() []*BytesMessage
	SeekReceived([]byte, Device) (handled_length int, shouldStartNew bool)
}

func (u *SerialTcpSeeker) Run(ctx context.Context, handler interface{}) (context.Context, error) {
	var err error
	var d, to time.Duration
	if d, err = time.ParseDuration(u.Period); err != nil {
		return ctx, err
	} else {
		fmt.Printf("tcp send package by (%s) period\n", d)
	}
	if to, err = time.ParseDuration(u.TimeOut); err != nil {
		return ctx, err
	}
	if uh, ok := handler.(SerialTcpSeekHandler); ok {
		go u.handleTcpSeek(ctx, d, to, uh)
		return ctx, err
	}
	return ctx, errors.New("the plugin is not a tcp serial seek handler with PacketSend and SeekReceived function")
}

type Seeking struct {
	conn          *net.TCPConn
	chanAvailable chan bool
	chanFinish    chan bool
	device        Device
	to            time.Duration
	handler       SerialTcpSeekHandler
	toReminder    *time.Timer
}

func (s *Seeking) PacketReceived(bts []byte, conn *net.TCPConn) int {
	_, shouldStartNew := s.handler.SeekReceived(bts, s.device)
	if shouldStartNew {
		s.chanFinish <- true
	}
	return len(bts)
}

func (u *SerialTcpSeeker) handleTcpSeek(ctx context.Context, d, to time.Duration, handler SerialTcpSeekHandler) {
	var seekings = make(map[string]*Seeking)
	for {
		select {
		case <-time.After(d):
			msgs := handler.PacketSend()
			for _, msg := range msgs {
				var err error
				var addr *net.TCPAddr
				var seeking *Seeking
				if addr, err = net.ResolveTCPAddr("tcp", msg.To); err == nil {
					var ok bool
					if seeking, ok = seekings[addr.String()]; !ok {
						var localPort *net.TCPAddr
						if u.Port != 0 {
							localPort, _ = net.ResolveTCPAddr("tcp", fmt.Sprintf(":%d", u.Port))
						}
						var conn *net.TCPConn
						conn, err = net.DialTCP("tcp", localPort, addr)
						seeking = &Seeking{
							conn:          conn,
							chanAvailable: make(chan bool, 1),
							chanFinish:    make(chan bool),
							to:            to,
							handler:       handler,
						}
						go handleTcpConn(conn, seeking)
						seekings[addr.String()] = seeking
						seeking.chanAvailable <- true
					}
					<-seeking.chanAvailable
					seeking.device = msg.For
					glog.Info("start for", msg.For)
					_, err = seeking.conn.Write(msg.Message)
					select {
					case <-time.After(seeking.to):
						glog.Infoln("timeout")
						seeking.chanAvailable <- false
					case <-seeking.chanFinish:
						glog.Infoln("finished")
						seeking.chanAvailable <- true
					}
				}
				if err != nil {
					fmt.Println(err)
				}
			}
		}
	}
}
<|endoftext|>


<commit_before>package xmlrpc

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/cookiejar"
	"net/rpc"
	"net/url"
	"sync"
)

type Client struct {
	*rpc.Client
}

// clientCodec is rpc.ClientCodec interface implementation.
type clientCodec struct {
	// url presents url of xmlrpc service
	url *url.URL

	// httpClient works with HTTP protocol
	httpClient *http.Client

	// cookies stores cookies received on last request
	cookies http.CookieJar

	// responses presents map of active requests. It is required to return request id, that
	// rpc.Client can mark them as done.
	responses map[uint64]*http.Response
	mutex     sync.Mutex

	response Response

	// ready presents channel, that is used to link request and it`s response.
	ready chan uint64

	// close notifies codec is closed.
	close chan uint64
}

func (codec *clientCodec) WriteRequest(request *rpc.Request, args interface{}) (err error) {
	httpRequest, err := NewRequest(codec.url.String(), request.ServiceMethod, args)

	if codec.cookies != nil {
		for _, cookie := range codec.cookies.Cookies(codec.url) {
			httpRequest.AddCookie(cookie)
		}
	}

	if err != nil {
		return err
	}

	var httpResponse *http.Response
	httpResponse, err = codec.httpClient.Do(httpRequest)

	if err != nil {
		return err
	}

	if codec.cookies != nil {
		codec.cookies.SetCookies(codec.url, httpResponse.Cookies())
	}

	codec.mutex.Lock()
	codec.responses[request.Seq] = httpResponse
	codec.mutex.Unlock()

	codec.ready <- request.Seq

	return nil
}

func (codec *clientCodec) ReadResponseHeader(response *rpc.Response) (err error) {
	var seq uint64
	select {
	case seq = <-codec.ready:
	case <-codec.close:
		return errors.New("codec is closed")
	}

	codec.mutex.Lock()
	httpResponse := codec.responses[seq]
	delete(codec.responses, seq)
	codec.mutex.Unlock()

	if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 {
		response.Error = fmt.Sprintf("request error: bad status code - %d", httpResponse.StatusCode)
	}

	defer httpResponse.Body.Close()
	body, err := ioutil.ReadAll(httpResponse.Body)
	if err != nil {
		response.Error = err.Error()
	}

	resp := Response(body)
	if err := resp.Err(); err != nil {
		response.Error = err.Error()
	}

	codec.mutex.Lock()
	codec.response = resp
	codec.mutex.Unlock()

	response.Seq = seq

	return nil
}

func (codec *clientCodec) ReadResponseBody(v interface{}) (err error) {
	if v == nil {
		return nil
	}
	return codec.response.Unmarshal(v)
}

func (codec *clientCodec) Close() error {
	if transport, ok := codec.httpClient.Transport.(*http.Transport); ok {
		transport.CloseIdleConnections()
	}

	close(codec.close)

	return nil
}

// NewClient returns instance of rpc.Client object, that is used to send request to xmlrpc service.
func NewClient(requrl string, transport http.RoundTripper) (*Client, error) {
	if transport == nil {
		transport = http.DefaultTransport
	}

	httpClient := &http.Client{Transport: transport}

	jar, err := cookiejar.New(nil)

	if err != nil {
		return nil, err
	}

	u, err := url.Parse(requrl)

	if err != nil {
		return nil, err
	}

	codec := clientCodec{
		url:        u,
		httpClient: httpClient,
		close:      make(chan uint64),
		ready:      make(chan uint64),
		responses:  make(map[uint64]*http.Response),
		cookies:    jar,
	}

	return &Client{rpc.NewClientWithCodec(&codec)}, nil
}
<commit_msg>don't overwrite error<commit_after>package xmlrpc

import (
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/http/cookiejar"
	"net/rpc"
	"net/url"
	"sync"
)

type Client struct {
	*rpc.Client
}

// clientCodec is rpc.ClientCodec interface implementation.
type clientCodec struct {
	// url presents url of xmlrpc service
	url *url.URL

	// httpClient works with HTTP protocol
	httpClient *http.Client

	// cookies stores cookies received on last request
	cookies http.CookieJar

	// responses presents map of active requests. It is required to return request id, that
	// rpc.Client can mark them as done.
	responses map[uint64]*http.Response
	mutex     sync.Mutex

	response Response

	// ready presents channel, that is used to link request and it`s response.
	ready chan uint64

	// close notifies codec is closed.
	close chan uint64
}

func (codec *clientCodec) WriteRequest(request *rpc.Request, args interface{}) (err error) {
	httpRequest, err := NewRequest(codec.url.String(), request.ServiceMethod, args)

	if codec.cookies != nil {
		for _, cookie := range codec.cookies.Cookies(codec.url) {
			httpRequest.AddCookie(cookie)
		}
	}

	if err != nil {
		return err
	}

	var httpResponse *http.Response
	httpResponse, err = codec.httpClient.Do(httpRequest)

	if err != nil {
		return err
	}

	if codec.cookies != nil {
		codec.cookies.SetCookies(codec.url, httpResponse.Cookies())
	}

	codec.mutex.Lock()
	codec.responses[request.Seq] = httpResponse
	codec.mutex.Unlock()

	codec.ready <- request.Seq

	return nil
}

func (codec *clientCodec) ReadResponseHeader(response *rpc.Response) (err error) {
	var seq uint64
	select {
	case seq = <-codec.ready:
	case <-codec.close:
		return errors.New("codec is closed")
	}

	codec.mutex.Lock()
	httpResponse := codec.responses[seq]
	delete(codec.responses, seq)
	codec.mutex.Unlock()

	if httpResponse.StatusCode < 200 || httpResponse.StatusCode >= 300 {
		response.Error = fmt.Sprintf("request error: bad status code - %d", httpResponse.StatusCode)
	}

	body, err := ioutil.ReadAll(httpResponse.Body)
	if err != nil {
		if response.Error == "" {
			response.Error = err.Error()
		}
	}
	httpResponse.Body.Close()

	resp := Response(body)
	if err := resp.Err(); err != nil {
		if response.Error == "" {
			response.Error = err.Error()
		}
	}

	codec.mutex.Lock()
	codec.response = resp
	codec.mutex.Unlock()

	response.Seq = seq

	return nil
}

func (codec *clientCodec) ReadResponseBody(v interface{}) (err error) {
	if v == nil {
		return nil
	}
	return codec.response.Unmarshal(v)
}

func (codec *clientCodec) Close() error {
	if transport, ok := codec.httpClient.Transport.(*http.Transport); ok {
		transport.CloseIdleConnections()
	}

	close(codec.close)

	return nil
}

// NewClient returns instance of rpc.Client object, that is used to send request to xmlrpc service.
func NewClient(requrl string, transport http.RoundTripper) (*Client, error) {
	if transport == nil {
		transport = http.DefaultTransport
	}

	httpClient := &http.Client{Transport: transport}

	jar, err := cookiejar.New(nil)

	if err != nil {
		return nil, err
	}

	u, err := url.Parse(requrl)

	if err != nil {
		return nil, err
	}

	codec := clientCodec{
		url:        u,
		httpClient: httpClient,
		close:      make(chan uint64),
		ready:      make(chan uint64),
		responses:  make(map[uint64]*http.Response),
		cookies:    jar,
	}

	return &Client{rpc.NewClientWithCodec(&codec)}, nil
}
<|endoftext|>


<commit_before>package main

import (
	"compress/gzip"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/anacrolix/torrent"
	"github.com/anacrolix/torrent/iplist"
	"github.com/dustin/go-humanize"
)

const clearScreen = "\033[H\033[2J"

const torrentBlockListURL = "http://john.bitsurge.net/public/biglist.p2p.gz"

var isHTTP = regexp.MustCompile(`^https?:\/\/`)

// ClientError formats errors coming from the client.
type ClientError struct {
	Type   string
	Origin error
}

func (clientError ClientError) Error() string {
	return fmt.Sprintf("Error %s: %s\n", clientError.Type, clientError.Origin)
}

// Client manages the torrent downloading.
type Client struct {
	Client   *torrent.Client
	Torrent  *torrent.Torrent
	Progress int64
	Uploaded int64
	Config   ClientConfig
}

// ClientConfig specifies the behaviour of a client.
type ClientConfig struct {
	TorrentPath    string
	Port           int
	TorrentPort    int
	Seed           bool
	TCP            bool
	MaxConnections int
}

// NewClientConfig creates a new default configuration.
func NewClientConfig() ClientConfig {
	return ClientConfig{
		Port:           8080,
		TorrentPort:    50007,
		Seed:           false,
		TCP:            true,
		MaxConnections: 200,
	}
}

// NewClient creates a new torrent client based on a magnet or a torrent file.
// If the torrent file is on http, we try downloading it.
func NewClient(cfg ClientConfig) (client Client, err error) {
	var t *torrent.Torrent
	var c *torrent.Client

	client.Config = cfg

	blocklist := getBlocklist()
	torrentConfig := torrent.NewDefaultClientConfig()
	torrentConfig.DataDir = os.TempDir()
	torrentConfig.NoUpload = !cfg.Seed
	torrentConfig.DisableTCP = !cfg.TCP
	torrentConfig.ListenPort = cfg.TorrentPort
	torrentConfig.IPBlocklist = blocklist

	// Create client.
	c, err = torrent.NewClient(torrentConfig)

	if err != nil {
		return client, ClientError{Type: "creating torrent client", Origin: err}
	}

	client.Client = c

	// Add torrent.

	// Add as magnet url.
	if strings.HasPrefix(cfg.TorrentPath, "magnet:") {
		if t, err = c.AddMagnet(cfg.TorrentPath); err != nil {
			return client, ClientError{Type: "adding torrent", Origin: err}
		}
	} else {
		// Otherwise add as a torrent file.

		// If it's online, we try downloading the file.
		if isHTTP.MatchString(cfg.TorrentPath) {
			if cfg.TorrentPath, err = downloadFile(cfg.TorrentPath); err != nil {
				return client, ClientError{Type: "downloading torrent file", Origin: err}
			}
		}

		if t, err = c.AddTorrentFromFile(cfg.TorrentPath); err != nil {
			return client, ClientError{Type: "adding torrent to the client", Origin: err}
		}
	}

	client.Torrent = t
	client.Torrent.SetMaxEstablishedConns(cfg.MaxConnections)

	go func() {
		<-t.GotInfo()
		t.DownloadAll()

		// Prioritize first 5% of the file.
		largestFile := client.getLargestFile()
		firstPieceIndex := largestFile.Offset() * int64(t.NumPieces()) / t.Length()
		endPieceIndex := (largestFile.Offset() + largestFile.Length()) * int64(t.NumPieces()) / t.Length()
		for idx := firstPieceIndex; idx <= endPieceIndex*5/100; idx++ {
			t.Piece(int(idx)).SetPriority(torrent.PiecePriorityNow)
		}
	}()

	return
}

// Download and add the blocklist.
func getBlocklist() iplist.Ranger {
	var err error
	blocklistPath := os.TempDir() + "/go-peerflix-blocklist.gz"

	if _, err = os.Stat(blocklistPath); os.IsNotExist(err) {
		err = downloadBlockList(blocklistPath)
	}

	if err != nil {
		log.Printf("Error downloading blocklist: %s", err)
		return nil
	}

	// Load blocklist.
	// #nosec
	// We trust our temporary directory as we just wrote the file there ourselves.
	blocklistReader, err := os.Open(blocklistPath)
	if err != nil {
		log.Printf("Error opening blocklist: %s", err)
		return nil
	}

	// Extract file.
	gzipReader, err := gzip.NewReader(blocklistReader)
	if err != nil {
		log.Printf("Error extracting blocklist: %s", err)
		return nil
	}

	// Read as iplist.
	blocklist, err := iplist.NewFromReader(gzipReader)
	if err != nil {
		log.Printf("Error reading blocklist: %s", err)
		return nil
	}

	log.Printf("Loading blocklist.\nFound %d ranges\n", blocklist.NumRanges())
	return blocklist
}

func downloadBlockList(blocklistPath string) (err error) {
	log.Printf("Downloading blocklist")
	fileName, err := downloadFile(torrentBlockListURL)
	if err != nil {
		log.Printf("Error downloading blocklist: %s\n", err)
		return
	}

	return os.Rename(fileName, blocklistPath)
}

// Close cleans up the connections.
func (c *Client) Close() {
	c.Torrent.Drop()
	c.Client.Close()
}

// Render outputs the command line interface for the client.
func (c *Client) Render() {
	t := c.Torrent

	if t.Info() == nil {
		return
	}

	currentProgress := t.BytesCompleted()
	downloadSpeed := humanize.Bytes(uint64(currentProgress-c.Progress)) + "/s"
	c.Progress = currentProgress

	complete := humanize.Bytes(uint64(currentProgress))
	size := humanize.Bytes(uint64(t.Info().TotalLength()))

	bytesWrittenData := t.Stats().BytesWrittenData
	uploadProgress := (&bytesWrittenData).Int64() - c.Uploaded
	uploadSpeed := humanize.Bytes(uint64(uploadProgress)) + "/s"
	c.Uploaded = uploadProgress

	print(clearScreen)
	fmt.Println(t.Info().Name)
	fmt.Println(strings.Repeat("=", len(t.Info().Name)))
	if c.ReadyForPlayback() {
		fmt.Printf("Stream: \thttp://localhost:%d\n", c.Config.Port)
	}
	if currentProgress > 0 {
		fmt.Printf("Progress: \t%s / %s  %.2f%%\n", complete, size, c.percentage())
	}
	if currentProgress < t.Info().TotalLength() {
		fmt.Printf("Download speed: %s\n", downloadSpeed)
	}
	if c.Config.Seed {
		fmt.Printf("Upload speed: \t%s\n", uploadSpeed)
	}
}

func (c Client) getLargestFile() *torrent.File {
	var target *torrent.File
	var maxSize int64

	for _, file := range c.Torrent.Files() {
		if maxSize < file.Length() {
			maxSize = file.Length()
			target = file
		}
	}

	return target
}

/*
func (c Client) RenderPieces() (output string) {
	pieces := c.Torrent.PieceStateRuns()
	for i := range pieces {
		piece := pieces[i]

		if piece.Priority == torrent.PiecePriorityReadahead {
			output += "!"
		}

		if piece.Partial {
			output += "P"
		} else if piece.Checking {
			output += "c"
		} else if piece.Complete {
			output += "d"
		} else {
			output += "_"
		}
	}

	return
}
*/

// ReadyForPlayback checks if the torrent is ready for playback or not.
// We wait until 5% of the torrent to start playing.
func (c Client) ReadyForPlayback() bool {
	return c.percentage() > 5
}

// GetFile is an http handler to serve the biggest file managed by the client.
func (c Client) GetFile(w http.ResponseWriter, r *http.Request) {
	target := c.getLargestFile()
	entry, err := NewFileReader(target)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	defer func() {
		if err := entry.Close(); err != nil {
			log.Printf("Error closing file reader: %s\n", err)
		}
	}()

	w.Header().Set("Content-Disposition", "attachment; filename=\""+c.Torrent.Info().Name+"\"")
	http.ServeContent(w, r, target.DisplayPath(), time.Now(), entry)
}

func (c Client) percentage() float64 {
	info := c.Torrent.Info()

	if info == nil {
		return 0
	}

	return float64(c.Torrent.BytesCompleted()) / float64(info.TotalLength()) * 100
}

func downloadFile(URL string) (fileName string, err error) {
	var file *os.File
	if file, err = ioutil.TempFile(os.TempDir(), "go-peerflix"); err != nil {
		return
	}

	defer func() {
		if ferr := file.Close(); ferr != nil {
			log.Printf("Error closing torrent file: %s", ferr)
		}
	}()

	// #nosec
	// We are downloading the url the user passed to us, we trust it is a torrent file.
	response, err := http.Get(URL)
	if err != nil {
		return
	}

	defer func() {
		if ferr := response.Body.Close(); ferr != nil {
			log.Printf("Error closing torrent file: %s", ferr)
		}
	}()

	_, err = io.Copy(file, response.Body)

	return file.Name(), err
}
<commit_msg>Make progress output buffered<commit_after>package main

import (
	"bufio"
	"compress/gzip"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"regexp"
	"strings"
	"time"

	"github.com/anacrolix/torrent"
	"github.com/anacrolix/torrent/iplist"
	"github.com/dustin/go-humanize"
)

const clearScreen = "\033[H\033[2J"

const torrentBlockListURL = "http://john.bitsurge.net/public/biglist.p2p.gz"

var isHTTP = regexp.MustCompile(`^https?:\/\/`)

// ClientError formats errors coming from the client.
type ClientError struct {
	Type   string
	Origin error
}

func (clientError ClientError) Error() string {
	return fmt.Sprintf("Error %s: %s\n", clientError.Type, clientError.Origin)
}

// Client manages the torrent downloading.
type Client struct {
	Client   *torrent.Client
	Torrent  *torrent.Torrent
	Progress int64
	Uploaded int64
	Config   ClientConfig
}

// ClientConfig specifies the behaviour of a client.
type ClientConfig struct {
	TorrentPath    string
	Port           int
	TorrentPort    int
	Seed           bool
	TCP            bool
	MaxConnections int
}

// NewClientConfig creates a new default configuration.
func NewClientConfig() ClientConfig {
	return ClientConfig{
		Port:           8080,
		TorrentPort:    50007,
		Seed:           false,
		TCP:            true,
		MaxConnections: 200,
	}
}

// NewClient creates a new torrent client based on a magnet or a torrent file.
// If the torrent file is on http, we try downloading it.
func NewClient(cfg ClientConfig) (client Client, err error) {
	var t *torrent.Torrent
	var c *torrent.Client

	client.Config = cfg

	blocklist := getBlocklist()
	torrentConfig := torrent.NewDefaultClientConfig()
	torrentConfig.DataDir = os.TempDir()
	torrentConfig.NoUpload = !cfg.Seed
	torrentConfig.DisableTCP = !cfg.TCP
	torrentConfig.ListenPort = cfg.TorrentPort
	torrentConfig.IPBlocklist = blocklist

	// Create client.
	c, err = torrent.NewClient(torrentConfig)

	if err != nil {
		return client, ClientError{Type: "creating torrent client", Origin: err}
	}

	client.Client = c

	// Add torrent.

	// Add as magnet url.
	if strings.HasPrefix(cfg.TorrentPath, "magnet:") {
		if t, err = c.AddMagnet(cfg.TorrentPath); err != nil {
			return client, ClientError{Type: "adding torrent", Origin: err}
		}
	} else {
		// Otherwise add as a torrent file.

		// If it's online, we try downloading the file.
		if isHTTP.MatchString(cfg.TorrentPath) {
			if cfg.TorrentPath, err = downloadFile(cfg.TorrentPath); err != nil {
				return client, ClientError{Type: "downloading torrent file", Origin: err}
			}
		}

		if t, err = c.AddTorrentFromFile(cfg.TorrentPath); err != nil {
			return client, ClientError{Type: "adding torrent to the client", Origin: err}
		}
	}

	client.Torrent = t
	client.Torrent.SetMaxEstablishedConns(cfg.MaxConnections)

	go func() {
		<-t.GotInfo()
		t.DownloadAll()

		// Prioritize first 5% of the file.
		largestFile := client.getLargestFile()
		firstPieceIndex := largestFile.Offset() * int64(t.NumPieces()) / t.Length()
		endPieceIndex := (largestFile.Offset() + largestFile.Length()) * int64(t.NumPieces()) / t.Length()
		for idx := firstPieceIndex; idx <= endPieceIndex*5/100; idx++ {
			t.Piece(int(idx)).SetPriority(torrent.PiecePriorityNow)
		}
	}()

	return
}

// Download and add the blocklist.
func getBlocklist() iplist.Ranger {
	var err error
	blocklistPath := os.TempDir() + "/go-peerflix-blocklist.gz"

	if _, err = os.Stat(blocklistPath); os.IsNotExist(err) {
		err = downloadBlockList(blocklistPath)
	}

	if err != nil {
		log.Printf("Error downloading blocklist: %s", err)
		return nil
	}

	// Load blocklist.
	// #nosec
	// We trust our temporary directory as we just wrote the file there ourselves.
	blocklistReader, err := os.Open(blocklistPath)
	if err != nil {
		log.Printf("Error opening blocklist: %s", err)
		return nil
	}

	// Extract file.
	gzipReader, err := gzip.NewReader(blocklistReader)
	if err != nil {
		log.Printf("Error extracting blocklist: %s", err)
		return nil
	}

	// Read as iplist.
	blocklist, err := iplist.NewFromReader(gzipReader)
	if err != nil {
		log.Printf("Error reading blocklist: %s", err)
		return nil
	}

	log.Printf("Loading blocklist.\nFound %d ranges\n", blocklist.NumRanges())
	return blocklist
}

func downloadBlockList(blocklistPath string) (err error) {
	log.Printf("Downloading blocklist")
	fileName, err := downloadFile(torrentBlockListURL)
	if err != nil {
		log.Printf("Error downloading blocklist: %s\n", err)
		return
	}

	return os.Rename(fileName, blocklistPath)
}

// Close cleans up the connections.
func (c *Client) Close() {
	c.Torrent.Drop()
	c.Client.Close()
}

// Render outputs the command line interface for the client.
func (c *Client) Render() {
	t := c.Torrent

	if t.Info() == nil {
		return
	}

	currentProgress := t.BytesCompleted()
	downloadSpeed := humanize.Bytes(uint64(currentProgress-c.Progress)) + "/s"
	c.Progress = currentProgress

	complete := humanize.Bytes(uint64(currentProgress))
	size := humanize.Bytes(uint64(t.Info().TotalLength()))

	bytesWrittenData := t.Stats().BytesWrittenData
	uploadProgress := (&bytesWrittenData).Int64() - c.Uploaded
	uploadSpeed := humanize.Bytes(uint64(uploadProgress)) + "/s"
	c.Uploaded = uploadProgress
	percentage := c.percentage()
	totalLength := t.Info().TotalLength()

	output := bufio.NewWriter(os.Stdout)

	fmt.Fprint(output, clearScreen)
	fmt.Fprint(output, t.Info().Name+"\n")
	fmt.Fprint(output, strings.Repeat("=", len(t.Info().Name))+"\n")
	if c.ReadyForPlayback() {
		fmt.Fprintf(output, "Stream: \thttp://localhost:%d\n", c.Config.Port)
	}
	if currentProgress > 0 {
		fmt.Fprintf(output, "Progress: \t%s / %s  %.2f%%\n", complete, size, percentage)
	}
	if currentProgress < totalLength {
		fmt.Fprintf(output, "Download speed: %s\n", downloadSpeed)
	}
	if c.Config.Seed {
		fmt.Fprintf(output, "Upload speed: \t%s", uploadSpeed)
	}

	output.Flush()
}

func (c Client) getLargestFile() *torrent.File {
	var target *torrent.File
	var maxSize int64

	for _, file := range c.Torrent.Files() {
		if maxSize < file.Length() {
			maxSize = file.Length()
			target = file
		}
	}

	return target
}

/*
func (c Client) RenderPieces() (output string) {
	pieces := c.Torrent.PieceStateRuns()
	for i := range pieces {
		piece := pieces[i]

		if piece.Priority == torrent.PiecePriorityReadahead {
			output += "!"
		}

		if piece.Partial {
			output += "P"
		} else if piece.Checking {
			output += "c"
		} else if piece.Complete {
			output += "d"
		} else {
			output += "_"
		}
	}

	return
}
*/

// ReadyForPlayback checks if the torrent is ready for playback or not.
// We wait until 5% of the torrent to start playing.
func (c Client) ReadyForPlayback() bool {
	return c.percentage() > 5
}

// GetFile is an http handler to serve the biggest file managed by the client.
func (c Client) GetFile(w http.ResponseWriter, r *http.Request) {
	target := c.getLargestFile()
	entry, err := NewFileReader(target)
	if err != nil {
		http.Error(w, err.Error(), http.StatusInternalServerError)
		return
	}

	defer func() {
		if err := entry.Close(); err != nil {
			log.Printf("Error closing file reader: %s\n", err)
		}
	}()

	w.Header().Set("Content-Disposition", "attachment; filename=\""+c.Torrent.Info().Name+"\"")
	http.ServeContent(w, r, target.DisplayPath(), time.Now(), entry)
}

func (c Client) percentage() float64 {
	info := c.Torrent.Info()

	if info == nil {
		return 0
	}

	return float64(c.Torrent.BytesCompleted()) / float64(info.TotalLength()) * 100
}

func downloadFile(URL string) (fileName string, err error) {
	var file *os.File
	if file, err = ioutil.TempFile(os.TempDir(), "go-peerflix"); err != nil {
		return
	}

	defer func() {
		if ferr := file.Close(); ferr != nil {
			log.Printf("Error closing torrent file: %s", ferr)
		}
	}()

	// #nosec
	// We are downloading the url the user passed to us, we trust it is a torrent file.
	response, err := http.Get(URL)
	if err != nil {
		return
	}

	defer func() {
		if ferr := response.Body.Close(); ferr != nil {
			log.Printf("Error closing torrent file: %s", ferr)
		}
	}()

	_, err = io.Copy(file, response.Body)

	return file.Name(), err
}
<|endoftext|>


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

package git

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/mcuadros/go-version"
)

// Commit represents a git commit.
type Commit struct {
	Tree
	ID            sha1 // The ID of this commit object
	Author        *Signature
	Committer     *Signature
	CommitMessage string

	parents []sha1 // SHA1 strings
	// submodules map[string]*SubModule
}

func (c *Commit) GetCommitOfRelPath(relpath string) (*Commit, error) {
	return c.repo.getCommitOfRelPath(c.ID, relpath)
}

// AddAllChanges marks local changes to be ready for commit.
func AddChanges(repoPath string, all bool, files ...string) error {
	cmd := NewCommand("add")
	if all {
		cmd.AddArguments("--all")
	}
	_, err := cmd.AddArguments(files...).RunInDir(repoPath)
	return err
}

// CommitChanges commits local changes with given message and author.
func CommitChanges(repoPath, message string, author *Signature) error {
	cmd := NewCommand("commit", "-m", message)
	if author != nil {
		cmd.AddArguments(fmt.Sprintf("--author='%s <%s>'", author.Name, author.Email))
	}
	_, err := cmd.RunInDir(repoPath)

	// No stderr but exit status 1 means nothing to commit.
	if err != nil && err.Error() == "exit status 1" {
		return nil
	}
	return err
}

// CommitsCount returns number of total commits of until given revision.
func CommitsCount(repoPath, revision string) (int64, error) {
	if version.Compare(gitVersion, "1.8.0", "<") {
		stdout, err := NewCommand("log", "--pretty=format:''", revision).RunInDir(repoPath)
		if err != nil {
			return 0, err
		}
		return int64(len(strings.Split(stdout, "\n"))), nil
	}

	stdout, err := NewCommand("rev-list", "--count", revision).RunInDir(repoPath)
	if err != nil {
		return 0, err
	}
	return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
}
<commit_msg>more APIs<commit_after>// Copyright 2015 The Gogs Authors. All rights reserved.
// Use of this source code is governed by a MIT-style
// license that can be found in the LICENSE file.

package git

import (
	"fmt"
	"strconv"
	"strings"

	"github.com/mcuadros/go-version"
)

// Commit represents a git commit.
type Commit struct {
	Tree
	ID            sha1 // The ID of this commit object
	Author        *Signature
	Committer     *Signature
	CommitMessage string

	parents []sha1 // SHA1 strings
	// submodules map[string]*SubModule
}

// ParentID returns oid of n-th parent (0-based index).
// It returns nil if no such parent exists.
func (c *Commit) ParentID(n int) (sha1, error) {
	if n >= len(c.parents) {
		return sha1{}, ErrNotExist{"", ""}
	}
	return c.parents[n], nil
}

// Parent returns n-th parent (0-based index) of the commit.
func (c *Commit) Parent(n int) (*Commit, error) {
	id, err := c.ParentID(n)
	if err != nil {
		return nil, err
	}
	parent, err := c.repo.getCommit(id)
	if err != nil {
		return nil, err
	}
	return parent, nil
}

// ParentCount returns number of parents of the commit.
// 0 if this is the root commit, otherwise 1,2, etc.
func (c *Commit) ParentCount() int {
	return len(c.parents)
}

// GetCommitOfRelPath return the commit of relative path object.
func (c *Commit) GetCommitOfRelPath(relpath string) (*Commit, error) {
	return c.repo.getCommitOfRelPath(c.ID, relpath)
}

// AddAllChanges marks local changes to be ready for commit.
func AddChanges(repoPath string, all bool, files ...string) error {
	cmd := NewCommand("add")
	if all {
		cmd.AddArguments("--all")
	}
	_, err := cmd.AddArguments(files...).RunInDir(repoPath)
	return err
}

// CommitChanges commits local changes with given message and author.
func CommitChanges(repoPath, message string, author *Signature) error {
	cmd := NewCommand("commit", "-m", message)
	if author != nil {
		cmd.AddArguments(fmt.Sprintf("--author='%s <%s>'", author.Name, author.Email))
	}
	_, err := cmd.RunInDir(repoPath)

	// No stderr but exit status 1 means nothing to commit.
	if err != nil && err.Error() == "exit status 1" {
		return nil
	}
	return err
}

// CommitsCount returns number of total commits of until given revision.
func CommitsCount(repoPath, revision string) (int64, error) {
	if version.Compare(gitVersion, "1.8.0", "<") {
		stdout, err := NewCommand("log", "--pretty=format:''", revision).RunInDir(repoPath)
		if err != nil {
			return 0, err
		}
		return int64(len(strings.Split(stdout, "\n"))), nil
	}

	stdout, err := NewCommand("rev-list", "--count", revision).RunInDir(repoPath)
	if err != nil {
		return 0, err
	}
	return strconv.ParseInt(strings.TrimSpace(stdout), 10, 64)
}
<|endoftext|>


<commit_before>package gop

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"time"

	"github.com/vaughan0/go-ini"

	"fmt"
	"os"
	"strconv"
	"strings"
)

type ConfigSource interface {
	Get(sectionName, optionName string, defaultValue string) (string, bool)
	Add(sectionName, optionName, optionValue string)
	Sections() []string
	SectionKeys(sectionName string) []string
}

type Config struct {
	source              ConfigMap
	persistentOverrides ConfigMap
	transientOverrides  ConfigMap
	overrideFname       string
	onChangeCallbacks   []func(cfg *Config)
}

type ConfigMap map[string]map[string]string

func (a *App) getConfigFilename(forceCurrentWorkingDir bool) string {

	rootEnvName := strings.ToUpper(a.ProjectName) + "_CFG_ROOT"
	configRoot := os.Getenv(rootEnvName)
	if configRoot == "" {
		configRoot = "/etc/" + a.ProjectName
	}

	if forceCurrentWorkingDir {
		configRoot = "."
	}

	fileEnvName := strings.ToUpper(a.ProjectName) + "_" + strings.ToUpper(a.AppName) + "_CFG_FILE"
	configFname := os.Getenv(fileEnvName)
	if configFname == "" {
		configFname = configRoot + "/" + a.AppName + ".conf"
	}

	return configFname
}

func (cm *ConfigMap) loadFromIniFile(fname string) error {
	iniCfg, err := ini.LoadFile(fname)
	if err != nil {
		return err
	}
	for section, m := range iniCfg {
		for k, v := range m {
			cm.Add(section, k, v)
		}
	}

	return nil
}

func (cm *ConfigMap) loadFromJsonFile(fname string) error {
	overrideJsonBytes, err := ioutil.ReadFile(fname)
	if err != nil {
		return err
	}
	err = json.Unmarshal(overrideJsonBytes, cm)
	if err != nil {
		return err
	}
	return nil
}

func (cm *ConfigMap) saveToJsonFile(fname string) error {
	jsonBytes, err := json.Marshal(cm)
	if err != nil {
		return err
	}
	return ioutil.WriteFile(fname, jsonBytes, 0644)
}

func (a *App) loadAppConfigFile(requireConfig bool) {
	// We do not have logging set up yet. We just panic() on error.
	source := make(ConfigMap)

	configFname := a.getConfigFilename(false)
	err := source.loadFromIniFile(configFname)
	if err != nil && !os.IsNotExist(err) {
		// Can't log, it's all too early. This is fatal, tho
		panic(fmt.Sprintf("Can't load config file [%s]: %s", configFname, err.Error()))
	}

	if err != nil {
		// Try again in cwd
		configFname = a.getConfigFilename(true)
		err = source.loadFromIniFile(configFname)
		if err != nil {
			if os.IsNotExist(err) && !requireConfig {
				// OK - you're allowed to not fail in this case
				return
			}
			// Can't log, it's all too early. This is fatal, tho
			panic(fmt.Sprintf("Can't load config file [%s] after fallback to cwd: %s", configFname, err.Error()))
		}
	}

	persistentOverrides := make(ConfigMap)
	overrideFname := configFname + ".override"
	fi, err := os.Stat(overrideFname)
	if err == nil && fi.Size() > 0 {
		err = persistentOverrides.loadFromJsonFile(overrideFname)
		if err != nil {
			// Don't have logging yet, so use log. and hope
			log.Printf("Failed to load or parse override config file [%s]: %s\n", overrideFname, err.Error())
			// Don't want to fail here, just continue without overrides
			err = nil
		}
	}

	a.Cfg = Config{
		source:              source,
		persistentOverrides: persistentOverrides,
		transientOverrides:  make(ConfigMap),
		overrideFname:       overrideFname,
		onChangeCallbacks:   make([]func(cfg *Config), 0),
	}
}

// Get an option value for the given sectionName.
// Will return defaultValue if the section or the option does not exist.
// The second return value is True if the requested option value was returned and False if the default value was returned.
func (cfgMap *ConfigMap) Get(sectionName, optionName string, defaultValue string) (string, bool) {
	s, ok := map[string]map[string]string(*cfgMap)[sectionName]
	if !ok {
		return defaultValue, false
	}
	v, ok := map[string]string(s)[optionName]
	if !ok {
		return defaultValue, false
	}
	return v, true
}

// Set the given option to the specified value for the named section.
// Create the section if it does not exist.
func (cfgMap *ConfigMap) Add(sectionName, optionName, optionValue string) {
	_, ok := (*cfgMap)[sectionName]
	if !ok {
		(*cfgMap)[sectionName] = make(map[string]string)
	}
	(*cfgMap)[sectionName][optionName] = optionValue
}

// Get a list of the names of the avaliable sections.
func (cfgMap *ConfigMap) Sections() []string {
	sections := make([]string, 0)
	for k := range *cfgMap {
		sections = append(sections, k)
	}
	return sections
}

// Get a list of options for the named section.
// Will return an empty list if the section does not exist.
func (cfgMap *ConfigMap) SectionKeys(sectionName string) []string {
	keys := make([]string, 0)
	section, ok := (*cfgMap)[sectionName]
	if !ok {
		return keys
	}
	for k := range section {
		keys = append(keys, k)
	}
	return keys
}

func (cfg *Config) AddOnChangeCallback(f func(cfg *Config)) {
	cfg.onChangeCallbacks = append(cfg.onChangeCallbacks, f)
}

func (cfg *Config) notifyChange() {
	for _, f := range cfg.onChangeCallbacks {
		// These should be quick!
		f(cfg)
	}
}

func (cfg *Config) savePersistentOverrides() error {
	return cfg.persistentOverrides.saveToJsonFile(cfg.overrideFname)
}

// Get a list of the names of the available sections, including those specified in the override file.
func (cfg *Config) Sections() []string {
	sectionMap := make(map[string]bool)

	sourceSections := cfg.source.Sections()
	for _, section := range sourceSections {
		sectionMap[section] = true
	}
	for section := range cfg.persistentOverrides {
		sectionMap[section] = true
	}
	for section := range cfg.transientOverrides {
		sectionMap[section] = true
	}

	sections := make([]string, 0)
	for k := range sectionMap {
		sections = append(sections, k)
	}
	return sections
}

// Get a list of options for the named section, including those specified in the override file.
func (cfg *Config) SectionKeys(sectionName string) []string {
	keyMap := make(map[string]bool)

	sourceKeys := cfg.source.SectionKeys(sectionName)
	for _, key := range sourceKeys {
		keyMap[key] = true
	}

	overrideSection, ok := cfg.persistentOverrides[sectionName]
	if ok {
		for key := range overrideSection {
			keyMap[key] = true
		}
	}

	overrideSection, ok = cfg.transientOverrides[sectionName]
	if ok {
		for key := range overrideSection {
			keyMap[key] = true
		}
	}

	keys := make([]string, 0)
	for k := range keyMap {
		keys = append(keys, k)
	}
	return keys
}

// Get a copy of the config as a map that maps each section to a map that maps the options to the values.
func (cfg *Config) AsMap() map[string]map[string]string {
	configMap := make(map[string]map[string]string)
	sections := cfg.Sections()
	for _, section := range sections {
		configMap[section] = make(map[string]string)
		keys := cfg.SectionKeys(section)
		for _, key := range keys {
			configMap[section][key], _ = cfg.Get(section, key, "")
		}
	}
	return configMap
}

func (cfg *Config) PersistentOverride(sectionName, optionName, optionValue string) {
	section, ok := cfg.persistentOverrides[sectionName]
	if !ok {
		cfg.persistentOverrides[sectionName] = make(map[string]string)
		section = cfg.persistentOverrides[sectionName]
	}
	section[optionName] = optionValue
	err := cfg.savePersistentOverrides()
	if err != nil {
		log.Printf("Failed to save to override file [%s]: %s\n", cfg.overrideFname, err.Error())
	}
	cfg.notifyChange()
	return
}

func (cfg *Config) TransientOverride(sectionName, optionName, optionValue string) {
	section, ok := cfg.transientOverrides[sectionName]
	if !ok {
		cfg.transientOverrides[sectionName] = make(map[string]string)
		section = cfg.transientOverrides[sectionName]
	}
	section[optionName] = optionValue
	cfg.notifyChange()
	return
}

func (cfg *Config) Get(sectionName, optionName string, defaultValue string) (string, bool) {
	str, found := cfg.transientOverrides.Get(sectionName, optionName, defaultValue)
	if found {
		return str, true
	}
	str, found = cfg.persistentOverrides.Get(sectionName, optionName, defaultValue)
	if found {
		return str, true
	}

	// Not found, just punt it to the base
	return cfg.source.Get(sectionName, optionName, defaultValue)
}

// Same as Config.Get, but returns the value as int.
func (cfg *Config) GetInt(sectionName, optionName string, defaultValue int) (int, bool) {
	v, found := cfg.Get(sectionName, optionName, "")
	if !found {
		return defaultValue, false
	}
	r, err := strconv.Atoi(v)
	if err == nil {
		return r, true
	}
	panic(fmt.Sprintf("Non-numeric config key %s: %s [%s]", optionName, v, err))
}

// Same as Config.Get, but returns the value as int64.
// The integer has to be written in the config in decimal format. This means that for the value written in
// the config as "08" this method will return 8 instead of 10. And "0x8" will generate an error.
func (cfg *Config) GetInt64(sectionName, optionName string, defaultValue int64) (int64, bool) {
	v, found := cfg.Get(sectionName, optionName, "")
	if !found {
		return defaultValue, false
	}
	r, err := strconv.ParseInt(v, 10, 64)
	if err == nil {
		return r, true
	}
	panic(fmt.Sprintf("Non-numeric config key %s: %s [%s]", optionName, v, err))
}

// Same as Config.Get, but returns the value as boolean.
// The option value should be one that strconv.ParseBool understands.
func (cfg *Config) GetBool(sectionName, optionName string, defaultValue bool) (bool, bool) {
	v, found := cfg.Get(sectionName, optionName, "")
	if !found {
		return defaultValue, false
	}
	r, err := strconv.ParseBool(v)
	if err == nil {
		return r, true
	}
	panic(fmt.Sprintf("Bad boolean config key %s: %s", optionName, v))
}

// Same as Config.Get, but returns the value as float32.
func (cfg *Config) GetFloat32(sectionName, optionName string, defaultValue float32) (float32, bool) {
	v, found := cfg.Get(sectionName, optionName, "")
	if !found {
		return defaultValue, false
	}
	r, err := strconv.ParseFloat(v, 32)
	if err == nil {
		return float32(r), true
	}
	panic(fmt.Sprintf("Non-numeric float32 config key %s: %s [%s]", optionName, v, err))
}

// Same as Config.Get, but returns the value as float64
func (cfg *Config) GetFloat64(sectionName, optionName string, defaultValue float64) (float64, bool) {
	v, found := cfg.Get(sectionName, optionName, "")
	if !found {
		return defaultValue, false
	}
	r, err := strconv.ParseFloat(v, 64)
	if err == nil {
		return float64(r), true
	}
	panic(fmt.Sprintf("Non-numeric float64 config key %s: %s [%s]", optionName, v, err))
}

// Return a list of strings for a config value that is written as a comma-separated list.
// Each value will be stripped out of leading and trailing white spaces as defined by Unicode.
func (cfg *Config) GetList(sectionName, optionName string, defaultValue []string) ([]string, bool) {
	vStr, found := cfg.Get(sectionName, optionName, "")
	if !found {
		return defaultValue, false
	}
	v := strings.Split(vStr, ",")
	for i := 0; i < len(v); i++ {
		v[i] = strings.TrimSpace(v[i])
	}
	return v, true
}

// Same as Config.Get but returns the value as time.Duration.
// The value in the config file should be in the format that time.ParseDuration() understands.
func (cfg *Config) GetDuration(sectionName, optionName string, defaultValue time.Duration) (time.Duration, bool) {
	vStr, found := cfg.Get(sectionName, optionName, "")
	if !found {
		return defaultValue, false
	}
	v, err := time.ParseDuration(vStr)
	if err != nil {
		return defaultValue, false
	}
	return v, true
}

func expandTildeToHome(fname string) string {
	homeDir := os.Getenv("HOME")
	if homeDir == "" {
		return fname
	}
	return strings.Replace(fname, "~", homeDir, -1)
}

// Same as Config.Get but consider the string as a filename path and
// expands ~ characters to the homedir of the current uid
func (cfg *Config) GetPath(sectionName, optionName string, defaultValue string) (string, bool) {

	vStr, found := cfg.Get(sectionName, optionName, "")
	if !found {
		return expandTildeToHome(defaultValue), false
	}
	v := expandTildeToHome(vStr)
	return v, true
}

func (cfg *Config) GetMap(sectionName, kPrefix string, defaultValue map[string]string) (map[string]string, bool) {
	keys := cfg.SectionKeys(sectionName)
	v := make(map[string]string)
	for _, k := range keys {
		if strings.HasPrefix(k, kPrefix) {
			kTrimmed := strings.TrimPrefix(k, kPrefix)
			v[kTrimmed], _ = cfg.Get(sectionName, k, "")
		}
	}
	found := len(v) > 0
	if !found {
		return defaultValue, false
	}
	return v, true
}
<commit_msg>make sure we have the app config structure in place for apps which don't require config<commit_after>package gop

import (
	"encoding/json"
	"io/ioutil"
	"log"
	"time"

	"github.com/vaughan0/go-ini"

	"fmt"
	"os"
	"strconv"
	"strings"
)

type ConfigSource interface {
	Get(sectionName, optionName string, defaultValue string) (string, bool)
	Add(sectionName, optionName, optionValue string)
	Sections() []string
	SectionKeys(sectionName string) []string
}

type Config struct {
	source              ConfigMap
	persistentOverrides ConfigMap
	transientOverrides  ConfigMap
	overrideFname       string
	onChangeCallbacks   []func(cfg *Config)
}

type ConfigMap map[string]map[string]string

func (a *App) getConfigFilename(forceCurrentWorkingDir bool) string {

	rootEnvName := strings.ToUpper(a.ProjectName) + "_CFG_ROOT"
	configRoot := os.Getenv(rootEnvName)
	if configRoot == "" {
		configRoot = "/etc/" + a.ProjectName
	}

	if forceCurrentWorkingDir {
		configRoot = "."
	}

	fileEnvName := strings.ToUpper(a.ProjectName) + "_" + strings.ToUpper(a.AppName) + "_CFG_FILE"
	configFname := os.Getenv(fileEnvName)
	if configFname == "" {
		configFname = configRoot + "/" + a.AppName + ".conf"
	}

	return configFname
}

func (cm *ConfigMap) loadFromIniFile(fname string) error {
	iniCfg, err := ini.LoadFile(fname)
	if err != nil {
		return err
	}
	for section, m := range iniCfg {
		for k, v := range m {
			cm.Add(section, k, v)
		}
	}

	return nil
}

func (cm *ConfigMap) loadFromJsonFile(fname string) error {
	overrideJsonBytes, err := ioutil.ReadFile(fname)
	if err != nil {
		return err
	}
	err = json.Unmarshal(overrideJsonBytes, cm)
	if err != nil {
		return err
	}
	return nil
}

func (cm *ConfigMap) saveToJsonFile(fname string) error {
	jsonBytes, err := json.Marshal(cm)
	if err != nil {
		return err
	}
	return ioutil.WriteFile(fname, jsonBytes, 0644)
}

func (a *App) loadAppConfigFile(requireConfig bool) {
	// We do not have logging set up yet. We just panic() on error.

	// Set up a null config so we have the structure in place on early return
	source := make(ConfigMap)
	persistentOverrides := make(ConfigMap)

	a.Cfg = Config{
		source:              source,
		persistentOverrides: persistentOverrides,
		transientOverrides:  make(ConfigMap),
		onChangeCallbacks:   make([]func(cfg *Config), 0),
	}

	configFname := a.getConfigFilename(false)
	err := source.loadFromIniFile(configFname)
	if err != nil && !os.IsNotExist(err) {
		// Can't log, it's all too early. This is fatal, tho
		panic(fmt.Sprintf("Can't load config file [%s]: %s", configFname, err.Error()))
	}

	if err != nil {
		// Try again in cwd
		configFname = a.getConfigFilename(true)
		err = source.loadFromIniFile(configFname)
		if err != nil {
			if os.IsNotExist(err) && !requireConfig {
				// OK - you're allowed to not fail in this case
				return
			}
			// Can't log, it's all too early. This is fatal, tho
			panic(fmt.Sprintf("Can't load config file [%s] after fallback to cwd: %s", configFname, err.Error()))
		}
	}

	a.Cfg.overrideFname = configFname + ".override"
	fi, err := os.Stat(a.Cfg.overrideFname)
	if err == nil && fi.Size() > 0 {
		err = persistentOverrides.loadFromJsonFile(a.Cfg.overrideFname)
		if err != nil {
			// Don't have logging yet, so use log. and hope
			log.Printf("Failed to load or parse override config file [%s]: %s\n", a.Cfg.overrideFname, err.Error())
			// Don't want to fail here, just continue without overrides
			err = nil
		}
	}

}

// Get an option value for the given sectionName.
// Will return defaultValue if the section or the option does not exist.
// The second return value is True if the requested option value was returned and False if the default value was returned.
func (cfgMap *ConfigMap) Get(sectionName, optionName string, defaultValue string) (string, bool) {
	s, ok := map[string]map[string]string(*cfgMap)[sectionName]
	if !ok {
		return defaultValue, false
	}
	v, ok := map[string]string(s)[optionName]
	if !ok {
		return defaultValue, false
	}
	return v, true
}

// Set the given option to the specified value for the named section.
// Create the section if it does not exist.
func (cfgMap *ConfigMap) Add(sectionName, optionName, optionValue string) {
	_, ok := (*cfgMap)[sectionName]
	if !ok {
		(*cfgMap)[sectionName] = make(map[string]string)
	}
	(*cfgMap)[sectionName][optionName] = optionValue
}

// Get a list of the names of the avaliable sections.
func (cfgMap *ConfigMap) Sections() []string {
	sections := make([]string, 0)
	for k := range *cfgMap {
		sections = append(sections, k)
	}
	return sections
}

// Get a list of options for the named section.
// Will return an empty list if the section does not exist.
func (cfgMap *ConfigMap) SectionKeys(sectionName string) []string {
	keys := make([]string, 0)
	section, ok := (*cfgMap)[sectionName]
	if !ok {
		return keys
	}
	for k := range section {
		keys = append(keys, k)
	}
	return keys
}

func (cfg *Config) AddOnChangeCallback(f func(cfg *Config)) {
	cfg.onChangeCallbacks = append(cfg.onChangeCallbacks, f)
}

func (cfg *Config) notifyChange() {
	for _, f := range cfg.onChangeCallbacks {
		// These should be quick!
		f(cfg)
	}
}

func (cfg *Config) savePersistentOverrides() error {
	return cfg.persistentOverrides.saveToJsonFile(cfg.overrideFname)
}

// Get a list of the names of the available sections, including those specified in the override file.
func (cfg *Config) Sections() []string {
	sectionMap := make(map[string]bool)

	sourceSections := cfg.source.Sections()
	for _, section := range sourceSections {
		sectionMap[section] = true
	}
	for section := range cfg.persistentOverrides {
		sectionMap[section] = true
	}
	for section := range cfg.transientOverrides {
		sectionMap[section] = true
	}

	sections := make([]string, 0)
	for k := range sectionMap {
		sections = append(sections, k)
	}
	return sections
}

// Get a list of options for the named section, including those specified in the override file.
func (cfg *Config) SectionKeys(sectionName string) []string {
	keyMap := make(map[string]bool)

	sourceKeys := cfg.source.SectionKeys(sectionName)
	for _, key := range sourceKeys {
		keyMap[key] = true
	}

	overrideSection, ok := cfg.persistentOverrides[sectionName]
	if ok {
		for key := range overrideSection {
			keyMap[key] = true
		}
	}

	overrideSection, ok = cfg.transientOverrides[sectionName]
	if ok {
		for key := range overrideSection {
			keyMap[key] = true
		}
	}

	keys := make([]string, 0)
	for k := range keyMap {
		keys = append(keys, k)
	}
	return keys
}

// Get a copy of the config as a map that maps each section to a map that maps the options to the values.
func (cfg *Config) AsMap() map[string]map[string]string {
	configMap := make(map[string]map[string]string)
	sections := cfg.Sections()
	for _, section := range sections {
		configMap[section] = make(map[string]string)
		keys := cfg.SectionKeys(section)
		for _, key := range keys {
			configMap[section][key], _ = cfg.Get(section, key, "")
		}
	}
	return configMap
}

func (cfg *Config) PersistentOverride(sectionName, optionName, optionValue string) {
	section, ok := cfg.persistentOverrides[sectionName]
	if !ok {
		cfg.persistentOverrides[sectionName] = make(map[string]string)
		section = cfg.persistentOverrides[sectionName]
	}
	section[optionName] = optionValue
	err := cfg.savePersistentOverrides()
	if err != nil {
		log.Printf("Failed to save to override file [%s]: %s\n", cfg.overrideFname, err.Error())
	}
	cfg.notifyChange()
	return
}

func (cfg *Config) TransientOverride(sectionName, optionName, optionValue string) {
	section, ok := cfg.transientOverrides[sectionName]
	if !ok {
		cfg.transientOverrides[sectionName] = make(map[string]string)
		section = cfg.transientOverrides[sectionName]
	}
	section[optionName] = optionValue
	cfg.notifyChange()
	return
}

func (cfg *Config) Get(sectionName, optionName string, defaultValue string) (string, bool) {
	str, found := cfg.transientOverrides.Get(sectionName, optionName, defaultValue)
	if found {
		return str, true
	}
	str, found = cfg.persistentOverrides.Get(sectionName, optionName, defaultValue)
	if found {
		return str, true
	}

	// Not found, just punt it to the base
	return cfg.source.Get(sectionName, optionName, defaultValue)
}

// Same as Config.Get, but returns the value as int.
func (cfg *Config) GetInt(sectionName, optionName string, defaultValue int) (int, bool) {
	v, found := cfg.Get(sectionName, optionName, "")
	if !found {
		return defaultValue, false
	}
	r, err := strconv.Atoi(v)
	if err == nil {
		return r, true
	}
	panic(fmt.Sprintf("Non-numeric config key %s: %s [%s]", optionName, v, err))
}

// Same as Config.Get, but returns the value as int64.
// The integer has to be written in the config in decimal format. This means that for the value written in
// the config as "08" this method will return 8 instead of 10. And "0x8" will generate an error.
func (cfg *Config) GetInt64(sectionName, optionName string, defaultValue int64) (int64, bool) {
	v, found := cfg.Get(sectionName, optionName, "")
	if !found {
		return defaultValue, false
	}
	r, err := strconv.ParseInt(v, 10, 64)
	if err == nil {
		return r, true
	}
	panic(fmt.Sprintf("Non-numeric config key %s: %s [%s]", optionName, v, err))
}

// Same as Config.Get, but returns the value as boolean.
// The option value should be one that strconv.ParseBool understands.
func (cfg *Config) GetBool(sectionName, optionName string, defaultValue bool) (bool, bool) {
	v, found := cfg.Get(sectionName, optionName, "")
	if !found {
		return defaultValue, false
	}
	r, err := strconv.ParseBool(v)
	if err == nil {
		return r, true
	}
	panic(fmt.Sprintf("Bad boolean config key %s: %s", optionName, v))
}

// Same as Config.Get, but returns the value as float32.
func (cfg *Config) GetFloat32(sectionName, optionName string, defaultValue float32) (float32, bool) {
	v, found := cfg.Get(sectionName, optionName, "")
	if !found {
		return defaultValue, false
	}
	r, err := strconv.ParseFloat(v, 32)
	if err == nil {
		return float32(r), true
	}
	panic(fmt.Sprintf("Non-numeric float32 config key %s: %s [%s]", optionName, v, err))
}

// Same as Config.Get, but returns the value as float64
func (cfg *Config) GetFloat64(sectionName, optionName string, defaultValue float64) (float64, bool) {
	v, found := cfg.Get(sectionName, optionName, "")
	if !found {
		return defaultValue, false
	}
	r, err := strconv.ParseFloat(v, 64)
	if err == nil {
		return float64(r), true
	}
	panic(fmt.Sprintf("Non-numeric float64 config key %s: %s [%s]", optionName, v, err))
}

// Return a list of strings for a config value that is written as a comma-separated list.
// Each value will be stripped out of leading and trailing white spaces as defined by Unicode.
func (cfg *Config) GetList(sectionName, optionName string, defaultValue []string) ([]string, bool) {
	vStr, found := cfg.Get(sectionName, optionName, "")
	if !found {
		return defaultValue, false
	}
	v := strings.Split(vStr, ",")
	for i := 0; i < len(v); i++ {
		v[i] = strings.TrimSpace(v[i])
	}
	return v, true
}

// Same as Config.Get but returns the value as time.Duration.
// The value in the config file should be in the format that time.ParseDuration() understands.
func (cfg *Config) GetDuration(sectionName, optionName string, defaultValue time.Duration) (time.Duration, bool) {
	vStr, found := cfg.Get(sectionName, optionName, "")
	if !found {
		return defaultValue, false
	}
	v, err := time.ParseDuration(vStr)
	if err != nil {
		return defaultValue, false
	}
	return v, true
}

func expandTildeToHome(fname string) string {
	homeDir := os.Getenv("HOME")
	if homeDir == "" {
		return fname
	}
	return strings.Replace(fname, "~", homeDir, -1)
}

// Same as Config.Get but consider the string as a filename path and
// expands ~ characters to the homedir of the current uid
func (cfg *Config) GetPath(sectionName, optionName string, defaultValue string) (string, bool) {

	vStr, found := cfg.Get(sectionName, optionName, "")
	if !found {
		return expandTildeToHome(defaultValue), false
	}
	v := expandTildeToHome(vStr)
	return v, true
}

func (cfg *Config) GetMap(sectionName, kPrefix string, defaultValue map[string]string) (map[string]string, bool) {
	keys := cfg.SectionKeys(sectionName)
	v := make(map[string]string)
	for _, k := range keys {
		if strings.HasPrefix(k, kPrefix) {
			kTrimmed := strings.TrimPrefix(k, kPrefix)
			v[kTrimmed], _ = cfg.Get(sectionName, k, "")
		}
	}
	found := len(v) > 0
	if !found {
		return defaultValue, false
	}
	return v, true
}
<|endoftext|>


<commit_before>package main

const GutVersion = "1.0.1"
const GitRepoUrl = "https://github.com/git/git.git"
const GitVersion = "v2.5.0"
const GitWinRepoUrl = "https://github.com/git-for-windows/git.git"
const GitWinVersion = "v2.4.4"
const MsysgitRepoUrl = "https://github.com/msysgit/msysgit.git"
const MsysgitVersion = "Git-1.9.5-preview20150319"
const InotifyWinRepoUrl = "https://github.com/thekid/inotify-win.git"
const InotifyWinVersion = "9b547cfde0f546df8abeebf47ec36f36d7bd91ef"

var gutTarballHashes = map[string]string{
	"darwin-amd64": "2cbf485213af3061a3d5ce27211295ae804d535ed4854f9da6d57418bcc39424",
	"linux-386":    "b3ee92d6147c20d154843739c5a94fe28822f835f99d3ea20821d79ce107a313",
	"linux-amd64":  "d437b2008d313974b4b5a4293bcf93b8b681e65919c74099e6016975387d7eae",
}

const GutPath = "~/.guts"
const GutSrcPath = GutPath + "/gut-src"
const GutSrcTmpPath = GutPath + "/gut-src-tmp"
const GutWinSrcPath = GutPath + "/gut-win-src"
const MsysgitPath = GutPath + "/msysgit"
const InotifyWinPath = GutPath + "/inotify-win"
const GutDistPath = GutPath + "/gut-build"
const PidfilesPath = GutPath + "/pidfiles"
const GutExePath = GutDistPath + "/bin/gut"
const GutDaemonPath = GutPath + "/repos"

const MinRandomPort = 34000
const MaxRandomPort = 34999

// Ignore files that are probably transient or machine-specific by default.
// You can add/remove additional globs to both the root .gutignore and to
// any other .gutignore file in the repo hierarchy.
const DefaultGutignore = ("# Added by `gut sync` during repo init:" + `
*.lock
.#*

# Various compiled resources:
*.pyc
*.o
*.a
*.so
`)

var AllGutCommands = [...]string{
	// These are all the names of executables in libexec/gut-core/
	"add",
	"am",
	"annotate",
	"apply",
	"archimport",
	"archive",
	"bisect",
	"blame",
	"branch",
	"bundle",
	"cat-file",
	"check-attr",
	"check-ignore",
	"check-mailmap",
	"checkout",
	"checkout-index",
	"check-ref-format",
	"cherry",
	"cherry-pick",
	"citool",
	"clean",
	"clone",
	"column",
	"commit",
	"commit-tree",
	"config",
	"count-objects",
	"credential",
	"credential-cache",
	"credential-store",
	"cvsexportcommit",
	"cvsimport",
	"cvsserver",
	"daemon",
	"describe",
	"diff",
	"diff-files",
	"diff-index",
	"difftool",
	"diff-tree",
	"fast-export",
	"fast-import",
	"fetch",
	"fetch-pack",
	"filter-branch",
	"fmt-merge-msg",
	"for-each-ref",
	"format-patch",
	"fsck",
	"fsck-objects",
	"gc",
	"get-tar-commit-id",
	"grep",
	"gui",
	"hash-object",
	"help",
	"http-backend",
	"imap-send",
	"index-pack",
	"init",
	"init-db",
	"instaweb",
	"interpret-trailers",
	"log",
	"ls-files",
	"ls-remote",
	"ls-tree",
	"mailinfo",
	"mailsplit",
	"merge",
	"merge-base",
	"merge-file",
	"merge-index",
	"merge-octopus",
	"merge-one-file",
	"merge-ours",
	"merge-recursive",
	"merge-resolve",
	"merge-subtree",
	"mergetool",
	"merge-tree",
	"mktag",
	"mktree",
	"mv",
	"name-rev",
	"notes",
	"p4",
	"pack-objects",
	"pack-redundant",
	"pack-refs",
	"parse-remote",
	"patch-id",
	"prune",
	"prune-packed",
	"pull",
	"push",
	"quiltimport",
	"read-tree",
	"rebase",
	"receive-pack",
	"reflog",
	"relink",
	"remote",
	"remote-ext",
	"remote-fd",
	"remote-testsvn",
	"repack",
	"replace",
	"request-pull",
	"rerere",
	"reset",
	"revert",
	"rev-list",
	"rev-parse",
	"rm",
	"send-email",
	"send-pack",
	"shell",
	"sh-i18n",
	"shortlog",
	"show",
	"show-branch",
	"show-index",
	"show-ref",
	"sh-setup",
	"stage",
	"stash",
	"status",
	"stripspace",
	"submodule",
	"svn",
	"symbolic-ref",
	"tag",
	"unpack-file",
	"unpack-objects",
	"update-index",
	"update-ref",
	"update-server-info",
	"upload-archive",
	"upload-pack",
	"var",
	"verify-commit",
	"verify-pack",
	"verify-tag",
	"whatchanged",
	"write-tree",

	// This is an extra/special built-in (an alias for --version, I presume):
	"version",
	"--version",
}

var DangerousGitCommands = []string{
	"reset",
	"checkout",
	"clean",
	"rm",
}
<commit_msg>Bump version to 1.0.2<commit_after>package main

const GutVersion = "1.0.2"
const GitRepoUrl = "https://github.com/git/git.git"
const GitVersion = "v2.5.0"
const GitWinRepoUrl = "https://github.com/git-for-windows/git.git"
const GitWinVersion = "v2.4.4"
const MsysgitRepoUrl = "https://github.com/msysgit/msysgit.git"
const MsysgitVersion = "Git-1.9.5-preview20150319"
const InotifyWinRepoUrl = "https://github.com/thekid/inotify-win.git"
const InotifyWinVersion = "9b547cfde0f546df8abeebf47ec36f36d7bd91ef"

var gutTarballHashes = map[string]string{
	"darwin-amd64": "2cbf485213af3061a3d5ce27211295ae804d535ed4854f9da6d57418bcc39424",
	"linux-386":    "b3ee92d6147c20d154843739c5a94fe28822f835f99d3ea20821d79ce107a313",
	"linux-amd64":  "d437b2008d313974b4b5a4293bcf93b8b681e65919c74099e6016975387d7eae",
}

const GutPath = "~/.guts"
const GutSrcPath = GutPath + "/gut-src"
const GutSrcTmpPath = GutPath + "/gut-src-tmp"
const GutWinSrcPath = GutPath + "/gut-win-src"
const MsysgitPath = GutPath + "/msysgit"
const InotifyWinPath = GutPath + "/inotify-win"
const GutDistPath = GutPath + "/gut-build"
const PidfilesPath = GutPath + "/pidfiles"
const GutExePath = GutDistPath + "/bin/gut"
const GutDaemonPath = GutPath + "/repos"

const MinRandomPort = 34000
const MaxRandomPort = 34999

// Ignore files that are probably transient or machine-specific by default.
// You can add/remove additional globs to both the root .gutignore and to
// any other .gutignore file in the repo hierarchy.
const DefaultGutignore = ("# Added by `gut sync` during repo init:" + `
*.lock
.#*

# Various compiled resources:
*.pyc
*.o
*.a
*.so
`)

var AllGutCommands = [...]string{
	// These are all the names of executables in libexec/gut-core/
	"add",
	"am",
	"annotate",
	"apply",
	"archimport",
	"archive",
	"bisect",
	"blame",
	"branch",
	"bundle",
	"cat-file",
	"check-attr",
	"check-ignore",
	"check-mailmap",
	"checkout",
	"checkout-index",
	"check-ref-format",
	"cherry",
	"cherry-pick",
	"citool",
	"clean",
	"clone",
	"column",
	"commit",
	"commit-tree",
	"config",
	"count-objects",
	"credential",
	"credential-cache",
	"credential-store",
	"cvsexportcommit",
	"cvsimport",
	"cvsserver",
	"daemon",
	"describe",
	"diff",
	"diff-files",
	"diff-index",
	"difftool",
	"diff-tree",
	"fast-export",
	"fast-import",
	"fetch",
	"fetch-pack",
	"filter-branch",
	"fmt-merge-msg",
	"for-each-ref",
	"format-patch",
	"fsck",
	"fsck-objects",
	"gc",
	"get-tar-commit-id",
	"grep",
	"gui",
	"hash-object",
	"help",
	"http-backend",
	"imap-send",
	"index-pack",
	"init",
	"init-db",
	"instaweb",
	"interpret-trailers",
	"log",
	"ls-files",
	"ls-remote",
	"ls-tree",
	"mailinfo",
	"mailsplit",
	"merge",
	"merge-base",
	"merge-file",
	"merge-index",
	"merge-octopus",
	"merge-one-file",
	"merge-ours",
	"merge-recursive",
	"merge-resolve",
	"merge-subtree",
	"mergetool",
	"merge-tree",
	"mktag",
	"mktree",
	"mv",
	"name-rev",
	"notes",
	"p4",
	"pack-objects",
	"pack-redundant",
	"pack-refs",
	"parse-remote",
	"patch-id",
	"prune",
	"prune-packed",
	"pull",
	"push",
	"quiltimport",
	"read-tree",
	"rebase",
	"receive-pack",
	"reflog",
	"relink",
	"remote",
	"remote-ext",
	"remote-fd",
	"remote-testsvn",
	"repack",
	"replace",
	"request-pull",
	"rerere",
	"reset",
	"revert",
	"rev-list",
	"rev-parse",
	"rm",
	"send-email",
	"send-pack",
	"shell",
	"sh-i18n",
	"shortlog",
	"show",
	"show-branch",
	"show-index",
	"show-ref",
	"sh-setup",
	"stage",
	"stash",
	"status",
	"stripspace",
	"submodule",
	"svn",
	"symbolic-ref",
	"tag",
	"unpack-file",
	"unpack-objects",
	"update-index",
	"update-ref",
	"update-server-info",
	"upload-archive",
	"upload-pack",
	"var",
	"verify-commit",
	"verify-pack",
	"verify-tag",
	"whatchanged",
	"write-tree",

	// This is an extra/special built-in (an alias for --version, I presume):
	"version",
	"--version",
}

var DangerousGitCommands = []string{
	"reset",
	"checkout",
	"clean",
	"rm",
}
<|endoftext|>


<commit_before>// Copyright 2016 Afshin Darian. All rights reserved.
// Use of this source code is governed by The MIT License
// that can be found in the LICENSE file.

package sleuth

import (
	"net/http"

	"github.com/ursiform/logger"
)

// Config is the configuration specification for sleuth client instantiation.
// It has JSON tag values defined for all public fields except Handler in order
// to allow users to store sleuth configuration in JSON files. All fields are
// optional, but in production settings, Interface is recommended, if known.
type Config struct {
	// Handler is the HTTP handler for a service made available via sleuth.
	Handler http.Handler `json:"-"`

	// Interface is the system network interface sleuth should use, e.g. "en0".
	Interface string `json:"interface,omitempty"`

	// LogLevel is the ursiform.Logger level for sleuth. The default is "listen".
	// The options, in order of increasing verbosity, are:
	// 	"silent"	No log output at all.
	// 	"error"		Only errors are logged.
	// 	"blocked"	Blocking calls and lower are logged.
	// 	"warn"		Warnings and lower are logged.
	// 	"reject"	Rejections (e.g., in a firewall) and lower are logged.
	// 	"listen"	Listeners and lower are logged.
	// 	"install"	Install notifications and lower are logged.
	// 	"init"		Initialization notifications and lower are logged.
	// 	"request"	Incoming requests and lower are logged.
	// 	"info"		Info output and lower are logged.
	// 	"debug"		All log output is shown.
	LogLevel string `json:"loglevel,omitempty"`

	// Port is the UDP port that sleuth should broadcast on. The default is 5670.
	Port int `json:"port,omitempty"`

	// Service is the name of the service being offered if a Handler exists.
	Service string `json:"service,omitempty"`

	// Version is the optional version string of the service being offered.
	Version string `json:"version,omitempty"`

	logLevel int
}

func initConfig(config *Config) *Config {
	if config == nil {
		config = new(Config)
	}
	if len(config.LogLevel) == 0 {
		config.LogLevel = "listen"
	}
	if level, ok := logger.LogLevel[config.LogLevel]; !ok {
		logger.MustError("LogLevel=\"%s\" is invalid; using \"%s\" [%d]",
			config.LogLevel, "debug", errLogLevel)
		config.LogLevel = "debug"
		config.logLevel = logger.Debug
	} else {
		config.logLevel = level
	}
	return config
}
<commit_msg>docs tweak<commit_after>// Copyright 2016 Afshin Darian. All rights reserved.
// Use of this source code is governed by The MIT License
// that can be found in the LICENSE file.

package sleuth

import (
	"net/http"

	"github.com/ursiform/logger"
)

// Config is the configuration specification for sleuth client instantiation.
// It has JSON tag values defined for all public fields except Handler in order
// to allow users to store sleuth configuration in JSON files. All fields are
// optional, but Interface is particularly important to guarantee all peers
// reside on the same subnet.
type Config struct {
	// Handler is the HTTP handler for a service made available via sleuth.
	Handler http.Handler `json:"-"`

	// Interface is the system network interface sleuth should use, e.g. "en0".
	Interface string `json:"interface,omitempty"`

	// LogLevel is the ursiform.Logger level for sleuth. The default is "listen".
	// The options, in order of increasing verbosity, are:
	// 	"silent"	No log output at all.
	// 	"error"		Only errors are logged.
	// 	"blocked"	Blocking calls and lower are logged.
	// 	"warn"		Warnings and lower are logged.
	// 	"reject"	Rejections (e.g., in a firewall) and lower are logged.
	// 	"listen"	Listeners and lower are logged.
	// 	"install"	Install notifications and lower are logged.
	// 	"init"		Initialization notifications and lower are logged.
	// 	"request"	Incoming requests and lower are logged.
	// 	"info"		Info output and lower are logged.
	// 	"debug"		All log output is shown.
	LogLevel string `json:"loglevel,omitempty"`

	// Port is the UDP port that sleuth should broadcast on. The default is 5670.
	Port int `json:"port,omitempty"`

	// Service is the name of the service being offered if a Handler exists.
	Service string `json:"service,omitempty"`

	// Version is the optional version string of the service being offered.
	Version string `json:"version,omitempty"`

	logLevel int
}

func initConfig(config *Config) *Config {
	if config == nil {
		config = new(Config)
	}
	if len(config.LogLevel) == 0 {
		config.LogLevel = "listen"
	}
	if level, ok := logger.LogLevel[config.LogLevel]; !ok {
		logger.MustError("LogLevel=\"%s\" is invalid; using \"%s\" [%d]",
			config.LogLevel, "debug", errLogLevel)
		config.LogLevel = "debug"
		config.logLevel = logger.Debug
	} else {
		config.logLevel = level
	}
	return config
}
<|endoftext|>