<commit_before>//
// model.go
// template model implementation
//
package srnd

import (
  "fmt"
  "io"
  "path/filepath"
  "strings"
  "time"
)

// base model type
type BaseModel interface {

  // site url prefix
  Prefix() string

  // render to a writer
  RenderTo(wr io.Writer) error

}

// for attachments
type AttachmentModel interface {

  BaseModel

  Thumbnail() string
  Source() string
  Filename() string

}

// for individual posts
type PostModel interface {

  BaseModel

  CSSClass() string

  MessageID() string
  PostHash() string
  ShortHash() string
  PostURL() string
  Frontend() string
  Subject() string
  Name() string
  Date() string
  OP() bool
  Attachments() []AttachmentModel
  Board() string
  Sage() bool
  Pubkey() string
  Reference() string

  RenderBody() string
  RenderPost() string

  IsI2P() bool
  IsTor() bool
  IsClearnet() bool

  // truncate body to a certain size
  // return copy
  Truncate() PostModel

}

// interface for models that have a navbar
type NavbarModel interface {

  Navbar() string

}

// for threads
type ThreadModel interface {

  BaseModel
  NavbarModel

  OP() PostModel
  Replies() []PostModel
  Board() string
  BoardURL() string
  // return a short version of the thread
  // does not include all replies
  Truncate() ThreadModel
  // update the thread's replies
  // return the updated model
  Update(db Database) ThreadModel
}

// board interface
// for 1 page on a board
type BoardModel interface {

  BaseModel
  NavbarModel

  Frontend() string
  Name() string
  Threads() []ThreadModel

  // JUST update this thread
  // if we don't have it already loaded do nothing
  UpdateThread(message_id string, db Database) BoardModel

  // get a thread model with this id
  // returns nil if we don't have it
  GetThread(message_id string) ThreadModel

  // deprecated, use GetThread
  HasThread(message_id string) bool

  // update the board's contents
  // return the updated model
  Update(db Database) BoardModel
}

type LinkModel interface {

  Text() string
  LinkURL() string
}

// newsgroup model
// every page on a newsgroup
type GroupModel []BoardModel

// TODO: optimize using 1 query?
// update every page
// return updated model
func (self GroupModel) UpdateAll(db Database) GroupModel {
  for idx, page := range self {
    self[idx] = page.Update(db)
  }
  return self
}

// update a certain page
// does nothing if out of bounds
func (self GroupModel) Update(page int, db Database) GroupModel {
  if len(self) > page {
    self[page] = self[page].Update(db)
  }
  return self
}

type boardModel struct {
  frontend string
  prefix string
  board string
  page int
  pages int
  threads []ThreadModel
}

func (self boardModel) Navbar() string {
  param := make(map[string]interface{})
  param["name"] = fmt.Sprintf("page %d for %s", self.page, self.board)
  param["frontend"] = self.frontend
  var links []LinkModel
  for i := 0 ; i < self.pages ; i ++ {
    links = append(links, linkModel{
      link: fmt.Sprintf("%s%s-%d.html", self.prefix, self.board, i),
      text: fmt.Sprintf("[ %d ]", i),
    })
  }
  param["prefix"] = self.prefix
  param["links"] = links
  return template.renderTemplate("navbar.mustache", param)
}

func (self boardModel) UpdateThread(messageID string, db Database) BoardModel {

  for idx, th := range self.threads {
    if th.OP().MessageID() == messageID {
      // found it
      self.threads[idx] = th.Update(db)
      break
    }
  }
  return self
}

func (self boardModel) GetThread(messageID string) ThreadModel {
  for _, th := range self.threads {
    if th.OP().MessageID() == messageID {
      return th
    }
  }
  return nil
}

func (self boardModel) HasThread(messageID string) bool {
  return self.GetThread(messageID) != nil
}

func (self boardModel) Frontend() string {
  return self.frontend
}

func (self boardModel) Prefix() string {
  return self.prefix
}

func (self boardModel) Name() string {
  return self.board
}

func (self boardModel) Threads() []ThreadModel {
  return self.threads
}

func (self boardModel) RenderTo(wr io.Writer) error {
  param := make(map[string]interface{})
  param["board"] = self
  param["form"] = renderPostForm(self.Prefix(), self.board, "")
  _, err := io.WriteString(wr, template.renderTemplate("board.mustache", param))
  return err
}

// refetch all threads on this page
func (self boardModel) Update(db Database) BoardModel {
  // ignore error
  perpage, _ := db.GetThreadsPerPage(self.board)
  // refetch all on this page
  model := db.GetGroupForPage(self.prefix, self.frontend, self.board, self.page, int(perpage))
  var threads []ThreadModel
  for _, th := range model.Threads() {
    threads = append(threads, th.Update(db))
  }
  return boardModel{
    frontend: self.frontend,
    prefix: self.prefix,
    board: self.board,
    page: self.page,
    pages: self.pages,
    threads: threads,
  }
}

type post struct {
  prefix string
  board string
  name string
  subject string
  message string
  message_rendered string
  message_id string
  path string
  addr string
  op bool
  posted int64
  parent string
  sage bool
  pubkey string
  reference string
  attachments []AttachmentModel
}

type attachment struct {
  prefix string
  filepath string
  filename string
}

func (self attachment) Prefix() string {
  return self.prefix
}

func (self attachment) RenderTo(wr io.Writer) error {
  // does nothing
  return nil
}

func (self attachment) Thumbnail() string {
  return self.prefix + "thm/" + self.filepath + ".jpg"
}

func (self attachment) Source() string {
  return self.prefix + "img/" + self.filepath
}

func (self attachment) Filename() string {
  return self.filename
}

func PostModelFromMessage(parent, prefix string, nntp NNTPMessage) PostModel {
  p := post{}
  p.name = nntp.Name()
  p.subject = nntp.Subject()
  p.message = nntp.Message()
  p.path = nntp.Path()
  p.message_id = nntp.MessageID()
  p.board = nntp.Newsgroup()
  p.posted = nntp.Posted()
  p.op = nntp.OP()
  p.prefix = prefix
  p.parent = parent
  p.addr = nntp.Addr()
  p.sage = nntp.Sage()
  p.pubkey = nntp.Pubkey()
  for _, att := range nntp.Attachments() {
    p.attachments = append(p.attachments, att.ToModel(prefix))
  }
  return p
}

func (self post) Reference() string {
  return self.parent
}

func (self post) ShortHash() string {
  return ShortHashMessageID(self.message_id)
}

func (self post) Pubkey() string {
  if len(self.pubkey) > 0 {
    return fmt.Sprintf("%s", self.pubkey, makeTripcode(self.pubkey))
  }
  return ""
}

func (self post) Sage() bool {
  return self.sage
}

func (self post) CSSClass() string {
  if self.OP() {
    return "post op"
  } else {
    return "post reply"
  }
}

func (self post) OP() bool {
  return self.parent == self.message_id || len(self.parent) == 0
}

func (self post) Date() string {
  return time.Unix(self.posted, 0).Format(time.ANSIC)
}

func (self post) TemplateDir() string {
  return filepath.Join("contrib", "templates", "default")
}

func (self post) MessageID() string  {
  return self.message_id
}

func (self post) Frontend() string {
  idx := strings.LastIndex(self.path, "!")
  if idx == -1 {
    return self.path
  }
  return self.path[idx+1:]
}

func (self post) Board() string {
  return self.board
}

func (self post) PostHash() string {
  return HashMessageID(self.message_id)
}

func (self post) Name() string {
  return self.name
}

func (self post) Subject() string {
  return self.subject
}

func (self post) Attachments() []AttachmentModel {
  return self.attachments
}

func (self post) PostURL() string {
  return fmt.Sprintf("%sthread-%s.html#%s", self.Prefix(), ShortHashMessageID(self.parent), self.PostHash())
}

func (self post) Prefix() string {
  return self.prefix
}

func (self post) IsClearnet() bool {
  return len(self.addr) == encAddrLen()
}

func (self post) IsI2P() bool {
  return len(self.addr) == i2pDestHashLen()
}

func (self post) IsTor() bool {
  return len(self.addr) == 0
}

func (self post) RenderTo(wr io.Writer) error {
  _, err := io.WriteString(wr, self.RenderPost())
  return err
}

func (self post) RenderPost() string {
  return template.renderTemplate("post.mustache", self)
}

func (self post) Truncate() PostModel {
  if len(self.message) > 500 {
    message := self.message[:500] + "\n...\n[Post Truncated]\n"
    return post{
      message: message,
      prefix: self.prefix,
      board: self.board,
      name: self.name,
      subject: self.subject,
      message_id: self.message_id,
      path: self.path,
      op: self.op,
      posted: self.posted,
      parent: self.parent,
      sage: self.sage,
      pubkey: self.pubkey,
      reference: self.reference,
      attachments: self.attachments,
      addr: self.addr,
    }
  }
  return self
}

func (self post) RenderShortBody() string {
  // TODO: hardcoded limit
  return memeposting(self.message)
}

func (self post) RenderBody() string {
  // :^)
  if len(self.message_rendered) == 0 {
    self.message_rendered = memeposting(self.message)
  }
  return self.message_rendered
}

type thread struct {
  prefix string
  links []LinkModel
  posts []PostModel
}

func (self thread) Prefix() string {
  return self.prefix
}

func (self thread) Navbar() string {
  param := make(map[string]interface{})
  param["name"] = fmt.Sprintf("Thread %s", self.posts[0].ShortHash())
  param["frontend"] = self.Board()
  param["links"] = self.links
  param["prefix"] = self.prefix
  return template.renderTemplate("navbar.mustache", param)
}

func (self thread) Board() string {
  return self.posts[0].Board()
}

func (self thread) BoardURL() string {
  return fmt.Sprintf("%s%s-0.html", self.Prefix(), self.Board())
}

// get our default template dir
func defaultTemplateDir() string {
  return  filepath.Join("contrib", "templates", "default")
}

func (self thread) RenderTo(wr io.Writer) error {
  postform := renderPostForm(self.prefix, self.Board(), self.posts[0].MessageID())
  data := template.renderTemplate("thread.mustache", map[string]interface{} { "thread": self, "form" : postform})
  io.WriteString(wr, data)
  return nil
}

func (self thread) OP() PostModel {
  return self.posts[0]
}

func (self thread) Replies() []PostModel {
  if len(self.posts) > 1 {
    return self.posts[1:]
  }
  return []PostModel{}
}

func (self thread) Truncate() ThreadModel {
  trunc := 5
  if len(self.posts) > trunc {
    return thread{
      links: self.links,
      posts: append([]PostModel{self.posts[0]}, self.posts[len(self.posts)-trunc:]...),
      prefix: self.prefix,
    }
  }
  return self
}

// refetch all replies if anything differs
func (self thread) Update(db Database) ThreadModel {
  root := self.posts[0].MessageID()
  reply_count := db.CountThreadReplies(root)

  if int(reply_count) + 1 != len(self.posts) {

    return thread{
      posts: append([]PostModel{self.posts[0]}, db.GetThreadReplyPostModels(self.prefix, root, 0)...),
      links: self.links,
      prefix: self.prefix,
    }
  }
  return self
}

type linkModel struct {
  text string
  link string
}

func (self linkModel) LinkURL() string {
  return self.link
}

func (self linkModel) Text() string {
  return self.text
}

type boardPageRow struct {
  Board string
  Hour int64
  Day int64
  All int64
}

type boardPageRows []boardPageRow

func (self boardPageRows) Len() int {
  return len(self)
}

func (self boardPageRows) Less(i, j int) bool {
  i_val := self[i]
  j_val := self[j]
  return (i_val.Day + i_val.Hour * 24 ) > ( j_val.Day + j_val.Hour * 24)
}

func (self boardPageRows) Swap(i, j int) {
  self[i] , self[j] = self[j], self[i]
}

type postsGraphRow struct {
  day time.Time
  Num int64
  mag int64
}

func (p *postsGraphRow) GraphRune(r string) (s string) {
  var num int64
  if p.mag > 0 {
    num = p.Num / p.mag
  } else {
    num = p.Num
  }
  for num > 0 {
    s += r
    num --
  }
  return
}

func (p postsGraphRow) Day() (s string) {
  return p.day.String()[5:11]
}

func (p postsGraphRow) RegularGraph() (s string) {
  return p.GraphRune("=")
}

// :0========3 overcock :3 graph of data
func (p postsGraphRow) OvercockGraph() (s string) {
  var num int64
  if p.mag > 0 {
    num = p.Num / p.mag
  } else {
    num = p.Num
  }
  if num > 0 {
    s = ":0"
    num -= 1
    for num > 0 {
      s += "="
      num--
    }
    s += "3"
  } else {
    s = ":3"
  }
  return
}

type postsGraph []postsGraphRow

func (graph postsGraph) Render() string {
  return template.renderTemplate("posts_graph.mustache", map[string]interface{} { "graph": graph})
}

func (self postsGraph) Len() int {
  return len(self)
}

func (self postsGraph) Less(i, j int) bool {
  i_val := self[i]
  j_val := self[j]
  return i_val.day.Day() > j_val.day.Day()
}

func (self postsGraph) Swap(i, j int) {
  self[i] , self[j] = self[j], self[i]
}

func (self postsGraph) Scale() (graph postsGraph) {
  // find max
  max := int64(0)
  for _, p := range self {
    if p.Num > max {
      max = p.Num
    }
  }
  mag := max / 25
  for _, p := range self {
    p.mag = mag
    graph = append(graph, p)
  }
  return
}

type overviewModel []PostModel

func (o overviewModel) Render() string {
  return template.renderTemplate("overview.mustache", map[string]interface{} { "overview" : o })
}
<commit_msg>truncate large subjects in posts<commit_after>//
// model.go
// template model implementation
//
package srnd

import (
  "fmt"
  "io"
  "path/filepath"
  "strings"
  "time"
)

// base model type
type BaseModel interface {

  // site url prefix
  Prefix() string

  // render to a writer
  RenderTo(wr io.Writer) error

}

// for attachments
type AttachmentModel interface {

  BaseModel

  Thumbnail() string
  Source() string
  Filename() string

}

// for individual posts
type PostModel interface {

  BaseModel

  CSSClass() string

  MessageID() string
  PostHash() string
  ShortHash() string
  PostURL() string
  Frontend() string
  Subject() string
  Name() string
  Date() string
  OP() bool
  Attachments() []AttachmentModel
  Board() string
  Sage() bool
  Pubkey() string
  Reference() string

  RenderBody() string
  RenderPost() string

  IsI2P() bool
  IsTor() bool
  IsClearnet() bool

  // truncate body to a certain size
  // return copy
  Truncate() PostModel

}

// interface for models that have a navbar
type NavbarModel interface {

  Navbar() string

}

// for threads
type ThreadModel interface {

  BaseModel
  NavbarModel

  OP() PostModel
  Replies() []PostModel
  Board() string
  BoardURL() string
  // return a short version of the thread
  // does not include all replies
  Truncate() ThreadModel
  // update the thread's replies
  // return the updated model
  Update(db Database) ThreadModel
}

// board interface
// for 1 page on a board
type BoardModel interface {

  BaseModel
  NavbarModel

  Frontend() string
  Name() string
  Threads() []ThreadModel

  // JUST update this thread
  // if we don't have it already loaded do nothing
  UpdateThread(message_id string, db Database) BoardModel

  // get a thread model with this id
  // returns nil if we don't have it
  GetThread(message_id string) ThreadModel

  // deprecated, use GetThread
  HasThread(message_id string) bool

  // update the board's contents
  // return the updated model
  Update(db Database) BoardModel
}

type LinkModel interface {

  Text() string
  LinkURL() string
}

// newsgroup model
// every page on a newsgroup
type GroupModel []BoardModel

// TODO: optimize using 1 query?
// update every page
// return updated model
func (self GroupModel) UpdateAll(db Database) GroupModel {
  for idx, page := range self {
    self[idx] = page.Update(db)
  }
  return self
}

// update a certain page
// does nothing if out of bounds
func (self GroupModel) Update(page int, db Database) GroupModel {
  if len(self) > page {
    self[page] = self[page].Update(db)
  }
  return self
}

type boardModel struct {
  frontend string
  prefix string
  board string
  page int
  pages int
  threads []ThreadModel
}

func (self boardModel) Navbar() string {
  param := make(map[string]interface{})
  param["name"] = fmt.Sprintf("page %d for %s", self.page, self.board)
  param["frontend"] = self.frontend
  var links []LinkModel
  for i := 0 ; i < self.pages ; i ++ {
    links = append(links, linkModel{
      link: fmt.Sprintf("%s%s-%d.html", self.prefix, self.board, i),
      text: fmt.Sprintf("[ %d ]", i),
    })
  }
  param["prefix"] = self.prefix
  param["links"] = links
  return template.renderTemplate("navbar.mustache", param)
}

func (self boardModel) UpdateThread(messageID string, db Database) BoardModel {

  for idx, th := range self.threads {
    if th.OP().MessageID() == messageID {
      // found it
      self.threads[idx] = th.Update(db)
      break
    }
  }
  return self
}

func (self boardModel) GetThread(messageID string) ThreadModel {
  for _, th := range self.threads {
    if th.OP().MessageID() == messageID {
      return th
    }
  }
  return nil
}

func (self boardModel) HasThread(messageID string) bool {
  return self.GetThread(messageID) != nil
}

func (self boardModel) Frontend() string {
  return self.frontend
}

func (self boardModel) Prefix() string {
  return self.prefix
}

func (self boardModel) Name() string {
  return self.board
}

func (self boardModel) Threads() []ThreadModel {
  return self.threads
}

func (self boardModel) RenderTo(wr io.Writer) error {
  param := make(map[string]interface{})
  param["board"] = self
  param["form"] = renderPostForm(self.Prefix(), self.board, "")
  _, err := io.WriteString(wr, template.renderTemplate("board.mustache", param))
  return err
}

// refetch all threads on this page
func (self boardModel) Update(db Database) BoardModel {
  // ignore error
  perpage, _ := db.GetThreadsPerPage(self.board)
  // refetch all on this page
  model := db.GetGroupForPage(self.prefix, self.frontend, self.board, self.page, int(perpage))
  var threads []ThreadModel
  for _, th := range model.Threads() {
    threads = append(threads, th.Update(db))
  }
  return boardModel{
    frontend: self.frontend,
    prefix: self.prefix,
    board: self.board,
    page: self.page,
    pages: self.pages,
    threads: threads,
  }
}

type post struct {
  prefix string
  board string
  name string
  subject string
  message string
  message_rendered string
  message_id string
  path string
  addr string
  op bool
  posted int64
  parent string
  sage bool
  pubkey string
  reference string
  attachments []AttachmentModel
}

type attachment struct {
  prefix string
  filepath string
  filename string
}

func (self attachment) Prefix() string {
  return self.prefix
}

func (self attachment) RenderTo(wr io.Writer) error {
  // does nothing
  return nil
}

func (self attachment) Thumbnail() string {
  return self.prefix + "thm/" + self.filepath + ".jpg"
}

func (self attachment) Source() string {
  return self.prefix + "img/" + self.filepath
}

func (self attachment) Filename() string {
  return self.filename
}

func PostModelFromMessage(parent, prefix string, nntp NNTPMessage) PostModel {
  p := post{}
  p.name = nntp.Name()
  p.subject = nntp.Subject()
  p.message = nntp.Message()
  p.path = nntp.Path()
  p.message_id = nntp.MessageID()
  p.board = nntp.Newsgroup()
  p.posted = nntp.Posted()
  p.op = nntp.OP()
  p.prefix = prefix
  p.parent = parent
  p.addr = nntp.Addr()
  p.sage = nntp.Sage()
  p.pubkey = nntp.Pubkey()
  for _, att := range nntp.Attachments() {
    p.attachments = append(p.attachments, att.ToModel(prefix))
  }
  return p
}

func (self post) Reference() string {
  return self.parent
}

func (self post) ShortHash() string {
  return ShortHashMessageID(self.message_id)
}

func (self post) Pubkey() string {
  if len(self.pubkey) > 0 {
    return fmt.Sprintf("%s", self.pubkey, makeTripcode(self.pubkey))
  }
  return ""
}

func (self post) Sage() bool {
  return self.sage
}

func (self post) CSSClass() string {
  if self.OP() {
    return "post op"
  } else {
    return "post reply"
  }
}

func (self post) OP() bool {
  return self.parent == self.message_id || len(self.parent) == 0
}

func (self post) Date() string {
  return time.Unix(self.posted, 0).Format(time.ANSIC)
}

func (self post) TemplateDir() string {
  return filepath.Join("contrib", "templates", "default")
}

func (self post) MessageID() string  {
  return self.message_id
}

func (self post) Frontend() string {
  idx := strings.LastIndex(self.path, "!")
  if idx == -1 {
    return self.path
  }
  return self.path[idx+1:]
}

func (self post) Board() string {
  return self.board
}

func (self post) PostHash() string {
  return HashMessageID(self.message_id)
}

func (self post) Name() string {
  return self.name
}

func (self post) Subject() string {
  return self.subject
}

func (self post) Attachments() []AttachmentModel {
  return self.attachments
}

func (self post) PostURL() string {
  return fmt.Sprintf("%sthread-%s.html#%s", self.Prefix(), ShortHashMessageID(self.parent), self.PostHash())
}

func (self post) Prefix() string {
  return self.prefix
}

func (self post) IsClearnet() bool {
  return len(self.addr) == encAddrLen()
}

func (self post) IsI2P() bool {
  return len(self.addr) == i2pDestHashLen()
}

func (self post) IsTor() bool {
  return len(self.addr) == 0
}

func (self post) RenderTo(wr io.Writer) error {
  _, err := io.WriteString(wr, self.RenderPost())
  return err
}

func (self post) RenderPost() string {
  return template.renderTemplate("post.mustache", self)
}

func (self post) Truncate() PostModel {
  if len(self.message) > 500 {
    message := self.message[:500] + "\n...\n[Post Truncated]\n"
    subject := self.subject
    if len(subject) > 30 {
      subject = subject[:30]
    }
    return post{
      message: message,
      prefix: self.prefix,
      board: self.board,
      name: self.name,
      subject: subject,
      message_id: self.message_id,
      path: self.path,
      op: self.op,
      posted: self.posted,
      parent: self.parent,
      sage: self.sage,
      pubkey: self.pubkey,
      reference: self.reference,
      attachments: self.attachments,
      addr: self.addr,
    }
  }
  return self
}

func (self post) RenderShortBody() string {
  // TODO: hardcoded limit
  return memeposting(self.message)
}

func (self post) RenderBody() string {
  // :^)
  if len(self.message_rendered) == 0 {
    self.message_rendered = memeposting(self.message)
  }
  return self.message_rendered
}

type thread struct {
  prefix string
  links []LinkModel
  posts []PostModel
}

func (self thread) Prefix() string {
  return self.prefix
}

func (self thread) Navbar() string {
  param := make(map[string]interface{})
  param["name"] = fmt.Sprintf("Thread %s", self.posts[0].ShortHash())
  param["frontend"] = self.Board()
  param["links"] = self.links
  param["prefix"] = self.prefix
  return template.renderTemplate("navbar.mustache", param)
}

func (self thread) Board() string {
  return self.posts[0].Board()
}

func (self thread) BoardURL() string {
  return fmt.Sprintf("%s%s-0.html", self.Prefix(), self.Board())
}

// get our default template dir
func defaultTemplateDir() string {
  return  filepath.Join("contrib", "templates", "default")
}

func (self thread) RenderTo(wr io.Writer) error {
  postform := renderPostForm(self.prefix, self.Board(), self.posts[0].MessageID())
  data := template.renderTemplate("thread.mustache", map[string]interface{} { "thread": self, "form" : postform})
  io.WriteString(wr, data)
  return nil
}

func (self thread) OP() PostModel {
  return self.posts[0]
}

func (self thread) Replies() []PostModel {
  if len(self.posts) > 1 {
    return self.posts[1:]
  }
  return []PostModel{}
}

func (self thread) Truncate() ThreadModel {
  trunc := 5
  if len(self.posts) > trunc {
    return thread{
      links: self.links,
      posts: append([]PostModel{self.posts[0]}, self.posts[len(self.posts)-trunc:]...),
      prefix: self.prefix,
    }
  }
  return self
}

// refetch all replies if anything differs
func (self thread) Update(db Database) ThreadModel {
  root := self.posts[0].MessageID()
  reply_count := db.CountThreadReplies(root)

  if int(reply_count) + 1 != len(self.posts) {

    return thread{
      posts: append([]PostModel{self.posts[0]}, db.GetThreadReplyPostModels(self.prefix, root, 0)...),
      links: self.links,
      prefix: self.prefix,
    }
  }
  return self
}

type linkModel struct {
  text string
  link string
}

func (self linkModel) LinkURL() string {
  return self.link
}

func (self linkModel) Text() string {
  return self.text
}

type boardPageRow struct {
  Board string
  Hour int64
  Day int64
  All int64
}

type boardPageRows []boardPageRow

func (self boardPageRows) Len() int {
  return len(self)
}

func (self boardPageRows) Less(i, j int) bool {
  i_val := self[i]
  j_val := self[j]
  return (i_val.Day + i_val.Hour * 24 ) > ( j_val.Day + j_val.Hour * 24)
}

func (self boardPageRows) Swap(i, j int) {
  self[i] , self[j] = self[j], self[i]
}

type postsGraphRow struct {
  day time.Time
  Num int64
  mag int64
}

func (p *postsGraphRow) GraphRune(r string) (s string) {
  var num int64
  if p.mag > 0 {
    num = p.Num / p.mag
  } else {
    num = p.Num
  }
  for num > 0 {
    s += r
    num --
  }
  return
}

func (p postsGraphRow) Day() (s string) {
  return p.day.String()[5:11]
}

func (p postsGraphRow) RegularGraph() (s string) {
  return p.GraphRune("=")
}

// :0========3 overcock :3 graph of data
func (p postsGraphRow) OvercockGraph() (s string) {
  var num int64
  if p.mag > 0 {
    num = p.Num / p.mag
  } else {
    num = p.Num
  }
  if num > 0 {
    s = ":0"
    num -= 1
    for num > 0 {
      s += "="
      num--
    }
    s += "3"
  } else {
    s = ":3"
  }
  return
}

type postsGraph []postsGraphRow

func (graph postsGraph) Render() string {
  return template.renderTemplate("posts_graph.mustache", map[string]interface{} { "graph": graph})
}

func (self postsGraph) Len() int {
  return len(self)
}

func (self postsGraph) Less(i, j int) bool {
  i_val := self[i]
  j_val := self[j]
  return i_val.day.Day() > j_val.day.Day()
}

func (self postsGraph) Swap(i, j int) {
  self[i] , self[j] = self[j], self[i]
}

func (self postsGraph) Scale() (graph postsGraph) {
  // find max
  max := int64(0)
  for _, p := range self {
    if p.Num > max {
      max = p.Num
    }
  }
  mag := max / 25
  for _, p := range self {
    p.mag = mag
    graph = append(graph, p)
  }
  return
}

type overviewModel []PostModel

func (o overviewModel) Render() string {
  return template.renderTemplate("overview.mustache", map[string]interface{} { "overview" : o })
}
<|endoftext|>


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

package beaconing

import (
	"context"
	"net"
	"sync"
	"time"

	"github.com/scionproto/scion/go/cs/beacon"
	"github.com/scionproto/scion/go/cs/metrics"
	"github.com/scionproto/scion/go/lib/addr"
	"github.com/scionproto/scion/go/lib/common"
	"github.com/scionproto/scion/go/lib/ctrl/path_mgmt"
	"github.com/scionproto/scion/go/lib/ctrl/seg"
	"github.com/scionproto/scion/go/lib/infra"
	"github.com/scionproto/scion/go/lib/infra/messenger"
	"github.com/scionproto/scion/go/lib/infra/modules/seghandler"
	"github.com/scionproto/scion/go/lib/log"
	"github.com/scionproto/scion/go/lib/periodic"
	"github.com/scionproto/scion/go/lib/snet/addrutil"
	"github.com/scionproto/scion/go/lib/topology"
	"github.com/scionproto/scion/go/proto"
)

// SegmentProvider provides segments to register for the specified type.
type SegmentProvider interface {
	SegmentsToRegister(ctx context.Context, segType proto.PathSegType) (
		<-chan beacon.BeaconOrErr, error)
}

// SegmentStore stores segments in the path database.
type SegmentStore interface {
	StoreSegs(context.Context, []*seghandler.SegWithHP) (seghandler.SegStats, error)
}

var _ periodic.Task = (*Registrar)(nil)

// RegistrarConf is the configuration to create a new registrar.
type RegistrarConf struct {
	Config       ExtenderConf
	SegProvider  SegmentProvider
	SegStore     SegmentStore
	TopoProvider topology.Provider
	Msgr         infra.Messenger
	Period       time.Duration
	SegType      proto.PathSegType
}

// Registrar is used to periodically register path segments with the appropriate
// path servers. Core and Up segments are registered with the local path server.
// Down segments are registered at the core.
type Registrar struct {
	*segExtender
	msgr         infra.Messenger
	segProvider  SegmentProvider
	segStore     SegmentStore
	topoProvider topology.Provider
	segType      proto.PathSegType

	// mutable fields
	lastSucc time.Time
	tick     tick
}

// New creates a new segment regsitration task.
func (cfg RegistrarConf) New() (*Registrar, error) {
	cfg.Config.task = "registrar"
	extender, err := cfg.Config.new()
	if err != nil {
		return nil, err
	}
	r := &Registrar{
		segProvider:  cfg.SegProvider,
		segStore:     cfg.SegStore,
		topoProvider: cfg.TopoProvider,
		segType:      cfg.SegType,
		msgr:         cfg.Msgr,
		tick:         tick{period: cfg.Period},
		segExtender:  extender,
	}
	return r, nil
}

// Name returns the tasks name.
func (r *Registrar) Name() string {
	return "bs_beaconing_registrar"
}

// Run registers path segments for the specified type to path servers.
func (r *Registrar) Run(ctx context.Context) {
	r.tick.now = time.Now()
	if err := r.run(ctx); err != nil {
		log.FromCtx(ctx).Error("[beaconing.Registrar] Unable to register",
			"type", r.segType, "err", err)
	}
	metrics.Registrar.RuntimeWithType(r.segType.String()).Add(time.Since(r.tick.now).Seconds())
	r.tick.updateLast()
}

func (r *Registrar) run(ctx context.Context) error {
	if r.tick.now.Sub(r.lastSucc) < r.tick.period && !r.tick.passed() {
		return nil
	}
	logger := log.FromCtx(ctx)
	segments, err := r.segProvider.SegmentsToRegister(ctx, r.segType)
	if err != nil {
		return err
	}
	peers, nonActivePeers := sortedIntfs(r.cfg.Intfs, topology.Peer)
	if len(nonActivePeers) > 0 {
		logger.Debug("[beaconing.Registrar] Ignore non-active peer interfaces", "type", r.segType,
			"intfs", nonActivePeers)
	}
	if r.segType != proto.PathSegType_down {
		return r.registerLocal(ctx, segments, peers)
	}
	return r.registerRemote(ctx, segments, peers)
}

func (r *Registrar) registerRemote(ctx context.Context, segments <-chan beacon.BeaconOrErr,
	peers []common.IFIDType) error {

	logger := log.FromCtx(ctx)
	s := newSummary()
	var expected int
	var wg sync.WaitGroup
	for bOrErr := range segments {
		if bOrErr.Err != nil {
			logger.Error("[beaconing.Registrar] Unable to get beacon", "err", bOrErr.Err)
			metrics.Registrar.InternalErrorsWithType(r.segType.String()).Inc()
			continue
		}
		if !r.IntfActive(bOrErr.Beacon.InIfId) {
			continue
		}
		if err := r.extend(bOrErr.Beacon.Segment, bOrErr.Beacon.InIfId, 0, peers); err != nil {
			metrics.Registrar.InternalErrorsWithType(r.segType.String()).Inc()
			logger.Error("[beaconing.Registrar] Unable to terminate beacon",
				"beacon", bOrErr.Beacon, "err", err)
			continue
		}
		expected++
		s := remoteRegistrar{
			segType:      r.segType,
			msgr:         r.msgr,
			topoProvider: r.topoProvider,
			summary:      s,
			wg:           &wg,
		}

		// Avoid head-of-line blocking when sending message to slow servers.
		s.start(ctx, bOrErr.Beacon)
	}
	wg.Wait()
	if expected > 0 && s.count <= 0 {
		return common.NewBasicError("No beacons registered", nil, "candidates", expected)
	}
	r.lastSucc = r.tick.now
	r.logSummary(logger, s)
	return nil
}

func (r *Registrar) registerLocal(ctx context.Context, segments <-chan beacon.BeaconOrErr,
	peers []common.IFIDType) error {

	logger := log.FromCtx(ctx)
	beacons := make(map[string]beacon.Beacon)
	var toRegister []*seghandler.SegWithHP
	for bOrErr := range segments {
		if bOrErr.Err != nil {
			logger.Error("[beaconing.Registrar] Unable to get beacon", "err", bOrErr.Err)
			metrics.Registrar.InternalErrorsWithType(r.segType.String()).Inc()
			continue
		}
		if !r.IntfActive(bOrErr.Beacon.InIfId) {
			continue
		}
		if err := r.extend(bOrErr.Beacon.Segment, bOrErr.Beacon.InIfId, 0, peers); err != nil {
			metrics.Registrar.InternalErrorsWithType(r.segType.String()).Inc()
			logger.Error("[beaconing.Registrar] Unable to terminate beacon",
				"beacon", bOrErr.Beacon, "err", err)
			continue
		}
		toRegister = append(toRegister, &seghandler.SegWithHP{
			Seg: &seg.Meta{Type: r.segType, Segment: bOrErr.Beacon.Segment},
		})
		beacons[bOrErr.Beacon.Segment.GetLoggingID()] = bOrErr.Beacon
	}
	if len(toRegister) == 0 {
		return nil
	}
	stats, err := r.segStore.StoreSegs(ctx, toRegister)
	updateMetricsFromStat(stats, beacons, r.segType.String())
	if err != nil {
		return err
	}
	r.lastSucc = r.tick.now
	r.logSummary(logger, summarizeStats(stats, beacons))
	return nil
}

func (r *Registrar) logSummary(logger log.Logger, s *summary) {
	if r.tick.passed() {
		logger.Info("[beaconing.Registrar] Registered beacons", "type", r.segType, "count", s.count,
			"startIAs", len(s.srcs))
		return
	}
	if s.count > 0 {
		logger.Info("[beaconing.Registrar] Registered beacons after stale period",
			"type", r.segType, "count", s.count, "startIAs", len(s.srcs))
	}
}

// remoteRegistrar registers one segment with the path server.
type remoteRegistrar struct {
	segType      proto.PathSegType
	msgr         infra.Messenger
	topoProvider topology.Provider
	summary      *summary
	wg           *sync.WaitGroup
}

// start extends the beacon and starts a go routine that registers the beacon
// with the path server.
func (r *remoteRegistrar) start(ctx context.Context, bseg beacon.Beacon) {
	logger := log.FromCtx(ctx)
	reg := &path_mgmt.SegReg{
		SegRecs: &path_mgmt.SegRecs{
			Recs: []*seg.Meta{
				{
					Type:    r.segType,
					Segment: bseg.Segment,
				},
			},
		},
	}
	addr, err := addrutil.GetPath(addr.SvcPS, bseg.Segment, r.topoProvider)
	if err != nil {
		metrics.Registrar.InternalErrorsWithType(r.segType.String()).Inc()
		logger.Error("[beaconing.Registrar] Unable to choose server", "err", err)
		return
	}
	r.startSendSegReg(ctx, bseg, reg, addr)
}

// startSendSegReg adds to the wait group and starts a goroutine that sends the
// registration message to the peer.
func (r *remoteRegistrar) startSendSegReg(ctx context.Context, bseg beacon.Beacon,
	reg *path_mgmt.SegReg, addr net.Addr) {

	r.wg.Add(1)
	go func() {
		defer log.HandlePanic()
		defer r.wg.Done()
		logger := log.FromCtx(ctx)
		if err := r.msgr.SendSegReg(ctx, reg, addr, messenger.NextId()); err != nil {
			logger.Error("[beaconing.Registrar] Unable to register segment", "type", r.segType,
				"addr", addr, "err", err)
			metrics.Registrar.InternalErrorsWithType(r.segType.String()).Inc()
			return
		}
		r.summary.AddSrc(bseg.Segment.FirstIA())
		r.summary.Inc()
		l := metrics.RegistrarLabels{
			SegType: r.segType.String(),
			StartIA: bseg.Segment.FirstIA(),
			InIfID:  bseg.InIfId,
			Result:  metrics.Success,
		}
		metrics.Registrar.Beacons(l).Inc()
		logger.Trace("[beaconing.Registrar] Successfully registered segment", "type", r.segType,
			"addr", addr, "seg", bseg.Segment)
	}()
}

func updateMetricsFromStat(s seghandler.SegStats, b map[string]beacon.Beacon, segType string) {
	for _, id := range s.InsertedSegs {
		metrics.Registrar.Beacons(metrics.RegistrarLabels{
			InIfID:  b[id].InIfId,
			StartIA: b[id].Segment.FirstIA(),
			Result:  metrics.OkNew,
			SegType: segType,
		}).Inc()
	}
	for _, id := range s.UpdatedSegs {
		metrics.Registrar.Beacons(metrics.RegistrarLabels{
			InIfID:  b[id].InIfId,
			StartIA: b[id].Segment.FirstIA(),
			Result:  metrics.OkUpdated,
			SegType: segType,
		}).Inc()
	}
	if c := len(b) - s.Total(); c > 0 {
		metrics.Registrar.InternalErrorsWithType(segType).Add(float64(c))
	}
}

func summarizeStats(s seghandler.SegStats, b map[string]beacon.Beacon) *summary {
	sum := newSummary()
	for _, id := range append(s.InsertedSegs, s.UpdatedSegs...) {
		sum.AddSrc(b[id].Segment.FirstIA())
		sum.Inc()
	}
	return sum
}
<commit_msg>cs: fix fast beaconing recovery (#3718)<commit_after>// Copyright 2019 Anapaya Systems
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package beaconing

import (
	"context"
	"net"
	"sync"
	"time"

	"github.com/scionproto/scion/go/cs/beacon"
	"github.com/scionproto/scion/go/cs/metrics"
	"github.com/scionproto/scion/go/lib/addr"
	"github.com/scionproto/scion/go/lib/common"
	"github.com/scionproto/scion/go/lib/ctrl/path_mgmt"
	"github.com/scionproto/scion/go/lib/ctrl/seg"
	"github.com/scionproto/scion/go/lib/infra"
	"github.com/scionproto/scion/go/lib/infra/messenger"
	"github.com/scionproto/scion/go/lib/infra/modules/seghandler"
	"github.com/scionproto/scion/go/lib/log"
	"github.com/scionproto/scion/go/lib/periodic"
	"github.com/scionproto/scion/go/lib/snet/addrutil"
	"github.com/scionproto/scion/go/lib/topology"
	"github.com/scionproto/scion/go/proto"
)

// SegmentProvider provides segments to register for the specified type.
type SegmentProvider interface {
	SegmentsToRegister(ctx context.Context, segType proto.PathSegType) (
		<-chan beacon.BeaconOrErr, error)
}

// SegmentStore stores segments in the path database.
type SegmentStore interface {
	StoreSegs(context.Context, []*seghandler.SegWithHP) (seghandler.SegStats, error)
}

var _ periodic.Task = (*Registrar)(nil)

// RegistrarConf is the configuration to create a new registrar.
type RegistrarConf struct {
	Config       ExtenderConf
	SegProvider  SegmentProvider
	SegStore     SegmentStore
	TopoProvider topology.Provider
	Msgr         infra.Messenger
	Period       time.Duration
	SegType      proto.PathSegType
}

// Registrar is used to periodically register path segments with the appropriate
// path servers. Core and Up segments are registered with the local path server.
// Down segments are registered at the core.
type Registrar struct {
	*segExtender
	msgr         infra.Messenger
	segProvider  SegmentProvider
	segStore     SegmentStore
	topoProvider topology.Provider
	segType      proto.PathSegType

	// mutable fields
	lastSucc time.Time
	tick     tick
}

// New creates a new segment regsitration task.
func (cfg RegistrarConf) New() (*Registrar, error) {
	cfg.Config.task = "registrar"
	extender, err := cfg.Config.new()
	if err != nil {
		return nil, err
	}
	r := &Registrar{
		segProvider:  cfg.SegProvider,
		segStore:     cfg.SegStore,
		topoProvider: cfg.TopoProvider,
		segType:      cfg.SegType,
		msgr:         cfg.Msgr,
		tick:         tick{period: cfg.Period},
		segExtender:  extender,
	}
	return r, nil
}

// Name returns the tasks name.
func (r *Registrar) Name() string {
	return "bs_beaconing_registrar"
}

// Run registers path segments for the specified type to path servers.
func (r *Registrar) Run(ctx context.Context) {
	r.tick.now = time.Now()
	if err := r.run(ctx); err != nil {
		log.FromCtx(ctx).Error("[beaconing.Registrar] Unable to register",
			"type", r.segType, "err", err)
	}
	metrics.Registrar.RuntimeWithType(r.segType.String()).Add(time.Since(r.tick.now).Seconds())
	r.tick.updateLast()
}

func (r *Registrar) run(ctx context.Context) error {
	if r.tick.now.Sub(r.lastSucc) < r.tick.period && !r.tick.passed() {
		return nil
	}
	logger := log.FromCtx(ctx)
	segments, err := r.segProvider.SegmentsToRegister(ctx, r.segType)
	if err != nil {
		return err
	}
	peers, nonActivePeers := sortedIntfs(r.cfg.Intfs, topology.Peer)
	if len(nonActivePeers) > 0 {
		logger.Debug("[beaconing.Registrar] Ignore non-active peer interfaces", "type", r.segType,
			"intfs", nonActivePeers)
	}
	if r.segType != proto.PathSegType_down {
		return r.registerLocal(ctx, segments, peers)
	}
	return r.registerRemote(ctx, segments, peers)
}

func (r *Registrar) registerRemote(ctx context.Context, segments <-chan beacon.BeaconOrErr,
	peers []common.IFIDType) error {

	logger := log.FromCtx(ctx)
	s := newSummary()
	var expected int
	var wg sync.WaitGroup
	for bOrErr := range segments {
		if bOrErr.Err != nil {
			logger.Error("[beaconing.Registrar] Unable to get beacon", "err", bOrErr.Err)
			metrics.Registrar.InternalErrorsWithType(r.segType.String()).Inc()
			continue
		}
		if !r.IntfActive(bOrErr.Beacon.InIfId) {
			continue
		}
		if err := r.extend(bOrErr.Beacon.Segment, bOrErr.Beacon.InIfId, 0, peers); err != nil {
			metrics.Registrar.InternalErrorsWithType(r.segType.String()).Inc()
			logger.Error("[beaconing.Registrar] Unable to terminate beacon",
				"beacon", bOrErr.Beacon, "err", err)
			continue
		}
		expected++
		s := remoteRegistrar{
			segType:      r.segType,
			msgr:         r.msgr,
			topoProvider: r.topoProvider,
			summary:      s,
			wg:           &wg,
		}

		// Avoid head-of-line blocking when sending message to slow servers.
		s.start(ctx, bOrErr.Beacon)
	}
	wg.Wait()
	if expected > 0 && s.count <= 0 {
		return common.NewBasicError("No beacons registered", nil, "candidates", expected)
	}
	if s.count > 0 {
		r.lastSucc = r.tick.now
	}
	r.logSummary(logger, s)
	return nil
}

func (r *Registrar) registerLocal(ctx context.Context, segments <-chan beacon.BeaconOrErr,
	peers []common.IFIDType) error {

	logger := log.FromCtx(ctx)
	beacons := make(map[string]beacon.Beacon)
	var toRegister []*seghandler.SegWithHP
	for bOrErr := range segments {
		if bOrErr.Err != nil {
			logger.Error("[beaconing.Registrar] Unable to get beacon", "err", bOrErr.Err)
			metrics.Registrar.InternalErrorsWithType(r.segType.String()).Inc()
			continue
		}
		if !r.IntfActive(bOrErr.Beacon.InIfId) {
			continue
		}
		if err := r.extend(bOrErr.Beacon.Segment, bOrErr.Beacon.InIfId, 0, peers); err != nil {
			metrics.Registrar.InternalErrorsWithType(r.segType.String()).Inc()
			logger.Error("[beaconing.Registrar] Unable to terminate beacon",
				"beacon", bOrErr.Beacon, "err", err)
			continue
		}
		toRegister = append(toRegister, &seghandler.SegWithHP{
			Seg: &seg.Meta{Type: r.segType, Segment: bOrErr.Beacon.Segment},
		})
		beacons[bOrErr.Beacon.Segment.GetLoggingID()] = bOrErr.Beacon
	}
	if len(toRegister) == 0 {
		return nil
	}
	stats, err := r.segStore.StoreSegs(ctx, toRegister)
	updateMetricsFromStat(stats, beacons, r.segType.String())
	if err != nil {
		return err
	}
	r.lastSucc = r.tick.now
	r.logSummary(logger, summarizeStats(stats, beacons))
	return nil
}

func (r *Registrar) logSummary(logger log.Logger, s *summary) {
	if r.tick.passed() {
		logger.Info("[beaconing.Registrar] Registered beacons", "type", r.segType, "count", s.count,
			"startIAs", len(s.srcs))
		return
	}
	if s.count > 0 {
		logger.Info("[beaconing.Registrar] Registered beacons after stale period",
			"type", r.segType, "count", s.count, "startIAs", len(s.srcs))
	}
}

// remoteRegistrar registers one segment with the path server.
type remoteRegistrar struct {
	segType      proto.PathSegType
	msgr         infra.Messenger
	topoProvider topology.Provider
	summary      *summary
	wg           *sync.WaitGroup
}

// start extends the beacon and starts a go routine that registers the beacon
// with the path server.
func (r *remoteRegistrar) start(ctx context.Context, bseg beacon.Beacon) {
	logger := log.FromCtx(ctx)
	reg := &path_mgmt.SegReg{
		SegRecs: &path_mgmt.SegRecs{
			Recs: []*seg.Meta{
				{
					Type:    r.segType,
					Segment: bseg.Segment,
				},
			},
		},
	}
	addr, err := addrutil.GetPath(addr.SvcPS, bseg.Segment, r.topoProvider)
	if err != nil {
		metrics.Registrar.InternalErrorsWithType(r.segType.String()).Inc()
		logger.Error("[beaconing.Registrar] Unable to choose server", "err", err)
		return
	}
	r.startSendSegReg(ctx, bseg, reg, addr)
}

// startSendSegReg adds to the wait group and starts a goroutine that sends the
// registration message to the peer.
func (r *remoteRegistrar) startSendSegReg(ctx context.Context, bseg beacon.Beacon,
	reg *path_mgmt.SegReg, addr net.Addr) {

	r.wg.Add(1)
	go func() {
		defer log.HandlePanic()
		defer r.wg.Done()
		logger := log.FromCtx(ctx)
		if err := r.msgr.SendSegReg(ctx, reg, addr, messenger.NextId()); err != nil {
			logger.Error("[beaconing.Registrar] Unable to register segment", "type", r.segType,
				"addr", addr, "err", err)
			metrics.Registrar.InternalErrorsWithType(r.segType.String()).Inc()
			return
		}
		r.summary.AddSrc(bseg.Segment.FirstIA())
		r.summary.Inc()
		l := metrics.RegistrarLabels{
			SegType: r.segType.String(),
			StartIA: bseg.Segment.FirstIA(),
			InIfID:  bseg.InIfId,
			Result:  metrics.Success,
		}
		metrics.Registrar.Beacons(l).Inc()
		logger.Trace("[beaconing.Registrar] Successfully registered segment", "type", r.segType,
			"addr", addr, "seg", bseg.Segment)
	}()
}

func updateMetricsFromStat(s seghandler.SegStats, b map[string]beacon.Beacon, segType string) {
	for _, id := range s.InsertedSegs {
		metrics.Registrar.Beacons(metrics.RegistrarLabels{
			InIfID:  b[id].InIfId,
			StartIA: b[id].Segment.FirstIA(),
			Result:  metrics.OkNew,
			SegType: segType,
		}).Inc()
	}
	for _, id := range s.UpdatedSegs {
		metrics.Registrar.Beacons(metrics.RegistrarLabels{
			InIfID:  b[id].InIfId,
			StartIA: b[id].Segment.FirstIA(),
			Result:  metrics.OkUpdated,
			SegType: segType,
		}).Inc()
	}
	if c := len(b) - s.Total(); c > 0 {
		metrics.Registrar.InternalErrorsWithType(segType).Add(float64(c))
	}
}

func summarizeStats(s seghandler.SegStats, b map[string]beacon.Beacon) *summary {
	sum := newSummary()
	for _, id := range append(s.InsertedSegs, s.UpdatedSegs...) {
		sum.AddSrc(b[id].Segment.FirstIA())
		sum.Inc()
	}
	return sum
}
<|endoftext|>


<commit_before>package paddlecloud

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"os"
)

// HTTPOK is ok status of http api call
const HTTPOK = "200 OK"

var httpTransport = &http.Transport{}

func makeRequest(uri string, method string, body io.Reader,
	contentType string, query map[string]string,
	authHeader map[string]string) (*http.Request, error) {
	req, err := http.NewRequest(method, uri, nil)
	if err != nil {
		return nil, err
	}
	// default contentType is application/json
	if len(contentType) == 0 {
		req.Header.Set("Content-Type", "application/json")
	} else {
		req.Header.Set("Content-Type", contentType)
	}

	for k, v := range authHeader {
		req.Header.Set(k, v)
	}

	q := req.URL.Query()
	for k, v := range query {
		q.Add(k, v)
	}
	req.URL.RawQuery = q.Encode()
	return req, nil
}

// makeRequestToken use client token to make a authorized request
func makeRequestToken(uri string, method string, body io.Reader,
	contentType string, query map[string]string) (*http.Request, error) {
	// get client token
	token, err := token()
	if err != nil {
		return nil, err
	}
	authHeader := make(map[string]string)
	authHeader["Authorization"] = "Token " + token
	return makeRequest(uri, method, body, contentType, query, authHeader)
}

// NOTE: add other request makers if we need other auth methods

func getResponse(req *http.Request) ([]byte, error) {
	client := &http.Client{Transport: httpTransport}
	resp, err := client.Do(req)
	if err != nil {
		return []byte{}, err
	}
	defer resp.Body.Close()
	if resp.Status != HTTPOK {
		return []byte{}, errors.New("server error: " + resp.Status)
	}
	// FIXME: add more resp.Status checks
	return ioutil.ReadAll(resp.Body)
}

// GetCall make a GET call to targetURL with k-v params of query
func GetCall(targetURL string, query map[string]string) ([]byte, error) {
	req, err := makeRequestToken(targetURL, "GET", nil, "", query)
	if err != nil {
		return []byte{}, err
	}
	return getResponse(req)
}

// PostCall make a POST call to targetURL with a json body
func PostCall(targetURL string, jsonString []byte) ([]byte, error) {
	req, err := makeRequestToken(targetURL, "POST", bytes.NewBuffer(jsonString), "", nil)
	if err != nil {
		return []byte{}, err
	}
	return getResponse(req)
}

// DeleteCall make a DELETE call to targetURL with a json body
func DeleteCall(targetURL string, jsonString []byte) ([]byte, error) {
	req, err := makeRequestToken(targetURL, "DELETE", bytes.NewBuffer(jsonString), "", nil)
	if err != nil {
		return []byte{}, err
	}
	return getResponse(req)
}

// PostFile make a POST call to HTTP server to upload a file
func PostFile(targetURL string, filename string) ([]byte, error) {
	bodyBuf := &bytes.Buffer{}
	bodyWriter := multipart.NewWriter(bodyBuf)

	// this step is very important
	fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error writing to buffer: %v\n", err)
		return []byte{}, err
	}

	// open file handle
	fh, err := os.Open(filename)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error opening file: %v\n", err)
		return []byte{}, err
	}

	//iocopy
	_, err = io.Copy(fileWriter, fh)
	if err != nil {
		return []byte{}, err
	}

	contentType := bodyWriter.FormDataContentType()
	bodyWriter.Close()

	req, err := makeRequestToken(targetURL, "POST", bodyBuf, contentType, nil)
	if err != nil {
		return []byte{}, err
	}
	return getResponse(req)
}
<commit_msg>fix restclient<commit_after>package paddlecloud

import (
	"bytes"
	"errors"
	"fmt"
	"io"
	"io/ioutil"
	"mime/multipart"
	"net/http"
	"os"
)

// HTTPOK is ok status of http api call
const HTTPOK = "200 OK"

var httpTransport = &http.Transport{}

func makeRequest(uri string, method string, body io.Reader,
	contentType string, query map[string]string,
	authHeader map[string]string) (*http.Request, error) {
	req, err := http.NewRequest(method, uri, body)
	if err != nil {
		return nil, err
	}
	// default contentType is application/json
	if len(contentType) == 0 {
		req.Header.Set("Content-Type", "application/json")
	} else {
		req.Header.Set("Content-Type", contentType)
	}

	for k, v := range authHeader {
		req.Header.Set(k, v)
	}

	q := req.URL.Query()
	for k, v := range query {
		q.Add(k, v)
	}
	req.URL.RawQuery = q.Encode()
	return req, nil
}

// makeRequestToken use client token to make a authorized request
func makeRequestToken(uri string, method string, body io.Reader,
	contentType string, query map[string]string) (*http.Request, error) {
	// get client token
	token, err := token()
	if err != nil {
		return nil, err
	}
	authHeader := make(map[string]string)
	authHeader["Authorization"] = "Token " + token
	return makeRequest(uri, method, body, contentType, query, authHeader)
}

// NOTE: add other request makers if we need other auth methods

func getResponse(req *http.Request) ([]byte, error) {
	client := &http.Client{Transport: httpTransport}
	resp, err := client.Do(req)
	if err != nil {
		return []byte{}, err
	}
	defer resp.Body.Close()
	if resp.Status != HTTPOK {
		return []byte{}, errors.New("server error: " + resp.Status)
	}
	// FIXME: add more resp.Status checks
	return ioutil.ReadAll(resp.Body)
}

// GetCall make a GET call to targetURL with k-v params of query
func GetCall(targetURL string, query map[string]string) ([]byte, error) {
	req, err := makeRequestToken(targetURL, "GET", nil, "", query)
	if err != nil {
		return []byte{}, err
	}
	return getResponse(req)
}

// PostCall make a POST call to targetURL with a json body
func PostCall(targetURL string, jsonString []byte) ([]byte, error) {
	req, err := makeRequestToken(targetURL, "POST", bytes.NewBuffer(jsonString), "", nil)
	if err != nil {
		return []byte{}, err
	}
	return getResponse(req)
}

// DeleteCall make a DELETE call to targetURL with a json body
func DeleteCall(targetURL string, jsonString []byte) ([]byte, error) {
	req, err := makeRequestToken(targetURL, "DELETE", bytes.NewBuffer(jsonString), "", nil)
	if err != nil {
		return []byte{}, err
	}
	return getResponse(req)
}

// PostFile make a POST call to HTTP server to upload a file
func PostFile(targetURL string, filename string) ([]byte, error) {
	bodyBuf := &bytes.Buffer{}
	bodyWriter := multipart.NewWriter(bodyBuf)

	// this step is very important
	fileWriter, err := bodyWriter.CreateFormFile("uploadfile", filename)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error writing to buffer: %v\n", err)
		return []byte{}, err
	}

	// open file handle
	fh, err := os.Open(filename)
	if err != nil {
		fmt.Fprintf(os.Stderr, "error opening file: %v\n", err)
		return []byte{}, err
	}

	//iocopy
	_, err = io.Copy(fileWriter, fh)
	if err != nil {
		return []byte{}, err
	}

	contentType := bodyWriter.FormDataContentType()
	bodyWriter.Close()

	req, err := makeRequestToken(targetURL, "POST", bodyBuf, contentType, nil)
	if err != nil {
		return []byte{}, err
	}
	return getResponse(req)
}
<|endoftext|>


<commit_before>package main

import (
	"exp/inotify"
	"fmt"
	"io"
	"koding/tools/dnode"
	"koding/tools/kite"
	"koding/tools/log"
	"koding/virt"
	"os"
	"path"
	"regexp"
	"strconv"
	"sync"
	"time"
)

var watcher *inotify.Watcher
var watchMap = make(map[string][]*Watch)
var watchMutex sync.Mutex

type Watch struct {
	VOS      *virt.VOS
	Path     string
	Callback dnode.Callback
}

type FileEntry struct {
	Name     string      `json:"name"`
	IsDir    bool        `json:"isDir"`
	Size     int64       `json:"size"`
	Mode     os.FileMode `json:"mode"`
	Time     time.Time   `json:"time"`
	IsBroken bool        `json:"isBroken"`
	Readable bool        `json:"readable"`
	Writable bool        `json:"writable"`
}

func init() {
	var err error
	watcher, err = inotify.NewWatcher()
	if err != nil {
		panic(err)
	}

	go func() {
		for ev := range watcher.Event {
			if (ev.Mask & (inotify.IN_CREATE | inotify.IN_MOVED_TO | inotify.IN_ATTRIB)) != 0 {
				info, err := os.Lstat(ev.Name)
				if err != nil {
					if os.IsNotExist(err) {
						continue // skip this event, file is deleted and deletion event will follow
					}
					log.Warn("Watcher error", err)
					continue
				}
				watchMutex.Lock()
				for _, watch := range watchMap[path.Dir(ev.Name)] {
					watch.Callback(map[string]interface{}{
						"event": "added",
						"file":  makeFileEntry(watch.VOS, watch.Path, info),
					})
				}
				watchMutex.Unlock()
				continue
			}
			if (ev.Mask & (inotify.IN_DELETE | inotify.IN_MOVED_FROM)) != 0 {
				watchMutex.Lock()
				for _, watch := range watchMap[path.Dir(ev.Name)] {
					watch.Callback(map[string]interface{}{
						"event": "removed",
						"file":  FileEntry{Name: path.Base(ev.Name)},
					})
				}
				watchMutex.Unlock()
				continue
			}
		}
	}()
	go func() {
		for err := range watcher.Error {
			log.Warn("Watcher error", err)
		}
	}()
}

func registerFileSystemMethods(k *kite.Kite) {
	registerVmMethod(k, "fs.readDirectory", false, func(args *dnode.Partial, channel *kite.Channel, vos *virt.VOS) (interface{}, error) {
		var params struct {
			Path     string
			OnChange dnode.Callback
		}
		if args.Unmarshal(&params) != nil || params.Path == "" {
			return nil, &kite.ArgumentError{Expected: "{ path: [string], onChange: [function] }"}
		}

		response := make(map[string]interface{})

		if params.OnChange != nil {
			watchedPath, err := vos.AddWatch(watcher, params.Path, inotify.IN_CREATE|inotify.IN_DELETE|inotify.IN_MOVE|inotify.IN_ATTRIB)
			if err != nil {
				return nil, err
			}

			watchMutex.Lock()
			defer watchMutex.Unlock()

			watch := &Watch{vos, watchedPath, params.OnChange}
			watchMap[watchedPath] = append(watchMap[watchedPath], watch)
			channel.OnDisconnect(func() { watch.Close() })
			response["stopWatching"] = func() { watch.Close() }
		}

		dir, err := vos.Open(params.Path)
		if err != nil {
			return nil, err
		}
		defer dir.Close()

		infos, err := dir.Readdir(0)
		if err != nil {
			return nil, err
		}

		files := make([]FileEntry, len(infos))
		for i, info := range infos {
			files[i] = makeFileEntry(vos, params.Path, info)
		}
		response["files"] = files

		return response, nil
	})

	registerVmMethod(k, "fs.readFile", false, func(args *dnode.Partial, channel *kite.Channel, vos *virt.VOS) (interface{}, error) {
		var params struct {
			Path string
		}
		if args.Unmarshal(&params) != nil || params.Path == "" {
			return nil, &kite.ArgumentError{Expected: "{ path: [string] }"}
		}

		file, err := vos.Open(params.Path)
		if err != nil {
			return nil, err
		}
		defer file.Close()

		fi, err := file.Stat()
		if err != nil {
			return nil, err
		}

		if fi.Size() > 10*1024*1024 {
			return nil, fmt.Errorf("File larger than 10MiB.")
		}

		buf := make([]byte, fi.Size())
		if _, err := io.ReadFull(file, buf); err != nil {
			return nil, err
		}

		return map[string]interface{}{"content": buf}, nil
	})

	registerVmMethod(k, "fs.writeFile", false, func(args *dnode.Partial, channel *kite.Channel, vos *virt.VOS) (interface{}, error) {
		var params struct {
			Path           string
			Content        []byte
			DoNotOverwrite bool
		}
		if args.Unmarshal(&params) != nil || params.Path == "" || params.Content == nil {
			return nil, &kite.ArgumentError{Expected: "{ path: [string], content: [base64], doNotOverwrite: [bool] }"}
		}

		flags := os.O_RDWR | os.O_CREATE | os.O_TRUNC
		if params.DoNotOverwrite {
			flags |= os.O_EXCL
		}
		file, err := vos.OpenFile(params.Path, flags, 0666)
		if err != nil {
			return nil, err
		}
		defer file.Close()

		return file.Write(params.Content)
	})

	suffixRegexp, err := regexp.Compile(`.((_\d+)?)(\.\w*)?$`)
	if err != nil {
		panic(err)
	}

	registerVmMethod(k, "fs.ensureNonexistentPath", false, func(args *dnode.Partial, channel *kite.Channel, vos *virt.VOS) (interface{}, error) {
		var params struct {
			Path string
		}
		if args.Unmarshal(&params) != nil || params.Path == "" {
			return nil, &kite.ArgumentError{Expected: "{ path: [string] }"}
		}

		name := params.Path
		index := 1
		for {
			_, err := vos.Stat(name)
			if err != nil {
				if os.IsNotExist(err) {
					break
				}
				return nil, err
			}

			loc := suffixRegexp.FindStringSubmatchIndex(name)
			name = name[:loc[2]] + "_" + strconv.Itoa(index) + name[loc[3]:]
			index++
		}

		return name, nil
	})

	registerVmMethod(k, "fs.getInfo", false, func(args *dnode.Partial, channel *kite.Channel, vos *virt.VOS) (interface{}, error) {
		var params struct {
			Path string
		}
		if args.Unmarshal(&params) != nil || params.Path == "" {
			return nil, &kite.ArgumentError{Expected: "{ path: [string] }"}
		}

		fi, err := vos.Stat(params.Path)
		if err != nil {
			if os.IsNotExist(err) {
				return nil, nil
			}
			return nil, err
		}

		return makeFileEntry(vos, path.Dir(params.Path), fi), nil
	})

	registerVmMethod(k, "fs.setPermissions", false, func(args *dnode.Partial, channel *kite.Channel, vos *virt.VOS) (interface{}, error) {
		var params struct {
			Path      string
			Mode      os.FileMode
			Recursive bool
		}
		if args.Unmarshal(&params) != nil || params.Path == "" {
			return nil, &kite.ArgumentError{Expected: "{ path: [string], mode: [integer], recursive: [bool] }"}
		}

		var doChange func(name string) error
		doChange = func(name string) error {
			if err := vos.Chmod(name, params.Mode); err != nil {
				return err
			}
			if !params.Recursive {
				return nil
			}
			fi, err := vos.Stat(name)
			if err != nil {
				return err
			}
			if !fi.IsDir() {
				return nil
			}
			dir, err := vos.Open(name)
			if err != nil {
				return err
			}
			defer dir.Close()
			entries, err := dir.Readdirnames(0)
			if err != nil {
				return err
			}
			var firstErr error
			for _, entry := range entries {
				err := doChange(name + "/" + entry)
				if err != nil && firstErr == nil {
					firstErr = err
				}
			}
			return firstErr
		}
		if err := doChange(params.Path); err != nil {
			return nil, err
		}

		return true, nil
	})

	registerVmMethod(k, "fs.remove", false, func(args *dnode.Partial, channel *kite.Channel, vos *virt.VOS) (interface{}, error) {
		var params struct {
			Path      string
			Recursive bool
		}
		if args.Unmarshal(&params) != nil || params.Path == "" {
			return nil, &kite.ArgumentError{Expected: "{ path: [string], recursive: [bool] }"}
		}

		if params.Recursive {
			if err := vos.RemoveAll(params.Path); err != nil {
				return nil, err
			}
			return true, nil
		}

		if err := vos.Remove(params.Path); err != nil {
			return nil, err
		}
		return true, nil
	})

	registerVmMethod(k, "fs.rename", false, func(args *dnode.Partial, channel *kite.Channel, vos *virt.VOS) (interface{}, error) {
		var params struct {
			OldPath string
			NewPath string
		}
		if args.Unmarshal(&params) != nil || params.OldPath == "" || params.NewPath == "" {
			return nil, &kite.ArgumentError{Expected: "{ oldPath: [string], newPath: [string] }"}
		}

		if err := vos.Rename(params.OldPath, params.NewPath); err != nil {
			return nil, err
		}

		return true, nil
	})

	registerVmMethod(k, "fs.createDirectory", false, func(args *dnode.Partial, channel *kite.Channel, vos *virt.VOS) (interface{}, error) {
		var params struct {
			Path      string
			Recursive bool
		}
		if args.Unmarshal(&params) != nil || params.Path == "" {
			return nil, &kite.ArgumentError{Expected: "{ path: [string], recursive: [bool] }"}
		}

		if params.Recursive {
			if err := vos.MkdirAll(params.Path, 0755); err != nil {
				return nil, err
			}
			return true, nil
		}

		if err := vos.Mkdir(params.Path, 0755); err != nil {
			return nil, err
		}
		return true, nil
	})
}

func (watch *Watch) Close() error {
	watchMutex.Lock()
	defer watchMutex.Unlock()

	watches := watchMap[watch.Path]
	for i, w := range watches {
		if w == watch {
			watches[i] = watches[len(watches)-1]
			watches = watches[:len(watches)-1]
			break
		}
	}

	watchMap[watch.Path] = watches

	if len(watches) == 0 {
		return watcher.RemoveWatch(watch.Path)
	}

	return nil
}

func makeFileEntry(vos *virt.VOS, dir string, fi os.FileInfo) FileEntry {
	entry := FileEntry{
		Name:     fi.Name(),
		IsDir:    fi.IsDir(),
		Size:     fi.Size(),
		Mode:     fi.Mode(),
		Time:     fi.ModTime(),
		Readable: vos.IsReadable(fi),
		Writable: vos.IsWritable(fi),
	}

	if fi.Mode()&os.ModeSymlink != 0 {
		symlinkInfo, err := vos.Stat(dir + "/" + fi.Name())
		if err != nil {
			entry.IsBroken = true
			return entry
		}
		entry.IsDir = symlinkInfo.IsDir()
		entry.Size = symlinkInfo.Size()
		entry.Mode = symlinkInfo.Mode()
		entry.Time = symlinkInfo.ModTime()
	}

	return entry
}
<commit_msg>os kite: Recursive watching.<commit_after>package main

import (
	"exp/inotify"
	"fmt"
	"io"
	"koding/tools/dnode"
	"koding/tools/kite"
	"koding/tools/log"
	"koding/virt"
	"os"
	"path"
	"regexp"
	"strconv"
	"sync"
	"time"
)

var watcher *inotify.Watcher
var watchMap = make(map[string][]*Watch)
var watchMutex sync.Mutex

func init() {
	var err error
	watcher, err = inotify.NewWatcher()
	if err != nil {
		panic(err)
	}

	go func() {
		for ev := range watcher.Event {
			if (ev.Mask & (inotify.IN_CREATE | inotify.IN_MOVED_TO | inotify.IN_ATTRIB)) != 0 {
				info, err := os.Lstat(ev.Name)
				if err != nil {
					if os.IsNotExist(err) {
						continue // skip this event, file is deleted and deletion event will follow
					}
					log.Warn("Watcher error", err)
					continue
				}
				watchMutex.Lock()
				for _, watch := range watchMap[path.Dir(ev.Name)] {
					watch.Callback(map[string]interface{}{
						"event": "added",
						"file":  makeFileEntry(watch.VOS, path.Dir(ev.Name), info),
					})
				}
				watchMutex.Unlock()
				continue
			}
			if (ev.Mask & (inotify.IN_DELETE | inotify.IN_MOVED_FROM)) != 0 {
				watchMutex.Lock()
				for _, watch := range watchMap[path.Dir(ev.Name)] {
					watch.Callback(map[string]interface{}{
						"event": "removed",
						"file":  FileEntry{Name: path.Base(ev.Name)},
					})
				}
				watchMutex.Unlock()
				continue
			}
		}
	}()
	go func() {
		for err := range watcher.Error {
			log.Warn("Watcher error", err)
		}
	}()
}

func registerFileSystemMethods(k *kite.Kite) {
	registerVmMethod(k, "fs.readDirectory", false, func(args *dnode.Partial, channel *kite.Channel, vos *virt.VOS) (interface{}, error) {
		var params struct {
			Path                string
			OnChange            dnode.Callback
			WatchSubdirectories bool
		}
		if args.Unmarshal(&params) != nil || params.Path == "" {
			return nil, &kite.ArgumentError{Expected: "{ path: [string], onChange: [function], watchSubdirectories: [bool] }"}
		}

		response := make(map[string]interface{})

		if params.OnChange != nil {
			watch := &Watch{VOS: vos, Callback: params.OnChange}
			if err := addWatch(watch, params.Path, params.WatchSubdirectories); err != nil {
				watch.Close()
				return nil, err
			}
			channel.OnDisconnect(func() { watch.Close() })
			response["stopWatching"] = func() { watch.Close() }
		}

		dir, err := vos.Open(params.Path)
		if err != nil {
			return nil, err
		}
		defer dir.Close()

		infos, err := dir.Readdir(0)
		if err != nil {
			return nil, err
		}

		files := make([]FileEntry, len(infos))
		for i, info := range infos {
			files[i] = makeFileEntry(vos, params.Path, info)
		}
		response["files"] = files

		return response, nil
	})

	registerVmMethod(k, "fs.readFile", false, func(args *dnode.Partial, channel *kite.Channel, vos *virt.VOS) (interface{}, error) {
		var params struct {
			Path string
		}
		if args.Unmarshal(&params) != nil || params.Path == "" {
			return nil, &kite.ArgumentError{Expected: "{ path: [string] }"}
		}

		file, err := vos.Open(params.Path)
		if err != nil {
			return nil, err
		}
		defer file.Close()

		fi, err := file.Stat()
		if err != nil {
			return nil, err
		}

		if fi.Size() > 10*1024*1024 {
			return nil, fmt.Errorf("File larger than 10MiB.")
		}

		buf := make([]byte, fi.Size())
		if _, err := io.ReadFull(file, buf); err != nil {
			return nil, err
		}

		return map[string]interface{}{"content": buf}, nil
	})

	registerVmMethod(k, "fs.writeFile", false, func(args *dnode.Partial, channel *kite.Channel, vos *virt.VOS) (interface{}, error) {
		var params struct {
			Path           string
			Content        []byte
			DoNotOverwrite bool
		}
		if args.Unmarshal(&params) != nil || params.Path == "" || params.Content == nil {
			return nil, &kite.ArgumentError{Expected: "{ path: [string], content: [base64], doNotOverwrite: [bool] }"}
		}

		flags := os.O_RDWR | os.O_CREATE | os.O_TRUNC
		if params.DoNotOverwrite {
			flags |= os.O_EXCL
		}
		file, err := vos.OpenFile(params.Path, flags, 0666)
		if err != nil {
			return nil, err
		}
		defer file.Close()

		return file.Write(params.Content)
	})

	suffixRegexp, err := regexp.Compile(`.((_\d+)?)(\.\w*)?$`)
	if err != nil {
		panic(err)
	}

	registerVmMethod(k, "fs.ensureNonexistentPath", false, func(args *dnode.Partial, channel *kite.Channel, vos *virt.VOS) (interface{}, error) {
		var params struct {
			Path string
		}
		if args.Unmarshal(&params) != nil || params.Path == "" {
			return nil, &kite.ArgumentError{Expected: "{ path: [string] }"}
		}

		name := params.Path
		index := 1
		for {
			_, err := vos.Stat(name)
			if err != nil {
				if os.IsNotExist(err) {
					break
				}
				return nil, err
			}

			loc := suffixRegexp.FindStringSubmatchIndex(name)
			name = name[:loc[2]] + "_" + strconv.Itoa(index) + name[loc[3]:]
			index++
		}

		return name, nil
	})

	registerVmMethod(k, "fs.getInfo", false, func(args *dnode.Partial, channel *kite.Channel, vos *virt.VOS) (interface{}, error) {
		var params struct {
			Path string
		}
		if args.Unmarshal(&params) != nil || params.Path == "" {
			return nil, &kite.ArgumentError{Expected: "{ path: [string] }"}
		}

		fi, err := vos.Stat(params.Path)
		if err != nil {
			if os.IsNotExist(err) {
				return nil, nil
			}
			return nil, err
		}

		return makeFileEntry(vos, path.Dir(params.Path), fi), nil
	})

	registerVmMethod(k, "fs.setPermissions", false, func(args *dnode.Partial, channel *kite.Channel, vos *virt.VOS) (interface{}, error) {
		var params struct {
			Path      string
			Mode      os.FileMode
			Recursive bool
		}
		if args.Unmarshal(&params) != nil || params.Path == "" {
			return nil, &kite.ArgumentError{Expected: "{ path: [string], mode: [integer], recursive: [bool] }"}
		}

		var doChange func(name string) error
		doChange = func(name string) error {
			if err := vos.Chmod(name, params.Mode); err != nil {
				return err
			}
			if !params.Recursive {
				return nil
			}
			fi, err := vos.Stat(name)
			if err != nil {
				return err
			}
			if !fi.IsDir() {
				return nil
			}
			dir, err := vos.Open(name)
			if err != nil {
				return err
			}
			defer dir.Close()
			entries, err := dir.Readdirnames(0)
			if err != nil {
				return err
			}
			var firstErr error
			for _, entry := range entries {
				err := doChange(name + "/" + entry)
				if err != nil && firstErr == nil {
					firstErr = err
				}
			}
			return firstErr
		}
		if err := doChange(params.Path); err != nil {
			return nil, err
		}

		return true, nil
	})

	registerVmMethod(k, "fs.remove", false, func(args *dnode.Partial, channel *kite.Channel, vos *virt.VOS) (interface{}, error) {
		var params struct {
			Path      string
			Recursive bool
		}
		if args.Unmarshal(&params) != nil || params.Path == "" {
			return nil, &kite.ArgumentError{Expected: "{ path: [string], recursive: [bool] }"}
		}

		if params.Recursive {
			if err := vos.RemoveAll(params.Path); err != nil {
				return nil, err
			}
			return true, nil
		}

		if err := vos.Remove(params.Path); err != nil {
			return nil, err
		}
		return true, nil
	})

	registerVmMethod(k, "fs.rename", false, func(args *dnode.Partial, channel *kite.Channel, vos *virt.VOS) (interface{}, error) {
		var params struct {
			OldPath string
			NewPath string
		}
		if args.Unmarshal(&params) != nil || params.OldPath == "" || params.NewPath == "" {
			return nil, &kite.ArgumentError{Expected: "{ oldPath: [string], newPath: [string] }"}
		}

		if err := vos.Rename(params.OldPath, params.NewPath); err != nil {
			return nil, err
		}

		return true, nil
	})

	registerVmMethod(k, "fs.createDirectory", false, func(args *dnode.Partial, channel *kite.Channel, vos *virt.VOS) (interface{}, error) {
		var params struct {
			Path      string
			Recursive bool
		}
		if args.Unmarshal(&params) != nil || params.Path == "" {
			return nil, &kite.ArgumentError{Expected: "{ path: [string], recursive: [bool] }"}
		}

		if params.Recursive {
			if err := vos.MkdirAll(params.Path, 0755); err != nil {
				return nil, err
			}
			return true, nil
		}

		if err := vos.Mkdir(params.Path, 0755); err != nil {
			return nil, err
		}
		return true, nil
	})
}

type Watch struct {
	VOS      *virt.VOS
	Paths    []string
	Callback dnode.Callback
}

func addWatch(watch *Watch, path string, watchSubdirectories bool) error {
	if len(watch.Paths) >= 100 {
		return fmt.Errorf("Too many subdirectories to watch.")
	}

	watchMutex.Lock()
	watchedPath, err := watch.VOS.AddWatch(watcher, path, inotify.IN_CREATE|inotify.IN_DELETE|inotify.IN_MOVE|inotify.IN_ATTRIB)
	if err != nil {
		watchMutex.Unlock()
		return err
	}
	watch.Paths = append(watch.Paths, watchedPath)
	watchMap[watchedPath] = append(watchMap[watchedPath], watch)
	watchMutex.Unlock()

	if watchSubdirectories {
		dir, err := watch.VOS.Open(path)
		if err != nil {
			return err
		}
		defer dir.Close()

		infos, err := dir.Readdir(0)
		if err != nil {
			return err
		}

		for _, info := range infos {
			if info.Mode().IsDir() {
				if err := addWatch(watch, path+"/"+info.Name(), true); err != nil {
					return err
				}
			}
		}
	}

	return nil
}

func (watch *Watch) Close() error {
	watchMutex.Lock()
	defer watchMutex.Unlock()

	for _, path := range watch.Paths {
		watches := watchMap[path]
		for i, w := range watches {
			if w == watch {
				watches[i] = watches[len(watches)-1]
				watches = watches[:len(watches)-1]
				break
			}
		}
		watchMap[path] = watches
		if len(watches) == 0 {
			return watcher.RemoveWatch(path)
		}
	}

	return nil
}

type FileEntry struct {
	Name     string      `json:"name"`
	IsDir    bool        `json:"isDir"`
	Size     int64       `json:"size"`
	Mode     os.FileMode `json:"mode"`
	Time     time.Time   `json:"time"`
	IsBroken bool        `json:"isBroken"`
	Readable bool        `json:"readable"`
	Writable bool        `json:"writable"`
}

func makeFileEntry(vos *virt.VOS, dir string, fi os.FileInfo) FileEntry {
	entry := FileEntry{
		Name:     fi.Name(),
		IsDir:    fi.IsDir(),
		Size:     fi.Size(),
		Mode:     fi.Mode(),
		Time:     fi.ModTime(),
		Readable: vos.IsReadable(fi),
		Writable: vos.IsWritable(fi),
	}

	if fi.Mode()&os.ModeSymlink != 0 {
		symlinkInfo, err := vos.Stat(dir + "/" + fi.Name())
		if err != nil {
			entry.IsBroken = true
			return entry
		}
		entry.IsDir = symlinkInfo.IsDir()
		entry.Size = symlinkInfo.Size()
		entry.Mode = symlinkInfo.Mode()
		entry.Time = symlinkInfo.ModTime()
	}

	return entry
}
<|endoftext|>


<commit_before>package compactcorpus

// dit is gecopieerd uit dictzip

//. Imports

import (
	"compress/flate"
	"fmt"
	"hash/crc32"
	"io"
	"io/ioutil"
	"os"
	"path"
	"sync"
	"time"
)

//. Writer

/*
Levels range from 1 (BestSpeed) to 9 (BestCompression), Level 0 (NoCompression), -1 (DefaultCompression)
*/
func write(r io.Reader, filename string, level int) error {

	const blocksize = 58315

	dirname := path.Dir(filename)
	root := path.Base(filename)
	fptmp, err := ioutil.TempFile(dirname, root)
	if err != nil {
		return err
	}
	defer func(name string) {
		fptmp.Close()
		os.Remove(name)
	}(fptmp.Name())

	crc := crc32.NewIEEE()
	isize := 0

	fw, err := flate.NewWriter(fptmp, level)
	if err != nil {
		return err
	}
	sizes := make([]int64, 0)
	b := make([]byte, blocksize)
	total := int64(0)
	eof := false
	for !eof {
		n, err := readfull(r, b)
		if err != nil {
			if err != io.EOF {
				return err
			} else {
				eof = true
			}
		}
		if n > 0 {
			crc.Write(b[:n])
			isize += n

			fw.Write(b[:n])
			fw.Flush()
			fw.Reset(fptmp)

			s, _ := fptmp.Stat()
			l := s.Size()
			sizes = append(sizes, l-total)
			total = l
		}
	}
	fw.Close()

	fp, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer fp.Close()

	xfl := byte(0)
	if level == flate.BestCompression {
		xfl = 2
	} else if level == flate.BestSpeed {
		xfl = 4
	}
	now := time.Now().Unix()
	_, err = fp.Write([]byte{
		31, 139, 8, 4,
		byte(now & 255), byte((now >> 8) & 255), byte((now >> 16) & 255), byte((now >> 24) & 255),
		xfl, 255})
	if err != nil {
		return err
	}

	xlen := 10 + 2*len(sizes)
	ln := 6 + 2*len(sizes)
	_, err = fp.Write([]byte{
		byte(xlen & 255), byte((xlen >> 8) & 255),
		'R', 'A', byte(ln & 255), byte((ln >> 8) & 255),
		1, 0,
		byte(blocksize & 255), byte((blocksize >> 8) & 255),
		byte(len(sizes) & 255), byte((len(sizes) >> 8) & 255)})
	if err != nil {
		return err
	}
	for _, o := range sizes {
		_, err = fp.Write([]byte{byte(o & 255), byte((o >> 8) & 255)})
		if err != nil {
			return err
		}
	}

	fptmp.Seek(0, 0)
	io.Copy(fp, fptmp)

	c := crc.Sum32()
	_, err = fp.Write([]byte{
		byte(c & 255), byte((c >> 8) & 255), byte((c >> 16) & 255), byte((c >> 24) & 255),
		byte(isize & 255), byte((isize >> 8) & 255), byte((isize >> 16) & 255), byte((isize >> 24) & 255),
	})
	if err != nil {
		return err
	}

	return nil
}

//. Reader

type reader struct {
	fp        io.ReadSeeker
	offsets   []int64
	blocksize int64
	lock      sync.Mutex
}

func newReader(rs io.ReadSeeker) (*reader, error) {

	dz := &reader{fp: rs}

	_, err := dz.fp.Seek(0, 0)
	if err != nil {
		return nil, err
	}

	metadata := []byte{}

	p := 0

	h := make([]byte, 10)
	n, err := readfull(dz.fp, h)
	if err != nil {
		return nil, err
	}
	p += n

	if h[0] != 31 || h[1] != 139 {
		return nil, fmt.Errorf("Invalid header: %02X %02X\n", h[0], h[1])
	}

	if h[2] != 8 {
		return nil, fmt.Errorf("Unknown compression method: %v", h[2])
	}

	flg := h[3]

	if flg&4 != 0 {
		h := make([]byte, 2)
		n, err := readfull(dz.fp, h)
		if err != nil {
			return nil, err
		}
		p += n

		xlen := int(h[0]) + 256*int(h[1])
		h = make([]byte, xlen)
		n, err = readfull(dz.fp, h)
		if err != nil {
			return nil, err
		}
		p += n

		for q := 0; q < len(h); {
			si1 := h[q]
			si2 := h[q+1]
			ln := int(h[q+2]) + 256*int(h[q+3])

			if si1 == 'R' && si2 == 'A' {
				metadata = h[q+4 : q+4+ln]
			}

			q += 4 + ln
		}

	}

	// skip file name (8), file comment (16)
	for _, f := range []byte{8, 16} {
		if flg&f != 0 {
			h := make([]byte, 1)
			for {
				n, err := readfull(dz.fp, h)
				if err != nil {
					return nil, err
				}
				p += n
				if h[0] == 0 {
					break
				}
			}
		}
	}

	if flg&2 != 0 {
		h := make([]byte, 2)
		n, err := readfull(dz.fp, h)
		if err != nil {
			return nil, err
		}
		p += n
	}

	if len(metadata) < 6 {
		return nil, fmt.Errorf("Missing dictzip metadata")
	}

	version := int(metadata[0]) + 256*int(metadata[1])

	if version != 1 {
		return nil, fmt.Errorf("Unknown dictzip version: %v", version)
	}

	dz.blocksize = int64(metadata[2]) + 256*int64(metadata[3])
	blockcnt := int(metadata[4]) + 256*int(metadata[5])

	dz.offsets = make([]int64, blockcnt+1)
	dz.offsets[0] = int64(p)
	for i := 0; i < blockcnt; i++ {
		dz.offsets[i+1] = dz.offsets[i] + int64(metadata[6+2*i]) + 256*int64(metadata[7+2*i])
	}

	return dz, nil

}

func (dz *reader) get(start, size int64) ([]byte, error) {

	if size == 0 {
		return []byte{}, nil
	}

	if start < 0 || size < 0 {
		return nil, fmt.Errorf("Negative start or size")
	}

	if int(start/dz.blocksize) >= len(dz.offsets) {
		return nil, fmt.Errorf("Start passed end of archive")
	}

	start1 := dz.blocksize * (start / dz.blocksize)
	size1 := size + (start - start1)

	dz.lock.Lock()
	defer dz.lock.Unlock()

	_, err := dz.fp.Seek(dz.offsets[start/dz.blocksize], 0)
	if err != nil {
		return nil, err
	}
	rd := flate.NewReader(dz.fp)

	data := make([]byte, size1)
	_, err = readfull(rd, data)
	if err != nil {
		return nil, err
	}

	return data[start-start1:], nil
}

// Start and size in base64 notation, such as used by the `dictunzip` program.
func (dz *reader) getB64(start, size string) ([]byte, error) {
	start2, err := decode(start)
	if err != nil {
		return nil, err
	}
	size2, err := decode(size)
	if err != nil {
		return nil, err
	}
	return dz.get(int64(start2), int64(size2))
}

//. Helper function

func readfull(fp io.Reader, buf []byte) (int, error) {
	ln := len(buf)
	for p := 0; p < ln; {
		n, err := fp.Read(buf[p:])
		p += n
		if err != nil {
			if err != io.EOF || p < ln {
				return p, err
			} else {
				return p, nil
			}
		}
	}
	return ln, nil
}
<commit_msg>use io.ReadFull() instead of readfull()<commit_after>package compactcorpus

// dit is gecopieerd uit dictzip

//. Imports

import (
	"compress/flate"
	"fmt"
	"hash/crc32"
	"io"
	"io/ioutil"
	"os"
	"path"
	"sync"
	"time"
)

//. Writer

/*
Levels range from 1 (BestSpeed) to 9 (BestCompression), Level 0 (NoCompression), -1 (DefaultCompression)
*/
func write(r io.Reader, filename string, level int) error {

	const blocksize = 58315

	dirname := path.Dir(filename)
	root := path.Base(filename)
	fptmp, err := ioutil.TempFile(dirname, root)
	if err != nil {
		return err
	}
	defer func(name string) {
		fptmp.Close()
		os.Remove(name)
	}(fptmp.Name())

	crc := crc32.NewIEEE()
	isize := 0

	fw, err := flate.NewWriter(fptmp, level)
	if err != nil {
		return err
	}
	sizes := make([]int64, 0)
	b := make([]byte, blocksize)
	total := int64(0)
	eof := false
	for !eof {
		n, err := io.ReadFull(r, b)
		if err != nil {
			if err != io.EOF && err != io.ErrUnexpectedEOF {
				return err
			} else {
				eof = true
			}
		}
		if n > 0 {
			crc.Write(b[:n])
			isize += n

			fw.Write(b[:n])
			fw.Flush()
			fw.Reset(fptmp)

			s, _ := fptmp.Stat()
			l := s.Size()
			sizes = append(sizes, l-total)
			total = l
		}
	}
	fw.Close()

	fp, err := os.Create(filename)
	if err != nil {
		return err
	}
	defer fp.Close()

	xfl := byte(0)
	if level == flate.BestCompression {
		xfl = 2
	} else if level == flate.BestSpeed {
		xfl = 4
	}
	now := time.Now().Unix()
	_, err = fp.Write([]byte{
		31, 139, 8, 4,
		byte(now & 255), byte((now >> 8) & 255), byte((now >> 16) & 255), byte((now >> 24) & 255),
		xfl, 255})
	if err != nil {
		return err
	}

	xlen := 10 + 2*len(sizes)
	ln := 6 + 2*len(sizes)
	_, err = fp.Write([]byte{
		byte(xlen & 255), byte((xlen >> 8) & 255),
		'R', 'A', byte(ln & 255), byte((ln >> 8) & 255),
		1, 0,
		byte(blocksize & 255), byte((blocksize >> 8) & 255),
		byte(len(sizes) & 255), byte((len(sizes) >> 8) & 255)})
	if err != nil {
		return err
	}
	for _, o := range sizes {
		_, err = fp.Write([]byte{byte(o & 255), byte((o >> 8) & 255)})
		if err != nil {
			return err
		}
	}

	fptmp.Seek(0, 0)
	io.Copy(fp, fptmp)

	c := crc.Sum32()
	_, err = fp.Write([]byte{
		byte(c & 255), byte((c >> 8) & 255), byte((c >> 16) & 255), byte((c >> 24) & 255),
		byte(isize & 255), byte((isize >> 8) & 255), byte((isize >> 16) & 255), byte((isize >> 24) & 255),
	})
	if err != nil {
		return err
	}

	return nil
}

//. Reader

type reader struct {
	fp        io.ReadSeeker
	offsets   []int64
	blocksize int64
	lock      sync.Mutex
}

func newReader(rs io.ReadSeeker) (*reader, error) {

	dz := &reader{fp: rs}

	_, err := dz.fp.Seek(0, 0)
	if err != nil {
		return nil, err
	}

	metadata := []byte{}

	p := 0

	h := make([]byte, 10)
	n, err := io.ReadFull(dz.fp, h)
	if err != nil {
		return nil, err
	}
	p += n

	if h[0] != 31 || h[1] != 139 {
		return nil, fmt.Errorf("Invalid header: %02X %02X\n", h[0], h[1])
	}

	if h[2] != 8 {
		return nil, fmt.Errorf("Unknown compression method: %v", h[2])
	}

	flg := h[3]

	if flg&4 != 0 {
		h := make([]byte, 2)
		n, err := io.ReadFull(dz.fp, h)
		if err != nil {
			return nil, err
		}
		p += n

		xlen := int(h[0]) + 256*int(h[1])
		h = make([]byte, xlen)
		n, err = io.ReadFull(dz.fp, h)
		if err != nil {
			return nil, err
		}
		p += n

		for q := 0; q < len(h); {
			si1 := h[q]
			si2 := h[q+1]
			ln := int(h[q+2]) + 256*int(h[q+3])

			if si1 == 'R' && si2 == 'A' {
				metadata = h[q+4 : q+4+ln]
			}

			q += 4 + ln
		}

	}

	// skip file name (8), file comment (16)
	for _, f := range []byte{8, 16} {
		if flg&f != 0 {
			h := make([]byte, 1)
			for {
				n, err := io.ReadFull(dz.fp, h)
				if err != nil {
					return nil, err
				}
				p += n
				if h[0] == 0 {
					break
				}
			}
		}
	}

	if flg&2 != 0 {
		h := make([]byte, 2)
		n, err := io.ReadFull(dz.fp, h)
		if err != nil {
			return nil, err
		}
		p += n
	}

	if len(metadata) < 6 {
		return nil, fmt.Errorf("Missing dictzip metadata")
	}

	version := int(metadata[0]) + 256*int(metadata[1])

	if version != 1 {
		return nil, fmt.Errorf("Unknown dictzip version: %v", version)
	}

	dz.blocksize = int64(metadata[2]) + 256*int64(metadata[3])
	blockcnt := int(metadata[4]) + 256*int(metadata[5])

	dz.offsets = make([]int64, blockcnt+1)
	dz.offsets[0] = int64(p)
	for i := 0; i < blockcnt; i++ {
		dz.offsets[i+1] = dz.offsets[i] + int64(metadata[6+2*i]) + 256*int64(metadata[7+2*i])
	}

	return dz, nil

}

func (dz *reader) get(start, size int64) ([]byte, error) {

	if size == 0 {
		return []byte{}, nil
	}

	if start < 0 || size < 0 {
		return nil, fmt.Errorf("Negative start or size")
	}

	if int(start/dz.blocksize) >= len(dz.offsets) {
		return nil, fmt.Errorf("Start passed end of archive")
	}

	start1 := dz.blocksize * (start / dz.blocksize)
	size1 := size + (start - start1)

	dz.lock.Lock()
	defer dz.lock.Unlock()

	_, err := dz.fp.Seek(dz.offsets[start/dz.blocksize], 0)
	if err != nil {
		return nil, err
	}
	rd := flate.NewReader(dz.fp)

	data := make([]byte, size1)
	_, err = io.ReadFull(rd, data)
	if err != nil {
		return nil, err
	}

	return data[start-start1:], nil
}

// Start and size in base64 notation, such as used by the `dictunzip` program.
func (dz *reader) getB64(start, size string) ([]byte, error) {
	start2, err := decode(start)
	if err != nil {
		return nil, err
	}
	size2, err := decode(size)
	if err != nil {
		return nil, err
	}
	return dz.get(int64(start2), int64(size2))
}

<|endoftext|>


<commit_before>// Discordgo - Discord bindings for Go
// Available at https://github.com/bwmarrin/discordgo

// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file contains high level helper functions and easy entry points for the
// entire discordgo package.  These functions are beling developed and are very
// experimental at this point.  They will most likley change so please use the
// low level functions if that's a problem.

// Package discordgo provides Discord binding for Go
package discordgo

import (
	"fmt"
	"reflect"
)

// VERSION of Discordgo, follows Symantic Versioning. (http://semver.org/)
const VERSION = "0.12.0-alpha"

// New creates a new Discord session and will automate some startup
// tasks if given enough information to do so.  Currently you can pass zero
// arguments and it will return an empty Discord session.
// There are 3 ways to call New:
//     With a single auth token - All requests will use the token blindly,
//         no verification of the token will be done and requests may fail.
//     With an email and password - Discord will sign in with the provided
//         credentials.
//     With an email, password and auth token - Discord will verify the auth
//         token, if it is invalid it will sign in with the provided
//         credentials. This is the Discord recommended way to sign in.
func New(args ...interface{}) (s *Session, err error) {

	// Create an empty Session interface.
	s = &Session{
		State:                  NewState(),
		StateEnabled:           true,
		Compress:               true,
		ShouldReconnectOnError: true,
	}

	// If no arguments are passed return the empty Session interface.
	// Later I will add default values, if appropriate.
	if args == nil {
		return
	}

	// Variables used below when parsing func arguments
	var auth, pass string

	// Parse passed arguments
	for _, arg := range args {

		switch v := arg.(type) {

		case []string:
			if len(v) > 3 {
				err = fmt.Errorf("Too many string parameters provided.")
				return
			}

			// First string is either token or username
			if len(v) > 0 {
				auth = v[0]
			}

			// If second string exists, it must be a password.
			if len(v) > 1 {
				pass = v[1]
			}

			// If third string exists, it must be an auth token.
			if len(v) > 2 {
				s.Token = v[2]
			}

		case string:
			// First string must be either auth token or username.
			// Second string must be a password.
			// Only 2 input strings are supported.

			if auth == "" {
				auth = v
			} else if pass == "" {
				pass = v
			} else if s.Token == "" {
				s.Token = v
			} else {
				err = fmt.Errorf("Too many string parameters provided.")
				return
			}

			//		case Config:
			// TODO: Parse configuration

		default:
			err = fmt.Errorf("Unsupported parameter type provided.")
			return
		}
	}

	// If only one string was provided, assume it is an auth token.
	// Otherwise get auth token from Discord, if a token was specified
	// Discord will verify it for free, or log the user in if it is
	// invalid.
	if pass == "" {
		s.Token = auth
	} else {
		err = s.Login(auth, pass)
		if err != nil || s.Token == "" {
			err = fmt.Errorf("Unable to fetch discord authentication token. %v", err)
			return
		}
	}

	// The Session is now able to have RestAPI methods called on it.
	// It is recommended that you now call Open() so that events will trigger.

	return
}

// validateHandler takes an event handler func, and returns the type of event.
// eg.
//     Session.validateHandler(func (s *discordgo.Session, m *discordgo.MessageCreate))
//     will return the reflect.Type of *discordgo.MessageCreate
func (s *Session) validateHandler(handler interface{}) reflect.Type {
	handlerType := reflect.TypeOf(handler)

	if handlerType.NumIn() != 2 {
		panic("Unable to add event handler, handler must be of the type func(*discordgo.Session, *discordgo.EventType).")
	}

	if handlerType.In(0) != reflect.TypeOf(s) {
		panic("Unable to add event handler, first argument must be of type *discordgo.Session.")
	}

	eventType := handlerType.In(1)

	// Support handlers of type interface{}, this is a special handler, which is triggered on every event.
	if eventType.Kind() == reflect.Interface {
		eventType = nil
	}

	return eventType
}

// AddHandler allows you to add an event handler that will be fired anytime
// the Discord WSAPI event that matches the interface fires.
// eventToInterface in events.go has a list of all the Discord WSAPI events
// and their respective interface.
// eg:
//     Session.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) {
//     })
//
// or:
//     Session.AddHandler(func(s *discordgo.Session, m *discordgo.PresenceUpdate) {
//     })
// The return value of this method is a function, that when called will remove the
// event handler.
func (s *Session) AddHandler(handler interface{}) func() {
	s.initialize()

	eventType := s.validateHandler(handler)

	s.handlersMu.Lock()
	defer s.handlersMu.Unlock()

	h := reflect.ValueOf(handler)

	s.handlers[eventType] = append(s.handlers[eventType], h)

	// This must be done as we need a consistent reference to the
	// reflected value, otherwise a RemoveHandler method would have
	// been nice.
	return func() {
		s.handlersMu.Lock()
		defer s.handlersMu.Unlock()

		handlers := s.handlers[eventType]
		for i, v := range handlers {
			if h == v {
				s.handlers[eventType] = append(handlers[:i], handlers[i+1:]...)
				return
			}
		}
	}
}

// handle calls any handlers that match the event type and any handlers of
// interface{}.
func (s *Session) handle(event interface{}) {
	s.handlersMu.RLock()
	defer s.handlersMu.RUnlock()

	if s.handlers == nil {
		return
	}

	handlerParameters := []reflect.Value{reflect.ValueOf(s), reflect.ValueOf(event)}

	if handlers, ok := s.handlers[reflect.TypeOf(event)]; ok {
		for _, handler := range handlers {
			handler.Call(handlerParameters)
		}
	}

	if handlers, ok := s.handlers[nil]; ok {
		for _, handler := range handlers {
			handler.Call(handlerParameters)
		}
	}
}

// initialize adds all internal handlers and state tracking handlers.
func (s *Session) initialize() {
	s.handlersMu.Lock()
	if s.handlers != nil {
		s.handlersMu.Unlock()
		return
	}

	s.handlers = map[interface{}][]reflect.Value{}
	s.handlersMu.Unlock()

	s.AddHandler(s.onEvent)
	s.AddHandler(s.onReady)
	s.AddHandler(s.onVoiceServerUpdate)
	s.AddHandler(s.onVoiceStateUpdate)
	s.AddHandler(s.State.onInterface)
}

// onEvent handles events that are unhandled or errored while unmarshalling
func (s *Session) onEvent(se *Session, e *Event) {
	printEvent(e)
}

// onReady handles the ready event.
func (s *Session) onReady(se *Session, r *Ready) {
	go s.heartbeat(s.wsConn, s.listening, r.HeartbeatInterval)
}
<commit_msg>Removed onEvent handler<commit_after>// Discordgo - Discord bindings for Go
// Available at https://github.com/bwmarrin/discordgo

// Copyright 2015-2016 Bruce Marriner <bruce@sqls.net>.  All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file contains high level helper functions and easy entry points for the
// entire discordgo package.  These functions are beling developed and are very
// experimental at this point.  They will most likley change so please use the
// low level functions if that's a problem.

// Package discordgo provides Discord binding for Go
package discordgo

import (
	"fmt"
	"reflect"
)

// VERSION of Discordgo, follows Symantic Versioning. (http://semver.org/)
const VERSION = "0.12.0-alpha"

// New creates a new Discord session and will automate some startup
// tasks if given enough information to do so.  Currently you can pass zero
// arguments and it will return an empty Discord session.
// There are 3 ways to call New:
//     With a single auth token - All requests will use the token blindly,
//         no verification of the token will be done and requests may fail.
//     With an email and password - Discord will sign in with the provided
//         credentials.
//     With an email, password and auth token - Discord will verify the auth
//         token, if it is invalid it will sign in with the provided
//         credentials. This is the Discord recommended way to sign in.
func New(args ...interface{}) (s *Session, err error) {

	// Create an empty Session interface.
	s = &Session{
		State:                  NewState(),
		StateEnabled:           true,
		Compress:               true,
		ShouldReconnectOnError: true,
	}

	// If no arguments are passed return the empty Session interface.
	// Later I will add default values, if appropriate.
	if args == nil {
		return
	}

	// Variables used below when parsing func arguments
	var auth, pass string

	// Parse passed arguments
	for _, arg := range args {

		switch v := arg.(type) {

		case []string:
			if len(v) > 3 {
				err = fmt.Errorf("Too many string parameters provided.")
				return
			}

			// First string is either token or username
			if len(v) > 0 {
				auth = v[0]
			}

			// If second string exists, it must be a password.
			if len(v) > 1 {
				pass = v[1]
			}

			// If third string exists, it must be an auth token.
			if len(v) > 2 {
				s.Token = v[2]
			}

		case string:
			// First string must be either auth token or username.
			// Second string must be a password.
			// Only 2 input strings are supported.

			if auth == "" {
				auth = v
			} else if pass == "" {
				pass = v
			} else if s.Token == "" {
				s.Token = v
			} else {
				err = fmt.Errorf("Too many string parameters provided.")
				return
			}

			//		case Config:
			// TODO: Parse configuration

		default:
			err = fmt.Errorf("Unsupported parameter type provided.")
			return
		}
	}

	// If only one string was provided, assume it is an auth token.
	// Otherwise get auth token from Discord, if a token was specified
	// Discord will verify it for free, or log the user in if it is
	// invalid.
	if pass == "" {
		s.Token = auth
	} else {
		err = s.Login(auth, pass)
		if err != nil || s.Token == "" {
			err = fmt.Errorf("Unable to fetch discord authentication token. %v", err)
			return
		}
	}

	// The Session is now able to have RestAPI methods called on it.
	// It is recommended that you now call Open() so that events will trigger.

	return
}

// validateHandler takes an event handler func, and returns the type of event.
// eg.
//     Session.validateHandler(func (s *discordgo.Session, m *discordgo.MessageCreate))
//     will return the reflect.Type of *discordgo.MessageCreate
func (s *Session) validateHandler(handler interface{}) reflect.Type {
	handlerType := reflect.TypeOf(handler)

	if handlerType.NumIn() != 2 {
		panic("Unable to add event handler, handler must be of the type func(*discordgo.Session, *discordgo.EventType).")
	}

	if handlerType.In(0) != reflect.TypeOf(s) {
		panic("Unable to add event handler, first argument must be of type *discordgo.Session.")
	}

	eventType := handlerType.In(1)

	// Support handlers of type interface{}, this is a special handler, which is triggered on every event.
	if eventType.Kind() == reflect.Interface {
		eventType = nil
	}

	return eventType
}

// AddHandler allows you to add an event handler that will be fired anytime
// the Discord WSAPI event that matches the interface fires.
// eventToInterface in events.go has a list of all the Discord WSAPI events
// and their respective interface.
// eg:
//     Session.AddHandler(func(s *discordgo.Session, m *discordgo.MessageCreate) {
//     })
//
// or:
//     Session.AddHandler(func(s *discordgo.Session, m *discordgo.PresenceUpdate) {
//     })
// The return value of this method is a function, that when called will remove the
// event handler.
func (s *Session) AddHandler(handler interface{}) func() {
	s.initialize()

	eventType := s.validateHandler(handler)

	s.handlersMu.Lock()
	defer s.handlersMu.Unlock()

	h := reflect.ValueOf(handler)

	s.handlers[eventType] = append(s.handlers[eventType], h)

	// This must be done as we need a consistent reference to the
	// reflected value, otherwise a RemoveHandler method would have
	// been nice.
	return func() {
		s.handlersMu.Lock()
		defer s.handlersMu.Unlock()

		handlers := s.handlers[eventType]
		for i, v := range handlers {
			if h == v {
				s.handlers[eventType] = append(handlers[:i], handlers[i+1:]...)
				return
			}
		}
	}
}

// handle calls any handlers that match the event type and any handlers of
// interface{}.
func (s *Session) handle(event interface{}) {
	s.handlersMu.RLock()
	defer s.handlersMu.RUnlock()

	if s.handlers == nil {
		return
	}

	handlerParameters := []reflect.Value{reflect.ValueOf(s), reflect.ValueOf(event)}

	if handlers, ok := s.handlers[reflect.TypeOf(event)]; ok {
		for _, handler := range handlers {
			handler.Call(handlerParameters)
		}
	}

	if handlers, ok := s.handlers[nil]; ok {
		for _, handler := range handlers {
			handler.Call(handlerParameters)
		}
	}
}

// initialize adds all internal handlers and state tracking handlers.
func (s *Session) initialize() {
	s.handlersMu.Lock()
	if s.handlers != nil {
		s.handlersMu.Unlock()
		return
	}

	s.handlers = map[interface{}][]reflect.Value{}
	s.handlersMu.Unlock()

	s.AddHandler(s.onReady)
	s.AddHandler(s.onVoiceServerUpdate)
	s.AddHandler(s.onVoiceStateUpdate)
	s.AddHandler(s.State.onInterface)
}

// onEvent handles events that are unhandled or errored while unmarshalling
func (s *Session) onEvent(se *Session, e *Event) {
	printEvent(e)
}
<|endoftext|>