<commit_before>package main

import (
  "path"
  "os"
  "sync"
  "log"
  "sort"
  "io"
  "io/ioutil"
  "path/filepath"
  "os/exec"
)

const (
  CopyPrice = 100
  RenamePrice = 10

  RemoveBackupPrice = 1000
  RemoveFactor = RenamePrice
  UpdateFactor = RenamePrice + CopyPrice
  AddFactor = CopyPrice
)

const (
  BackupExt = ".bak"
)

type BackupPair struct {
  relpath string
  newpath string
}

type ProgressHandler interface {
  HandleSystemMessage(message string)
  HandlePercentChange(percent int)
  HandleFinish()
}

type LogProgressHandler struct {
}

type ProgressReporter struct {
  grandTotal uint64
  currentProgress uint64
  progressChan chan int64
  progressWG sync.WaitGroup
  percent int //0..100
  systemMessageChan chan string
  finished chan bool
  progressHandler ProgressHandler
}

type PackageInstaller struct {
  backups map[string]string
  backupsChan chan BackupPair
  backupsWG sync.WaitGroup
  progressReporter *ProgressReporter
  installDir string
  packageDir string
  removeSelfPath string // if updating the installer
  failInTheEnd bool // for debugging purposes
}

func (pi *PackageInstaller) Install(filesProvider UpdateFilesProvider) error {
  pi.progressReporter.grandTotal = pi.calculateGrandTotals(filesProvider)

  go pi.progressReporter.reportingLoop()

  pi.beforeInstall()

  err := pi.installPackage(filesProvider)

  if (err == nil) && (!pi.failInTheEnd) {
    pi.afterSuccess()
  } else {
    pi.afterFailure(filesProvider)
  }

  pi.progressReporter.waitProgressReported()
  pi.progressReporter.shutdown()

  return err
}

func (pi *PackageInstaller) calculateGrandTotals(filesProvider UpdateFilesProvider) uint64 {
  var sum uint64

  for _, fi := range filesProvider.FilesToRemove() {
    sum += uint64(fi.FileSize * RemoveFactor) / 100
    sum += uint64(RemoveBackupPrice)
  }

  for _, fi := range filesProvider.FilesToUpdate() {
    sum += uint64(fi.FileSize * UpdateFactor) / 100
    sum += uint64(RemoveBackupPrice)
  }

  for _, fi := range filesProvider.FilesToAdd() {
    sum += uint64(fi.FileSize * AddFactor) / 100
  }

  return sum
}

func (pi *PackageInstaller) beforeInstall() {
  pi.removeOldBackups()
}

func (pi *PackageInstaller) installPackage(filesProvider UpdateFilesProvider) (err error) {
  log.Println("Installing package...")

  go func() {
    for bp := range pi.backupsChan {
      pi.backups[bp.relpath] = bp.newpath
      pi.backupsWG.Done()
    }

    log.Printf("Backups accounting finished. %v backups available", len(pi.backups))
  }()

  defer func() {
    log.Println("Stopping backups accounting routine...")
    close(pi.backupsChan)
  }()

  pi.progressReporter.systemMessageChan <- "Removing components"
  err = pi.removeFiles(filesProvider.FilesToRemove())
  if err != nil {
    return err
  }

  pi.progressReporter.systemMessageChan <- "Updating components"
  err = pi.updateFiles(filesProvider.FilesToUpdate())
  if err != nil {
    return err
  }

  pi.progressReporter.systemMessageChan <- "Adding components"
  err = pi.addFiles(filesProvider.FilesToAdd())
  if err != nil {
    return err
  }

  log.Println("Waiting for backups to finish accounting...")
  pi.backupsWG.Wait()

  return err
}

func (pi *PackageInstaller) afterSuccess() {
  log.Println("After success")
  pi.progressReporter.systemMessageChan <- "Finishing the installation..."
  pi.removeBackups();
  cleanupEmptyDirs(pi.installDir)
}

func (pi *PackageInstaller) afterFailure(filesProvider UpdateFilesProvider) {
  log.Println("After failure")
  pi.progressReporter.systemMessageChan <- "Cleaning up..."
  purgeFiles(pi.installDir, filesProvider.FilesToAdd())
  pi.restoreBackups()
  pi.removeBackups()
  cleanupEmptyDirs(pi.installDir)
}

func copyFile(src, dst string) (err error) {
  log.Printf("About to copy file %v to %v", src, dst)

  fi, err := os.Stat(src)
  if err != nil { return err }
  sourceMode := fi.Mode()

  in, err := os.Open(src)
  if err != nil {
    log.Printf("Failed to open source: %v", err)
    return err
  }

  defer in.Close()

  out, err := os.OpenFile(dst, os.O_RDWR | os.O_TRUNC | os.O_CREATE, sourceMode)
  if err != nil {
    log.Printf("Failed to create destination: %v", err)
    return
  }

  defer func() {
    cerr := out.Close()
    if err == nil {
      err = cerr
    }
  }()

  if _, err = io.Copy(out, in); err != nil {
    return
  }

  err = out.Sync()
  return
}

func (pi *PackageInstaller) backupFile(relpath string) error {
  log.Printf("Backing up %v", relpath)

  oldpath := path.Join(pi.installDir, relpath)
  backupPath := relpath + BackupExt

  newpath := path.Join(pi.installDir, backupPath)
  // remove previous backup if any
  os.Remove(newpath)

  err := os.Rename(oldpath, newpath)

  if err == nil {
    pi.backupsWG.Add(1)
    go func() {
      pi.backupsChan <- BackupPair{relpath: relpath, newpath: newpath}
    }()
  } else {
    log.Printf("Backup failed: %v", err)
  }

  return err
}

func (pi *PackageInstaller) restoreBackups() {
  log.Printf("Restoring %v backups", len(pi.backups))

  var wg sync.WaitGroup

  for relpath, backuppath := range pi.backups {
    wg.Add(1)

    go func(relativePath, pathToRestore string) {
      defer wg.Done()

      oldpath := path.Join(pi.installDir, relativePath)
      log.Printf("Restoring %v to %v", pathToRestore, oldpath)
      err := os.Rename(pathToRestore, oldpath)

      if err != nil {
        log.Printf("Error while restoring %v: %v", pathToRestore, err)
      }
    }(relpath, backuppath)
  }

  wg.Wait()
}

func (pi *PackageInstaller) removeOldBackups() {
  backeduppath := currentExeFullPath + BackupExt
  err := os.Remove(backeduppath)
  if err == nil {
    log.Println("Old installer backup removed", backeduppath)
  } else if os.IsNotExist(err) {
    log.Println("Old installer backup was not found")
  } else {
    log.Printf("Error while removing old backup: %v", err)
  }
}

func (pi *PackageInstaller) removeBackups() {
  log.Printf("Removing %v backups", len(pi.backups))

  selfpath, err := filepath.Rel(pi.installDir, currentExeFullPath)
  if err == nil {
    if backuppath, ok := pi.backups[selfpath]; ok {
      pi.removeSelfPath = backuppath
      delete(pi.backups, selfpath)
      log.Printf("Removed exe path %v from backups", selfpath)
    }
  }

  var wg sync.WaitGroup

  for _, backuppath := range pi.backups {
    wg.Add(1)

    go func(pathToRemove string) {
      defer wg.Done()

      err := os.Remove(pathToRemove)
      if err != nil {
        log.Printf("Error while removing %v: %v", pathToRemove err)
      }

      pi.progressReporter.accountBackupRemove()
    }(backuppath)
  }

  wg.Wait()
}

func (pi *PackageInstaller) removeFiles(files []*UpdateFileInfo) error {
  log.Printf("Removing %v files", len(files))

  var wg sync.WaitGroup
  errc := make(chan error)
  done := make(chan bool)

  for _, fi := range files {
    wg.Add(1)
    pathToRemove, filesize := fi.Filepath, fi.FileSize

    go func() {
      defer wg.Done()

      select {
      case <-done: return
      default:
      }

      fullpath := filepath.Join(pi.installDir, pathToRemove)
      log.Printf("Removing file %v", fullpath)

      err := pi.backupFile(pathToRemove)

      if err != nil {
        log.Printf("Removing file %v failed: %v", pathToRemove, err)
        errc <- err
        close(done)
      } else {
        pi.progressReporter.accountRemove(filesize)
      }
    }()
  }

  go func() {
    errc <- nil
  }()

  wg.Wait()

  if err := <-errc; err != nil {
    return err
  }

  return nil
}

func (pi *PackageInstaller) updateFiles(files []*UpdateFileInfo) error {
  log.Printf("Updating %v files", len(files))

  var wg sync.WaitGroup
  errc := make(chan error)
  done := make(chan bool)

  for _, fi := range files {
    wg.Add(1)

    pathToUpdate, filesize := fi.Filepath, fi.FileSize

    go func() {
      defer wg.Done()

      select {
      case <-done: return
      default:
      }

      oldpath := path.Join(pi.installDir, pathToUpdate)
      log.Printf("Updating file %v", oldpath)

      err := pi.backupFile(pathToUpdate)

      if err == nil {
        newpath := path.Join(pi.packageDir, pathToUpdate)
        err = os.Rename(newpath, oldpath)
      }

      if err != nil {
        log.Printf("Updating file %v failed", pathToUpdate)
        log.Println(err)
        errc <- err
        close(done)
      } else {
        pi.progressReporter.accountUpdate(filesize)
      }
    }()
  }

  go func() {
    errc <- nil
  }()

  wg.Wait()

  if err := <-errc; err != nil {
    return err
  }

  return nil
}

func (pi *PackageInstaller) addFiles(files []*UpdateFileInfo) error {
  log.Printf("Adding %v files", len(files))

  var wg sync.WaitGroup
  errc := make(chan error)
  done := make(chan bool)

  for _, fi := range files {
    wg.Add(1)

    pathToAdd, filesize := fi.Filepath, fi.FileSize

    go func() {
      defer wg.Done()

      select {
      case <-done: return
      default:
      }

      oldpath := path.Join(pi.installDir, pathToAdd)
      ensureDirExists(oldpath)

      newpath := path.Join(pi.packageDir, pathToAdd)
      err := os.Rename(newpath, oldpath)

      log.Printf("Adding file %v", pathToAdd)

      if err != nil {
        log.Printf("Adding file %v failed: %v", pathToAdd, err)
        errc <- err
        close(done)
      } else {
        pi.progressReporter.accountAdd(filesize)
      }
    }()
  }

  go func() {
    errc <- nil
  }()

  wg.Wait()

  if err := <-errc; err != nil {
    return err
  }

  return nil
}

func (pi *PackageInstaller) removeSelfIfNeeded() {
  if len(pi.removeSelfPath) == 0 {
    log.Println("No need to remove itself")
    return
  }

  // TODO: move this to windows-only define
  pathToRemove := filepath.FromSlash(pi.removeSelfPath)
  log.Println("Removing exe backup", pathToRemove)
  cmd := exec.Command("cmd", "/C", "ping localhost -n 2 -w 5000 > nul & del", pathToRemove)
  err := cmd.Start()
  if err != nil {
    log.Println(err)
  }
}

func purgeFiles(root string, files []*UpdateFileInfo) {
  log.Printf("Purging %v files", len(files))

  var wg sync.WaitGroup

  for _, fi := range files {
    wg.Add(1)

    go func(fileToPurge string) {
      defer wg.Done()

      fullpath := path.Join(root, fileToPurge)
      err := os.Remove(fullpath)
      if err != nil {
        log.Printf("Error while purgin %v: %v", fullpath, err)
      }
    }(fi.Filepath)
  }

  wg.Wait()
}

func ensureDirExists(fullpath string) (err error) {
  dirpath := path.Dir(fullpath)
  err = os.MkdirAll(dirpath, os.ModeDir)
  if err != nil {
    log.Printf("Failed to create directory %v", dirpath)
  }

  return err
}

type ByLength []string

func (s ByLength) Len() int {
    return len(s)
}
func (s ByLength) Swap(i, j int) {
    s[i], s[j] = s[j], s[i]
}
func (s ByLength) Less(i, j int) bool {
    return len(s[i]) > len(s[j])
}

func cleanupEmptyDirs(root string) {
  dirs := make([]string, 0, 10)

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

    if info.Mode().IsDir() {
      dirs = append(dirs, path)
    }

    return nil
  })

  if err != nil {
    log.Printf("Error while cleaning up empty dirs: %v", err)
  }

  removeEmptyDirs(dirs)
}

func removeEmptyDirs(dirs []string) {
  sort.Sort(ByLength(dirs))

  for _, dirpath := range dirs {
    entries, err := ioutil.ReadDir(dirpath)
    if err != nil { continue }

    if len(entries) == 0 {
      log.Printf("Removing empty dir %v", dirpath)

      err = os.Remove(dirpath)
      if err != nil {
        log.Printf("Error while removing dir %v: %v", dirpath, err)
      }
    }
  }
}

func (pr *ProgressReporter) accountRemove(progress int64) {
  pr.progressWG.Add(1)
  go func() {
    pr.progressChan <- (progress*RemoveFactor)/100
  }()
}

func (pr *ProgressReporter) accountUpdate(progress int64) {
  pr.progressWG.Add(1)
  go func() {
    pr.progressChan <- (progress*UpdateFactor)/100
  }()
}

func (pr *ProgressReporter) accountAdd(progress int64) {
  pr.progressWG.Add(1)
  go func() {
    pr.progressChan <- (progress*AddFactor)/100
  }()
}

func (pr *ProgressReporter) accountBackupRemove() {
  // exact size of files is not known when removeBackups()
  // so using some arbitrary value (fair dice roll)
  pr.progressWG.Add(1)
  go func() {
    pr.progressChan <- RemoveBackupPrice
  }()
}

func (pr *ProgressReporter) reportingLoop() {
  for chunk := range pr.progressChan {
    pr.currentProgress += uint64(chunk)

    percent := (pr.currentProgress*100) / pr.grandTotal
    pr.percent = int(percent)

    pr.progressHandler.HandlePercentChange(pr.percent)
    pr.progressWG.Done()
  }

  log.Println("Reporting loop finished")
}

func (pr *ProgressReporter) waitProgressReported() {
  log.Println("Waiting for progress reporting to finish")
  pr.progressWG.Wait()
}

func (pr *ProgressReporter) shutdown() {
  log.Println("Shutting down progress reporter...")
  close(pr.progressChan)
  go func() {
    pr.finished <- true
  }()
}

func (pr *ProgressReporter) receiveSystemMessages() {
  for msg := range pr.systemMessageChan {
    pr.progressHandler.HandleSystemMessage(msg)
  }
}

func (pr *ProgressReporter) receiveFinish() {
  <- pr.finished
  pr.progressHandler.HandleFinish()
}

func (pr *ProgressReporter) handleProgress() {
  go pr.receiveSystemMessages()
  go pr.receiveFinish()
}

func (ph *LogProgressHandler) HandlePercentChange(percent int) {
  log.Printf("Completed %v%%", percent)
}

func (ph *LogProgressHandler) HandleSystemMessage(msg string) {
  log.Printf("System message: %v", msg)
}

func (ph *LogProgressHandler) HandleFinish() {
  log.Printf("Finished")
}
<commit_msg>Fixed typos<commit_after>package main

import (
  "path"
  "os"
  "sync"
  "log"
  "sort"
  "io"
  "io/ioutil"
  "path/filepath"
  "os/exec"
)

const (
  CopyPrice = 100
  RenamePrice = 10

  RemoveBackupPrice = 1000
  RemoveFactor = RenamePrice
  UpdateFactor = RenamePrice + CopyPrice
  AddFactor = CopyPrice
)

const (
  BackupExt = ".bak"
)

type BackupPair struct {
  relpath string
  newpath string
}

type ProgressHandler interface {
  HandleSystemMessage(message string)
  HandlePercentChange(percent int)
  HandleFinish()
}

type LogProgressHandler struct {
}

type ProgressReporter struct {
  grandTotal uint64
  currentProgress uint64
  progressChan chan int64
  progressWG sync.WaitGroup
  percent int //0..100
  systemMessageChan chan string
  finished chan bool
  progressHandler ProgressHandler
}

type PackageInstaller struct {
  backups map[string]string
  backupsChan chan BackupPair
  backupsWG sync.WaitGroup
  progressReporter *ProgressReporter
  installDir string
  packageDir string
  removeSelfPath string // if updating the installer
  failInTheEnd bool // for debugging purposes
}

func (pi *PackageInstaller) Install(filesProvider UpdateFilesProvider) error {
  pi.progressReporter.grandTotal = pi.calculateGrandTotals(filesProvider)

  go pi.progressReporter.reportingLoop()

  pi.beforeInstall()

  err := pi.installPackage(filesProvider)

  if (err == nil) && (!pi.failInTheEnd) {
    pi.afterSuccess()
  } else {
    pi.afterFailure(filesProvider)
  }

  pi.progressReporter.waitProgressReported()
  pi.progressReporter.shutdown()

  return err
}

func (pi *PackageInstaller) calculateGrandTotals(filesProvider UpdateFilesProvider) uint64 {
  var sum uint64

  for _, fi := range filesProvider.FilesToRemove() {
    sum += uint64(fi.FileSize * RemoveFactor) / 100
    sum += uint64(RemoveBackupPrice)
  }

  for _, fi := range filesProvider.FilesToUpdate() {
    sum += uint64(fi.FileSize * UpdateFactor) / 100
    sum += uint64(RemoveBackupPrice)
  }

  for _, fi := range filesProvider.FilesToAdd() {
    sum += uint64(fi.FileSize * AddFactor) / 100
  }

  return sum
}

func (pi *PackageInstaller) beforeInstall() {
  pi.removeOldBackups()
}

func (pi *PackageInstaller) installPackage(filesProvider UpdateFilesProvider) (err error) {
  log.Println("Installing package...")

  go func() {
    for bp := range pi.backupsChan {
      pi.backups[bp.relpath] = bp.newpath
      pi.backupsWG.Done()
    }

    log.Printf("Backups accounting finished. %v backups available", len(pi.backups))
  }()

  defer func() {
    log.Println("Stopping backups accounting routine...")
    close(pi.backupsChan)
  }()

  pi.progressReporter.systemMessageChan <- "Removing components"
  err = pi.removeFiles(filesProvider.FilesToRemove())
  if err != nil {
    return err
  }

  pi.progressReporter.systemMessageChan <- "Updating components"
  err = pi.updateFiles(filesProvider.FilesToUpdate())
  if err != nil {
    return err
  }

  pi.progressReporter.systemMessageChan <- "Adding components"
  err = pi.addFiles(filesProvider.FilesToAdd())
  if err != nil {
    return err
  }

  log.Println("Waiting for backups to finish accounting...")
  pi.backupsWG.Wait()

  return err
}

func (pi *PackageInstaller) afterSuccess() {
  log.Println("After success")
  pi.progressReporter.systemMessageChan <- "Finishing the installation..."
  pi.removeBackups();
  cleanupEmptyDirs(pi.installDir)
}

func (pi *PackageInstaller) afterFailure(filesProvider UpdateFilesProvider) {
  log.Println("After failure")
  pi.progressReporter.systemMessageChan <- "Cleaning up..."
  purgeFiles(pi.installDir, filesProvider.FilesToAdd())
  pi.restoreBackups()
  pi.removeBackups()
  cleanupEmptyDirs(pi.installDir)
}

func copyFile(src, dst string) (err error) {
  log.Printf("About to copy file %v to %v", src, dst)

  fi, err := os.Stat(src)
  if err != nil { return err }
  sourceMode := fi.Mode()

  in, err := os.Open(src)
  if err != nil {
    log.Printf("Failed to open source: %v", err)
    return err
  }

  defer in.Close()

  out, err := os.OpenFile(dst, os.O_RDWR | os.O_TRUNC | os.O_CREATE, sourceMode)
  if err != nil {
    log.Printf("Failed to create destination: %v", err)
    return
  }

  defer func() {
    cerr := out.Close()
    if err == nil {
      err = cerr
    }
  }()

  if _, err = io.Copy(out, in); err != nil {
    return
  }

  err = out.Sync()
  return
}

func (pi *PackageInstaller) backupFile(relpath string) error {
  log.Printf("Backing up %v", relpath)

  oldpath := path.Join(pi.installDir, relpath)
  backupPath := relpath + BackupExt

  newpath := path.Join(pi.installDir, backupPath)
  // remove previous backup if any
  os.Remove(newpath)

  err := os.Rename(oldpath, newpath)

  if err == nil {
    pi.backupsWG.Add(1)
    go func() {
      pi.backupsChan <- BackupPair{relpath: relpath, newpath: newpath}
    }()
  } else {
    log.Printf("Backup failed: %v", err)
  }

  return err
}

func (pi *PackageInstaller) restoreBackups() {
  log.Printf("Restoring %v backups", len(pi.backups))

  var wg sync.WaitGroup

  for relpath, backuppath := range pi.backups {
    wg.Add(1)

    go func(relativePath, pathToRestore string) {
      defer wg.Done()

      oldpath := path.Join(pi.installDir, relativePath)
      log.Printf("Restoring %v to %v", pathToRestore, oldpath)
      err := os.Rename(pathToRestore, oldpath)

      if err != nil {
        log.Printf("Error while restoring %v: %v", pathToRestore, err)
      }
    }(relpath, backuppath)
  }

  wg.Wait()
}

func (pi *PackageInstaller) removeOldBackups() {
  backeduppath := currentExeFullPath + BackupExt
  err := os.Remove(backeduppath)
  if err == nil {
    log.Println("Old installer backup removed", backeduppath)
  } else if os.IsNotExist(err) {
    log.Println("Old installer backup was not found")
  } else {
    log.Printf("Error while removing old backup: %v", err)
  }
}

func (pi *PackageInstaller) removeBackups() {
  log.Printf("Removing %v backups", len(pi.backups))

  selfpath, err := filepath.Rel(pi.installDir, currentExeFullPath)
  if err == nil {
    if backuppath, ok := pi.backups[selfpath]; ok {
      pi.removeSelfPath = backuppath
      delete(pi.backups, selfpath)
      log.Printf("Removed exe path %v from backups", selfpath)
    }
  }

  var wg sync.WaitGroup

  for _, backuppath := range pi.backups {
    wg.Add(1)

    go func(pathToRemove string) {
      defer wg.Done()

      err := os.Remove(pathToRemove)
      if err != nil {
        log.Printf("Error while removing %v: %v", pathToRemove, err)
      }

      pi.progressReporter.accountBackupRemove()
    }(backuppath)
  }

  wg.Wait()
}

func (pi *PackageInstaller) removeFiles(files []*UpdateFileInfo) error {
  log.Printf("Removing %v files", len(files))

  var wg sync.WaitGroup
  errc := make(chan error)
  done := make(chan bool)

  for _, fi := range files {
    wg.Add(1)
    pathToRemove, filesize := fi.Filepath, fi.FileSize

    go func() {
      defer wg.Done()

      select {
      case <-done: return
      default:
      }

      fullpath := filepath.Join(pi.installDir, pathToRemove)
      log.Printf("Removing file %v", fullpath)

      err := pi.backupFile(pathToRemove)

      if err != nil {
        log.Printf("Removing file %v failed: %v", pathToRemove, err)
        errc <- err
        close(done)
      } else {
        pi.progressReporter.accountRemove(filesize)
      }
    }()
  }

  go func() {
    errc <- nil
  }()

  wg.Wait()

  if err := <-errc; err != nil {
    return err
  }

  return nil
}

func (pi *PackageInstaller) updateFiles(files []*UpdateFileInfo) error {
  log.Printf("Updating %v files", len(files))

  var wg sync.WaitGroup
  errc := make(chan error)
  done := make(chan bool)

  for _, fi := range files {
    wg.Add(1)

    pathToUpdate, filesize := fi.Filepath, fi.FileSize

    go func() {
      defer wg.Done()

      select {
      case <-done: return
      default:
      }

      oldpath := path.Join(pi.installDir, pathToUpdate)
      log.Printf("Updating file %v", oldpath)

      err := pi.backupFile(pathToUpdate)

      if err == nil {
        newpath := path.Join(pi.packageDir, pathToUpdate)
        err = os.Rename(newpath, oldpath)
      }

      if err != nil {
        log.Printf("Updating file %v failed", pathToUpdate)
        log.Println(err)
        errc <- err
        close(done)
      } else {
        pi.progressReporter.accountUpdate(filesize)
      }
    }()
  }

  go func() {
    errc <- nil
  }()

  wg.Wait()

  if err := <-errc; err != nil {
    return err
  }

  return nil
}

func (pi *PackageInstaller) addFiles(files []*UpdateFileInfo) error {
  log.Printf("Adding %v files", len(files))

  var wg sync.WaitGroup
  errc := make(chan error)
  done := make(chan bool)

  for _, fi := range files {
    wg.Add(1)

    pathToAdd, filesize := fi.Filepath, fi.FileSize

    go func() {
      defer wg.Done()

      select {
      case <-done: return
      default:
      }

      oldpath := path.Join(pi.installDir, pathToAdd)
      ensureDirExists(oldpath)

      newpath := path.Join(pi.packageDir, pathToAdd)
      err := os.Rename(newpath, oldpath)

      log.Printf("Adding file %v", pathToAdd)

      if err != nil {
        log.Printf("Adding file %v failed: %v", pathToAdd, err)
        errc <- err
        close(done)
      } else {
        pi.progressReporter.accountAdd(filesize)
      }
    }()
  }

  go func() {
    errc <- nil
  }()

  wg.Wait()

  if err := <-errc; err != nil {
    return err
  }

  return nil
}

func (pi *PackageInstaller) removeSelfIfNeeded() {
  if len(pi.removeSelfPath) == 0 {
    log.Println("No need to remove itself")
    return
  }

  // TODO: move this to windows-only define
  pathToRemove := filepath.FromSlash(pi.removeSelfPath)
  log.Println("Removing exe backup", pathToRemove)
  cmd := exec.Command("cmd", "/C", "ping localhost -n 2 -w 5000 > nul & del", pathToRemove)
  err := cmd.Start()
  if err != nil {
    log.Println(err)
  }
}

func purgeFiles(root string, files []*UpdateFileInfo) {
  log.Printf("Purging %v files", len(files))

  var wg sync.WaitGroup

  for _, fi := range files {
    wg.Add(1)

    go func(fileToPurge string) {
      defer wg.Done()

      fullpath := path.Join(root, fileToPurge)
      err := os.Remove(fullpath)
      if err != nil {
        log.Printf("Error while purging %v: %v", fullpath, err)
      }
    }(fi.Filepath)
  }

  wg.Wait()
}

func ensureDirExists(fullpath string) (err error) {
  dirpath := path.Dir(fullpath)
  err = os.MkdirAll(dirpath, os.ModeDir)
  if err != nil {
    log.Printf("Failed to create directory %v", dirpath)
  }

  return err
}

type ByLength []string

func (s ByLength) Len() int {
    return len(s)
}
func (s ByLength) Swap(i, j int) {
    s[i], s[j] = s[j], s[i]
}
func (s ByLength) Less(i, j int) bool {
    return len(s[i]) > len(s[j])
}

func cleanupEmptyDirs(root string) {
  dirs := make([]string, 0, 10)

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

    if info.Mode().IsDir() {
      dirs = append(dirs, path)
    }

    return nil
  })

  if err != nil {
    log.Printf("Error while cleaning up empty dirs: %v", err)
  }

  removeEmptyDirs(dirs)
}

func removeEmptyDirs(dirs []string) {
  sort.Sort(ByLength(dirs))

  for _, dirpath := range dirs {
    entries, err := ioutil.ReadDir(dirpath)
    if err != nil { continue }

    if len(entries) == 0 {
      log.Printf("Removing empty dir %v", dirpath)

      err = os.Remove(dirpath)
      if err != nil {
        log.Printf("Error while removing dir %v: %v", dirpath, err)
      }
    }
  }
}

func (pr *ProgressReporter) accountRemove(progress int64) {
  pr.progressWG.Add(1)
  go func() {
    pr.progressChan <- (progress*RemoveFactor)/100
  }()
}

func (pr *ProgressReporter) accountUpdate(progress int64) {
  pr.progressWG.Add(1)
  go func() {
    pr.progressChan <- (progress*UpdateFactor)/100
  }()
}

func (pr *ProgressReporter) accountAdd(progress int64) {
  pr.progressWG.Add(1)
  go func() {
    pr.progressChan <- (progress*AddFactor)/100
  }()
}

func (pr *ProgressReporter) accountBackupRemove() {
  // exact size of files is not known when removeBackups()
  // so using some arbitrary value (fair dice roll)
  pr.progressWG.Add(1)
  go func() {
    pr.progressChan <- RemoveBackupPrice
  }()
}

func (pr *ProgressReporter) reportingLoop() {
  for chunk := range pr.progressChan {
    pr.currentProgress += uint64(chunk)

    percent := (pr.currentProgress*100) / pr.grandTotal
    pr.percent = int(percent)

    pr.progressHandler.HandlePercentChange(pr.percent)
    pr.progressWG.Done()
  }

  log.Println("Reporting loop finished")
}

func (pr *ProgressReporter) waitProgressReported() {
  log.Println("Waiting for progress reporting to finish")
  pr.progressWG.Wait()
}

func (pr *ProgressReporter) shutdown() {
  log.Println("Shutting down progress reporter...")
  close(pr.progressChan)
  go func() {
    pr.finished <- true
  }()
}

func (pr *ProgressReporter) receiveSystemMessages() {
  for msg := range pr.systemMessageChan {
    pr.progressHandler.HandleSystemMessage(msg)
  }
}

func (pr *ProgressReporter) receiveFinish() {
  <- pr.finished
  pr.progressHandler.HandleFinish()
}

func (pr *ProgressReporter) handleProgress() {
  go pr.receiveSystemMessages()
  go pr.receiveFinish()
}

func (ph *LogProgressHandler) HandlePercentChange(percent int) {
  log.Printf("Completed %v%%", percent)
}

func (ph *LogProgressHandler) HandleSystemMessage(msg string) {
  log.Printf("System message: %v", msg)
}

func (ph *LogProgressHandler) HandleFinish() {
  log.Printf("Finished")
}
<|endoftext|>


<commit_before>package honeybadger

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"
	"os"
	"runtime"
	"strings"

	_ "crypto/sha512"
)

type Notifier struct {
	Name     string `json:"name"`
	Url      string `json:"url"`
	Version  string `json:"version"`
	Language string `json:"language"`
}

type BacktraceLine struct {
	Method string `json:"method"`
	File   string `json:"file"`
	Number string `json:"number"`
}

type Error struct {
	Class     string                 `json:"class"`
	Message   string                 `json:"message"`
	Backtrace []*BacktraceLine       `json:"backtrace"`
	Source    map[string]interface{} `json:"source"`
}

type Request struct {
	Url       string                 `json:"url"`
	Component string                 `json:"component"`
	Action    string                 `json:"action"`
	Params    map[string]interface{} `json:"params"`
	Session   map[string]interface{} `json:"session"`
	CgiData   map[string]interface{} `json:"cgi_data"`
	Context   map[string]interface{} `json:"context"`
}

type Server struct {
	ProjectRoot     map[string]interface{} `json:"project_root"`
	EnvironmentName string                 `json:"environment_name"`
	Hostname        string                 `json:"hostname"`
}

type Report struct {
	Notifier *Notifier `json:"notifier"`
	Error    *Error    `json:"error"`
	Request  *Request  `json:"request"`
	Server   *Server   `json:"server"`
}

var (
	ApiKey      string
	Environment string
	Client      *http.Client = http.DefaultClient
	reports     chan *Report
)

func Start(apiKey, environment string) {
	ApiKey = apiKey
	Environment = environment

	if ApiKey == "" {
		log.Println("Honeybadger disabled")
		return
	}

	const numWorkers = 10
	const bufferSize = 1024

	reports = make(chan *Report, bufferSize)
	for i := 0; i < numWorkers; i++ {
		go consume()
	}

	log.Println("Honeybadger client started")
}

func consume() {
	for report := range reports {
		if e := report.Send(); e != nil {
			log.Printf("honeybadger: could not send report: %s", e)
		}
	}
}

// Send sends a report to HB synchronously and returns the error
func Send(err error) error {
	if ApiKey == "" {
		return err
	}
	report, e := NewReport(err)
	if e != nil {
		log.Printf("honeybadger: could not create report: %s", e)
		return err
	}
	if e := report.Send(); e != nil {
		log.Printf("honeybadger: could not send report: %s", e)
	}
	return err
}

// Dispatch sends a report to HB asynchronously and returns the error
func Dispatch(err error) error {
	return DispatchWithContext(err, nil)
}

// DispatchWithContext sends a report to HB asynchronously with context and
// returns the error
func DispatchWithContext(err error, context map[string]interface{}) error {
	if ApiKey == "" {
		return err
	}
	report, rErr := NewReport(err)
	if rErr != nil {
		log.Printf("honeybadger: could not create report: %s", rErr)
		return err
	}
	if context != nil {
		for k, v := range context {
			report.AddContext(k, v)
		}
	}

	select {
	case reports <- report:
	default:
		log.Printf("honeybadger: queue is full, dropping on the floor: %s", err)
	}
	return err
}

// Create a new report using the given error message and current call stack.
func NewReport(msg interface{}) (r *Report, err error) {
	return NewReportWithSkipCallers(msg, 0)
}

func fullMessage(msg interface{}) string {
	return fmt.Sprintf("%s", msg)
}

func exceptionClass(message string) string {
	pieces := strings.Split(message, ":")
	for i := len(pieces)-1; i >= 0; i-- {
		err := strings.TrimSpace(pieces[i])
		if len(err) > 0 {
			return err
		}
	}
	return ""
}

// Create a new report using the given error message and current call stack.
// Supply an integer indicating how many callers to skip (0 is none).
func NewReportWithSkipCallers(msg interface{}, skipCallers int) (r *Report, err error) {
	var cwd, hostname string

	if ApiKey == "" {
		err = errors.New("You must set an API key first")
		return
	}

	if Environment == "" {
		err = errors.New("You must set an environment first")
		return
	}

	cwd, _ = os.Getwd()
	hostname, _ = os.Hostname()

	fullMessage := fullMessage(msg)
	exceptionClass := exceptionClass(fullMessage)

	r = &Report{
		Notifier: &Notifier{
			Name:     "Honeybadger (Go)",
			Url:      "https://github.com/librato/honeybadger-go",
			Version:  "1.0",
			Language: "Go",
		},
		Error: &Error{
			Class:     exceptionClass,
			Message:   fullMessage,
			Backtrace: make([]*BacktraceLine, 0),
			Source:    make(map[string]interface{}),
		},
		Request: &Request{
			Params:  make(map[string]interface{}),
			Session: make(map[string]interface{}),
			CgiData: make(map[string]interface{}),
			Context: make(map[string]interface{}),
		},
		Server: &Server{
			ProjectRoot:     make(map[string]interface{}),
			EnvironmentName: Environment,
			Hostname:        hostname,
		},
	}

	callers := make([]uintptr, 10)
	runtime.Callers(0, callers)

	for i, pc := range callers {
		if i < 3 { // skip self-inflicted depth
			continue
		}

		fc := runtime.FuncForPC(pc)
		if fc != nil {
			ms := strings.Split(fc.Name(), "/")
			m := ms[len(ms)-1]
			f, n := fc.FileLine(pc)

			// If we reach our origin depth, use the given function name as error class
			if i == skipCallers+2 {
				r.Error.Class = m
			}

			r.Error.Backtrace = append(r.Error.Backtrace, &BacktraceLine{
				Method: m,
				File:   f,
				Number: fmt.Sprintf("%d", n),
			})
		}
	}

	r.Server.ProjectRoot["path"] = cwd

	return
}

// Add a key and given value to the report as context
func (r *Report) AddContext(k string, v interface{}) {
	r.Request.Context[k] = v
}

// Add a key and given value to the report as parameters
func (r *Report) AddParam(k string, v interface{}) {
	r.Request.Params[k] = v
}

// Add a key and given value to the report as session
func (r *Report) AddSession(k string, v interface{}) {
	r.Request.Session[k] = v
}

// Send the report asynchronously
func (r *Report) Dispatch() {
	go func() {
		if err := r.Send(); err != nil {
			log.Printf("honeybadger: could not send report: %s", err)
		}
	}()
}

// Send the report and return an error if present
func (r *Report) Send() (err error) {
	var req *http.Request
	var resp *http.Response
	var payload []byte

	if payload, err = json.MarshalIndent(r, "", "  "); err != nil {
		return
	}

	if req, err = http.NewRequest("POST", "https://api.honeybadger.io/v1/notices", bytes.NewBuffer(payload)); err != nil {
		return
	}

	req.Header.Add("Content-Type", "application/json")
	req.Header.Add("Accept", "application/json")
	req.Header.Add("X-API-Key", ApiKey)

	if resp, err = Client.Do(req); err != nil {
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode < 201 {
		err = errors.New(fmt.Sprintf("unable to send: error %d", resp.StatusCode))
	}
	return
}
<commit_msg>go fmt<commit_after>package honeybadger

import (
	"bytes"
	"encoding/json"
	"errors"
	"fmt"
	"log"
	"net/http"
	"os"
	"runtime"
	"strings"

	_ "crypto/sha512"
)

type Notifier struct {
	Name     string `json:"name"`
	Url      string `json:"url"`
	Version  string `json:"version"`
	Language string `json:"language"`
}

type BacktraceLine struct {
	Method string `json:"method"`
	File   string `json:"file"`
	Number string `json:"number"`
}

type Error struct {
	Class     string                 `json:"class"`
	Message   string                 `json:"message"`
	Backtrace []*BacktraceLine       `json:"backtrace"`
	Source    map[string]interface{} `json:"source"`
}

type Request struct {
	Url       string                 `json:"url"`
	Component string                 `json:"component"`
	Action    string                 `json:"action"`
	Params    map[string]interface{} `json:"params"`
	Session   map[string]interface{} `json:"session"`
	CgiData   map[string]interface{} `json:"cgi_data"`
	Context   map[string]interface{} `json:"context"`
}

type Server struct {
	ProjectRoot     map[string]interface{} `json:"project_root"`
	EnvironmentName string                 `json:"environment_name"`
	Hostname        string                 `json:"hostname"`
}

type Report struct {
	Notifier *Notifier `json:"notifier"`
	Error    *Error    `json:"error"`
	Request  *Request  `json:"request"`
	Server   *Server   `json:"server"`
}

var (
	ApiKey      string
	Environment string
	Client      *http.Client = http.DefaultClient
	reports     chan *Report
)

func Start(apiKey, environment string) {
	ApiKey = apiKey
	Environment = environment

	if ApiKey == "" {
		log.Println("Honeybadger disabled")
		return
	}

	const numWorkers = 10
	const bufferSize = 1024

	reports = make(chan *Report, bufferSize)
	for i := 0; i < numWorkers; i++ {
		go consume()
	}

	log.Println("Honeybadger client started")
}

func consume() {
	for report := range reports {
		if e := report.Send(); e != nil {
			log.Printf("honeybadger: could not send report: %s", e)
		}
	}
}

// Send sends a report to HB synchronously and returns the error
func Send(err error) error {
	if ApiKey == "" {
		return err
	}
	report, e := NewReport(err)
	if e != nil {
		log.Printf("honeybadger: could not create report: %s", e)
		return err
	}
	if e := report.Send(); e != nil {
		log.Printf("honeybadger: could not send report: %s", e)
	}
	return err
}

// Dispatch sends a report to HB asynchronously and returns the error
func Dispatch(err error) error {
	return DispatchWithContext(err, nil)
}

// DispatchWithContext sends a report to HB asynchronously with context and
// returns the error
func DispatchWithContext(err error, context map[string]interface{}) error {
	if ApiKey == "" {
		return err
	}
	report, rErr := NewReport(err)
	if rErr != nil {
		log.Printf("honeybadger: could not create report: %s", rErr)
		return err
	}
	if context != nil {
		for k, v := range context {
			report.AddContext(k, v)
		}
	}

	select {
	case reports <- report:
	default:
		log.Printf("honeybadger: queue is full, dropping on the floor: %s", err)
	}
	return err
}

// Create a new report using the given error message and current call stack.
func NewReport(msg interface{}) (r *Report, err error) {
	return NewReportWithSkipCallers(msg, 0)
}

func fullMessage(msg interface{}) string {
	return fmt.Sprintf("%s", msg)
}

func exceptionClass(message string) string {
	pieces := strings.Split(message, ":")
	for i := len(pieces) - 1; i >= 0; i-- {
		err := strings.TrimSpace(pieces[i])
		if len(err) > 0 {
			return err
		}
	}
	return ""
}

// Create a new report using the given error message and current call stack.
// Supply an integer indicating how many callers to skip (0 is none).
func NewReportWithSkipCallers(msg interface{}, skipCallers int) (r *Report, err error) {
	var cwd, hostname string

	if ApiKey == "" {
		err = errors.New("You must set an API key first")
		return
	}

	if Environment == "" {
		err = errors.New("You must set an environment first")
		return
	}

	cwd, _ = os.Getwd()
	hostname, _ = os.Hostname()

	fullMessage := fullMessage(msg)
	exceptionClass := exceptionClass(fullMessage)

	r = &Report{
		Notifier: &Notifier{
			Name:     "Honeybadger (Go)",
			Url:      "https://github.com/librato/honeybadger-go",
			Version:  "1.0",
			Language: "Go",
		},
		Error: &Error{
			Class:     exceptionClass,
			Message:   fullMessage,
			Backtrace: make([]*BacktraceLine, 0),
			Source:    make(map[string]interface{}),
		},
		Request: &Request{
			Params:  make(map[string]interface{}),
			Session: make(map[string]interface{}),
			CgiData: make(map[string]interface{}),
			Context: make(map[string]interface{}),
		},
		Server: &Server{
			ProjectRoot:     make(map[string]interface{}),
			EnvironmentName: Environment,
			Hostname:        hostname,
		},
	}

	callers := make([]uintptr, 10)
	runtime.Callers(0, callers)

	for i, pc := range callers {
		if i < 3 { // skip self-inflicted depth
			continue
		}

		fc := runtime.FuncForPC(pc)
		if fc != nil {
			ms := strings.Split(fc.Name(), "/")
			m := ms[len(ms)-1]
			f, n := fc.FileLine(pc)

			// If we reach our origin depth, use the given function name as error class
			if i == skipCallers+2 {
				r.Error.Class = m
			}

			r.Error.Backtrace = append(r.Error.Backtrace, &BacktraceLine{
				Method: m,
				File:   f,
				Number: fmt.Sprintf("%d", n),
			})
		}
	}

	r.Server.ProjectRoot["path"] = cwd

	return
}

// Add a key and given value to the report as context
func (r *Report) AddContext(k string, v interface{}) {
	r.Request.Context[k] = v
}

// Add a key and given value to the report as parameters
func (r *Report) AddParam(k string, v interface{}) {
	r.Request.Params[k] = v
}

// Add a key and given value to the report as session
func (r *Report) AddSession(k string, v interface{}) {
	r.Request.Session[k] = v
}

// Send the report asynchronously
func (r *Report) Dispatch() {
	go func() {
		if err := r.Send(); err != nil {
			log.Printf("honeybadger: could not send report: %s", err)
		}
	}()
}

// Send the report and return an error if present
func (r *Report) Send() (err error) {
	var req *http.Request
	var resp *http.Response
	var payload []byte

	if payload, err = json.MarshalIndent(r, "", "  "); err != nil {
		return
	}

	if req, err = http.NewRequest("POST", "https://api.honeybadger.io/v1/notices", bytes.NewBuffer(payload)); err != nil {
		return
	}

	req.Header.Add("Content-Type", "application/json")
	req.Header.Add("Accept", "application/json")
	req.Header.Add("X-API-Key", ApiKey)

	if resp, err = Client.Do(req); err != nil {
		return
	}
	defer resp.Body.Close()

	if resp.StatusCode < 201 {
		err = errors.New(fmt.Sprintf("unable to send: error %d", resp.StatusCode))
	}
	return
}
<|endoftext|>


<commit_before>/*
Go interface to the Android Debug Bridge (adb).

The client/server spec is defined at https://android.googlesource.com/platform/system/core/+/master/adb/OVERVIEW.TXT.

WARNING This library is under heavy development, and its API is likely to change without notice.
*/
package goadb

import (
	"fmt"
	"os/exec"
	"strconv"

	"github.com/zach-klippenstein/goadb/wire"
)

// Dialer is a function that knows how to create a connection to an adb server.
type Dialer func() (*wire.Conn, error)

/*
Interacts with host services on the adb server.

Eg.
	dialer := &HostClient{wire.Dial}
	dialer.GetServerVersion()

TODO make this a real example.

TODO Finish implementing services.

See list of services at https://android.googlesource.com/platform/system/core/+/master/adb/SERVICES.TXT.
*/
type HostClient struct {
	Dialer
}

// GetServerVersion asks the ADB server for its internal version number.
func (c *HostClient) GetServerVersion() (int, error) {
	resp, err := c.roundTripSingleResponse([]byte("host:version"))
	if err != nil {
		return 0, err
	}

	version, err := strconv.ParseInt(string(resp), 16, 32)
	return int(version), err
}

/*
KillServer tells the server to quit immediately.

Corresponds to the command:
	adb kill-server
*/
func (c *HostClient) KillServer() error {
	conn, err := c.Dialer()
	if err != nil {
		return err
	}
	defer conn.Close()

	if err = conn.SendMessage([]byte("host:kill")); err != nil {
		return err
	}

	return nil
}

/*
StartServer ensures there is a server running.

Currently implemented by just running
	adb start-server
*/
func (c *HostClient) StartServer() error {
	cmd := exec.Command("adb", "start-server")
	return cmd.Run()
}

/*
ListDeviceSerials returns the serial numbers of all attached devices.

Corresponds to the command:
	adb devices
*/
func (c *HostClient) ListDeviceSerials() ([]string, error) {
	resp, err := c.roundTripSingleResponse([]byte("host:devices"))
	if err != nil {
		return nil, err
	}

	devices, err := parseDeviceList(string(resp), parseDeviceShort)
	if err != nil {
		return nil, err
	}

	serials := make([]string, len(devices))
	for i, dev := range devices {
		serials[i] = dev.Serial
	}
	return serials, nil
}

/*
ListDevices returns the list of connected devices.

Corresponds to the command:
	adb devices -l
*/
func (c *HostClient) ListDevices() ([]*Device, error) {
	resp, err := c.roundTripSingleResponse([]byte("host:devices-l"))
	if err != nil {
		return nil, err
	}

	return parseDeviceList(string(resp), parseDeviceLong)
}

func (c *HostClient) roundTripSingleResponse(req []byte) (resp []byte, err error) {
	conn, err := c.Dialer()
	if err != nil {
		return nil, err
	}
	defer conn.Close()

	if err = conn.SendMessage(req); err != nil {
		return nil, err
	}

	err = c.readStatusFailureAsError(conn)
	if err != nil {
		return nil, err
	}

	return conn.ReadMessage()
}

// Reads the status, and if failure, reads the message and returns it as an error.
// If the status is success, doesn't read the message.
func (c *HostClient) readStatusFailureAsError(conn *wire.Conn) error {
	status, err := conn.ReadStatus()
	if err != nil {
		return err
	}

	if !status.IsSuccess() {
		msg, err := conn.ReadMessage()
		if err != nil {
			return err
		}

		return fmt.Errorf("server error: %s", msg)
	}

	return nil
}
<commit_msg>Lint fixes.<commit_after>/*
Package goadb is a Go interface to the Android Debug Bridge (adb).

The client/server spec is defined at https://android.googlesource.com/platform/system/core/+/master/adb/OVERVIEW.TXT.

WARNING This library is under heavy development, and its API is likely to change without notice.
*/
package goadb

import (
	"fmt"
	"os/exec"
	"strconv"

	"github.com/zach-klippenstein/goadb/wire"
)

// Dialer is a function that knows how to create a connection to an adb server.
type Dialer func() (*wire.Conn, error)

/*
HostClient interacts with host services on the adb server.

Eg.
	dialer := &HostClient{wire.Dial}
	dialer.GetServerVersion()

TODO make this a real example.

TODO Finish implementing services.

See list of services at https://android.googlesource.com/platform/system/core/+/master/adb/SERVICES.TXT.
*/
type HostClient struct {
	Dialer
}

// GetServerVersion asks the ADB server for its internal version number.
func (c *HostClient) GetServerVersion() (int, error) {
	resp, err := c.roundTripSingleResponse([]byte("host:version"))
	if err != nil {
		return 0, err
	}

	version, err := strconv.ParseInt(string(resp), 16, 32)
	return int(version), err
}

/*
KillServer tells the server to quit immediately.

Corresponds to the command:
	adb kill-server
*/
func (c *HostClient) KillServer() error {
	conn, err := c.Dialer()
	if err != nil {
		return err
	}
	defer conn.Close()

	if err = conn.SendMessage([]byte("host:kill")); err != nil {
		return err
	}

	return nil
}

/*
StartServer ensures there is a server running.

Currently implemented by just running
	adb start-server
*/
func (c *HostClient) StartServer() error {
	cmd := exec.Command("adb", "start-server")
	return cmd.Run()
}

/*
ListDeviceSerials returns the serial numbers of all attached devices.

Corresponds to the command:
	adb devices
*/
func (c *HostClient) ListDeviceSerials() ([]string, error) {
	resp, err := c.roundTripSingleResponse([]byte("host:devices"))
	if err != nil {
		return nil, err
	}

	devices, err := parseDeviceList(string(resp), parseDeviceShort)
	if err != nil {
		return nil, err
	}

	serials := make([]string, len(devices))
	for i, dev := range devices {
		serials[i] = dev.Serial
	}
	return serials, nil
}

/*
ListDevices returns the list of connected devices.

Corresponds to the command:
	adb devices -l
*/
func (c *HostClient) ListDevices() ([]*Device, error) {
	resp, err := c.roundTripSingleResponse([]byte("host:devices-l"))
	if err != nil {
		return nil, err
	}

	return parseDeviceList(string(resp), parseDeviceLong)
}

func (c *HostClient) roundTripSingleResponse(req []byte) (resp []byte, err error) {
	conn, err := c.Dialer()
	if err != nil {
		return nil, err
	}
	defer conn.Close()

	if err = conn.SendMessage(req); err != nil {
		return nil, err
	}

	err = c.readStatusFailureAsError(conn)
	if err != nil {
		return nil, err
	}

	return conn.ReadMessage()
}

// Reads the status, and if failure, reads the message and returns it as an error.
// If the status is success, doesn't read the message.
func (c *HostClient) readStatusFailureAsError(conn *wire.Conn) error {
	status, err := conn.ReadStatus()
	if err != nil {
		return err
	}

	if !status.IsSuccess() {
		msg, err := conn.ReadMessage()
		if err != nil {
			return err
		}

		return fmt.Errorf("server error: %s", msg)
	}

	return nil
}
<|endoftext|>


<commit_before>/*

   account.go
       Account (Signed) Endpoints for Binance Exchange API

*/
package binance

import (
	"fmt"
)

// Get Basic Account Information
func (b *Binance) GetAccountInfo() (account Account, err error) {

	reqUrl := fmt.Sprintf("api/v3/account")

	_, err = b.client.do("GET", reqUrl, "", true, &account)
	if err != nil {
		return
	}

	return
}

// Filter Basic Account Information To Retrieve Current Holdings
func (b *Binance) GetPositions() (positions []Balance, err error) {

	reqUrl := fmt.Sprintf("api/v3/account")
	account := Account{}

	_, err = b.client.do("GET", reqUrl, "", true, &account)
	if err != nil {
		return
	}

	positions = make([]Balance, len(account.Balances))
	i := 0

	for _, balance := range account.Balances {
		if balance.Free != 0.0 || balance.Locked != 0.0 {
			positions[i] = balance
			i++
		}
	}

	return positions[:i], nil
}

// Place a Limit Order
func (b *Binance) PlaceLimitOrder(l LimitOrder) (res PlacedOrder, err error) {

	err = l.ValidateLimitOrder()
	if err != nil {
		return
	}

	reqUrl := fmt.Sprintf("api/v3/order?symbol=%s&side=%s&type=%s&timeInForce=%s&quantity=%f&price=%f&recvWindow=%d", l.Symbol, l.Side, l.Type, l.TimeInForce, l.Quantity, l.Price, l.RecvWindow)

	_, err = b.client.do("POST", reqUrl, "", true, &res)
	if err != nil {
		return
	}

	return
}

// Place a Market Order
func (b *Binance) PlaceMarketOrder(m MarketOrder) (res PlacedOrder, err error) {

	err = m.ValidateMarketOrder()
	if err != nil {
		return
	}

	reqUrl := fmt.Sprintf("api/v3/order?symbol=%s&side=%s&type=%s&quantity=%f&recvWindow=%d", m.Symbol, m.Side, m.Type, m.Quantity, m.RecvWindow)

	_, err = b.client.do("POST", reqUrl, "", true, &res)
	if err != nil {
		return
	}

	return
}

// Cancel an Order
func (b *Binance) CancelOrder(query OrderQuery) (order CanceledOrder, err error) {

	err = query.ValidateOrderQuery()
	if err != nil {
		return
	}

	reqUrl := fmt.Sprintf("api/v3/order?symbol=%s&orderId=%d&recvWindow=%d", query.Symbol, query.OrderId, query.RecvWindow)

	_, err = b.client.do("DELETE", reqUrl, "", true, &order)
	if err != nil {
		return
	}

	return
}

// Check the Status of an Order
func (b *Binance) CheckOrder(query OrderQuery) (status OrderStatus, err error) {

	err = query.ValidateOrderQuery()
	if err != nil {
		return
	}

	reqUrl := fmt.Sprintf("api/v3/order?symbol=%s&orderId=%d&origClientOrderId=%s&recvWindow=%d", query.Symbol, query.OrderId, query.RecvWindow)

	_, err = b.client.do("GET", reqUrl, "", true, &status)
	if err != nil {
		return
	}

	return
}

// Retrieve All Open Orders
func (b *Binance) GetAllOpenOrders() (orders []OrderStatus, err error) {
	_, err = b.client.do("GET", "api/v3/openOrders", "", true, &orders)

	if err != nil {
		return
	}

	return
}

// Retrieve All Open Orders for a given symbol
func (b *Binance) GetOpenOrders(query OpenOrdersQuery) (orders []OrderStatus, err error) {

	err = query.ValidateOpenOrdersQuery()
	if err != nil {
		return
	}
	reqUrl := fmt.Sprintf("api/v3/openOrders?symbol=%s&recvWindow=%d", query.Symbol, query.RecvWindow)
	_, err = b.client.do("GET", reqUrl, "", true, &orders)
	if err != nil {
		return
	}

	return
}

// Get all account orders; active, canceled, or filled.
func (b *Binance) GetAllOrders(query AllOrdersQuery) (orders []OrderStatus, err error) {
	err = query.ValidateAllOrdersQuery()
	if err != nil {
		return
	}
	reqUrl := fmt.Sprintf("api/v3/allOrders?symbol=%s&recvWindow=%d&limit=%d", query.Symbol, query.RecvWindow, query.Limit)
	if query.OrderId != 0 {
		reqUrl += fmt.Sprintf("&orderId=%d", query.OrderId)
	}
	_, err = b.client.do("GET", reqUrl, "", true, &orders)
	if err != nil {
		return
	}

	return
}

// Retrieves all trades
func (b *Binance) GetTrades(symbol string) (trades []Trade, err error) {

	reqUrl := fmt.Sprintf("api/v3/myTrades?symbol=%s", symbol)

	_, err = b.client.do("GET", reqUrl, "", true, &trades)

	if err != nil {
		return
	}
	return
}

func (b *Binance) GetTrade(symbol string, id int64) (trade Trade, err error) {

    reqUrl := fmt.Sprintf("api/v3/myTrades?symbol=%s&fromId=%d", symbol, id)

    var trades []Trade
    _, err = b.client.do("GET", reqUrl, "", true, &trades)
    if err != nil {
        return
    }

    for _, t := range trades {
        if t.Id == id {
            trade = t
            break
        }
    }
    return
}

//
// Retrieves all withdrawals
func (b *Binance) GetWithdrawHistory() (withdraws WithdrawList, err error) {

	reqUrl := fmt.Sprintf("wapi/v3/withdrawHistory.html")

	_, err = b.client.do("GET", reqUrl, "", true, &withdraws)
	if err != nil {
		return
	}
	return
}

//
// Retrieves all deposits
func (b *Binance) GetDepositHistory() (deposits DepositList, err error) {

	reqUrl := fmt.Sprintf("wapi/v3/depositHistory.html")

	_, err = b.client.do("GET", reqUrl, "", true, &deposits)
	if err != nil {
		return
	}
	return
}
<commit_msg>changes GetOrder to use orderid instead of trade id<commit_after>/*

   account.go
       Account (Signed) Endpoints for Binance Exchange API

*/
package binance

import (
	"fmt"
)

// Get Basic Account Information
func (b *Binance) GetAccountInfo() (account Account, err error) {

	reqUrl := fmt.Sprintf("api/v3/account")

	_, err = b.client.do("GET", reqUrl, "", true, &account)
	if err != nil {
		return
	}

	return
}

// Filter Basic Account Information To Retrieve Current Holdings
func (b *Binance) GetPositions() (positions []Balance, err error) {

	reqUrl := fmt.Sprintf("api/v3/account")
	account := Account{}

	_, err = b.client.do("GET", reqUrl, "", true, &account)
	if err != nil {
		return
	}

	positions = make([]Balance, len(account.Balances))
	i := 0

	for _, balance := range account.Balances {
		if balance.Free != 0.0 || balance.Locked != 0.0 {
			positions[i] = balance
			i++
		}
	}

	return positions[:i], nil
}

// Place a Limit Order
func (b *Binance) PlaceLimitOrder(l LimitOrder) (res PlacedOrder, err error) {

	err = l.ValidateLimitOrder()
	if err != nil {
		return
	}

	reqUrl := fmt.Sprintf("api/v3/order?symbol=%s&side=%s&type=%s&timeInForce=%s&quantity=%f&price=%f&recvWindow=%d", l.Symbol, l.Side, l.Type, l.TimeInForce, l.Quantity, l.Price, l.RecvWindow)

	_, err = b.client.do("POST", reqUrl, "", true, &res)
	if err != nil {
		return
	}

	return
}

// Place a Market Order
func (b *Binance) PlaceMarketOrder(m MarketOrder) (res PlacedOrder, err error) {

	err = m.ValidateMarketOrder()
	if err != nil {
		return
	}

	reqUrl := fmt.Sprintf("api/v3/order?symbol=%s&side=%s&type=%s&quantity=%f&recvWindow=%d", m.Symbol, m.Side, m.Type, m.Quantity, m.RecvWindow)

	_, err = b.client.do("POST", reqUrl, "", true, &res)
	if err != nil {
		return
	}

	return
}

// Cancel an Order
func (b *Binance) CancelOrder(query OrderQuery) (order CanceledOrder, err error) {

	err = query.ValidateOrderQuery()
	if err != nil {
		return
	}

	reqUrl := fmt.Sprintf("api/v3/order?symbol=%s&orderId=%d&recvWindow=%d", query.Symbol, query.OrderId, query.RecvWindow)

	_, err = b.client.do("DELETE", reqUrl, "", true, &order)
	if err != nil {
		return
	}

	return
}

// Check the Status of an Order
func (b *Binance) CheckOrder(query OrderQuery) (status OrderStatus, err error) {

	err = query.ValidateOrderQuery()
	if err != nil {
		return
	}

	reqUrl := fmt.Sprintf("api/v3/order?symbol=%s&orderId=%d&origClientOrderId=%s&recvWindow=%d", query.Symbol, query.OrderId, query.RecvWindow)

	_, err = b.client.do("GET", reqUrl, "", true, &status)
	if err != nil {
		return
	}

	return
}

// Retrieve All Open Orders
func (b *Binance) GetAllOpenOrders() (orders []OrderStatus, err error) {
	_, err = b.client.do("GET", "api/v3/openOrders", "", true, &orders)

	if err != nil {
		return
	}

	return
}

// Retrieve All Open Orders for a given symbol
func (b *Binance) GetOpenOrders(query OpenOrdersQuery) (orders []OrderStatus, err error) {

	err = query.ValidateOpenOrdersQuery()
	if err != nil {
		return
	}
	reqUrl := fmt.Sprintf("api/v3/openOrders?symbol=%s&recvWindow=%d", query.Symbol, query.RecvWindow)
	_, err = b.client.do("GET", reqUrl, "", true, &orders)
	if err != nil {
		return
	}

	return
}

// Get all account orders; active, canceled, or filled.
func (b *Binance) GetAllOrders(query AllOrdersQuery) (orders []OrderStatus, err error) {
	err = query.ValidateAllOrdersQuery()
	if err != nil {
		return
	}
	reqUrl := fmt.Sprintf("api/v3/allOrders?symbol=%s&recvWindow=%d&limit=%d", query.Symbol, query.RecvWindow, query.Limit)
	if query.OrderId != 0 {
		reqUrl += fmt.Sprintf("&orderId=%d", query.OrderId)
	}
	_, err = b.client.do("GET", reqUrl, "", true, &orders)
	if err != nil {
		return
	}

	return
}

// Retrieves all trades
func (b *Binance) GetTrades(symbol string) (trades []Trade, err error) {

	reqUrl := fmt.Sprintf("api/v3/myTrades?symbol=%s", symbol)

	_, err = b.client.do("GET", reqUrl, "", true, &trades)

	if err != nil {
		return
	}
	return
}

func (b *Binance) GetTrade(symbol string, id int64) (trade Trade, err error) {

    reqUrl := fmt.Sprintf("api/v3/myTrades?symbol=%s", symbol)

    var trades []Trade
    _, err = b.client.do("GET", reqUrl, "", true, &trades)
    if err != nil {
        return
    }

    for _, t := range trades {
        if t.OrderId == id {
            trade = t
            break
        }
    }
    return
}

//
// Retrieves all withdrawals
func (b *Binance) GetWithdrawHistory() (withdraws WithdrawList, err error) {

	reqUrl := fmt.Sprintf("wapi/v3/withdrawHistory.html")

	_, err = b.client.do("GET", reqUrl, "", true, &withdraws)
	if err != nil {
		return
	}
	return
}

//
// Retrieves all deposits
func (b *Binance) GetDepositHistory() (deposits DepositList, err error) {

	reqUrl := fmt.Sprintf("wapi/v3/depositHistory.html")

	_, err = b.client.do("GET", reqUrl, "", true, &deposits)
	if err != nil {
		return
	}
	return
}
<|endoftext|>


<commit_before>package main

import (
	"fmt"
	"log"
	"strconv"
)

func main() {
	bitwiseAnd()
}

func bitwiseAnd() {
	fmt.Println("\nbitwiseAND")
	a := bin("101")
	b := bin("011")

	// bit is 1 only if
	// sources(a,b) bit are 1.
	c := a & b

	prt("a", a)
	prt("b", b)
	prt("a & b", c)
}

// bin converts binary string to integer.
func bin(s string) int64 {
	i, err := strconv.ParseInt(s, 2, 64)
	if err != nil {
		log.Fatal(err)
	}
	return i
}

func prt(s string, i int64) {
	fmt.Printf("%-10s: %8b = %d\n", s, i, i)
}
<commit_msg>bitwise: add bitwiseOR<commit_after>package main

import (
	"fmt"
	"log"
	"strconv"
)

func main() {
	bitwiseAND()

	bitwiseOR()
}

func bitwiseAND() {
	fmt.Println("\nbitwiseAND")
	a := bin("101")
	b := bin("011")

	// both bit must be 1
	c := a & b

	prt("a", a)
	prt("b", b)
	prt("a & b", c)
}

func bitwiseOR() {
	fmt.Println("\nbitwiseOR")
	a := bin("101")
	b := bin("011")

	// at least one bit must be 1.
	c := a | b

	prt("a", a)
	prt("b", b)
	prt("a | b", c)
}

// bin converts binary string to integer.
func bin(s string) int64 {
	i, err := strconv.ParseInt(s, 2, 64)
	if err != nil {
		log.Fatal(err)
	}
	return i
}

func prt(s string, i int64) {
	fmt.Printf("%-10s: %8b = %d\n", s, i, i)
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2016 Red Hat, Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */

package http

import (
	"bytes"
	"compress/gzip"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"net/url"

	"github.com/skydive-project/skydive/common"
	"github.com/skydive-project/skydive/config"
	"github.com/skydive-project/skydive/logging"
)

type RestClient struct {
	authClient *AuthenticationClient
	client     *http.Client
	url        *url.URL
}

type CrudClient struct {
	RestClient
}

func readBody(resp *http.Response) string {
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return ""
	}
	return string(data)
}

func getHttpClient() *http.Client {
	client := &http.Client{}
	if config.IsTLSenabled() {
		tlsConfig := getTLSConfig(true)
		tr := &http.Transport{TLSClientConfig: tlsConfig}
		client = &http.Client{Transport: tr}
	}
	return client
}

func NewRestClient(url *url.URL, authOptions *AuthenticationOpts) *RestClient {
	client := getHttpClient()
	authClient := NewAuthenticationClient(url, authOptions)
	return &RestClient{
		client:     client,
		authClient: authClient,
		url:        url,
	}
}

func (c *RestClient) debug() bool {
	return config.GetBool("http.rest.debug")
}

func (c *RestClient) Request(method, path string, body io.Reader, header http.Header) (*http.Response, error) {
	if !c.authClient.Authenticated() {
		if err := c.authClient.Authenticate(); err != nil {
			return nil, err
		}
	}

	url := c.url.ResolveReference(&url.URL{Path: path})
	req, err := http.NewRequest(method, url.String(), body)
	if err != nil {
		return nil, err
	}

	if header != nil {
		req.Header = header
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Add("Accept-Encoding", "gzip")

	setCookies(&req.Header, c.authClient)

	if c.debug() {
		if buf, err := httputil.DumpRequest(req, true); err == nil {
			logging.GetLogger().Debugf("Request:\n%s", buf)
		}
	}

	resp, err := c.client.Do(req)
	if err != nil {
		return resp, err
	}

	switch resp.Header.Get("Content-Encoding") {
	case "gzip":
		resp.Body, err = gzip.NewReader(resp.Body)
		resp.Uncompressed = true
		resp.ContentLength = -1
		if err != nil {
			return nil, err
		}
	}

	if c.debug() {
		buf, err := httputil.DumpResponse(resp, true)
		if err == nil {
			logging.GetLogger().Debugf("Response:\n%s", buf)
		} else {
			logging.GetLogger().Debugf("Response (error):\n%s", err)
		}

	}

	return resp, nil
}

func NewCrudClient(url *url.URL, authOpts *AuthenticationOpts) *CrudClient {
	restClient := NewRestClient(url, authOpts)
	return &CrudClient{
		RestClient: *restClient,
	}
}

func (c *CrudClient) List(resource string, values interface{}) error {
	resp, err := c.Request("GET", resource, nil, nil)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("Failed to list %s, %s: %s", resource, resp.Status, readBody(resp))
	}

	return common.JSONDecode(resp.Body, values)
}

func (c *CrudClient) Get(resource string, id string, value interface{}) error {
	resp, err := c.Request("GET", resource+"/"+id, nil, nil)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("Failed to get %s, %s: %s", resource, resp.Status, readBody(resp))
	}

	return common.JSONDecode(resp.Body, value)
}

func (c *CrudClient) Create(resource string, value interface{}) error {
	s, err := json.Marshal(value)
	if err != nil {
		return err
	}

	contentReader := bytes.NewReader(s)
	resp, err := c.Request("POST", resource, contentReader, nil)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("Failed to create %s, %s: %s", resource, resp.Status, readBody(resp))
	}

	return common.JSONDecode(resp.Body, value)
}

func (c *CrudClient) Update(resource string, id string, value interface{}) error {
	s, err := json.Marshal(value)
	if err != nil {
		return err
	}

	contentReader := bytes.NewReader(s)
	resp, err := c.Request("PUT", resource+"/"+id, contentReader, nil)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("Failed to update %s, %s: %s", resource, resp.Status, readBody(resp))
	}

	return common.JSONDecode(resp.Body, value)
}

func (c *CrudClient) Delete(resource string, id string) error {
	resp, err := c.Request("DELETE", resource+"/"+id, nil, nil)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("Failed to delete %s, %s: %s", resource, resp.Status, readBody(resp))
	}

	return nil
}
<commit_msg>client : don't authenticate if username is not set<commit_after>/*
 * Copyright (C) 2016 Red Hat, Inc.
 *
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 *
 */

package http

import (
	"bytes"
	"compress/gzip"
	"encoding/json"
	"fmt"
	"io"
	"io/ioutil"
	"net/http"
	"net/http/httputil"
	"net/url"

	"github.com/skydive-project/skydive/common"
	"github.com/skydive-project/skydive/config"
	"github.com/skydive-project/skydive/logging"
)

type RestClient struct {
	authClient *AuthenticationClient
	client     *http.Client
	url        *url.URL
}

type CrudClient struct {
	RestClient
}

func readBody(resp *http.Response) string {
	data, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return ""
	}
	return string(data)
}

func getHttpClient() *http.Client {
	client := &http.Client{}
	if config.IsTLSenabled() {
		tlsConfig := getTLSConfig(true)
		tr := &http.Transport{TLSClientConfig: tlsConfig}
		client = &http.Client{Transport: tr}
	}
	return client
}

func NewRestClient(url *url.URL, authOptions *AuthenticationOpts) *RestClient {
	client := getHttpClient()
	rc := &RestClient{
		client: client,
		url:    url,
	}
	if authOptions.Username != "" {
		rc.authClient = NewAuthenticationClient(url, authOptions)
	}
	return rc
}

func (c *RestClient) debug() bool {
	return config.GetBool("http.rest.debug")
}

func (c *RestClient) Request(method, path string, body io.Reader, header http.Header) (*http.Response, error) {
	if c.authClient != nil && !c.authClient.Authenticated() {
		if err := c.authClient.Authenticate(); err != nil {
			return nil, err
		}
	}

	url := c.url.ResolveReference(&url.URL{Path: path})
	req, err := http.NewRequest(method, url.String(), body)
	if err != nil {
		return nil, err
	}

	if header != nil {
		req.Header = header
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Add("Accept-Encoding", "gzip")

	setCookies(&req.Header, c.authClient)

	if c.debug() {
		if buf, err := httputil.DumpRequest(req, true); err == nil {
			logging.GetLogger().Debugf("Request:\n%s", buf)
		}
	}

	resp, err := c.client.Do(req)
	if err != nil {
		return resp, err
	}

	switch resp.Header.Get("Content-Encoding") {
	case "gzip":
		resp.Body, err = gzip.NewReader(resp.Body)
		resp.Uncompressed = true
		resp.ContentLength = -1
		if err != nil {
			return nil, err
		}
	}

	if c.debug() {
		buf, err := httputil.DumpResponse(resp, true)
		if err == nil {
			logging.GetLogger().Debugf("Response:\n%s", buf)
		} else {
			logging.GetLogger().Debugf("Response (error):\n%s", err)
		}

	}

	return resp, nil
}

func NewCrudClient(url *url.URL, authOpts *AuthenticationOpts) *CrudClient {
	restClient := NewRestClient(url, authOpts)
	return &CrudClient{
		RestClient: *restClient,
	}
}

func (c *CrudClient) List(resource string, values interface{}) error {
	resp, err := c.Request("GET", resource, nil, nil)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("Failed to list %s, %s: %s", resource, resp.Status, readBody(resp))
	}

	return common.JSONDecode(resp.Body, values)
}

func (c *CrudClient) Get(resource string, id string, value interface{}) error {
	resp, err := c.Request("GET", resource+"/"+id, nil, nil)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("Failed to get %s, %s: %s", resource, resp.Status, readBody(resp))
	}

	return common.JSONDecode(resp.Body, value)
}

func (c *CrudClient) Create(resource string, value interface{}) error {
	s, err := json.Marshal(value)
	if err != nil {
		return err
	}

	contentReader := bytes.NewReader(s)
	resp, err := c.Request("POST", resource, contentReader, nil)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("Failed to create %s, %s: %s", resource, resp.Status, readBody(resp))
	}

	return common.JSONDecode(resp.Body, value)
}

func (c *CrudClient) Update(resource string, id string, value interface{}) error {
	s, err := json.Marshal(value)
	if err != nil {
		return err
	}

	contentReader := bytes.NewReader(s)
	resp, err := c.Request("PUT", resource+"/"+id, contentReader, nil)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("Failed to update %s, %s: %s", resource, resp.Status, readBody(resp))
	}

	return common.JSONDecode(resp.Body, value)
}

func (c *CrudClient) Delete(resource string, id string) error {
	resp, err := c.Request("DELETE", resource+"/"+id, nil, nil)
	if err != nil {
		return err
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return fmt.Errorf("Failed to delete %s, %s: %s", resource, resp.Status, readBody(resp))
	}

	return nil
}
<|endoftext|>


<commit_before>package plugin

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

	"code.cloudfoundry.org/cli/integration/helpers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"
)

var _ = Describe("uninstall-plugin command", func() {
	BeforeEach(func() {
		// This removes plugin artefacts from other plugin tests
		uninstallTestPlugin()
	})

	Describe("help", func() {
		Context("when --help flag is provided", func() {
			It("displays command usage to output", func() {
				session := helpers.CF("uninstall-plugin", "--help")
				Eventually(session.Out).Should(Say("NAME:"))
				Eventually(session.Out).Should(Say("uninstall-plugin - Uninstall CLI plugin"))
				Eventually(session.Out).Should(Say("USAGE:"))
				Eventually(session.Out).Should(Say("cf uninstall-plugin PLUGIN-NAME"))
				Eventually(session.Out).Should(Say("SEE ALSO:"))
				Eventually(session.Out).Should(Say("plugins"))
				Eventually(session).Should(Exit(0))
			})
		})
	})

	Context("when the plugin is not installed", func() {
		It("informs the user that no such plugin is present and exits 1", func() {
			session := helpers.CF("uninstall-plugin", "bananarama")
			Eventually(session.Err).Should(Say("Plugin bananarama does not exist\\."))
			Eventually(session).Should(Exit(1))
		})
	})

	Context("when the plugin is installed", func() {
		BeforeEach(func() {
			helpers.CreateBasicPlugin("banana-plugin-name-1", "2.0.1", []helpers.PluginCommand{
				{Name: "banana-command-1", Help: "banana-command-1"},
			})
			helpers.CreateBasicPlugin("banana-plugin-name-2", "1.4.3", []helpers.PluginCommand{
				{Name: "banana-command-2", Help: "banana-command-2"},
			})
		})

		Context("when no errors are encountered", func() {
			It("does not list the plugin after it is uninstalled", func() {
				session := helpers.CF("uninstall-plugin", "banana-plugin-name-1")
				Eventually(session.Out).Should(Say("Uninstalling plugin banana-plugin-name-1\\.\\.\\."))
				// Test that RPC works
				Eventually(session.Out).Should(Say("[0-9]{1,5} CLI-MESSAGE-UNINSTALL"))
				Eventually(session.Out).Should(Say("OK"))
				Eventually(session.Out).Should(Say("Plugin banana-plugin-name-1 2\\.0\\.1 successfully uninstalled\\."))
				Eventually(session).Should(Exit(0))

				session = helpers.CF("plugins")
				Consistently(session.Out).ShouldNot(Say("banana-plugin-name-1"))
				Eventually(session.Out).Should(Say("banana-plugin-name-2"))
				Eventually(session).Should(Exit(0))
			})
		})

		Context("when the plugin encounters an error during cleanup", func() {
			BeforeEach(func() {
				helpers.CreateBasicFailingPlugin("failing-plugin", "2.0.1", []helpers.PluginCommand{
					{Name: "failing-command-1", Help: "failing-command-1"},
				})
			})

			It("exits with an error, and does not remove the plugin", func() {
				session := helpers.CF("uninstall-plugin", "failing-plugin")
				Eventually(session.Out).Should(Say("Uninstalling plugin failing-plugin\\.\\.\\."))
				Eventually(session.Out).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("I'm failing...I'm failing..."))
				Eventually(session).Should(Exit(1))

				session = helpers.CF("plugins")
				Eventually(session.Out).Should(Say("banana-plugin-name-1"))
				Eventually(session.Out).Should(Say("banana-plugin-name-2"))
				Eventually(session.Out).Should(Say("failing-plugin"))
				Eventually(session).Should(Exit(0))
			})
		})

		Context("and the user cannot remove the plugin's executable", func() {
			var executablePath string
			var pluginsRootDir string

			BeforeEach(func() {
				pluginsRootDir = filepath.Join(homeDir, ".cf", "plugins")
				executablePath = filepath.Join(pluginsRootDir, "banana-plugin-name-1")
				rawConfig := fmt.Sprintf(`
				{
					"Plugins": {
						"banana-plugin-name-1": {
							"Location": "%s",
							"Version": {
								"Major": 1,
								"Minor": 0,
								"Build": 1
							},
							"Commands": [
								{
									"Name": "enable-diego",
									"Alias": "",
									"HelpText": "enable Diego support for an app",
									"UsageDetails": {
										"Usage": "cf enable-diego APP_NAME",
										"Options": null
									}
								}
							]
						},
						"banana-plugin-name-2": {
							"Location": "%s",
							"Version": {
								"Major": 1,
								"Minor": 0,
								"Build": 1
							},
							"Commands": [
								{
									"Name": "enable-banana",
									"Alias": "",
									"HelpText": "enable banana support for an app",
									"UsageDetails": {
										"Usage": "cf enable-banana APP_NAME",
										"Options": null
									}
								}
							]
						}
					}
				}`, executablePath)

				os.MkdirAll(pluginsRootDir, 0700)
				err := ioutil.WriteFile(filepath.Join(pluginsRootDir, "config.json"), []byte(rawConfig), 644)
				Expect(err).ToNot(HaveOccurred())
				err = os.MkdirAll(executablePath, 0700)
				Expect(err).ToNot(HaveOccurred())
				err = ioutil.WriteFile(filepath.Join(executablePath, "foooooooo"), nil, 644)
				Expect(err).ToNot(HaveOccurred())
			})

			It("exits with an error, and does not remove the plugin", func() {
				session := helpers.CF("uninstall-plugin", "banana-plugin-name-1")
				Eventually(session.Out).Should(Say("Uninstalling plugin banana-plugin-name-1\\.\\.\\."))
				Eventually(session.Out).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("banana-plugin-name-1: permission denied"))
				Eventually(session).Should(Exit(1))

				session = helpers.CF("plugins")
				Eventually(session.Out).Should(Say("banana-plugin-name-1"))
				Eventually(session.Out).Should(Say("banana-plugin-name-2"))
				Eventually(session).Should(Exit(0))
			})
		})
	})
})
<commit_msg>escape the backslashes in Windows paths<commit_after>package plugin

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

	"code.cloudfoundry.org/cli/integration/helpers"
	. "github.com/onsi/ginkgo"
	. "github.com/onsi/gomega"
	. "github.com/onsi/gomega/gbytes"
	. "github.com/onsi/gomega/gexec"
)

var _ = Describe("uninstall-plugin command", func() {
	BeforeEach(func() {
		// This removes plugin artefacts from other plugin tests
		uninstallTestPlugin()
	})

	Describe("help", func() {
		Context("when --help flag is provided", func() {
			It("displays command usage to output", func() {
				session := helpers.CF("uninstall-plugin", "--help")
				Eventually(session.Out).Should(Say("NAME:"))
				Eventually(session.Out).Should(Say("uninstall-plugin - Uninstall CLI plugin"))
				Eventually(session.Out).Should(Say("USAGE:"))
				Eventually(session.Out).Should(Say("cf uninstall-plugin PLUGIN-NAME"))
				Eventually(session.Out).Should(Say("SEE ALSO:"))
				Eventually(session.Out).Should(Say("plugins"))
				Eventually(session).Should(Exit(0))
			})
		})
	})

	Context("when the plugin is not installed", func() {
		It("informs the user that no such plugin is present and exits 1", func() {
			session := helpers.CF("uninstall-plugin", "bananarama")
			Eventually(session.Err).Should(Say("Plugin bananarama does not exist\\."))
			Eventually(session).Should(Exit(1))
		})
	})

	Context("when the plugin is installed", func() {
		BeforeEach(func() {
			helpers.CreateBasicPlugin("banana-plugin-name-1", "2.0.1", []helpers.PluginCommand{
				{Name: "banana-command-1", Help: "banana-command-1"},
			})
			helpers.CreateBasicPlugin("banana-plugin-name-2", "1.4.3", []helpers.PluginCommand{
				{Name: "banana-command-2", Help: "banana-command-2"},
			})
		})

		Context("when no errors are encountered", func() {
			It("does not list the plugin after it is uninstalled", func() {
				session := helpers.CF("uninstall-plugin", "banana-plugin-name-1")
				Eventually(session.Out).Should(Say("Uninstalling plugin banana-plugin-name-1\\.\\.\\."))
				// Test that RPC works
				Eventually(session.Out).Should(Say("[0-9]{1,5} CLI-MESSAGE-UNINSTALL"))
				Eventually(session.Out).Should(Say("OK"))
				Eventually(session.Out).Should(Say("Plugin banana-plugin-name-1 2\\.0\\.1 successfully uninstalled\\."))
				Eventually(session).Should(Exit(0))

				session = helpers.CF("plugins")
				Consistently(session.Out).ShouldNot(Say("banana-plugin-name-1"))
				Eventually(session.Out).Should(Say("banana-plugin-name-2"))
				Eventually(session).Should(Exit(0))
			})
		})

		Context("when the plugin encounters an error during cleanup", func() {
			BeforeEach(func() {
				helpers.CreateBasicFailingPlugin("failing-plugin", "2.0.1", []helpers.PluginCommand{
					{Name: "failing-command-1", Help: "failing-command-1"},
				})
			})

			It("exits with an error, and does not remove the plugin", func() {
				session := helpers.CF("uninstall-plugin", "failing-plugin")
				Eventually(session.Out).Should(Say("Uninstalling plugin failing-plugin\\.\\.\\."))
				Eventually(session.Out).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("I'm failing...I'm failing..."))
				Eventually(session).Should(Exit(1))

				session = helpers.CF("plugins")
				Eventually(session.Out).Should(Say("banana-plugin-name-1"))
				Eventually(session.Out).Should(Say("banana-plugin-name-2"))
				Eventually(session.Out).Should(Say("failing-plugin"))
				Eventually(session).Should(Exit(0))
			})
		})

		Context("and the user cannot remove the plugin's executable", func() {
			var executablePath string
			var pluginsRootDir string

			BeforeEach(func() {
				pluginsRootDir = filepath.Join(homeDir, ".cf", "plugins")
				executablePath = strings.Replace(filepath.Join(pluginsRootDir, "banana-plugin-name-1"), "\\", "\\\\", -1)
				rawConfig := fmt.Sprintf(`
				{
					"Plugins": {
						"banana-plugin-name-1": {
							"Location": "%s",
							"Version": {
								"Major": 1,
								"Minor": 0,
								"Build": 1
							},
							"Commands": [
								{
									"Name": "enable-diego",
									"Alias": "",
									"HelpText": "enable Diego support for an app",
									"UsageDetails": {
										"Usage": "cf enable-diego APP_NAME",
										"Options": null
									}
								}
							]
						},
						"banana-plugin-name-2": {
							"Location": "%s",
							"Version": {
								"Major": 1,
								"Minor": 0,
								"Build": 1
							},
							"Commands": [
								{
									"Name": "enable-banana",
									"Alias": "",
									"HelpText": "enable banana support for an app",
									"UsageDetails": {
										"Usage": "cf enable-banana APP_NAME",
										"Options": null
									}
								}
							]
						}
					}
				}`, executablePath)

				os.MkdirAll(pluginsRootDir, 0700)
				err := ioutil.WriteFile(filepath.Join(pluginsRootDir, "config.json"), []byte(rawConfig), 644)
				Expect(err).ToNot(HaveOccurred())
				err = os.MkdirAll(executablePath, 0700)
				Expect(err).ToNot(HaveOccurred())
				err = ioutil.WriteFile(filepath.Join(executablePath, "foooooooo"), nil, 644)
				Expect(err).ToNot(HaveOccurred())
			})

			It("exits with an error, and does not remove the plugin", func() {
				session := helpers.CF("uninstall-plugin", "banana-plugin-name-1")
				Eventually(session.Out).Should(Say("Uninstalling plugin banana-plugin-name-1\\.\\.\\."))
				Eventually(session.Out).Should(Say("FAILED"))
				Eventually(session.Err).Should(Say("banana-plugin-name-1: permission denied"))
				Eventually(session).Should(Exit(1))

				session = helpers.CF("plugins")
				Eventually(session.Out).Should(Say("banana-plugin-name-1"))
				Eventually(session.Out).Should(Say("banana-plugin-name-2"))
				Eventually(session).Should(Exit(0))
			})
		})
	})
})
<|endoftext|>


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

package featuretests

import (
	"fmt"

	"github.com/juju/cmd"
	"github.com/juju/cmd/cmdtesting"
	"github.com/juju/loggo"
	jc "github.com/juju/testing/checkers"
	"github.com/juju/version"
	gc "gopkg.in/check.v1"
	"gopkg.in/juju/names.v2"

	"github.com/juju/juju/cmd/juju/commands"
	jujutesting "github.com/juju/juju/juju/testing"
	"github.com/juju/juju/state"
	"github.com/juju/juju/testing"
	"github.com/juju/juju/testing/factory"
	coreversion "github.com/juju/juju/version"
)

type cmdUpgradeSuite struct {
	jujutesting.JujuConnSuite

	hostedModelUser    string
	hostedModelUserTag names.UserTag

	hostedModel string
}

func (s *cmdUpgradeSuite) SetUpTest(c *gc.C) {
	v, _ := version.Parse(oldVersion)
	s.PatchValue(&coreversion.Current, v)

	s.JujuConnSuite.SetUpTest(c)

	s.AddToolsToState(c, version.MustParseBinary(fmt.Sprintf("%v-precise-amd64", newVersion)))
	s.AddToolsToState(c, version.MustParseBinary(fmt.Sprintf("%v-trusty-amd64", newVersion)))
	s.AddToolsToState(c, version.MustParseBinary(fmt.Sprintf("%v-xenial-amd64", newVersion)))
	s.AddToolsToState(c, version.MustParseBinary(fmt.Sprintf("%v-yakkety-amd64", newVersion)))

	s.hostedModelUser = "otheruser"
	s.hostedModelUserTag = names.NewUserTag(s.hostedModelUser)

	s.hostedModel = "othermodel"
}

func (s *cmdUpgradeSuite) TestControllerAdminCanUpgradeHostedModel(c *gc.C) {
	testing.SkipIfWindowsBug(c, "lp:1446885")

	s.Factory.MakeUser(c, &factory.UserParams{Name: s.hostedModelUser})

	// Ensure we have hosted model.
	ctx := s.run(c, "add-model", s.hostedModel, "--owner", s.hostedModelUser)
	expectedModelAddedMsg := fmt.Sprintf("Added '%v' model on dummy/dummy-region for user '%v'", s.hostedModel, s.hostedModelUser)
	c.Assert(cmdtesting.Stderr(ctx), jc.Contains, expectedModelAddedMsg)
	s.assertHostModelAgentVersion(c, oldVersion)

	// We are only testing here that controller admin can upgrade hosted model,
	// so it does not matter that the model is empty.
	// Upgrade hosted model.
	v, _ := version.Parse(newVersion)
	s.PatchValue(&coreversion.Current, v)
	ctx = s.run(c, "upgrade-juju", "-m", fmt.Sprintf("%v/%v", s.hostedModelUser, s.hostedModel))
	expectedUpgradeMsg := fmt.Sprintf("started upgrade to %v", newVersion)
	c.Assert(cmdtesting.Stdout(ctx), jc.Contains, expectedUpgradeMsg)
	s.assertHostModelAgentVersion(c, newVersion)
}

var (
	oldVersion = "2.22.2"
	newVersion = "2.22.3"
)

func (s *cmdUpgradeSuite) run(c *gc.C, args ...string) *cmd.Context {
	context := cmdtesting.Context(c)
	jujuCmd := commands.NewJujuCommand(context)
	err := cmdtesting.InitCommand(jujuCmd, args)
	c.Assert(err, jc.ErrorIsNil)
	err = jujuCmd.Run(context)
	loggo.RemoveWriter("warning")
	c.Assert(err, jc.ErrorIsNil)
	return context
}

func (s *cmdUpgradeSuite) assertHostModelAgentVersion(c *gc.C, desiredAgentVersion string) {
	userModels, err := s.State.ModelsForUser(s.hostedModelUserTag)
	c.Assert(err, jc.ErrorIsNil)

	var desiredModel *state.UserModel
	for _, m := range userModels {
		if m.Name() == s.hostedModel {
			desiredModel = m
		}
	}
	c.Assert(desiredModel, gc.NotNil)

	cfg, err := desiredModel.Config()
	c.Assert(err, jc.ErrorIsNil)
	currentVersion, exists := cfg.AgentVersion()
	c.Assert(exists, jc.IsTrue)
	c.Assert(currentVersion.String(), gc.Equals, desiredAgentVersion)
}
<commit_msg>Change test setup to upload tools for supported lts series as well as for the series of the host machine.<commit_after>// Copyright 2017 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.

package featuretests

import (
	"fmt"

	"github.com/juju/cmd"
	"github.com/juju/cmd/cmdtesting"
	"github.com/juju/loggo"
	jc "github.com/juju/testing/checkers"
	"github.com/juju/utils/series"
	"github.com/juju/version"
	gc "gopkg.in/check.v1"
	"gopkg.in/juju/names.v2"

	"github.com/juju/juju/cmd/juju/commands"
	jujutesting "github.com/juju/juju/juju/testing"
	"github.com/juju/juju/state"
	"github.com/juju/juju/testing"
	"github.com/juju/juju/testing/factory"
	coreversion "github.com/juju/juju/version"
)

type cmdUpgradeSuite struct {
	jujutesting.JujuConnSuite

	hostedModelUser    string
	hostedModelUserTag names.UserTag

	hostedModel string
}

func (s *cmdUpgradeSuite) SetUpTest(c *gc.C) {
	v, _ := version.Parse(oldVersion)
	s.PatchValue(&coreversion.Current, v)

	s.JujuConnSuite.SetUpTest(c)

	supported := series.SupportedLts()
	supported = append(supported, series.MustHostSeries())
	for _, aSeries := range supported {
		s.AddToolsToState(c, version.MustParseBinary(fmt.Sprintf("%v-%v-amd64", newVersion, aSeries)))
	}

	s.hostedModelUser = "otheruser"
	s.hostedModelUserTag = names.NewUserTag(s.hostedModelUser)

	s.hostedModel = "othermodel"
}

func (s *cmdUpgradeSuite) TestControllerAdminCanUpgradeHostedModel(c *gc.C) {
	testing.SkipIfWindowsBug(c, "lp:1446885")

	s.Factory.MakeUser(c, &factory.UserParams{Name: s.hostedModelUser})

	// Ensure we have hosted model.
	ctx := s.run(c, "add-model", s.hostedModel, "--owner", s.hostedModelUser)
	expectedModelAddedMsg := fmt.Sprintf("Added '%v' model on dummy/dummy-region for user '%v'", s.hostedModel, s.hostedModelUser)
	c.Assert(cmdtesting.Stderr(ctx), jc.Contains, expectedModelAddedMsg)
	s.assertHostModelAgentVersion(c, oldVersion)

	// We are only testing here that controller admin can upgrade hosted model,
	// so it does not matter that the model is empty.
	// Upgrade hosted model.
	v, _ := version.Parse(newVersion)
	s.PatchValue(&coreversion.Current, v)
	ctx = s.run(c, "upgrade-juju", "-m", fmt.Sprintf("%v/%v", s.hostedModelUser, s.hostedModel))
	expectedUpgradeMsg := fmt.Sprintf("started upgrade to %v", newVersion)
	c.Assert(cmdtesting.Stdout(ctx), jc.Contains, expectedUpgradeMsg)
	s.assertHostModelAgentVersion(c, newVersion)
}

var (
	oldVersion = "2.22.2"
	newVersion = "2.22.3"
)

func (s *cmdUpgradeSuite) run(c *gc.C, args ...string) *cmd.Context {
	context := cmdtesting.Context(c)
	jujuCmd := commands.NewJujuCommand(context)
	err := cmdtesting.InitCommand(jujuCmd, args)
	c.Assert(err, jc.ErrorIsNil)
	err = jujuCmd.Run(context)
	loggo.RemoveWriter("warning")
	c.Assert(err, jc.ErrorIsNil)
	return context
}

func (s *cmdUpgradeSuite) assertHostModelAgentVersion(c *gc.C, desiredAgentVersion string) {
	userModels, err := s.State.ModelsForUser(s.hostedModelUserTag)
	c.Assert(err, jc.ErrorIsNil)

	var desiredModel *state.UserModel
	for _, m := range userModels {
		if m.Name() == s.hostedModel {
			desiredModel = m
		}
	}
	c.Assert(desiredModel, gc.NotNil)

	cfg, err := desiredModel.Config()
	c.Assert(err, jc.ErrorIsNil)
	currentVersion, exists := cfg.AgentVersion()
	c.Assert(exists, jc.IsTrue)
	c.Assert(currentVersion.String(), gc.Equals, desiredAgentVersion)
}
<|endoftext|>