<commit_before>/*
 * This file is a part of linuxdeploy - tool for
 * creating standalone applications for Linux
 *
 * Copyright (C) 2017 Taras Kushnir <kushnirTV@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the MIT License.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

package main

import (
  "log"
  "strings"
  "os"
  "os/exec"
  "io/ioutil"
  "errors"
  "path/filepath"
  "encoding/json"
)

type QMakeKey int

const (
  QT_INSTALL_PREFIX QMakeKey = iota
  QT_INSTALL_ARCHDATA
  QT_INSTALL_DATA
  QT_INSTALL_DOCS
  QT_INSTALL_HEADERS
  QT_INSTALL_LIBS
  QT_INSTALL_LIBEXECS
  QT_INSTALL_BINS
  QT_INSTALL_TESTS
  QT_INSTALL_PLUGINS
  QT_INSTALL_IMPORTS
  QT_INSTALL_QML
  QT_INSTALL_TRANSLATIONS
  QT_INSTALL_CONFIGURATION
  QT_INSTALL_EXAMPLES
  QT_INSTALL_DEMOS
  QT_HOST_PREFIX
  QT_HOST_DATA
  QT_HOST_BINS
  QT_HOST_LIBS
  QMAKE_VERSION
  QT_VERSION
)

type QtDeployer struct {
  qmakePath string
  qmakeVars map[string]string
  deployedQmlImports map[string]bool
  qtEnv map[QMakeKey]string
  qmlImportDirs []string
  privateWidgetsDeployed bool
  qtEnvironmentSet bool
  translationsRequired map[string]bool
}

func (qd *QtDeployer) queryQtEnv() error {
  log.Printf("Querying qmake environment using %v", qd.qmakePath)
  if len(qd.qmakePath) == 0 { return errors.New("QMake has not been resolved") }

  out, err := exec.Command(qd.qmakePath, "-query").Output()
  if err != nil { return err }

  output := string(out)
  // TODO: probably switch to bytes.Split for better performance
  lines := strings.Split(output, "\n")

  for _, line := range lines {
    line = strings.TrimSpace(line)
    if len(line) == 0 { continue }
    parts := strings.Split(line, ":")

    if len(parts) != 2 {
      log.Printf("Unexpected qmake output: %v", line)
      continue
    }

    qd.qmakeVars[parts[0]] = parts[1]
  }

  qd.parseQtVars()
  log.Println("Parsed qmake output: %v", qd.qtEnv)
  qd.qtEnvironmentSet = true
  return nil
}

func (qd *QtDeployer) parseQtVars() {
  qd.qtEnv[QT_INSTALL_PREFIX], _ = qd.qmakeVars["QT_INSTALL_PREFIX"]
  qd.qtEnv[QT_INSTALL_ARCHDATA], _ = qd.qmakeVars["QT_INSTALL_ARCHDATA"]
  qd.qtEnv[QT_INSTALL_DATA], _ = qd.qmakeVars["QT_INSTALL_DATA"]
  qd.qtEnv[QT_INSTALL_DOCS], _ = qd.qmakeVars["QT_INSTALL_DOCS"]
  qd.qtEnv[QT_INSTALL_HEADERS], _ = qd.qmakeVars["QT_INSTALL_HEADERS"]
  qd.qtEnv[QT_INSTALL_LIBS], _ = qd.qmakeVars["QT_INSTALL_LIBS"]
  qd.qtEnv[QT_INSTALL_LIBEXECS], _ = qd.qmakeVars["QT_INSTALL_LIBEXECS"]
  qd.qtEnv[QT_INSTALL_BINS], _ = qd.qmakeVars["QT_INSTALL_BINS"]
  qd.qtEnv[QT_INSTALL_PLUGINS], _ = qd.qmakeVars["QT_INSTALL_PLUGINS"]
  qd.qtEnv[QT_INSTALL_IMPORTS], _ = qd.qmakeVars["QT_INSTALL_IMPORTS"]
  qd.qtEnv[QT_INSTALL_QML], _ = qd.qmakeVars["QT_INSTALL_QML"]
  qd.qtEnv[QT_INSTALL_TRANSLATIONS], _ = qd.qmakeVars["QT_INSTALL_TRANSLATIONS"]
  qd.qtEnv[QT_INSTALL_CONFIGURATION], _ = qd.qmakeVars["QT_INSTALL_CONFIGURATION"]
  qd.qtEnv[QT_HOST_PREFIX], _ = qd.qmakeVars["QT_HOST_PREFIX"]
  qd.qtEnv[QT_HOST_DATA], _ = qd.qmakeVars["QT_HOST_DATA"]
  qd.qtEnv[QT_HOST_BINS], _ = qd.qmakeVars["QT_HOST_BINS"]
  qd.qtEnv[QT_HOST_LIBS], _ = qd.qmakeVars["QT_HOST_LIBS"]
  qd.qtEnv[QMAKE_VERSION], _ = qd.qmakeVars["QMAKE_VERSION"]
  qd.qtEnv[QT_VERSION], _ = qd.qmakeVars["QT_VERSION"]
}

func (qd *QtDeployer) BinPath() string {
  return qd.qtEnv[QT_INSTALL_BINS]
}

func (qd *QtDeployer) PluginsPath() string {
  return qd.qtEnv[QT_INSTALL_PLUGINS]
}

func (qd *QtDeployer) LibExecsPath() string {
  return qd.qtEnv[QT_INSTALL_LIBEXECS]
}

func (qd *QtDeployer) DataPath() string {
  return qd.qtEnv[QT_INSTALL_DATA]
}

func (qd *QtDeployer) TranslationsPath() string {
  return qd.qtEnv[QT_INSTALL_TRANSLATIONS]
}

func (qd *QtDeployer) QmlPath() string {
  return qd.qtEnv[QT_INSTALL_QML]
}

func (qd *QtDeployer) accountQmlImport(path string) {
  qd.deployedQmlImports[path] = true
}

func (qd *QtDeployer) isQmlImportDeployed(path string) (deployed bool) {
  // TODO: also check directory hierarchy?
  _, deployed = qd.deployedQmlImports[path]
  return deployed
}

func (ad *AppDeployer) processQtLibTasks() {
  if !ad.qtDeployer.qtEnvironmentSet {
    log.Printf("Qt Environment is not initialized")
    return
  }

  go ad.deployQmlImports()

  for libraryPath := range ad.qtChannel {
    ad.processQtLibTask(libraryPath)
    // rpath should be changed for all qt libs
    ad.addFixRPathTask(libraryPath)

    ad.waitGroup.Done()
  }

  log.Printf("Qt libraries processing finished")
}

func (ad *AppDeployer) processQtLibTask(libraryPath string) {
  libraryBasename := filepath.Base(libraryPath)
  libname := strings.ToLower(libraryBasename)

  if !strings.HasPrefix(libname, "libqt") { log.Fatal("Can only accept Qt libraries") }
  log.Printf("Inspecting Qt lib: %v", libraryBasename)

  ad.qtDeployer.accountQtLibrary(libname)

  deployFlags := LDD_DEPENDENCY_FLAG | DEPLOY_ONLY_LIBRARIES_FLAG | FIX_RPATH_FLAG

  if strings.HasPrefix(libname, "libqt5gui") {
    ad.addQtPluginTask("platforms/libqxcb.so")
    ad.deployRecursively(ad.qtDeployer.PluginsPath(), "imageformats", "plugins", deployFlags)
  } else
  if strings.HasPrefix(libname, "libqt5svg") {
    ad.addQtPluginTask("iconengines/libqsvgicon.so")
  } else
  if strings.HasPrefix(libname, "libqt5printsupport") {
    ad.addQtPluginTask("printsupport/libcupsprintersupport.so")
  } else
  if strings.HasPrefix(libname, "libqt5opengl") ||
    strings.HasPrefix(libname, "libqt5xcbqpa") {
    ad.deployRecursively(ad.qtDeployer.PluginsPath(), "xcbglintegrations", "plugins", deployFlags)
  } else
  if strings.HasPrefix(libname, "libqt5network") {
    ad.deployRecursively(ad.qtDeployer.PluginsPath(), "bearer", "plugins", deployFlags)
  } else
  if strings.HasPrefix(libname, "libqt5sql") {
    ad.deployRecursively(ad.qtDeployer.PluginsPath(), "sqldrivers", "plugins", deployFlags)
  } else
  if strings.HasPrefix(libname, "libqt5multimedia") {
    ad.deployRecursively(ad.qtDeployer.PluginsPath(), "mediaservice", "plugins", deployFlags)
    ad.deployRecursively(ad.qtDeployer.PluginsPath(), "audio", "plugins", deployFlags)
  } else
  if strings.HasPrefix(libname, "libqt5webenginecore") {
    ad.addCopyQtDepTask(ad.qtDeployer.LibExecsPath(), "QtWebEngineProcess", "libexecs")
    ad.copyRecursively(ad.qtDeployer.DataPath(), "resources", ".")
    ad.copyRecursively(ad.qtDeployer.TranslationsPath(), "qtwebengine_locales", "translations")
  } else
  if strings.HasPrefix(libname, "libqt5core") {
    go ad.patchQtCore(libraryPath)
  }
}

// copies one file
func (ad *AppDeployer) addCopyQtDepTask(sourceRoot, sourcePath, targetPath string) error {
  path := filepath.Join(sourceRoot, sourcePath)
  log.Printf("Copy once %v into %v", path, targetPath)
  relativePath, err := filepath.Rel(sourceRoot, path)
  if err != nil {
    log.Println(err)
  }

  ad.waitGroup.Add(1)
  go func() {
    ad.copyChannel <- &DeployRequest{
      sourceRoot: sourceRoot,
      sourcePath: relativePath,
      targetPath: targetPath,
      flags: FIX_RPATH_FLAG,
    }
  }()

  return err
}

func (ad *AppDeployer) addQtPluginTask(relpath string) {
  log.Printf("Deploying additional Qt plugin: %v", relpath)
  ad.addLibTask(ad.qtDeployer.PluginsPath(), relpath, "plugins", LDD_AND_RPATH_FLAG)
}

func (ad *AppDeployer) deployQmlImports() error {
  // rescue agains premature finish of the main loop
  ad.waitGroup.Add(1)
  defer ad.waitGroup.Done()

  log.Printf("Processing QML imports from %v", ad.qtDeployer.qmlImportDirs)

  scannerPath := filepath.Join(ad.qtDeployer.BinPath(), "qmlimportscanner")

  if _, err := os.Stat(scannerPath); err != nil {
    if scannerPath, err = exec.LookPath("qmlimportscanner"); err != nil {
      log.Printf("Cannot find qmlimportscanner")
      return err
    }
  }

  log.Printf("QML import scanner: %v", scannerPath)

  args := make([]string, 0, 10)
  for _, qmldir := range ad.qtDeployer.qmlImportDirs {
    args = append(args, "-rootPath")
    args = append(args, qmldir)
  }

  args = append(args, "-importPath")
  args = append(args, ad.qtDeployer.QmlPath())

  out, err := exec.Command(scannerPath, args...).Output()
  if err != nil {
    log.Printf("QML import scanner failed with %v", err)
    return err
  }

  err = ad.processQmlImportsJson(out)
  return err
}

type QmlImport struct {
  Classname string `json:"classname,omitempty"`
  Name string `json:"name,omitempty"`
  Path string `json:"path,omitempty"`
  Plugin string `json:"plugin,omitempty"`
  ImportType string `json:"type,omitempty"`
  Version string `json:"version,omitempty"`
}

func (ad *AppDeployer) processQmlImportsJson(jsonRaw []byte) (err error) {
  log.Printf("Parsing QML imports")

  var qmlImports []QmlImport
  err = json.Unmarshal(jsonRaw, &qmlImports)
  if err != nil { return err }
  log.Printf("Parsed %v imports", len(qmlImports))

  sourceRoot := ad.qtDeployer.QmlPath()

  for _, qmlImport := range qmlImports {
    relativePath, err := filepath.Rel(sourceRoot, qmlImport.Path)

    if err != nil || len(qmlImport.Name) == 0 {
      log.Printf("Skipping import %v", qmlImport)
      continue
    }

    if qmlImport.ImportType != "module" {
      log.Printf("Skipping non-module import %v", qmlImport)
      continue
    }

    if len(qmlImport.Path) == 0 {
      log.Printf("Skipping import without path %v", qmlImport)
      continue
    }

    if ad.qtDeployer.isQmlImportDeployed(qmlImport.Path) {
      log.Printf("Skipping already deployed QML import %v", qmlImport.Path)
      continue
    }

    if (qmlImport.Name == "QtQuick.Controls") && !ad.qtDeployer.privateWidgetsDeployed {
      ad.qtDeployer.privateWidgetsDeployed = true
      log.Printf("Deploying private widgets for QtQuick.Controls")
      ad.deployRecursively(sourceRoot, "QtQuick/PrivateWidgets", "qml", FIX_RPATH_FLAG)
    }

    log.Printf("Deploying QML import %v", qmlImport.Path)
    ad.qtDeployer.accountQmlImport(qmlImport.Path)
    ad.deployRecursively(sourceRoot, relativePath, "qml", FIX_RPATH_FLAG)
  }

  return nil
}

func (ad *AppDeployer) patchQtCore(libraryPath string) {
  // rescue agains premature finish of the main loop
  ad.waitGroup.Add(1)
  defer ad.waitGroup.Done()

  log.Printf("Patching libQt5Core at path %v", libraryPath)
  err := patchQtCore(libraryPath)
  if err != nil {
    log.Printf("QtCore patching failed! %v", err)
  }
}

func patchQtCore(path string) error {
  fi, err := os.Stat(path)
  if err != nil { return err }

  originalMode := fi.Mode()

  contents, err := ioutil.ReadFile(path)
  if err != nil { return err }

  // this list originates from https://github.com/probonopd/linuxdeployqt
  replaceVariable(contents, "qt_prfxpath=", ".");
  replaceVariable(contents, "qt_adatpath=", ".");
  replaceVariable(contents, "qt_docspath=", "doc");
  replaceVariable(contents, "qt_hdrspath=", "include");
  replaceVariable(contents, "qt_libspath=", "lib");
  replaceVariable(contents, "qt_lbexpath=", "libexec");
  replaceVariable(contents, "qt_binspath=", "bin");
  replaceVariable(contents, "qt_plugpath=", "plugins");
  replaceVariable(contents, "qt_impspath=", "imports");
  replaceVariable(contents, "qt_qml2path=", "qml");
  replaceVariable(contents, "qt_datapath=", ".");
  replaceVariable(contents, "qt_trnspath=", "translations");
  replaceVariable(contents, "qt_xmplpath=", "examples");
  replaceVariable(contents, "qt_demopath=", "demos");
  replaceVariable(contents, "qt_tstspath=", "tests");
  replaceVariable(contents, "qt_hpfxpath=", ".");
  replaceVariable(contents, "qt_hbinpath=", "bin");
  replaceVariable(contents, "qt_hdatpath=", ".");
  replaceVariable(contents, "qt_stngpath=", "."); // e.g., /opt/qt53/etc/xdg; does it load Trolltech.conf from there?

  /* Qt on Arch Linux comes with more hardcoded paths
  * https://github.com/probonopd/linuxdeployqt/issues/98
  replaceVariable(contents, "lib/qt/libexec", "libexec");
  replaceVariable(contents, "lib/qt/plugins", "plugins");
  replaceVariable(contents, "lib/qt/imports", "imports");
  replaceVariable(contents, "lib/qt/qml", "qml");
  replaceVariable(contents, "lib/qt", "");
  replaceVariable(contents, "share/doc/qt", "doc");
  replaceVariable(contents, "include/qt", "include");
  replaceVariable(contents, "share/qt", "");
  replaceVariable(contents, "share/qt/translations", "translations");
  replaceVariable(contents, "share/doc/qt/examples", "examples");
  */

  err = ioutil.WriteFile(path, contents, originalMode)
  return err
}
<commit_msg>Fix for race condition on patchelf and my pathing of libqt5core<commit_after>/*
 * This file is a part of linuxdeploy - tool for
 * creating standalone applications for Linux
 *
 * Copyright (C) 2017 Taras Kushnir <kushnirTV@gmail.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the MIT License.

 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 */

package main

import (
  "log"
  "strings"
  "os"
  "os/exec"
  "io/ioutil"
  "errors"
  "path/filepath"
  "encoding/json"
)

type QMakeKey int

const (
  QT_INSTALL_PREFIX QMakeKey = iota
  QT_INSTALL_ARCHDATA
  QT_INSTALL_DATA
  QT_INSTALL_DOCS
  QT_INSTALL_HEADERS
  QT_INSTALL_LIBS
  QT_INSTALL_LIBEXECS
  QT_INSTALL_BINS
  QT_INSTALL_TESTS
  QT_INSTALL_PLUGINS
  QT_INSTALL_IMPORTS
  QT_INSTALL_QML
  QT_INSTALL_TRANSLATIONS
  QT_INSTALL_CONFIGURATION
  QT_INSTALL_EXAMPLES
  QT_INSTALL_DEMOS
  QT_HOST_PREFIX
  QT_HOST_DATA
  QT_HOST_BINS
  QT_HOST_LIBS
  QMAKE_VERSION
  QT_VERSION
)

type QtDeployer struct {
  qmakePath string
  qmakeVars map[string]string
  deployedQmlImports map[string]bool
  qtEnv map[QMakeKey]string
  qmlImportDirs []string
  privateWidgetsDeployed bool
  qtEnvironmentSet bool
  translationsRequired map[string]bool
}

func (qd *QtDeployer) queryQtEnv() error {
  log.Printf("Querying qmake environment using %v", qd.qmakePath)
  if len(qd.qmakePath) == 0 { return errors.New("QMake has not been resolved") }

  out, err := exec.Command(qd.qmakePath, "-query").Output()
  if err != nil { return err }

  output := string(out)
  // TODO: probably switch to bytes.Split for better performance
  lines := strings.Split(output, "\n")

  for _, line := range lines {
    line = strings.TrimSpace(line)
    if len(line) == 0 { continue }
    parts := strings.Split(line, ":")

    if len(parts) != 2 {
      log.Printf("Unexpected qmake output: %v", line)
      continue
    }

    qd.qmakeVars[parts[0]] = parts[1]
  }

  qd.parseQtVars()
  log.Println("Parsed qmake output: %v", qd.qtEnv)
  qd.qtEnvironmentSet = true
  return nil
}

func (qd *QtDeployer) parseQtVars() {
  qd.qtEnv[QT_INSTALL_PREFIX], _ = qd.qmakeVars["QT_INSTALL_PREFIX"]
  qd.qtEnv[QT_INSTALL_ARCHDATA], _ = qd.qmakeVars["QT_INSTALL_ARCHDATA"]
  qd.qtEnv[QT_INSTALL_DATA], _ = qd.qmakeVars["QT_INSTALL_DATA"]
  qd.qtEnv[QT_INSTALL_DOCS], _ = qd.qmakeVars["QT_INSTALL_DOCS"]
  qd.qtEnv[QT_INSTALL_HEADERS], _ = qd.qmakeVars["QT_INSTALL_HEADERS"]
  qd.qtEnv[QT_INSTALL_LIBS], _ = qd.qmakeVars["QT_INSTALL_LIBS"]
  qd.qtEnv[QT_INSTALL_LIBEXECS], _ = qd.qmakeVars["QT_INSTALL_LIBEXECS"]
  qd.qtEnv[QT_INSTALL_BINS], _ = qd.qmakeVars["QT_INSTALL_BINS"]
  qd.qtEnv[QT_INSTALL_PLUGINS], _ = qd.qmakeVars["QT_INSTALL_PLUGINS"]
  qd.qtEnv[QT_INSTALL_IMPORTS], _ = qd.qmakeVars["QT_INSTALL_IMPORTS"]
  qd.qtEnv[QT_INSTALL_QML], _ = qd.qmakeVars["QT_INSTALL_QML"]
  qd.qtEnv[QT_INSTALL_TRANSLATIONS], _ = qd.qmakeVars["QT_INSTALL_TRANSLATIONS"]
  qd.qtEnv[QT_INSTALL_CONFIGURATION], _ = qd.qmakeVars["QT_INSTALL_CONFIGURATION"]
  qd.qtEnv[QT_HOST_PREFIX], _ = qd.qmakeVars["QT_HOST_PREFIX"]
  qd.qtEnv[QT_HOST_DATA], _ = qd.qmakeVars["QT_HOST_DATA"]
  qd.qtEnv[QT_HOST_BINS], _ = qd.qmakeVars["QT_HOST_BINS"]
  qd.qtEnv[QT_HOST_LIBS], _ = qd.qmakeVars["QT_HOST_LIBS"]
  qd.qtEnv[QMAKE_VERSION], _ = qd.qmakeVars["QMAKE_VERSION"]
  qd.qtEnv[QT_VERSION], _ = qd.qmakeVars["QT_VERSION"]
}

func (qd *QtDeployer) BinPath() string {
  return qd.qtEnv[QT_INSTALL_BINS]
}

func (qd *QtDeployer) PluginsPath() string {
  return qd.qtEnv[QT_INSTALL_PLUGINS]
}

func (qd *QtDeployer) LibExecsPath() string {
  return qd.qtEnv[QT_INSTALL_LIBEXECS]
}

func (qd *QtDeployer) DataPath() string {
  return qd.qtEnv[QT_INSTALL_DATA]
}

func (qd *QtDeployer) TranslationsPath() string {
  return qd.qtEnv[QT_INSTALL_TRANSLATIONS]
}

func (qd *QtDeployer) QmlPath() string {
  return qd.qtEnv[QT_INSTALL_QML]
}

func (qd *QtDeployer) accountQmlImport(path string) {
  qd.deployedQmlImports[path] = true
}

func (qd *QtDeployer) isQmlImportDeployed(path string) (deployed bool) {
  // TODO: also check directory hierarchy?
  _, deployed = qd.deployedQmlImports[path]
  return deployed
}

func (ad *AppDeployer) processQtLibTasks() {
  if !ad.qtDeployer.qtEnvironmentSet {
    log.Printf("Qt Environment is not initialized")
    return
  }

  go ad.deployQmlImports()

  for libraryPath := range ad.qtChannel {
    ad.processQtLibTask(libraryPath)
    // rpath should be changed for all qt libs
    ad.addFixRPathTask(libraryPath)

    ad.waitGroup.Done()
  }

  log.Printf("Qt libraries processing finished")
}

func (ad *AppDeployer) processQtLibTask(libraryPath string) {
  libraryBasename := filepath.Base(libraryPath)
  libname := strings.ToLower(libraryBasename)

  if !strings.HasPrefix(libname, "libqt") { log.Fatal("Can only accept Qt libraries") }
  log.Printf("Inspecting Qt lib: %v", libraryBasename)

  ad.qtDeployer.accountQtLibrary(libname)

  deployFlags := LDD_DEPENDENCY_FLAG | DEPLOY_ONLY_LIBRARIES_FLAG | FIX_RPATH_FLAG

  if strings.HasPrefix(libname, "libqt5gui") {
    ad.addQtPluginTask("platforms/libqxcb.so")
    ad.deployRecursively(ad.qtDeployer.PluginsPath(), "imageformats", "plugins", deployFlags)
  } else
  if strings.HasPrefix(libname, "libqt5svg") {
    ad.addQtPluginTask("iconengines/libqsvgicon.so")
  } else
  if strings.HasPrefix(libname, "libqt5printsupport") {
    ad.addQtPluginTask("printsupport/libcupsprintersupport.so")
  } else
  if strings.HasPrefix(libname, "libqt5opengl") ||
    strings.HasPrefix(libname, "libqt5xcbqpa") {
    ad.deployRecursively(ad.qtDeployer.PluginsPath(), "xcbglintegrations", "plugins", deployFlags)
  } else
  if strings.HasPrefix(libname, "libqt5network") {
    ad.deployRecursively(ad.qtDeployer.PluginsPath(), "bearer", "plugins", deployFlags)
  } else
  if strings.HasPrefix(libname, "libqt5sql") {
    ad.deployRecursively(ad.qtDeployer.PluginsPath(), "sqldrivers", "plugins", deployFlags)
  } else
  if strings.HasPrefix(libname, "libqt5multimedia") {
    ad.deployRecursively(ad.qtDeployer.PluginsPath(), "mediaservice", "plugins", deployFlags)
    ad.deployRecursively(ad.qtDeployer.PluginsPath(), "audio", "plugins", deployFlags)
  } else
  if strings.HasPrefix(libname, "libqt5webenginecore") {
    ad.addCopyQtDepTask(ad.qtDeployer.LibExecsPath(), "QtWebEngineProcess", "libexecs")
    ad.copyRecursively(ad.qtDeployer.DataPath(), "resources", ".")
    ad.copyRecursively(ad.qtDeployer.TranslationsPath(), "qtwebengine_locales", "translations")
  } else
  if strings.HasPrefix(libname, "libqt5core") {
    ad.patchQtCore(libraryPath)
  }
}

// copies one file
func (ad *AppDeployer) addCopyQtDepTask(sourceRoot, sourcePath, targetPath string) error {
  path := filepath.Join(sourceRoot, sourcePath)
  log.Printf("Copy once %v into %v", path, targetPath)
  relativePath, err := filepath.Rel(sourceRoot, path)
  if err != nil {
    log.Println(err)
  }

  ad.waitGroup.Add(1)
  go func() {
    ad.copyChannel <- &DeployRequest{
      sourceRoot: sourceRoot,
      sourcePath: relativePath,
      targetPath: targetPath,
      flags: FIX_RPATH_FLAG,
    }
  }()

  return err
}

func (ad *AppDeployer) addQtPluginTask(relpath string) {
  log.Printf("Deploying additional Qt plugin: %v", relpath)
  ad.addLibTask(ad.qtDeployer.PluginsPath(), relpath, "plugins", LDD_AND_RPATH_FLAG)
}

func (ad *AppDeployer) deployQmlImports() error {
  // rescue agains premature finish of the main loop
  ad.waitGroup.Add(1)
  defer ad.waitGroup.Done()

  log.Printf("Processing QML imports from %v", ad.qtDeployer.qmlImportDirs)

  scannerPath := filepath.Join(ad.qtDeployer.BinPath(), "qmlimportscanner")

  if _, err := os.Stat(scannerPath); err != nil {
    if scannerPath, err = exec.LookPath("qmlimportscanner"); err != nil {
      log.Printf("Cannot find qmlimportscanner")
      return err
    }
  }

  log.Printf("QML import scanner: %v", scannerPath)

  args := make([]string, 0, 10)
  for _, qmldir := range ad.qtDeployer.qmlImportDirs {
    args = append(args, "-rootPath")
    args = append(args, qmldir)
  }

  args = append(args, "-importPath")
  args = append(args, ad.qtDeployer.QmlPath())

  out, err := exec.Command(scannerPath, args...).Output()
  if err != nil {
    log.Printf("QML import scanner failed with %v", err)
    return err
  }

  err = ad.processQmlImportsJson(out)
  return err
}

type QmlImport struct {
  Classname string `json:"classname,omitempty"`
  Name string `json:"name,omitempty"`
  Path string `json:"path,omitempty"`
  Plugin string `json:"plugin,omitempty"`
  ImportType string `json:"type,omitempty"`
  Version string `json:"version,omitempty"`
}

func (ad *AppDeployer) processQmlImportsJson(jsonRaw []byte) (err error) {
  log.Printf("Parsing QML imports")

  var qmlImports []QmlImport
  err = json.Unmarshal(jsonRaw, &qmlImports)
  if err != nil { return err }
  log.Printf("Parsed %v imports", len(qmlImports))

  sourceRoot := ad.qtDeployer.QmlPath()

  for _, qmlImport := range qmlImports {
    relativePath, err := filepath.Rel(sourceRoot, qmlImport.Path)

    if err != nil || len(qmlImport.Name) == 0 {
      log.Printf("Skipping import %v", qmlImport)
      continue
    }

    if qmlImport.ImportType != "module" {
      log.Printf("Skipping non-module import %v", qmlImport)
      continue
    }

    if len(qmlImport.Path) == 0 {
      log.Printf("Skipping import without path %v", qmlImport)
      continue
    }

    if ad.qtDeployer.isQmlImportDeployed(qmlImport.Path) {
      log.Printf("Skipping already deployed QML import %v", qmlImport.Path)
      continue
    }

    if (qmlImport.Name == "QtQuick.Controls") && !ad.qtDeployer.privateWidgetsDeployed {
      ad.qtDeployer.privateWidgetsDeployed = true
      log.Printf("Deploying private widgets for QtQuick.Controls")
      ad.deployRecursively(sourceRoot, "QtQuick/PrivateWidgets", "qml", FIX_RPATH_FLAG)
    }

    log.Printf("Deploying QML import %v", qmlImport.Path)
    ad.qtDeployer.accountQmlImport(qmlImport.Path)
    ad.deployRecursively(sourceRoot, relativePath, "qml", FIX_RPATH_FLAG)
  }

  return nil
}

func (ad *AppDeployer) patchQtCore(libraryPath string) {
  // rescue agains premature finish of the main loop
  ad.waitGroup.Add(1)
  defer ad.waitGroup.Done()

  log.Printf("Patching libQt5Core at path %v", libraryPath)
  err := patchQtCore(libraryPath)
  if err != nil {
    log.Printf("QtCore patching failed! %v", err)
  }
}

func patchQtCore(path string) error {
  fi, err := os.Stat(path)
  if err != nil { return err }

  originalMode := fi.Mode()

  contents, err := ioutil.ReadFile(path)
  if err != nil { return err }

  // this list originates from https://github.com/probonopd/linuxdeployqt
  replaceVariable(contents, "qt_prfxpath=", ".");
  replaceVariable(contents, "qt_adatpath=", ".");
  replaceVariable(contents, "qt_docspath=", "doc");
  replaceVariable(contents, "qt_hdrspath=", "include");
  replaceVariable(contents, "qt_libspath=", "lib");
  replaceVariable(contents, "qt_lbexpath=", "libexec");
  replaceVariable(contents, "qt_binspath=", "bin");
  replaceVariable(contents, "qt_plugpath=", "plugins");
  replaceVariable(contents, "qt_impspath=", "imports");
  replaceVariable(contents, "qt_qml2path=", "qml");
  replaceVariable(contents, "qt_datapath=", ".");
  replaceVariable(contents, "qt_trnspath=", "translations");
  replaceVariable(contents, "qt_xmplpath=", "examples");
  replaceVariable(contents, "qt_demopath=", "demos");
  replaceVariable(contents, "qt_tstspath=", "tests");
  replaceVariable(contents, "qt_hpfxpath=", ".");
  replaceVariable(contents, "qt_hbinpath=", "bin");
  replaceVariable(contents, "qt_hdatpath=", ".");
  replaceVariable(contents, "qt_stngpath=", "."); // e.g., /opt/qt53/etc/xdg; does it load Trolltech.conf from there?

  /* Qt on Arch Linux comes with more hardcoded paths
  * https://github.com/probonopd/linuxdeployqt/issues/98
  replaceVariable(contents, "lib/qt/libexec", "libexec");
  replaceVariable(contents, "lib/qt/plugins", "plugins");
  replaceVariable(contents, "lib/qt/imports", "imports");
  replaceVariable(contents, "lib/qt/qml", "qml");
  replaceVariable(contents, "lib/qt", "");
  replaceVariable(contents, "share/doc/qt", "doc");
  replaceVariable(contents, "include/qt", "include");
  replaceVariable(contents, "share/qt", "");
  replaceVariable(contents, "share/qt/translations", "translations");
  replaceVariable(contents, "share/doc/qt/examples", "examples");
  */

  err = ioutil.WriteFile(path, contents, originalMode)
  return err
}
<|endoftext|>


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

package runtime

import "unsafe"

// Declarations for runtime services implemented in C or assembly.

const ptrSize = 4 << (^uintptr(0) >> 63) // unsafe.Sizeof(uintptr(0)) but an ideal const
const regSize = 4 << (^uintreg(0) >> 63) // unsafe.Sizeof(uintreg(0)) but an ideal const

// Should be a built-in for unsafe.Pointer?
//go:nosplit
func add(p unsafe.Pointer, x uintptr) unsafe.Pointer {
	return unsafe.Pointer(uintptr(p) + x)
}

// n must be a power of 2
func roundup(p unsafe.Pointer, n uintptr) unsafe.Pointer {
	delta := -uintptr(p) & (n - 1)
	return unsafe.Pointer(uintptr(p) + delta)
}

// in runtime.c
func getg() *g
func acquirem() *m
func releasem(mp *m)
func gomcache() *mcache
func readgstatus(*g) uint32 // proc.c

// mcall switches from the g to the g0 stack and invokes fn(g),
// where g is the goroutine that made the call.
// mcall saves g's current PC/SP in g->sched so that it can be restored later.
// It is up to fn to arrange for that later execution, typically by recording
// g in a data structure, causing something to call ready(g) later.
// mcall returns to the original goroutine g later, when g has been rescheduled.
// fn must not return at all; typically it ends by calling schedule, to let the m
// run other goroutines.
//
// mcall can only be called from g stacks (not g0, not gsignal).
//go:noescape
func mcall(fn func(*g))

// onM switches from the g to the g0 stack and invokes fn().
// When fn returns, onM switches back to the g and returns,
// continuing execution on the g stack.
// If arguments must be passed to fn, they can be written to
// g->m->ptrarg (pointers) and g->m->scalararg (non-pointers)
// before the call and then consulted during fn.
// Similarly, fn can pass return values back in those locations.
// If fn is written in Go, it can be a closure, which avoids the need for
// ptrarg and scalararg entirely.
// After reading values out of ptrarg and scalararg it is conventional
// to zero them to avoid (memory or information) leaks.
//
// If onM is called from a g0 stack, it invokes fn and returns,
// without any stack switches.
//
// If onM is called from a gsignal stack, it crashes the program.
// The implication is that functions used in signal handlers must
// not use onM.
//
// NOTE(rsc): We could introduce a separate onMsignal that is
// like onM but if called from a gsignal stack would just run fn on
// that stack. The caller of onMsignal would be required to save the
// old values of ptrarg/scalararg and restore them when the call
// was finished, in case the signal interrupted an onM sequence
// in progress on the g or g0 stacks. Until there is a clear need for this,
// we just reject onM in signal handling contexts entirely.
//
//go:noescape
func onM(fn func())

// onMsignal is like onM but is allowed to be used in code that
// might run on the gsignal stack. Code running on a signal stack
// may be interrupting an onM sequence on the main stack, so
// if the onMsignal calling sequence writes to ptrarg/scalararg,
// it must first save the old values and then restore them when
// finished. As an exception to the rule, it is fine not to save and
// restore the values if the program is trying to crash rather than
// return from the signal handler.
// Once all the runtime is written in Go, there will be no ptrarg/scalararg
// and the distinction between onM and onMsignal (and perhaps mcall)
// can go away.
//
// If onMsignal is called from a gsignal stack, it invokes fn directly,
// without a stack switch. Otherwise onMsignal behaves like onM.
//
//go:noescape
func onM_signalok(fn func())

func badonm() {
	gothrow("onM called from signal goroutine")
}

// C functions that run on the M stack.
// Call using mcall.
func gosched_m(*g)
func park_m(*g)
func recovery_m(*g)

// More C functions that run on the M stack.
// Call using onM.
func mcacheRefill_m()
func largeAlloc_m()
func gc_m()
func scavenge_m()
func setFinalizer_m()
func removeFinalizer_m()
func markallocated_m()
func unrollgcprog_m()
func unrollgcproginplace_m()
func setgcpercent_m()
func setmaxthreads_m()
func ready_m()
func deferproc_m()
func goexit_m()
func startpanic_m()
func dopanic_m()
func readmemstats_m()
func writeheapdump_m()

// memclr clears n bytes starting at ptr.
// in memclr_*.s
//go:noescape
func memclr(ptr unsafe.Pointer, n uintptr)

// memmove copies n bytes from "from" to "to".
// in memmove_*.s
//go:noescape
func memmove(to unsafe.Pointer, from unsafe.Pointer, n uintptr)

func starttheworld()
func stoptheworld()
func newextram()
func lockOSThread()
func unlockOSThread()

// exported value for testing
var hashLoad = loadFactor

// in asm_*.s
func fastrand1() uint32

// in asm_*.s
//go:noescape
func memeq(a, b unsafe.Pointer, size uintptr) bool

// Code pointers for the nohash/noequal algorithms. Used for producing better error messages.
var nohashcode uintptr
var noequalcode uintptr

// noescape hides a pointer from escape analysis.  noescape is
// the identity function but escape analysis doesn't think the
// output depends on the input.  noescape is inlined and currently
// compiles down to a single xor instruction.
// USE CAREFULLY!
//go:nosplit
func noescape(p unsafe.Pointer) unsafe.Pointer {
	x := uintptr(p)
	return unsafe.Pointer(x ^ 0)
}

func entersyscall()
func reentersyscall(pc uintptr, sp unsafe.Pointer)
func entersyscallblock()
func exitsyscall()

func cgocallback(fn, frame unsafe.Pointer, framesize uintptr)
func gogo(buf *gobuf)
func gosave(buf *gobuf)
func read(fd int32, p unsafe.Pointer, n int32) int32
func close(fd int32) int32
func mincore(addr unsafe.Pointer, n uintptr, dst *byte) int32

//go:noescape
func jmpdefer(fv *funcval, argp uintptr)
func exit1(code int32)
func asminit()
func setg(gg *g)
func exit(code int32)
func breakpoint()
func nanotime() int64
func usleep(usec uint32)
func cputicks() int64
func mmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) unsafe.Pointer
func munmap(addr unsafe.Pointer, n uintptr)
func madvise(addr unsafe.Pointer, n uintptr, flags int32)
func reflectcall(fn, arg unsafe.Pointer, n uint32, retoffset uint32)
func osyield()
func procyield(cycles uint32)
func cgocallback_gofunc(fv *funcval, frame unsafe.Pointer, framesize uintptr)
func readgogc() int32
func purgecachedstats(c *mcache)
func gostringnocopy(b *byte) string
func goexit()

//go:noescape
func write(fd uintptr, p unsafe.Pointer, n int32) int32

//go:noescape
func cas(ptr *uint32, old, new uint32) bool

//go:noescape
func casp(ptr *unsafe.Pointer, old, new unsafe.Pointer) bool

//go:noescape
func casuintptr(ptr *uintptr, old, new uintptr) bool

//go:noescape
func atomicstoreuintptr(ptr *uintptr, new uintptr)

//go:noescape
func atomicloaduintptr(ptr *uintptr) uintptr

//go:noescape
func atomicloaduint(ptr *uint) uint

//go:noescape
func setcallerpc(argp unsafe.Pointer, pc uintptr)

//go:noescape
func getcallerpc(argp unsafe.Pointer) uintptr

//go:noescape
func getcallersp(argp unsafe.Pointer) uintptr

//go:noescape
func asmcgocall(fn, arg unsafe.Pointer)

//go:noescape
func asmcgocall_errno(fn, arg unsafe.Pointer) int32

//go:noescape
func open(name *byte, mode, perm int32) int32

//go:noescape
func gotraceback(*bool) int32

const _NoArgs = ^uintptr(0)

func newstack()
func newproc()
func morestack()
func mstart()
func rt0_go()

// return0 is a stub used to return 0 from deferproc.
// It is called at the very end of deferproc to signal
// the calling Go function that it should not jump
// to deferreturn.
// in asm_*.s
func return0()

// thunk to call time.now.
func timenow() (sec int64, nsec int32)
<commit_msg>runtime: delete unused variables.<commit_after>// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package runtime

import "unsafe"

// Declarations for runtime services implemented in C or assembly.

const ptrSize = 4 << (^uintptr(0) >> 63) // unsafe.Sizeof(uintptr(0)) but an ideal const
const regSize = 4 << (^uintreg(0) >> 63) // unsafe.Sizeof(uintreg(0)) but an ideal const

// Should be a built-in for unsafe.Pointer?
//go:nosplit
func add(p unsafe.Pointer, x uintptr) unsafe.Pointer {
	return unsafe.Pointer(uintptr(p) + x)
}

// n must be a power of 2
func roundup(p unsafe.Pointer, n uintptr) unsafe.Pointer {
	delta := -uintptr(p) & (n - 1)
	return unsafe.Pointer(uintptr(p) + delta)
}

// in runtime.c
func getg() *g
func acquirem() *m
func releasem(mp *m)
func gomcache() *mcache
func readgstatus(*g) uint32 // proc.c

// mcall switches from the g to the g0 stack and invokes fn(g),
// where g is the goroutine that made the call.
// mcall saves g's current PC/SP in g->sched so that it can be restored later.
// It is up to fn to arrange for that later execution, typically by recording
// g in a data structure, causing something to call ready(g) later.
// mcall returns to the original goroutine g later, when g has been rescheduled.
// fn must not return at all; typically it ends by calling schedule, to let the m
// run other goroutines.
//
// mcall can only be called from g stacks (not g0, not gsignal).
//go:noescape
func mcall(fn func(*g))

// onM switches from the g to the g0 stack and invokes fn().
// When fn returns, onM switches back to the g and returns,
// continuing execution on the g stack.
// If arguments must be passed to fn, they can be written to
// g->m->ptrarg (pointers) and g->m->scalararg (non-pointers)
// before the call and then consulted during fn.
// Similarly, fn can pass return values back in those locations.
// If fn is written in Go, it can be a closure, which avoids the need for
// ptrarg and scalararg entirely.
// After reading values out of ptrarg and scalararg it is conventional
// to zero them to avoid (memory or information) leaks.
//
// If onM is called from a g0 stack, it invokes fn and returns,
// without any stack switches.
//
// If onM is called from a gsignal stack, it crashes the program.
// The implication is that functions used in signal handlers must
// not use onM.
//
// NOTE(rsc): We could introduce a separate onMsignal that is
// like onM but if called from a gsignal stack would just run fn on
// that stack. The caller of onMsignal would be required to save the
// old values of ptrarg/scalararg and restore them when the call
// was finished, in case the signal interrupted an onM sequence
// in progress on the g or g0 stacks. Until there is a clear need for this,
// we just reject onM in signal handling contexts entirely.
//
//go:noescape
func onM(fn func())

// onMsignal is like onM but is allowed to be used in code that
// might run on the gsignal stack. Code running on a signal stack
// may be interrupting an onM sequence on the main stack, so
// if the onMsignal calling sequence writes to ptrarg/scalararg,
// it must first save the old values and then restore them when
// finished. As an exception to the rule, it is fine not to save and
// restore the values if the program is trying to crash rather than
// return from the signal handler.
// Once all the runtime is written in Go, there will be no ptrarg/scalararg
// and the distinction between onM and onMsignal (and perhaps mcall)
// can go away.
//
// If onMsignal is called from a gsignal stack, it invokes fn directly,
// without a stack switch. Otherwise onMsignal behaves like onM.
//
//go:noescape
func onM_signalok(fn func())

func badonm() {
	gothrow("onM called from signal goroutine")
}

// C functions that run on the M stack.
// Call using mcall.
func gosched_m(*g)
func park_m(*g)
func recovery_m(*g)

// More C functions that run on the M stack.
// Call using onM.
func mcacheRefill_m()
func largeAlloc_m()
func gc_m()
func scavenge_m()
func setFinalizer_m()
func removeFinalizer_m()
func markallocated_m()
func unrollgcprog_m()
func unrollgcproginplace_m()
func setgcpercent_m()
func setmaxthreads_m()
func ready_m()
func deferproc_m()
func goexit_m()
func startpanic_m()
func dopanic_m()
func readmemstats_m()
func writeheapdump_m()

// memclr clears n bytes starting at ptr.
// in memclr_*.s
//go:noescape
func memclr(ptr unsafe.Pointer, n uintptr)

// memmove copies n bytes from "from" to "to".
// in memmove_*.s
//go:noescape
func memmove(to unsafe.Pointer, from unsafe.Pointer, n uintptr)

func starttheworld()
func stoptheworld()
func newextram()
func lockOSThread()
func unlockOSThread()

// exported value for testing
var hashLoad = loadFactor

// in asm_*.s
func fastrand1() uint32

// in asm_*.s
//go:noescape
func memeq(a, b unsafe.Pointer, size uintptr) bool

// noescape hides a pointer from escape analysis.  noescape is
// the identity function but escape analysis doesn't think the
// output depends on the input.  noescape is inlined and currently
// compiles down to a single xor instruction.
// USE CAREFULLY!
//go:nosplit
func noescape(p unsafe.Pointer) unsafe.Pointer {
	x := uintptr(p)
	return unsafe.Pointer(x ^ 0)
}

func entersyscall()
func reentersyscall(pc uintptr, sp unsafe.Pointer)
func entersyscallblock()
func exitsyscall()

func cgocallback(fn, frame unsafe.Pointer, framesize uintptr)
func gogo(buf *gobuf)
func gosave(buf *gobuf)
func read(fd int32, p unsafe.Pointer, n int32) int32
func close(fd int32) int32
func mincore(addr unsafe.Pointer, n uintptr, dst *byte) int32

//go:noescape
func jmpdefer(fv *funcval, argp uintptr)
func exit1(code int32)
func asminit()
func setg(gg *g)
func exit(code int32)
func breakpoint()
func nanotime() int64
func usleep(usec uint32)
func cputicks() int64
func mmap(addr unsafe.Pointer, n uintptr, prot, flags, fd int32, off uint32) unsafe.Pointer
func munmap(addr unsafe.Pointer, n uintptr)
func madvise(addr unsafe.Pointer, n uintptr, flags int32)
func reflectcall(fn, arg unsafe.Pointer, n uint32, retoffset uint32)
func osyield()
func procyield(cycles uint32)
func cgocallback_gofunc(fv *funcval, frame unsafe.Pointer, framesize uintptr)
func readgogc() int32
func purgecachedstats(c *mcache)
func gostringnocopy(b *byte) string
func goexit()

//go:noescape
func write(fd uintptr, p unsafe.Pointer, n int32) int32

//go:noescape
func cas(ptr *uint32, old, new uint32) bool

//go:noescape
func casp(ptr *unsafe.Pointer, old, new unsafe.Pointer) bool

//go:noescape
func casuintptr(ptr *uintptr, old, new uintptr) bool

//go:noescape
func atomicstoreuintptr(ptr *uintptr, new uintptr)

//go:noescape
func atomicloaduintptr(ptr *uintptr) uintptr

//go:noescape
func atomicloaduint(ptr *uint) uint

//go:noescape
func setcallerpc(argp unsafe.Pointer, pc uintptr)

//go:noescape
func getcallerpc(argp unsafe.Pointer) uintptr

//go:noescape
func getcallersp(argp unsafe.Pointer) uintptr

//go:noescape
func asmcgocall(fn, arg unsafe.Pointer)

//go:noescape
func asmcgocall_errno(fn, arg unsafe.Pointer) int32

//go:noescape
func open(name *byte, mode, perm int32) int32

//go:noescape
func gotraceback(*bool) int32

const _NoArgs = ^uintptr(0)

func newstack()
func newproc()
func morestack()
func mstart()
func rt0_go()

// return0 is a stub used to return 0 from deferproc.
// It is called at the very end of deferproc to signal
// the calling Go function that it should not jump
// to deferreturn.
// in asm_*.s
func return0()

// thunk to call time.now.
func timenow() (sec int64, nsec int32)
<|endoftext|>


<commit_before>// Copyright 2016 Jacob Taylor jacob@ablox.io
// License: Apache2 - http://www.apache.org/licenses/LICENSE-2.0
package main

import (
	"fmt"
	"github.com/rjeczalik/notify"
	"log"
	"os"
	"path/filepath"
	"sort"
	"sync"
	"syscall"
)

// ChangeHandler - Listener for tracking changes that happen to a storage system
type ChangeHandler interface {
	FolderCreated(name string) (err error)
	FolderDeleted(name string) (err error)
}

// StorageTracker - Listener that allows you to tell the tracker what has happened elsewhere so it can mirror the changes
type StorageTracker interface {
	CreateFolder(relativePath string) (err error)
	DeleteFolder(name string) (err error)
	ListFolders() (folderList []string)
}

// LogOnlyChangeHandler - sample change handler
type LogOnlyChangeHandler struct {
}

func (self *LogOnlyChangeHandler) FolderCreated(name string) (err error) {
	fmt.Printf("LogOnlyChangeHandler:FolderCreated: %s\n", name)
	return nil
}

func (self *LogOnlyChangeHandler) FolderDeleted(name string) (err error) {
	fmt.Printf("LogOnlyChangeHandler:FolderDeleted: %s\n", name)
	return nil
}

type FilesystemTracker struct {
	directory         string
	contents          map[string]Directory
	setup             bool
	watcher           *ChangeHandler
	fsEventsChannel   chan notify.EventInfo
	renamesInProgress map[uint64]string // map from inode to source/destination of items being moved
	fsLock            sync.RWMutex
}

type Directory struct {
	os.FileInfo
	contents map[string]os.FileInfo
}

func NewDirectory() *Directory {
	return &Directory{contents: make(map[string]os.FileInfo)}
}

func (self *FilesystemTracker) init(directory string) {
	self.fsLock.Lock()
	defer self.fsLock.Unlock()

	if self.setup {
		return
	}

	fmt.Printf("FilesystemTracker:init called with %s\n", directory)
	self.directory = directory

	// Make the channel buffered to ensure no event is dropped. Notify will drop
	// an event if the receiver is not able to keep up the sending pace.
	self.fsEventsChannel = make(chan notify.EventInfo, 10000)

	// Update the path that traffic is served from to be the filesystem canonical path. This will allow the event folders that come in to match what we have.
	fullPath := validatePath(directory)
	self.directory = fullPath

	if fullPath != globalSettings.Directory {
		fmt.Printf("Updating serving directory to: %s\n", fullPath)
		self.directory = fullPath
	}

	fmt.Println("Setting up filesystemTracker!")
	err := self.scanFolders()
	if err != nil {
		panic(err)
	}

	self.setup = true
}

func (self *FilesystemTracker) cleanup() {
	self.fsLock.Lock()
	defer self.fsLock.Unlock()

	if !self.setup {
		panic("cleanup called when not yet setup")
	}

	notify.Stop(self.fsEventsChannel)
}

func (self *FilesystemTracker) watchDirectory(watcher *ChangeHandler) {
	self.fsLock.Lock()
	defer self.fsLock.Unlock()

	if !self.setup {
		panic("FilesystemTracker:watchDirectory called when not yet setup")
	}

	if self.watcher != nil {
		panic("watchDirectory called a second time. Not allowed")
	}

	self.watcher = watcher

	go self.monitorLoop(self.fsEventsChannel)

	// Set up a watch point listening for events within a directory tree rooted at the specified folder
	err := notify.Watch(self.directory+"/...", self.fsEventsChannel, notify.All)
	if err != nil {
		log.Panic(err)
	}
}

func validatePath(directory string) (fullPath string) {
	fullPath, err := filepath.EvalSymlinks(directory)
	if err != nil {
		var err2, err3 error
		// We have an error. If the directory does not exist, then try to create it. Fail if we cannot create it and return the original error
		if os.IsNotExist(err) {
			err2 = os.Mkdir(directory, os.ModeDir+os.ModePerm)
			fullPath, err3 = filepath.EvalSymlinks(directory)
			if err2 != nil || err3 != nil {
				panic(fmt.Sprintf("err: %v\nerr2: %v\nerr3: %v\n", err, err2, err3))
			}
		} else {
			panic(err)
		}
	}

	return
}

func (self *FilesystemTracker) CreateFolder(relativePath string) (err error) {
	if !self.setup {
		panic("FilesystemTracker:CreateFolder called when not yet setup")
	}

	self.fsLock.Lock()
	defer self.fsLock.Unlock()

	absolutePath := self.directory + "/" + relativePath

	err = os.MkdirAll(absolutePath, os.ModeDir+os.ModePerm)
	if err != nil && !os.IsExist(err) {
		panic(fmt.Sprintf("Error creating folder %s: %v\n", absolutePath, err))
	}

	_, exists := self.contents[relativePath]
	fmt.Printf("CreateFolder: '%s' (%v)\n", relativePath, exists)

	if !exists {
		directory := Directory{}
		directory.FileInfo, err = os.Stat(absolutePath)
		self.contents[relativePath] = directory
	} else {
		fmt.Printf("for some reason the directory object already exists in the map: %s\n", relativePath)
	}

	return nil
}

func (self *FilesystemTracker) DeleteFolder(name string) (err error) {
	if !self.setup {
		panic("FilesystemTracker:DeleteFolder called when not yet setup")
	}

	self.fsLock.Lock()
	defer self.fsLock.Unlock()

	fmt.Printf("%d before delete of: %s\n", len(self.contents), name)
	fmt.Printf("DeleteFolder: '%s'\n", name)
	delete(self.contents, name)
	fmt.Printf("%d after delete of: %s\n", len(self.contents), name)

	return nil
}

func (self *FilesystemTracker) ListFolders() (folderList []string) {
	if !self.setup {
		panic("FilesystemTracker:ListFolders called when not yet setup")
	}

	self.fsLock.Lock()
	defer self.fsLock.Unlock()

	folderList = make([]string, len(self.contents))
	index := 0

	for k := range self.contents {
		folderList[index] = k
		index++
	}

	sort.Strings(folderList)
	return
}

// Monitor the filesystem looking for changes to files we are keeping track of.
func (self *FilesystemTracker) monitorLoop(c chan notify.EventInfo) {
	directoryLength := len(self.directory)
	for {
		ei := <-c

		fmt.Println("We have an event")
		fullPath := string(ei.Path())

		path := fullPath
		if len(fullPath) >= directoryLength && self.directory == fullPath[:directoryLength] {
			if len(fullPath) == directoryLength {
				path = "."
			} else {
				// update the path to not have this prefix
				path = fullPath[directoryLength+1:]
			}
		}

		event := Event{Name: ei.Event().String(), Message: path}
		log.Printf("Event captured name: %s location: %s, ei.Path(): %s", event.Name, event.Message, ei.Path())

		self.processEvent(event, path)
	}
}

func (self *FilesystemTracker) checkIfDirectory(event Event, path, fullPath string) bool {
	// Check to see if this was a path we knew about
	_, isDirectory := self.contents[path]
	var iNode uint64
	// if we have not found it yet, check to see if it can be stated
	info, err := os.Stat(fullPath)
	if err == nil {
		isDirectory = info.IsDir()
		sysInterface := info.Sys()
		fmt.Printf("sysInterface: %v\n", sysInterface)
		if sysInterface != nil {
			foo := sysInterface.(*syscall.Stat_t)
			iNode = foo.Ino
		}
	}

	fmt.Printf("checkIfDirectory: event raw data: %s with path: %s fullPath: %s isDirectory: %v iNode: %v\n", event.Name, path, fullPath, isDirectory, iNode)

	return isDirectory

}

func (self *FilesystemTracker) processEvent(event Event, pathName string) {
	log.Printf("handleFilsystemEvent name: %s pathName: %s serverMap: %v\n", event.Name, pathName, serverMap)

	currentValue, exists := self.contents[pathName]

	switch event.Name {
	case "notify.Create":
		fmt.Printf("processEvent: About to assign from one path to the next. Original: %s Map: %s\n", currentValue, self.contents)
		// make sure there is an entry in the DirTreeMap for this folder. Since and empty list will always be returned, we can use that
		if !exists {
			self.contents[pathName] = Directory{}
		}

		updated_value, exists := self.contents[pathName]
		if self.watcher != nil {
			(*self.watcher).FolderCreated(pathName)
		}
		fmt.Printf("notify.Create: Updated  value for %s: %v (%t)\n", pathName, updated_value, exists)

	case "notify.Remove":
		// clean out the entry in the DirTreeMap for this folder
		delete(self.contents, pathName)

		//todo FIXME: uhm, we just deleted this and now we are checking it? Uhm, ....
		updated_value, exists := self.contents[pathName]

		if self.watcher != nil {
			(*self.watcher).FolderDeleted(pathName)
		} else {
			fmt.Println("In the notify.Remove section but did not see a watcher")
		}

		fmt.Printf("notify.Remove: Updated  value for %s: %v (%t)\n", pathName, updated_value, exists)

	// todo fix this to handle the two rename events to be one event
	case "notify.Rename":
		currentValue, exists := self.contents[pathName]
		var iNode uint64
		//if exists && !(currentValue == nil) {
		if exists {
			sysInterface := currentValue.Sys()
			if sysInterface != nil {
				foo := sysInterface.(*syscall.Stat_t)
				iNode = foo.Ino
			}

			// At this point, the item is a directory and this is the original location
			destinationPath, exists := self.renamesInProgress[iNode]
			if exists {
				self.contents[destinationPath] = currentValue
				delete(self.contents, pathName)

				fmt.Printf("Renamed: %s to: %s", pathName, destinationPath)
				//os.Rename(pathName, destinationPath)
			} else {
				fmt.Printf("Could not find rename in progress for iNode: %d\n%v\n", iNode, self.renamesInProgress)
			}

		} else {
			fmt.Printf("Could not find %s in self.contents\n%v\n", pathName, self.contents)
		}

		//err := os.Remove(pathName)
		//if err != nil && !os.IsNotExist(err) {
		//	panic(fmt.Sprintf("Error deleting folder that was renamed %s: %v\n", pathName, err))
		//}
		//fmt.Printf("notify.Rename: %s\n", pathName)
	default:
		fmt.Printf("%s: %s not known, skipping\n", event.Name, pathName)
	}

	currentValue, exists = self.contents[pathName]
	fmt.Printf("After: %s: Existing value for %s: %v (%v)\n", event.Name, pathName, currentValue, exists)

	// sendEvent to manager
	//sendEvent(&event, globalSettings.ManagerAddress, globalSettings.ManagerCredentials)
	SendEvent(event)
}

// Scan the files and folders inside of the directory we are watching and add them to the contents. This function
// can only be called inside of a writelock on self.fsLock
func (self *FilesystemTracker) scanFolders() error {
	pendingPaths := make([]string, 0, 100)
	pendingPaths = append(pendingPaths, self.directory)
	self.contents = make(map[string]Directory)

	for len(pendingPaths) > 0 {
		currentPath := pendingPaths[0]
		directory := NewDirectory()
		pendingPaths = pendingPaths[1:]

		// Read the directories in the path
		f, err := os.Open(currentPath)
		if err != nil {
			return err
		}

		dirEntries, err := f.Readdir(-1)
		for _, entry := range dirEntries {
			if entry.IsDir() {
				newDirectory := filepath.Join(currentPath, entry.Name())
				pendingPaths = append(pendingPaths, newDirectory)
			} else {
				directory.contents[entry.Name()] = entry
			}
		}

		f.Close()
		if err != nil {
			return err
		}

		// Strip the base path off of the current path
		// make sure all of the paths are still '/' prefixed
		relativePath := currentPath[len(self.directory):]
		if relativePath == "" {
			relativePath = "."
		}

		//todo add the directory stat into fileinfo
		info, err := os.Stat(currentPath)
		if err != nil {
			panic(fmt.Sprintf("Could not get stats on directory %s", currentPath))
		}
		directory.FileInfo = info
		self.contents[relativePath] = *directory
	}

	return nil
}

// StringSlice attaches the methods of Interface to []string, sorting in increasing order.
type FileInfoSlice []os.FileInfo

func (p FileInfoSlice) Len() int           { return len(p) }
func (p FileInfoSlice) Less(i, j int) bool { return p[i].Name() < p[j].Name() }
func (p FileInfoSlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

// Sort is a convenience method.
//func (p FileInfoSlice) Sort() { Sort(p) }

// Strings sorts a slice of strings in increasing order.
//func fileInfos(a []os.FileInfo) { Sort(FileInfoSlice(a)) }

// sample code for monitoring the filesystem
//listOfFileInfo, err := scanDirectoryContents()
//if err != nil {
//	   log.Fatal(err)
//}
//
//dotCount := 0
//sleepSeconds := time.Duration(25 + rand.Intn(10))
//fmt.Printf("Full Sync time set to: %d seconds\n", sleepSeconds)
//for {
//	// Randomize the sync time to decrease oscillation
//	time.Sleep(time.Second * sleepSeconds)
//	changed, updatedState, newPaths, deletedPaths, matchingPaths := checkForChanges(listOfFileInfo, nil)
//	if changed {
//		fmt.Println("\nWe have changes, ship it (also updating saved state now that the changes were tracked)")
//		fmt.Printf("@Path report: new %d, deleted %d, matching %d, original %d, updated %d\n", len(newPaths), len(deletedPaths), len(matchingPaths), len(listOfFileInfo), len(updatedState))
//		fmt.Printf("@New paths: %v\n", newPaths)
//		fmt.Printf("@Deleted paths: %v\n", deletedPaths)
//		fmt.Println("******************************************************")
//		listOfFileInfo = updatedState
//
//		// Post the changes to the other side.
//		//sendFolderTree(listOfFileInfo)
//	} else {
//		fmt.Print(".")
//		dotCount++
//		if dotCount%100 == 0 {
//			fmt.Println("")
//		}
//	}
//}
<commit_msg>adding comments<commit_after>// Copyright 2016 Jacob Taylor jacob@ablox.io
// License: Apache2 - http://www.apache.org/licenses/LICENSE-2.0
package main

import (
	"fmt"
	"github.com/rjeczalik/notify"
	"log"
	"os"
	"path/filepath"
	"sort"
	"sync"
	"syscall"
)

// ChangeHandler - Listener for tracking changes that happen to a storage system
type ChangeHandler interface {
	FolderCreated(name string) (err error)
	FolderDeleted(name string) (err error)
}

// StorageTracker - Listener that allows you to tell the tracker what has happened elsewhere so it can mirror the changes
type StorageTracker interface {
	CreateFolder(relativePath string) (err error)
	DeleteFolder(name string) (err error)
	ListFolders() (folderList []string)
}

// LogOnlyChangeHandler - sample change handler
type LogOnlyChangeHandler struct {
}

// FolderCreated - track a new folder being created
func (self *LogOnlyChangeHandler) FolderCreated(name string) (err error) {
	fmt.Printf("LogOnlyChangeHandler:FolderCreated: %s\n", name)
	return nil
}

// FolderDeleted - track a new folder being deleted
func (self *LogOnlyChangeHandler) FolderDeleted(name string) (err error) {
	fmt.Printf("LogOnlyChangeHandler:FolderDeleted: %s\n", name)
	return nil
}

// FilesystemTracker - Track a filesystem and keep it in sync
type FilesystemTracker struct {
	directory         string
	contents          map[string]Directory
	setup             bool
	watcher           *ChangeHandler
	fsEventsChannel   chan notify.EventInfo
	renamesInProgress map[uint64]string // map from inode to source/destination of items being moved
	fsLock            sync.RWMutex
}

// Directory - struct
type Directory struct {
	os.FileInfo
	contents map[string]os.FileInfo
}

func NewDirectory() *Directory {
	return &Directory{contents: make(map[string]os.FileInfo)}
}

func (self *FilesystemTracker) init(directory string) {
	self.fsLock.Lock()
	defer self.fsLock.Unlock()

	if self.setup {
		return
	}

	fmt.Printf("FilesystemTracker:init called with %s\n", directory)
	self.directory = directory

	// Make the channel buffered to ensure no event is dropped. Notify will drop
	// an event if the receiver is not able to keep up the sending pace.
	self.fsEventsChannel = make(chan notify.EventInfo, 10000)

	// Update the path that traffic is served from to be the filesystem canonical path. This will allow the event folders that come in to match what we have.
	fullPath := validatePath(directory)
	self.directory = fullPath

	if fullPath != globalSettings.Directory {
		fmt.Printf("Updating serving directory to: %s\n", fullPath)
		self.directory = fullPath
	}

	fmt.Println("Setting up filesystemTracker!")
	err := self.scanFolders()
	if err != nil {
		panic(err)
	}

	self.setup = true
}

func (self *FilesystemTracker) cleanup() {
	self.fsLock.Lock()
	defer self.fsLock.Unlock()

	if !self.setup {
		panic("cleanup called when not yet setup")
	}

	notify.Stop(self.fsEventsChannel)
}

func (self *FilesystemTracker) watchDirectory(watcher *ChangeHandler) {
	self.fsLock.Lock()
	defer self.fsLock.Unlock()

	if !self.setup {
		panic("FilesystemTracker:watchDirectory called when not yet setup")
	}

	if self.watcher != nil {
		panic("watchDirectory called a second time. Not allowed")
	}

	self.watcher = watcher

	go self.monitorLoop(self.fsEventsChannel)

	// Set up a watch point listening for events within a directory tree rooted at the specified folder
	err := notify.Watch(self.directory+"/...", self.fsEventsChannel, notify.All)
	if err != nil {
		log.Panic(err)
	}
}

func validatePath(directory string) (fullPath string) {
	fullPath, err := filepath.EvalSymlinks(directory)
	if err != nil {
		var err2, err3 error
		// We have an error. If the directory does not exist, then try to create it. Fail if we cannot create it and return the original error
		if os.IsNotExist(err) {
			err2 = os.Mkdir(directory, os.ModeDir+os.ModePerm)
			fullPath, err3 = filepath.EvalSymlinks(directory)
			if err2 != nil || err3 != nil {
				panic(fmt.Sprintf("err: %v\nerr2: %v\nerr3: %v\n", err, err2, err3))
			}
		} else {
			panic(err)
		}
	}

	return
}

func (self *FilesystemTracker) CreateFolder(relativePath string) (err error) {
	if !self.setup {
		panic("FilesystemTracker:CreateFolder called when not yet setup")
	}

	self.fsLock.Lock()
	defer self.fsLock.Unlock()

	absolutePath := self.directory + "/" + relativePath

	err = os.MkdirAll(absolutePath, os.ModeDir+os.ModePerm)
	if err != nil && !os.IsExist(err) {
		panic(fmt.Sprintf("Error creating folder %s: %v\n", absolutePath, err))
	}

	_, exists := self.contents[relativePath]
	fmt.Printf("CreateFolder: '%s' (%v)\n", relativePath, exists)

	if !exists {
		directory := Directory{}
		directory.FileInfo, err = os.Stat(absolutePath)
		self.contents[relativePath] = directory
	} else {
		fmt.Printf("for some reason the directory object already exists in the map: %s\n", relativePath)
	}

	return nil
}

func (self *FilesystemTracker) DeleteFolder(name string) (err error) {
	if !self.setup {
		panic("FilesystemTracker:DeleteFolder called when not yet setup")
	}

	self.fsLock.Lock()
	defer self.fsLock.Unlock()

	fmt.Printf("%d before delete of: %s\n", len(self.contents), name)
	fmt.Printf("DeleteFolder: '%s'\n", name)
	delete(self.contents, name)
	fmt.Printf("%d after delete of: %s\n", len(self.contents), name)

	return nil
}

func (self *FilesystemTracker) ListFolders() (folderList []string) {
	if !self.setup {
		panic("FilesystemTracker:ListFolders called when not yet setup")
	}

	self.fsLock.Lock()
	defer self.fsLock.Unlock()

	folderList = make([]string, len(self.contents))
	index := 0

	for k := range self.contents {
		folderList[index] = k
		index++
	}

	sort.Strings(folderList)
	return
}

// Monitor the filesystem looking for changes to files we are keeping track of.
func (self *FilesystemTracker) monitorLoop(c chan notify.EventInfo) {
	directoryLength := len(self.directory)
	for {
		ei := <-c

		fmt.Println("We have an event")
		fullPath := string(ei.Path())

		path := fullPath
		if len(fullPath) >= directoryLength && self.directory == fullPath[:directoryLength] {
			if len(fullPath) == directoryLength {
				path = "."
			} else {
				// update the path to not have this prefix
				path = fullPath[directoryLength+1:]
			}
		}

		event := Event{Name: ei.Event().String(), Message: path}
		log.Printf("Event captured name: %s location: %s, ei.Path(): %s", event.Name, event.Message, ei.Path())

		self.processEvent(event, path)
	}
}

func (self *FilesystemTracker) checkIfDirectory(event Event, path, fullPath string) bool {
	// Check to see if this was a path we knew about
	_, isDirectory := self.contents[path]
	var iNode uint64
	// if we have not found it yet, check to see if it can be stated
	info, err := os.Stat(fullPath)
	if err == nil {
		isDirectory = info.IsDir()
		sysInterface := info.Sys()
		fmt.Printf("sysInterface: %v\n", sysInterface)
		if sysInterface != nil {
			foo := sysInterface.(*syscall.Stat_t)
			iNode = foo.Ino
		}
	}

	fmt.Printf("checkIfDirectory: event raw data: %s with path: %s fullPath: %s isDirectory: %v iNode: %v\n", event.Name, path, fullPath, isDirectory, iNode)

	return isDirectory

}

func (self *FilesystemTracker) processEvent(event Event, pathName string) {
	log.Printf("handleFilsystemEvent name: %s pathName: %s serverMap: %v\n", event.Name, pathName, serverMap)

	currentValue, exists := self.contents[pathName]

	switch event.Name {
	case "notify.Create":
		fmt.Printf("processEvent: About to assign from one path to the next. Original: %s Map: %s\n", currentValue, self.contents)
		// make sure there is an entry in the DirTreeMap for this folder. Since and empty list will always be returned, we can use that
		if !exists {
			self.contents[pathName] = Directory{}
		}

		updated_value, exists := self.contents[pathName]
		if self.watcher != nil {
			(*self.watcher).FolderCreated(pathName)
		}
		fmt.Printf("notify.Create: Updated  value for %s: %v (%t)\n", pathName, updated_value, exists)

	case "notify.Remove":
		// clean out the entry in the DirTreeMap for this folder
		delete(self.contents, pathName)

		//todo FIXME: uhm, we just deleted this and now we are checking it? Uhm, ....
		updated_value, exists := self.contents[pathName]

		if self.watcher != nil {
			(*self.watcher).FolderDeleted(pathName)
		} else {
			fmt.Println("In the notify.Remove section but did not see a watcher")
		}

		fmt.Printf("notify.Remove: Updated  value for %s: %v (%t)\n", pathName, updated_value, exists)

	// todo fix this to handle the two rename events to be one event
	case "notify.Rename":
		currentValue, exists := self.contents[pathName]
		var iNode uint64
		//if exists && !(currentValue == nil) {
		if exists {
			sysInterface := currentValue.Sys()
			if sysInterface != nil {
				foo := sysInterface.(*syscall.Stat_t)
				iNode = foo.Ino
			}

			// At this point, the item is a directory and this is the original location
			destinationPath, exists := self.renamesInProgress[iNode]
			if exists {
				self.contents[destinationPath] = currentValue
				delete(self.contents, pathName)

				fmt.Printf("Renamed: %s to: %s", pathName, destinationPath)
				//os.Rename(pathName, destinationPath)
			} else {
				fmt.Printf("Could not find rename in progress for iNode: %d\n%v\n", iNode, self.renamesInProgress)
			}

		} else {
			fmt.Printf("Could not find %s in self.contents\n%v\n", pathName, self.contents)
		}

		//err := os.Remove(pathName)
		//if err != nil && !os.IsNotExist(err) {
		//	panic(fmt.Sprintf("Error deleting folder that was renamed %s: %v\n", pathName, err))
		//}
		//fmt.Printf("notify.Rename: %s\n", pathName)
	default:
		fmt.Printf("%s: %s not known, skipping\n", event.Name, pathName)
	}

	currentValue, exists = self.contents[pathName]
	fmt.Printf("After: %s: Existing value for %s: %v (%v)\n", event.Name, pathName, currentValue, exists)

	// sendEvent to manager
	//sendEvent(&event, globalSettings.ManagerAddress, globalSettings.ManagerCredentials)
	SendEvent(event)
}

// Scan the files and folders inside of the directory we are watching and add them to the contents. This function
// can only be called inside of a writelock on self.fsLock
func (self *FilesystemTracker) scanFolders() error {
	pendingPaths := make([]string, 0, 100)
	pendingPaths = append(pendingPaths, self.directory)
	self.contents = make(map[string]Directory)

	for len(pendingPaths) > 0 {
		currentPath := pendingPaths[0]
		directory := NewDirectory()
		pendingPaths = pendingPaths[1:]

		// Read the directories in the path
		f, err := os.Open(currentPath)
		if err != nil {
			return err
		}

		dirEntries, err := f.Readdir(-1)
		for _, entry := range dirEntries {
			if entry.IsDir() {
				newDirectory := filepath.Join(currentPath, entry.Name())
				pendingPaths = append(pendingPaths, newDirectory)
			} else {
				directory.contents[entry.Name()] = entry
			}
		}

		f.Close()
		if err != nil {
			return err
		}

		// Strip the base path off of the current path
		// make sure all of the paths are still '/' prefixed
		relativePath := currentPath[len(self.directory):]
		if relativePath == "" {
			relativePath = "."
		}

		//todo add the directory stat into fileinfo
		info, err := os.Stat(currentPath)
		if err != nil {
			panic(fmt.Sprintf("Could not get stats on directory %s", currentPath))
		}
		directory.FileInfo = info
		self.contents[relativePath] = *directory
	}

	return nil
}

// StringSlice attaches the methods of Interface to []string, sorting in increasing order.
type FileInfoSlice []os.FileInfo

func (p FileInfoSlice) Len() int           { return len(p) }
func (p FileInfoSlice) Less(i, j int) bool { return p[i].Name() < p[j].Name() }
func (p FileInfoSlice) Swap(i, j int)      { p[i], p[j] = p[j], p[i] }

// Sort is a convenience method.
//func (p FileInfoSlice) Sort() { Sort(p) }

// Strings sorts a slice of strings in increasing order.
//func fileInfos(a []os.FileInfo) { Sort(FileInfoSlice(a)) }

// sample code for monitoring the filesystem
//listOfFileInfo, err := scanDirectoryContents()
//if err != nil {
//	   log.Fatal(err)
//}
//
//dotCount := 0
//sleepSeconds := time.Duration(25 + rand.Intn(10))
//fmt.Printf("Full Sync time set to: %d seconds\n", sleepSeconds)
//for {
//	// Randomize the sync time to decrease oscillation
//	time.Sleep(time.Second * sleepSeconds)
//	changed, updatedState, newPaths, deletedPaths, matchingPaths := checkForChanges(listOfFileInfo, nil)
//	if changed {
//		fmt.Println("\nWe have changes, ship it (also updating saved state now that the changes were tracked)")
//		fmt.Printf("@Path report: new %d, deleted %d, matching %d, original %d, updated %d\n", len(newPaths), len(deletedPaths), len(matchingPaths), len(listOfFileInfo), len(updatedState))
//		fmt.Printf("@New paths: %v\n", newPaths)
//		fmt.Printf("@Deleted paths: %v\n", deletedPaths)
//		fmt.Println("******************************************************")
//		listOfFileInfo = updatedState
//
//		// Post the changes to the other side.
//		//sendFolderTree(listOfFileInfo)
//	} else {
//		fmt.Print(".")
//		dotCount++
//		if dotCount%100 == 0 {
//			fmt.Println("")
//		}
//	}
//}
<|endoftext|>


<commit_before>package main

import "flag"
import "fmt"

type Formula struct {
    value string
}

func parseFormula(f string) Formula {
    result := Formula{
        value: f,
    }
    return result
}

func main() {
    formulaString := flag.String("formula", "", "The formula in propositional logic")
    flag.Parse()
    formula := parseFormula(*formulaString)
    fmt.Println("Formula: " + formula.value)
}
<commit_msg>Adapted code based on go fmt<commit_after>package main

import "flag"
import "fmt"

type Formula struct {
	value string
}

func parseFormula(f string) Formula {
	result := Formula{
		value: f,
	}
	return result
}

func main() {
	formulaString := flag.String("formula", "", "The formula in propositional logic")
	flag.Parse()
	formula := parseFormula(*formulaString)
	fmt.Println("Formula: " + formula.value)
}
<|endoftext|>


<commit_before>package bridge

import (
	"fmt"
	"log"
	"path/filepath"
)

type ITypeLibrary interface {
	AddType(pkg string, name string, t *Type) (alt *Type)
	GetType(pkg string, name string) *Type
	GetShortPackageName(pkg string) string
}

type TypeLibrary struct {
	shortNamesForPkg map[string]string
	pkgByShortName   map[string]string
	types            map[string]*Type
	typeCounter      int64
	pkgCounter       int
}

func NewTypeLibrary() *TypeLibrary {
	log.Println("Creating new type library... ")
	out := TypeLibrary{}
	out.pkgByShortName = make(map[string]string)
	out.shortNamesForPkg = make(map[string]string)
	out.types = make(map[string]*Type)
	return &out
}

func (tl *TypeLibrary) GetShortPackageName(pkg string) string {
	if value, ok := tl.shortNamesForPkg[pkg]; ok {
		return value
	}

	// create a name and return it (and save it ofcourse)
	name := ""
	for pkg != "" {
		name := filepath.Base(pkg) + name
		if _, ok := tl.shortNamesForPkg[pkg]; !ok {
			tl.shortNamesForPkg[name] = pkg
			tl.pkgByShortName[pkg] = name
			return name
		}
		pkg = filepath.Dir(pkg)
	}

	// return a random name!
	name = fmt.Sprintf("pkg%d", tl.pkgCounter)
	tl.shortNamesForPkg[name] = pkg
	tl.pkgByShortName[pkg] = name
	return name
}

/**
 * Adds a type to the type system.  If the type already exists then
 * the existing one is returned otherwise a new type is added and returned.
 * Also the type's ID will be set.
 */
func (tl *TypeLibrary) AddType(pkg string, name string, t *Type) (alt *Type) {
	key := pkg + "." + name
	if value, ok := tl.types[key]; ok {
		return value
	}
	tl.typeCounter++
	tl.types[key] = t
	return t
}

func (tl *TypeLibrary) GetType(pkg string, name string) *Type {
	key := pkg + "." + name
	return tl.types[key]
}

func (tl *TypeLibrary) FindType(typeClass int, typeData interface{}) string {
	return ""
}
<commit_msg>Adding basic types<commit_after>package bridge

import (
	"fmt"
	"log"
	"path/filepath"
)

type ITypeLibrary interface {
	AddType(pkg string, name string, t *Type) (alt *Type)
	GetType(pkg string, name string) *Type
	GetShortPackageName(pkg string) string
}

type TypeLibrary struct {
	shortNamesForPkg map[string]string
	pkgByShortName   map[string]string
	types            map[string]*Type
	typeCounter      int64
	pkgCounter       int
}

func NewTypeLibrary() *TypeLibrary {
	log.Println("Creating new type library... ")
	out := TypeLibrary{}
	out.pkgByShortName = make(map[string]string)
	out.shortNamesForPkg = make(map[string]string)
	out.types = make(map[string]*Type)

	// add some basic types
	out.AddBasicType("string")
	out.AddBasicType("float")
	out.AddBasicType("float32")
	out.AddBasicType("float64")
	out.AddBasicType("bool")
	out.AddBasicType("byte")
	out.AddBasicType("int")
	out.AddBasicType("int16")
	out.AddBasicType("int32")
	out.AddBasicType("int64")
	out.AddBasicType("uint")
	out.AddBasicType("uint16")
	out.AddBasicType("uint32")
	out.AddBasicType("uint64")
	return &out
}

func (tl *TypeLibrary) AddBasicType(name string) (alt *Type) {
	return tl.AddType("", name, &Type{BasicType, name})
}

/**
 * Adds a type to the type system.  If the type already exists then
 * the existing one is returned otherwise a new type is added and returned.
 * Also the type's ID will be set.
 */
func (tl *TypeLibrary) AddType(pkg string, name string, t *Type) (alt *Type) {
	key := pkg + "." + name
	if value, ok := tl.types[key]; ok {
		return value
	}
	tl.typeCounter++
	tl.types[key] = t
	return t
}

func (tl *TypeLibrary) GetType(pkg string, name string) *Type {
	key := pkg + "." + name
	return tl.types[key]
}

func (tl *TypeLibrary) GetShortPackageName(pkg string) string {
	if value, ok := tl.shortNamesForPkg[pkg]; ok {
		return value
	}

	// create a name and return it (and save it ofcourse)
	name := ""
	for pkg != "" {
		name := filepath.Base(pkg) + name
		if _, ok := tl.shortNamesForPkg[pkg]; !ok {
			tl.shortNamesForPkg[name] = pkg
			tl.pkgByShortName[pkg] = name
			return name
		}
		pkg = filepath.Dir(pkg)
	}

	// return a random name!
	name = fmt.Sprintf("pkg%d", tl.pkgCounter)
	tl.shortNamesForPkg[name] = pkg
	tl.pkgByShortName[pkg] = name
	return name
}
<|endoftext|>


<commit_before>package henchman

import (
	"bytes"
)

type TransportConfig map[string]string

type TransportInterface interface {
	Initialize(config *TransportConfig) error
	Exec(cmd string, params string) (*bytes.Buffer, error)
	Put(source string, destination string) error
}
<commit_msg>TransportInterface.Exec doesn't take params for now<commit_after>package henchman

import (
	"bytes"
)

type TransportConfig map[string]string

type TransportInterface interface {
	Initialize(config *TransportConfig) error
	Exec(cmd string) (*bytes.Buffer, error)
	Put(source string, destination string) error
}
<|endoftext|>


<commit_before>// MIT License
//
// Copyright (c) 2017 Stefan Wichmann
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main

import log "github.com/Sirupsen/logrus"
import "runtime"
import "os"
import "os/exec"
import "path/filepath"
import "github.com/hashicorp/go-version"
import "time"

const upgradeURL = "https://api.github.com/repos/stefanwichmann/kelvin/releases/latest"
const updateCheckIntervalInMinutes = 24 * 60

// CheckForUpdate will get the latest release information of Kelvin
// from github and compare it to the given version. If a newer version
// is found it will try to replace the running binary and restart.
func CheckForUpdate(currentVersion string) {
	// only look for update if version string matches a valid release version
	version, err := version.NewVersion(currentVersion)
	if err != nil {
		return
	}

	for {
		log.Printf("Looking for updates...\n")
		avail, url, err := updateAvailable(version, upgradeURL)
		if err != nil {
			log.Warningf("Error looking for update: %v\n", err)
		} else if avail {
			err = updateBinary(url)
			if err != nil {
				log.Warningf("Error updating binary: %v.\n", err)
			} else {
				log.Printf("Restarting...\n")
				Restart()
			}
		}
		// try again in 24 hours...
		time.Sleep(updateCheckIntervalInMinutes * time.Minute)
	}
}

// Restart the running binary.
// All arguments, pipes and environment variables will
// be preserved.
func Restart() {
	binary := os.Args[0]
	args := []string{}
	if len(os.Args) > 1 {
		args = os.Args[1:]
	}

	cmd := exec.Command(binary, args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Env = os.Environ()

	cmd.Start()
	os.Exit(0)
}

func updateAvailable(currentVersion *version.Version, url string) (bool, string, error) {
	releaseName, assetURL, err := downloadLatestReleaseInfo(url)
	if err != nil {
		return false, "", err
	}

	// parse name and compare
	version, err := version.NewVersion(releaseName)
	if err != nil {
		log.Debugf("Could not parse release name: %v\n", err)
		return false, "", err
	}

	if version.GreaterThan(currentVersion) {
		log.Printf("Found new release version %s.", version)
		return true, assetURL, nil
	}

	return false, "", nil
}

func updateBinary(assetURL string) error {
	currentBinary := os.Args[0]
	log.Printf("Downloading update archive %s\n", assetURL)
	archive, err := downloadReleaseArchive(assetURL)
	if err != nil {
		os.Remove(archive)
		return err
	}
	defer os.Remove(archive)
	log.Debugf("Update archive downloaded to %v\n", archive)

	// Find and extract binary
	var tempBinary string
	defer os.Remove(tempBinary)
	if runtime.GOOS == "windows" {
		tempBinary, err = extractBinaryFromZipArchive(archive, currentBinary, filepath.Dir(os.Args[0]))
		if err != nil {
			return err
		}
	} else {
		tempBinary, err = extractBinaryFromTarArchive(archive, currentBinary, filepath.Dir(os.Args[0]))
		if err != nil {
			return err
		}
	}

	// make binary executable
	err = os.Chmod(tempBinary, os.FileMode(0755))
	if err != nil {
		return err
	}

	// Replace binary
	log.Debugf("Replacing current binary %v with %v\n", currentBinary, tempBinary)
	err = replaceBinary(currentBinary, tempBinary)
	if err != nil {
		return err
	}

	log.Printf("Update successful\n")
	return nil
}

func replaceBinary(binaryFile, tempFile string) error {
	old := binaryFile + ".old"
	os.Remove(old) // remove old backup
	err := os.Rename(binaryFile, old)
	if err != nil {
		return err
	}
	if os.Rename(tempFile, binaryFile); err != nil {
		os.Rename(old, binaryFile)
		return err
	}
	return nil
}
<commit_msg>Remove obsolete new lines<commit_after>// MIT License
//
// Copyright (c) 2017 Stefan Wichmann
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
package main

import log "github.com/Sirupsen/logrus"
import "runtime"
import "os"
import "os/exec"
import "path/filepath"
import "github.com/hashicorp/go-version"
import "time"

const upgradeURL = "https://api.github.com/repos/stefanwichmann/kelvin/releases/latest"
const updateCheckIntervalInMinutes = 24 * 60

// CheckForUpdate will get the latest release information of Kelvin
// from github and compare it to the given version. If a newer version
// is found it will try to replace the running binary and restart.
func CheckForUpdate(currentVersion string) {
	// only look for update if version string matches a valid release version
	version, err := version.NewVersion(currentVersion)
	if err != nil {
		return
	}

	for {
		log.Printf("Looking for updates...")
		avail, url, err := updateAvailable(version, upgradeURL)
		if err != nil {
			log.Warningf("Error looking for update: %v", err)
		} else if avail {
			err = updateBinary(url)
			if err != nil {
				log.Warningf("Error updating binary: %v.", err)
			} else {
				log.Printf("Restarting...")
				Restart()
			}
		}
		// try again in 24 hours...
		time.Sleep(updateCheckIntervalInMinutes * time.Minute)
	}
}

// Restart the running binary.
// All arguments, pipes and environment variables will
// be preserved.
func Restart() {
	binary := os.Args[0]
	args := []string{}
	if len(os.Args) > 1 {
		args = os.Args[1:]
	}

	cmd := exec.Command(binary, args...)
	cmd.Stdout = os.Stdout
	cmd.Stderr = os.Stderr
	cmd.Env = os.Environ()

	cmd.Start()
	os.Exit(0)
}

func updateAvailable(currentVersion *version.Version, url string) (bool, string, error) {
	releaseName, assetURL, err := downloadLatestReleaseInfo(url)
	if err != nil {
		return false, "", err
	}

	// parse name and compare
	version, err := version.NewVersion(releaseName)
	if err != nil {
		log.Debugf("Could not parse release name: %v", err)
		return false, "", err
	}

	if version.GreaterThan(currentVersion) {
		log.Printf("Found new release version %s.", version)
		return true, assetURL, nil
	}

	return false, "", nil
}

func updateBinary(assetURL string) error {
	currentBinary := os.Args[0]
	log.Printf("Downloading update archive %s", assetURL)
	archive, err := downloadReleaseArchive(assetURL)
	if err != nil {
		os.Remove(archive)
		return err
	}
	defer os.Remove(archive)
	log.Debugf("Update archive downloaded to %v", archive)

	// Find and extract binary
	var tempBinary string
	defer os.Remove(tempBinary)
	if runtime.GOOS == "windows" {
		tempBinary, err = extractBinaryFromZipArchive(archive, currentBinary, filepath.Dir(os.Args[0]))
		if err != nil {
			return err
		}
	} else {
		tempBinary, err = extractBinaryFromTarArchive(archive, currentBinary, filepath.Dir(os.Args[0]))
		if err != nil {
			return err
		}
	}

	// make binary executable
	err = os.Chmod(tempBinary, os.FileMode(0755))
	if err != nil {
		return err
	}

	// Replace binary
	log.Debugf("Replacing current binary %v with %v", currentBinary, tempBinary)
	err = replaceBinary(currentBinary, tempBinary)
	if err != nil {
		return err
	}

	log.Printf("Update successful")
	return nil
}

func replaceBinary(binaryFile, tempFile string) error {
	old := binaryFile + ".old"
	os.Remove(old) // remove old backup
	err := os.Rename(binaryFile, old)
	if err != nil {
		return err
	}
	if os.Rename(tempFile, binaryFile); err != nil {
		os.Rename(old, binaryFile)
		return err
	}
	return nil
}
<|endoftext|>


<commit_before>// Copyright 2018 Istio Authors
//
// 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 probe

import (
	"errors"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
	"testing"
	"time"
)

const (
	testDuration     = 10 * time.Millisecond
	testFileDuration = 100 * time.Millisecond
)

var errClosed = errors.New("closed")

type dummyImpl struct {
	sync.Mutex
	lastStatus  error
	changeCount int
	updatec     chan struct{}
}

func (d *dummyImpl) GetStatus() error {
	d.Lock()
	defer d.Unlock()
	return d.lastStatus
}

func (d *dummyImpl) GetCount() int {
	d.Lock()
	defer d.Unlock()
	return d.changeCount
}

func (d *dummyImpl) onClose() error {
	d.Lock()
	defer d.Unlock()
	d.lastStatus = errClosed
	return nil
}

func (d *dummyImpl) onUpdate(newStatus error) {
	d.Lock()
	defer d.Unlock()
	d.lastStatus = newStatus
	d.changeCount++
	if d.updatec != nil {
		select {
		case d.updatec <- struct{}{}:
		default:
		}
	}
}

func (d *dummyImpl) wait(t *testing.T, timeout time.Duration) {
	t.Helper()
	d.Lock()
	if d.updatec != nil {
		close(d.updatec)
	}
	d.updatec = make(chan struct{})
	d.Unlock()
	for i := 0; i < 10; i++ {
		select {
		case <-d.updatec:
			return
		case <-time.After(timeout):
		}
	}
	t.Fatal("Failed to wait")
}

func newDummyController() (Controller, *dummyImpl) {
	d := &dummyImpl{}
	c := &controller{
		statuses: map[*Probe]error{},
		name:     "dummy",
		interval: testDuration,
		impl:     d,
	}
	c.Start()
	return c, d
}

func TestController(t *testing.T) {
	c, d := newDummyController()
	defer c.Close()

	p1 := NewProbe()
	p1.RegisterProbe(c, "p1")
	time.Sleep(testDuration)
	if err := d.GetStatus(); err == nil {
		t.Error("Got nil, want error")
	}

	prevCount := d.GetCount()
	p1.SetAvailable(nil)
	d.wait(t, testDuration)
	if err := d.GetStatus(); err != nil {
		t.Errorf("Got %v, want nil", err)
	}
	if count := d.GetCount(); count-prevCount < 1 {
		t.Errorf("Count should be incremented (%d -> %d)", prevCount, count)
	}

	p2 := NewProbe()
	p2.RegisterProbe(c, "p2")
	d.wait(t, testDuration)
	if err := d.GetStatus(); err == nil {
		t.Error("Got nil, want error")
	}

	p2.SetAvailable(nil)
	d.wait(t, testDuration)
	if err := d.GetStatus(); err != nil {
		t.Errorf("Got %v, want nil", err)
	}
	prevCount = d.GetCount()

	d.wait(t, testDuration*2)
	if err := d.GetStatus(); err != nil {
		t.Errorf("Got %v, want nil", err)
	}
	if count := d.GetCount(); count-prevCount < 1 {
		t.Errorf("Count should be incremented (%d -> %d)", prevCount, count)
	}

	p1.SetAvailable(errors.New("dummy"))
	d.wait(t, testDuration)
	if err := d.GetStatus(); err == nil {
		t.Error("Got nil, want error")
	}

	prevCount = d.GetCount()
	d.wait(t, testDuration*2)
	if err := d.GetStatus(); err == nil {
		t.Error("Got nil, want error")
	}
	if count := d.GetCount(); count-prevCount < 1 {
		t.Errorf("Count should be incremented (%d -> %d)", prevCount, count)
	}
}

func TestControllerRegisterTwice(t *testing.T) {
	c, _ := newDummyController()
	defer c.Close()

	getSize := func() int {
		cc := c.(*controller)
		cc.Lock()
		defer cc.Unlock()
		return len(cc.statuses)
	}
	p1 := NewProbe()
	p1.RegisterProbe(c, "p1")
	if size := getSize(); size != 1 {
		t.Errorf("Got %v, Want 1", size)
	}
	p1.RegisterProbe(c, "p1")
	if size := getSize(); size != 1 {
		t.Errorf("Got %v, Want 1", size)
	}
	p1.RegisterProbe(c, "p2")
	if size := getSize(); size != 1 {
		t.Errorf("Got %v, Want 1", size)
	}
}

func TestControllerAfterClose(t *testing.T) {
	c, d := newDummyController()

	p1 := NewProbe()
	p1.RegisterProbe(c, "p1")
	d.wait(t, testDuration)
	if err := d.GetStatus(); err == nil {
		t.Error("Got nil, want error")
	}

	p1.SetAvailable(nil)
	d.wait(t, testDuration)
	if err := d.GetStatus(); err != nil {
		t.Errorf("Got %s, want nil", err)
	}

	if err := c.Close(); err != nil {
		t.Errorf("failed to close: %v", err)
	}
	time.Sleep(testDuration * 2)
	if err := d.GetStatus(); err != errClosed {
		t.Errorf("Got %v, Want %v", err, errClosed)
	}

	p1.SetAvailable(errors.New("dummy"))
	time.Sleep(testDuration * 2)
	if err := d.GetStatus(); err != errClosed {
		t.Errorf("Got %v, Want %v", err, errClosed)
	}
	p1.SetAvailable(nil)
	time.Sleep(testDuration * 2)
	if err := d.GetStatus(); err != errClosed {
		t.Errorf("Got %v, Want %v", err, errClosed)
	}
}

func TestFileControllerMethods(t *testing.T) {
	d, err := ioutil.TempDir("", t.Name())
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(d)
	path := filepath.Join(d, "fc")
	fc := &fileController{path: path}
	client := NewFileClient(&Options{path, testFileDuration})
	if err := client.GetStatus(); err == nil {
		t.Error("Got nil, Want error")
	}
	fc.onUpdate(nil)
	if err := client.GetStatus(); err != nil {
		t.Errorf("Got %v, Want nil", err)
	}
	time.Sleep(testFileDuration * 3)
	if err := client.GetStatus(); err == nil {
		t.Error("Got nil, Want error")
	}
	fc.onUpdate(nil)
	if err := client.GetStatus(); err != nil {
		t.Errorf("Got %v, Want nil", err)
	}
	fc.onUpdate(errors.New("dummy"))
	if err := client.GetStatus(); !os.IsNotExist(err) {
		t.Errorf("Got %v, Want not-existed", err)
	}
}

func TestFileController(t *testing.T) {
	d, err := ioutil.TempDir("", t.Name())
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(d)
	opt := &Options{filepath.Join(d, "fc"), testFileDuration}
	fc := NewFileController(opt)
	client := NewFileClient(opt)
	fc.Start()
	defer fc.Close()

	p1 := NewProbe()
	p1.RegisterProbe(fc, "p1")
	time.Sleep(testFileDuration)
	if err := client.GetStatus(); err == nil {
		t.Errorf("Got nil, want error")
	}

	p1.SetAvailable(nil)
	time.Sleep(testFileDuration)
	if err := client.GetStatus(); err != nil {
		t.Errorf("Got %v, want nil", err)
	}

	p2 := NewProbe()
	p2.RegisterProbe(fc, "p2")
	time.Sleep(testFileDuration)
	if err := client.GetStatus(); err == nil {
		t.Errorf("Got nil, want error")
	}

	p2.SetAvailable(nil)
	time.Sleep(testFileDuration)
	if err := client.GetStatus(); err != nil {
		t.Errorf("Got %v, want nil", err)
	}

	time.Sleep(testFileDuration * 2)
	if err := client.GetStatus(); err != nil {
		t.Errorf("Got %v, want nil", err)
	}

	p1.SetAvailable(errors.New("dummy"))
	time.Sleep(testFileDuration)
	if err := client.GetStatus(); err == nil {
		t.Error("Got nil, want error")
	}

	time.Sleep(testFileDuration * 2)
	if err := client.GetStatus(); err == nil {
		t.Error("Got nil, want error")
	}
}
<commit_msg>disable it (#4268)<commit_after>// Copyright 2018 Istio Authors
//
// 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 probe

import (
	"errors"
	"io/ioutil"
	"os"
	"path/filepath"
	"sync"
	"testing"
	"time"
)

const (
	testDuration     = 10 * time.Millisecond
	testFileDuration = 100 * time.Millisecond
)

var errClosed = errors.New("closed")

type dummyImpl struct {
	sync.Mutex
	lastStatus  error
	changeCount int
	updatec     chan struct{}
}

func (d *dummyImpl) GetStatus() error {
	d.Lock()
	defer d.Unlock()
	return d.lastStatus
}

func (d *dummyImpl) GetCount() int {
	d.Lock()
	defer d.Unlock()
	return d.changeCount
}

func (d *dummyImpl) onClose() error {
	d.Lock()
	defer d.Unlock()
	d.lastStatus = errClosed
	return nil
}

func (d *dummyImpl) onUpdate(newStatus error) {
	d.Lock()
	defer d.Unlock()
	d.lastStatus = newStatus
	d.changeCount++
	if d.updatec != nil {
		select {
		case d.updatec <- struct{}{}:
		default:
		}
	}
}

func (d *dummyImpl) wait(t *testing.T, timeout time.Duration) {
	t.Helper()
	d.Lock()
	if d.updatec != nil {
		close(d.updatec)
	}
	d.updatec = make(chan struct{})
	d.Unlock()
	for i := 0; i < 10; i++ {
		select {
		case <-d.updatec:
			return
		case <-time.After(timeout):
		}
	}
	t.Fatal("Failed to wait")
}

func newDummyController() (Controller, *dummyImpl) {
	d := &dummyImpl{}
	c := &controller{
		statuses: map[*Probe]error{},
		name:     "dummy",
		interval: testDuration,
		impl:     d,
	}
	c.Start()
	return c, d
}

func TestController(t *testing.T) {
	t.Skip("issue https://github.com/istio/istio/issues/3082")
	c, d := newDummyController()
	defer c.Close()

	p1 := NewProbe()
	p1.RegisterProbe(c, "p1")
	time.Sleep(testDuration)
	if err := d.GetStatus(); err == nil {
		t.Error("Got nil, want error")
	}

	prevCount := d.GetCount()
	p1.SetAvailable(nil)
	d.wait(t, testDuration)
	if err := d.GetStatus(); err != nil {
		t.Errorf("Got %v, want nil", err)
	}
	if count := d.GetCount(); count-prevCount < 1 {
		t.Errorf("Count should be incremented (%d -> %d)", prevCount, count)
	}

	p2 := NewProbe()
	p2.RegisterProbe(c, "p2")
	d.wait(t, testDuration)
	if err := d.GetStatus(); err == nil {
		t.Error("Got nil, want error")
	}

	p2.SetAvailable(nil)
	d.wait(t, testDuration)
	if err := d.GetStatus(); err != nil {
		t.Errorf("Got %v, want nil", err)
	}
	prevCount = d.GetCount()

	d.wait(t, testDuration*2)
	if err := d.GetStatus(); err != nil {
		t.Errorf("Got %v, want nil", err)
	}
	if count := d.GetCount(); count-prevCount < 1 {
		t.Errorf("Count should be incremented (%d -> %d)", prevCount, count)
	}

	p1.SetAvailable(errors.New("dummy"))
	d.wait(t, testDuration)
	if err := d.GetStatus(); err == nil {
		t.Error("Got nil, want error")
	}

	prevCount = d.GetCount()
	d.wait(t, testDuration*2)
	if err := d.GetStatus(); err == nil {
		t.Error("Got nil, want error")
	}
	if count := d.GetCount(); count-prevCount < 1 {
		t.Errorf("Count should be incremented (%d -> %d)", prevCount, count)
	}
}

func TestControllerRegisterTwice(t *testing.T) {
	c, _ := newDummyController()
	defer c.Close()

	getSize := func() int {
		cc := c.(*controller)
		cc.Lock()
		defer cc.Unlock()
		return len(cc.statuses)
	}
	p1 := NewProbe()
	p1.RegisterProbe(c, "p1")
	if size := getSize(); size != 1 {
		t.Errorf("Got %v, Want 1", size)
	}
	p1.RegisterProbe(c, "p1")
	if size := getSize(); size != 1 {
		t.Errorf("Got %v, Want 1", size)
	}
	p1.RegisterProbe(c, "p2")
	if size := getSize(); size != 1 {
		t.Errorf("Got %v, Want 1", size)
	}
}

func TestControllerAfterClose(t *testing.T) {
	t.Skip("issue https://github.com/istio/istio/issues/3082")
	c, d := newDummyController()

	p1 := NewProbe()
	p1.RegisterProbe(c, "p1")
	d.wait(t, testDuration)
	if err := d.GetStatus(); err == nil {
		t.Error("Got nil, want error")
	}

	p1.SetAvailable(nil)
	d.wait(t, testDuration)
	if err := d.GetStatus(); err != nil {
		t.Errorf("Got %s, want nil", err)
	}

	if err := c.Close(); err != nil {
		t.Errorf("failed to close: %v", err)
	}
	time.Sleep(testDuration * 2)
	if err := d.GetStatus(); err != errClosed {
		t.Errorf("Got %v, Want %v", err, errClosed)
	}

	p1.SetAvailable(errors.New("dummy"))
	time.Sleep(testDuration * 2)
	if err := d.GetStatus(); err != errClosed {
		t.Errorf("Got %v, Want %v", err, errClosed)
	}
	p1.SetAvailable(nil)
	time.Sleep(testDuration * 2)
	if err := d.GetStatus(); err != errClosed {
		t.Errorf("Got %v, Want %v", err, errClosed)
	}
}

func TestFileControllerMethods(t *testing.T) {
	t.Skip("issue https://github.com/istio/istio/issues/3082")
	d, err := ioutil.TempDir("", t.Name())
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(d)
	path := filepath.Join(d, "fc")
	fc := &fileController{path: path}
	client := NewFileClient(&Options{path, testFileDuration})
	if err := client.GetStatus(); err == nil {
		t.Error("Got nil, Want error")
	}
	fc.onUpdate(nil)
	if err := client.GetStatus(); err != nil {
		t.Errorf("Got %v, Want nil", err)
	}
	time.Sleep(testFileDuration * 3)
	if err := client.GetStatus(); err == nil {
		t.Error("Got nil, Want error")
	}
	fc.onUpdate(nil)
	if err := client.GetStatus(); err != nil {
		t.Errorf("Got %v, Want nil", err)
	}
	fc.onUpdate(errors.New("dummy"))
	if err := client.GetStatus(); !os.IsNotExist(err) {
		t.Errorf("Got %v, Want not-existed", err)
	}
}

func TestFileController(t *testing.T) {
	t.Skip("issue https://github.com/istio/istio/issues/3082")
	d, err := ioutil.TempDir("", t.Name())
	if err != nil {
		t.Fatal(err)
	}
	defer os.RemoveAll(d)
	opt := &Options{filepath.Join(d, "fc"), testFileDuration}
	fc := NewFileController(opt)
	client := NewFileClient(opt)
	fc.Start()
	defer fc.Close()

	p1 := NewProbe()
	p1.RegisterProbe(fc, "p1")
	time.Sleep(testFileDuration)
	if err := client.GetStatus(); err == nil {
		t.Errorf("Got nil, want error")
	}

	p1.SetAvailable(nil)
	time.Sleep(testFileDuration)
	if err := client.GetStatus(); err != nil {
		t.Errorf("Got %v, want nil", err)
	}

	p2 := NewProbe()
	p2.RegisterProbe(fc, "p2")
	time.Sleep(testFileDuration)
	if err := client.GetStatus(); err == nil {
		t.Errorf("Got nil, want error")
	}

	p2.SetAvailable(nil)
	time.Sleep(testFileDuration)
	if err := client.GetStatus(); err != nil {
		t.Errorf("Got %v, want nil", err)
	}

	time.Sleep(testFileDuration * 2)
	if err := client.GetStatus(); err != nil {
		t.Errorf("Got %v, want nil", err)
	}

	p1.SetAvailable(errors.New("dummy"))
	time.Sleep(testFileDuration)
	if err := client.GetStatus(); err == nil {
		t.Error("Got nil, want error")
	}

	time.Sleep(testFileDuration * 2)
	if err := client.GetStatus(); err == nil {
		t.Error("Got nil, want error")
	}
}
<|endoftext|>