<commit_before>package todotxt

import (
        "time"
        "os"
        "bufio"
        "strings"
        "regexp"
)

type Task struct {
        todo string
        priority string
        create_date time.Time
        contexts []string
        projects []string
}

type TaskList []Task

func LoadTaskList (filename string) (TaskList) {

        var f, err = os.Open(filename)

        if err != nil {
                panic(err)
        }

        defer f.Close()

        var tasklist = TaskList{}

        scanner := bufio.NewScanner(f)

        for scanner.Scan() {
                var task = Task{}
                text := scanner.Text()
                splits := strings.Split(text, " ")
                date_regexp := "([\\d]{4})-([\\d]{2})-([\\d]{2})"

                if match, _ := regexp.MatchString(date_regexp, splits[0]); match {
                        if date, e := time.Parse("2006-01-02", splits[0]); e != nil {
                                panic(e)
                        } else {
                                task.create_date = date
                        }

                        task.todo = strings.Join(splits[1:], " ")
                } else {
                        task.todo = text
                }

                context_regexp, _ := regexp.Compile("@[[:word:]]+")
                contexts := context_regexp.FindAllStringSubmatch(text, -1)
                if len(contexts) != 0 {
                        task.contexts = contexts[0]
                }

                project_regexp, _ := regexp.Compile("\\+[[:word:]]+")
                projects := project_regexp.FindAllStringSubmatch(text, -1)
                if len(projects) != 0 {
                        task.projects = projects[0]
                }

                tasklist = append(tasklist, task)
        }

        if err := scanner.Err(); err != nil {
                panic(scanner.Err())
        }

        return tasklist
}

func (tasks TaskList) Count() int {
        return len(tasks)
}

func (task Task) Text() string {
        return task.todo
}
<commit_msg>another set of getters<commit_after>package todotxt

import (
        "time"
        "os"
        "bufio"
        "strings"
        "regexp"
)

type Task struct {
        todo string
        priority string
        create_date time.Time
        contexts []string
        projects []string
}

type TaskList []Task

func LoadTaskList (filename string) (TaskList) {

        var f, err = os.Open(filename)

        if err != nil {
                panic(err)
        }

        defer f.Close()

        var tasklist = TaskList{}

        scanner := bufio.NewScanner(f)

        for scanner.Scan() {
                var task = Task{}
                text := scanner.Text()
                splits := strings.Split(text, " ")
                date_regexp := "([\\d]{4})-([\\d]{2})-([\\d]{2})"

                if match, _ := regexp.MatchString(date_regexp, splits[0]); match {
                        if date, e := time.Parse("2006-01-02", splits[0]); e != nil {
                                panic(e)
                        } else {
                                task.create_date = date
                        }

                        task.todo = strings.Join(splits[1:], " ")
                } else {
                        task.todo = text
                }

                context_regexp, _ := regexp.Compile("@[[:word:]]+")
                contexts := context_regexp.FindAllStringSubmatch(text, -1)
                if len(contexts) != 0 {
                        task.contexts = contexts[0]
                }

                project_regexp, _ := regexp.Compile("\\+[[:word:]]+")
                projects := project_regexp.FindAllStringSubmatch(text, -1)
                if len(projects) != 0 {
                        task.projects = projects[0]
                }

                tasklist = append(tasklist, task)
        }

        if err := scanner.Err(); err != nil {
                panic(scanner.Err())
        }

        return tasklist
}

func (tasks TaskList) Count() int {
        return len(tasks)
}

func (task Task) Text() string {
        return task.todo
}

func (task Task) Priority() string {
        return task.priority
}

func (task Task) Contexts() []string {
        return task.contexts
}

func (task Task) Projects() []string {
        return task.projects
}

func (task Task) CreateDate() time.Time {
        return task.create_date
}
<|endoftext|>


<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") {
    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>More logging for Qt5Core patching<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("About to patch libQt5Core at path %v", libraryPath)
  err := patchQtCore(libraryPath)
  if err != nil {
    log.Printf("QtCore patching failed! %v", err)
  } else {
    log.Println("QtCore patching finished")
  }
}

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>package goselenium

import "time"

type waitResponse struct {
	s bool
	e error
}

// Until represents a function that will be continuously repeated until it
// succeeds or a timeout is reached.
type Until func(w WebDriver) (bool, error)

// UntilElementPresent attempts to locate an element on the page. It is
// determined as existing if the state is 'Success' and the error is nil.
func UntilElementPresent(by By, sleep time.Duration) Until {
	return func(w WebDriver) (bool, error) {
		time.Sleep(sleep)

		el, err := w.FindElement(by)
		return err == nil && el != nil, err
	}
}

func (s *seleniumWebDriver) Wait(u Until, timeout time.Duration) (bool, error) {
	response := make(chan *waitResponse, 1)

	go func() {
		for {
			s, e := u(s)
			if e == nil && s {
				response <- &waitResponse{s: s, e: e}
				break
			}
		}
	}()

	select {
	case r := <-response:
		return r.s, r.e
	case <-time.After(timeout):
		return false, WaitTimeoutError
	}
}
<commit_msg>Ensure goroutine is always closed when a response is returned.<commit_after>package goselenium

import "time"

type waitResponse struct {
	s bool
	e error
}

// Until represents a function that will be continuously repeated until it
// succeeds or a timeout is reached.
type Until func(w WebDriver) (bool, error)

// UntilElementPresent attempts to locate an element on the page. It is
// determined as existing if the state is 'Success' and the error is nil.
func UntilElementPresent(by By, sleep time.Duration) Until {
	return func(w WebDriver) (bool, error) {
		time.Sleep(sleep)

		el, err := w.FindElement(by)
		return err == nil && el != nil, err
	}
}

func (s *seleniumWebDriver) Wait(u Until, timeout time.Duration) (bool, error) {
	response := make(chan *waitResponse, 1)
	quit := make(chan bool, 1)

	go func() {
		for {
			select {
			case <-quit:
				break
			default:
				s, e := u(s)
				if e == nil && s {
					response <- &waitResponse{s: s, e: e}
					break
				}
			}
		}
	}()

	select {
	case r := <-response:
		return r.s, r.e
	case <-time.After(timeout):
		close(quit)
		return false, WaitTimeoutError
	}
}
<|endoftext|>


<commit_before>package main

import (
	"archive/zip"
	"bufio"
	"compress/flate"
	"encoding/binary"
	"encoding/hex"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
)

// from archive/zip struct.go

const (
	fileHeaderSignature     = 0x04034b50
	dataDescriptorSignature = 0x08074b50 // de-facto standard; required by OS X Finder
	fileHeaderLen           = 30         // + filename + extra
	dataDescriptorLen       = 16         // four uint32: descriptor signature, crc32, compressed size, size
	dataDescriptor64Len     = 24         // descriptor with 8 byte sizes

	// version numbers
	zipVersion20 = 20 // 2.0
	zipVersion45 = 45 // 4.5 (reads and writes zip64 archives)
)

type readBuf []byte

func (b *readBuf) uint16() uint16 {
	v := binary.LittleEndian.Uint16(*b)
	*b = (*b)[2:]
	return v
}

func (b *readBuf) uint32() uint32 {
	v := binary.LittleEndian.Uint32(*b)
	*b = (*b)[4:]
	return v
}

func (b *readBuf) uint64() uint64 {
	v := binary.LittleEndian.Uint64(*b)
	*b = (*b)[8:]
	return v
}

func main() {
	debug := flag.Bool("debug", false, "print debug info")
	//view := flag.Bool("v", false, "view list")

	flag.Parse()

	if flag.NArg() != 1 {
		log.Fatal("usage: ", os.Args[0], " {zip-file}")
	}

	zipfile := flag.Arg(0)

	f, err := os.Open(zipfile)
	if err != nil {
		log.Fatal("open ", err)
	}

	defer f.Close()

	r := bufio.NewReader(f)

	for {
		fmt.Println()

		var fh [fileHeaderLen]byte

		if _, err := io.ReadFull(r, fh[:]); err != nil {
			log.Fatal("file header ", err)
		}

		if *debug {
			fmt.Println(hex.Dump(fh[:]))
		}

		b := readBuf(fh[:])
		magic := b.uint32()
		version := b.uint16()
		flags := b.uint16()
		comp := b.uint16()
		ctime := b.uint16()
		cdate := b.uint16()
		crc32 := b.uint32()
		clen := b.uint32()
		ulen := b.uint32()
		flen := b.uint16()
		elen := b.uint16()

		if magic != fileHeaderSignature {
			log.Fatal("invalid file header signature ", magic)
		}

		if *debug {
			fmt.Printf("magic   %08x\n", magic)
			fmt.Printf("version %04x\n", version)
			fmt.Printf("flags   %04x\n", flags)
			fmt.Printf("comp    %04x\n", comp)
			fmt.Printf("time    %04x\n", ctime)
			fmt.Printf("date    %04x\n", cdate)
			fmt.Printf("crc32   %08x\n", crc32)
			fmt.Printf("compressed size   %d\n", clen)
			fmt.Printf("uncompressed size %d\n", ulen)
			fmt.Printf("filename length   %d\n", flen)
			fmt.Printf("extra length      %d\n", elen)
		}

		fn := make([]byte, flen)
		if _, err := io.ReadFull(r, fn); err != nil {
			log.Fatal("read file name ", err)
		}

		if *debug {
			fmt.Println()
			fmt.Println("filename", string(fn))
		}

		if elen > 0 {
			if _, err := io.CopyN(ioutil.Discard, r, int64(elen)); err != nil {
				log.Fatal("read extra ", err)
			}
		}

		switch comp {
		case zip.Deflate:
			dec := flate.NewReader(r)
			n, err := io.Copy(ioutil.Discard, dec)
			fmt.Println("decoded", n, "bytes")
			if err != nil {
				log.Fatal("decode file ", err)
			} else {
				dec.Close()
			}

		case zip.Store:
			if ulen > 0 {
				n, err := io.CopyN(ioutil.Discard, r, int64(ulen))
				fmt.Println("read", n, "bytes")
				if err != nil {
					log.Fatal("read file ", err)
				}
			} else {
				log.Fatal("missing lenght")
			}

		default:
			log.Fatal("unsupported compression mode ", comp)
		}

		if (flags & 0x08) != 0 {
			// data descriptor
			var dd [dataDescriptorLen]byte

			if _, err := io.ReadFull(r, dd[:]); err != nil {
				log.Fatal("data descriptor", err)
			}

			b := readBuf(dd[:])
			fmt.Println()

			magic := b.uint32()
			crc32 = b.uint32()
			clen = b.uint32()
			ulen = b.uint32()

			if magic != dataDescriptorSignature {
				log.Fatal("invalid data descriptor signature ", magic)
			}

			if *debug {
				fmt.Printf("magic   %08x\n", magic)
				fmt.Printf("crc32   %08x\n", crc32)
				fmt.Printf("compressed size   %d\n", clen)
				fmt.Printf("uncompressed size %d\n", ulen)
			}
		}
	}
}
<commit_msg>print file list "unzip -v style"<commit_after>package main

import (
	"archive/zip"
	"bufio"
	"compress/flate"
	"encoding/binary"
	"encoding/hex"
	"flag"
	"fmt"
	"io"
	"io/ioutil"
	"log"
	"os"
	"path"
)

// from archive/zip struct.go

const (
	fileHeaderSignature     = 0x04034b50
	dataDescriptorSignature = 0x08074b50 // de-facto standard; required by OS X Finder
	fileHeaderLen           = 30         // + filename + extra
	dataDescriptorLen       = 16         // four uint32: descriptor signature, crc32, compressed size, size
	dataDescriptor64Len     = 24         // descriptor with 8 byte sizes

	// version numbers
	zipVersion20 = 20 // 2.0
	zipVersion45 = 45 // 4.5 (reads and writes zip64 archives)
)

type readBuf []byte

func (b *readBuf) uint16() uint16 {
	v := binary.LittleEndian.Uint16(*b)
	*b = (*b)[2:]
	return v
}

func (b *readBuf) uint32() uint32 {
	v := binary.LittleEndian.Uint32(*b)
	*b = (*b)[4:]
	return v
}

func (b *readBuf) uint64() uint64 {
	v := binary.LittleEndian.Uint64(*b)
	*b = (*b)[8:]
	return v
}

func main() {
	debug := flag.Bool("debug", false, "print debug info")
	//view := flag.Bool("v", false, "view list")

	flag.Parse()

	if flag.NArg() != 1 {
		log.Fatal("usage: ", path.Base(os.Args[0]), " {zip-file}")
	}

	zipfile := flag.Arg(0)

	f, err := os.Open(zipfile)
	if err != nil {
		log.Fatal("open ", err)
	}

	defer f.Close()

	r := bufio.NewReader(f)

	for {
		var fh [fileHeaderLen]byte

		if _, err := io.ReadFull(r, fh[:]); err != nil {
			log.Fatal("file header ", err)
		}

		if *debug {
			fmt.Println(hex.Dump(fh[:]))
		}

		b := readBuf(fh[:])
		magic := b.uint32()
		version := b.uint16()
		flags := b.uint16()
		comp := b.uint16()
		ctime := b.uint16()
		cdate := b.uint16()
		crc32 := b.uint32()
		clen := b.uint32()
		ulen := b.uint32()
		flen := b.uint16()
		elen := b.uint16()

		ctype := ""

		if magic != fileHeaderSignature {
			log.Fatal("invalid file header signature ", magic)
		}

		if *debug {
			fmt.Println()
			fmt.Printf("magic   %08x\n", magic)
			fmt.Printf("version %04x\n", version)
			fmt.Printf("flags   %04x\n", flags)
			fmt.Printf("comp    %04x\n", comp)
			fmt.Printf("time    %04x\n", ctime)
			fmt.Printf("date    %04x\n", cdate)
			fmt.Printf("crc32   %08x\n", crc32)
			fmt.Printf("compressed size   %d\n", clen)
			fmt.Printf("uncompressed size %d\n", ulen)
			fmt.Printf("filename length   %d\n", flen)
			fmt.Printf("extra length      %d\n", elen)
		}

		fn := make([]byte, flen)
		if _, err := io.ReadFull(r, fn); err != nil {
			log.Fatal("read file name ", err)
		}

		if *debug {
			fmt.Println()
			fmt.Println("filename", string(fn))
		}

		if elen > 0 {
			if _, err := io.CopyN(ioutil.Discard, r, int64(elen)); err != nil {
				log.Fatal("read extra ", err)
			}
		}

		switch comp {
		case zip.Deflate:
			ctype = "Defl:N"

			dec := flate.NewReader(r)
			n, err := io.Copy(ioutil.Discard, dec)
			if *debug {
				fmt.Println("decoded", n, "bytes")
			}
			if err != nil {
				log.Fatal("decode file ", err)
			} else {
				dec.Close()
			}

		case zip.Store:
			ctype = "Stored"

			if ulen > 0 {
				n, err := io.CopyN(ioutil.Discard, r, int64(ulen))
				if *debug {
					fmt.Println("read", n, "bytes")
				}
				if err != nil {
					log.Fatal("read file ", err)
				}
			} else {
				log.Fatal("missing lenght")
			}

		default:
			log.Fatal("unsupported compression mode ", comp)
		}

		if (flags & 0x08) != 0 {
			// data descriptor
			var dd [dataDescriptorLen]byte

			if _, err := io.ReadFull(r, dd[:]); err != nil {
				log.Fatal("data descriptor", err)
			}

			b := readBuf(dd[:])
			magic := b.uint32()
			crc32 = b.uint32()
			clen = b.uint32()
			ulen = b.uint32()

			if magic != dataDescriptorSignature {
				log.Fatal("invalid data descriptor signature ", magic)
			}

			if *debug {
				fmt.Println()
				fmt.Printf("magic   %08x\n", magic)
				fmt.Printf("crc32   %08x\n", crc32)
				fmt.Printf("compressed size   %d\n", clen)
				fmt.Printf("uncompressed size %d\n", ulen)
			}
		}

		pc := 100 - (clen * 100 / ulen)
		fmt.Printf("%8d  %6s  %8d  %2d%%  %08x  %s\n", ulen, ctype, clen, pc, crc32, fn)
	}
}
<|endoftext|>


<commit_before>package dms

import (
	"encoding/xml"
	"errors"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"sort"
	"strings"
	"time"

	"bitbucket.org/anacrolix/dms/misc"

	"bitbucket.org/anacrolix/dms/dlna"
	"bitbucket.org/anacrolix/dms/ffmpeg"
	"bitbucket.org/anacrolix/dms/upnp"
	"bitbucket.org/anacrolix/dms/upnpav"
)

type contentDirectoryService struct {
	*Server
}

// Turns the given entry and DMS host into a UPnP object. A nil object is
// returned if the entry is not of interest.
func (me *contentDirectoryService) entryObject(entry cdsEntry, host, userAgent string) interface{} {
	obj := upnpav.Object{
		ID:         entry.object.ID(),
		Restricted: 1,
		ParentID:   entry.object.ParentID(),
	}
	if entry.FileInfo.IsDir() {
		childCount := me.objectChildCount(entry.object, userAgent)
		if childCount == 0 {
			return nil
		}
		obj.Class = "object.container.storageFolder"
		obj.Title = entry.FileInfo.Name()
		return upnpav.Container{
			Object:     obj,
			ChildCount: childCount,
		}
	}
	entryFilePath := entry.object.FilePath()
	mimeType := mimeTypeByPath(entryFilePath)
	mimeTypeType := mimeType.Type()
	if !mimeTypeType.IsMedia() {
		return nil
	}
	iconURI := (&url.URL{
		Scheme: "http",
		Host:   host,
		Path:   iconPath,
		RawQuery: url.Values{
			"path": {entry.object.Path},
		}.Encode(),
	}).String()
	obj.Icon = iconURI
	// TODO(anacrolix): This might not be necessary due to item res image
	// element.
	obj.AlbumArtURI = iconURI
	obj.Class = "object.item." + string(mimeTypeType) + "Item"
	var (
		nativeBitrate uint
		resDuration   string
	)
	ffInfo, probeErr := me.ffmpegProbe(entryFilePath)
	switch probeErr {
	case nil:
		if ffInfo != nil {
			nativeBitrate, _ = ffInfo.Bitrate()
			if d, err := ffInfo.Duration(); err == nil {
				resDuration = misc.FormatDurationSexagesimal(d)
			}
		}
	case ffmpeg.FfprobeUnavailableError:
	default:
		log.Printf("error probing %s: %s", entryFilePath, probeErr)
	}
	if obj.Title == "" {
		obj.Title = entry.FileInfo.Name()
	}
	resolution := func() string {
		if ffInfo != nil {
			for _, strm := range ffInfo.Streams {
				if strm["codec_type"] != "video" {
					continue
				}
				width := strm["width"]
				height := strm["height"]
				return fmt.Sprintf("%.0fx%.0f", width, height)
			}
		}
		return ""
	}()
	item := upnpav.Item{
		Object: obj,
		// Capacity: 1 for raw, 1 for icon, plus transcodes.
		Res: make([]upnpav.Resource, 0, 2+len(transcodes)),
	}
	item.Res = append(item.Res, upnpav.Resource{
		URL: (&url.URL{
			Scheme: "http",
			Host:   host,
			Path:   resPath,
			RawQuery: url.Values{
				"path": {entry.object.Path},
			}.Encode(),
		}).String(),
		ProtocolInfo: fmt.Sprintf("http-get:*:%s:%s", mimeType, dlna.ContentFeatures{
			SupportRange: true,
		}.String()),
		Bitrate:    nativeBitrate,
		Duration:   resDuration,
		Size:       uint64(entry.FileInfo.Size()),
		Resolution: resolution,
	})
	if mimeTypeType == "video" {
		if !me.NoTranscode {
			item.Res = append(item.Res, transcodeResources(host, entry.object.Path, resolution, resDuration)...)
		}
	}
	if mimeTypeType.IsMedia() {
		item.Res = append(item.Res, upnpav.Resource{
			URL: (&url.URL{
				Scheme: "http",
				Host:   host,
				Path:   iconPath,
				RawQuery: url.Values{
					"path": {entry.object.Path},
					"c":    {"jpeg"},
				}.Encode(),
			}).String(),
			ProtocolInfo: "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN",
		})
	}
	return item
}

// Returns all the upnpav objects in a directory.
func (me *contentDirectoryService) readContainer(o object, host, userAgent string) (ret []interface{}, err error) {
	sfis := sortableFileInfoSlice{
		// TODO(anacrolix): Dig up why this special cast was added.
		FoldersLast: strings.Contains(userAgent, `AwoX/1.1`),
	}
	sfis.fileInfoSlice, err = o.readDir()
	if err != nil {
		return
	}
	sort.Sort(sfis)
	for _, fi := range sfis.fileInfoSlice {
		obj := me.entryObject(cdsEntry{fi, object{path.Join(o.path, fi.Name()), me.RootObjectPath}}, host, userAgent)
		if obj != nil {
			ret = append(ret, obj)
		}
	}
	return
}

type browse struct {
	ObjectID       string
	BrowseFlag     string
	Filter         string
	StartingIndex  int
	RequestedCount int
}

// ContentDirectory object from ObjectID.
func (me *contentDirectoryService) objectFromID(id string) (o object, err error) {
	o.Path, err = url.QueryUnescape(id)
	if err != nil {
		return
	}
	if o.Path == "0" {
		o.Path = "/"
	}
	o.Path = path.Clean(o.Path)
	if !path.IsAbs(o.Path) {
		err = errors.New("bad ObjectID")
		return
	}
	o.RootObjectPath = me.RootObjectPath
	return
}

func (me *contentDirectoryService) Handle(action string, argsXML []byte, r *http.Request) (map[string]string, *upnp.Error) {
	host := r.Host
	userAgent := r.UserAgent()
	switch action {
	case "GetSystemUpdateID":
		return map[string]string{
			"Id": fmt.Sprintf("%d", uint32(os.Getpid())),
		}, nil
	case "GetSortCapabilities":
		return map[string]string{
			"SortCaps": "dc:title",
		}, nil
	case "Browse":
		var browse browse
		if err := xml.Unmarshal([]byte(argsXML), &browse); err != nil {
			panic(err)
		}
		obj, err := me.objectFromID(browse.ObjectID)
		if err != nil {
			return nil, &upnp.Error{
				Code: upnpav.NoSuchObjectErrorCode,
				Desc: err.Error(),
			}
		}
		switch browse.BrowseFlag {
		case "BrowseDirectChildren":
			objs, err := me.readContainer(obj, host, userAgent)
			if err != nil {
				return nil, &upnp.Error{
					// readContainer can only fail due to bad ObjectID afaict
					Code: upnpav.NoSuchObjectErrorCode,
					Desc: err.Error(),
				}
			}
			totalMatches := len(objs)
			objs = objs[browse.StartingIndex:]
			if browse.RequestedCount != 0 && int(browse.RequestedCount) < len(objs) {
				objs = objs[:browse.RequestedCount]
			}
			result, err := xml.Marshal(objs)
			if err != nil {
				panic(err)
			}
			return map[string]string{
				"TotalMatches":   fmt.Sprint(totalMatches),
				"NumberReturned": fmt.Sprint(len(objs)),
				"Result":         didl_lite(string(result)),
				"UpdateID":       fmt.Sprintf("%d", uint32(time.Now().Unix())),
			}, nil
		case "BrowseMetadata":
			fileInfo, err := os.Stat(obj.FilePath())
			if err != nil {
				return nil, &upnp.Error{
					Code: upnpav.NoSuchObjectErrorCode,
					Desc: err.Error(),
				}
			}
			return map[string]string{
				"TotalMatches":   "1",
				"NumberReturned": "1",
				"Result": didl_lite(func() string {
					buf, err := xml.Marshal(me.entryObject(cdsEntry{
						FileInfo: fileInfo,
						object:   obj,
					}, host, userAgent))
					if err != nil {
						panic(err) // because aliens
					}
					return string(buf)
				}()),
				"UpdateID": fmt.Sprintf("%d", uint32(time.Now().Unix())),
			}, nil
		default:
			return nil, &upnp.Error{
				Code: upnp.ArgumentValueInvalidErrorCode,
				Desc: fmt.Sprint("unhandled browse flag:", browse.BrowseFlag),
			}
		}
	case "GetSearchCapabilities":
		return map[string]string{
			"SearchCaps": "",
		}, nil
	}
	return nil, &upnp.InvalidActionError
}

// Represents a ContentDirectory object.
type object struct {
	Path           string // The cleaned, absolute path for the object relative to the server.
	RootObjectPath string
}

// Returns the number of children this object has, such as for a container.
func (cds *contentDirectoryService) objectChildCount(me object, userAgent string) int {
	objs, err := cds.readContainer(me, "", userAgent)
	if err != nil {
		log.Printf("error reading container: %s", err)
	}
	return len(objs)
}

// Returns the actual local filesystem path for the object.
func (o *object) FilePath() string {
	return filepath.Join(o.RootObjectPath, filepath.FromSlash(o.Path))
}

// Returns the ObjectID for the object. This is used in various ContentDirectory actions.
func (o object) ID() string {
	if !path.IsAbs(o.Path) {
		panic(o.Path)
	}
	if len(o.Path) == 1 {
		return "0"
	}
	return url.QueryEscape(o.Path)
}

func (o *object) IsRoot() bool {
	return o.Path == "/"
}

// Returns the object's parent ObjectID. Fortunately it can be deduced from the
// ObjectID (for now).
func (o object) ParentID() string {
	if o.IsRoot() {
		return "-1"
	}
	o.Path = path.Dir(o.Path)
	return o.ID()
}

// This function exists rather than just calling os.(*File).Readdir because I
// want to stat(), not lstat() each entry.
func (o *object) readDir() (fis []os.FileInfo, err error) {
	dirPath := o.FilePath()
	dirFile, err := os.Open(dirPath)
	if err != nil {
		return
	}
	defer dirFile.Close()
	var dirContent []string
	dirContent, err = dirFile.Readdirnames(-1)
	if err != nil {
		return
	}
	fis = make([]os.FileInfo, 0, len(dirContent))
	for _, file := range dirContent {
		fi, err := os.Stat(filepath.Join(dirPath, file))
		if err != nil {
			continue
		}
		fis = append(fis, fi)
	}
	return
}

// Puts an object with it's previously obtained FileInfo.
type cdsEntry struct {
	os.FileInfo
	object
}

type sortableFileInfoSlice struct {
	fileInfoSlice []os.FileInfo
	FoldersLast   bool
}

func (me sortableFileInfoSlice) Len() int {
	return len(me.fileInfoSlice)
}

func (me sortableFileInfoSlice) Less(i, j int) bool {
	if me.fileInfoSlice[i].IsDir() && !me.fileInfoSlice[j].IsDir() {
		return !me.FoldersLast
	}
	if !me.fileInfoSlice[i].IsDir() && me.fileInfoSlice[j].IsDir() {
		return me.FoldersLast
	}
	return strings.ToLower(me.fileInfoSlice[i].Name()) < strings.ToLower(me.fileInfoSlice[j].Name())
}

func (me sortableFileInfoSlice) Swap(i, j int) {
	me.fileInfoSlice[i], me.fileInfoSlice[j] = me.fileInfoSlice[j], me.fileInfoSlice[i]
}
<commit_msg>Fix code typo<commit_after>package dms

import (
	"encoding/xml"
	"errors"
	"fmt"
	"log"
	"net/http"
	"net/url"
	"os"
	"path"
	"path/filepath"
	"sort"
	"strings"
	"time"

	"bitbucket.org/anacrolix/dms/misc"

	"bitbucket.org/anacrolix/dms/dlna"
	"bitbucket.org/anacrolix/dms/ffmpeg"
	"bitbucket.org/anacrolix/dms/upnp"
	"bitbucket.org/anacrolix/dms/upnpav"
)

type contentDirectoryService struct {
	*Server
}

// Turns the given entry and DMS host into a UPnP object. A nil object is
// returned if the entry is not of interest.
func (me *contentDirectoryService) entryObject(entry cdsEntry, host, userAgent string) interface{} {
	obj := upnpav.Object{
		ID:         entry.object.ID(),
		Restricted: 1,
		ParentID:   entry.object.ParentID(),
	}
	if entry.FileInfo.IsDir() {
		childCount := me.objectChildCount(entry.object, userAgent)
		if childCount == 0 {
			return nil
		}
		obj.Class = "object.container.storageFolder"
		obj.Title = entry.FileInfo.Name()
		return upnpav.Container{
			Object:     obj,
			ChildCount: childCount,
		}
	}
	entryFilePath := entry.object.FilePath()
	mimeType := mimeTypeByPath(entryFilePath)
	mimeTypeType := mimeType.Type()
	if !mimeTypeType.IsMedia() {
		return nil
	}
	iconURI := (&url.URL{
		Scheme: "http",
		Host:   host,
		Path:   iconPath,
		RawQuery: url.Values{
			"path": {entry.object.Path},
		}.Encode(),
	}).String()
	obj.Icon = iconURI
	// TODO(anacrolix): This might not be necessary due to item res image
	// element.
	obj.AlbumArtURI = iconURI
	obj.Class = "object.item." + string(mimeTypeType) + "Item"
	var (
		nativeBitrate uint
		resDuration   string
	)
	ffInfo, probeErr := me.ffmpegProbe(entryFilePath)
	switch probeErr {
	case nil:
		if ffInfo != nil {
			nativeBitrate, _ = ffInfo.Bitrate()
			if d, err := ffInfo.Duration(); err == nil {
				resDuration = misc.FormatDurationSexagesimal(d)
			}
		}
	case ffmpeg.FfprobeUnavailableError:
	default:
		log.Printf("error probing %s: %s", entryFilePath, probeErr)
	}
	if obj.Title == "" {
		obj.Title = entry.FileInfo.Name()
	}
	resolution := func() string {
		if ffInfo != nil {
			for _, strm := range ffInfo.Streams {
				if strm["codec_type"] != "video" {
					continue
				}
				width := strm["width"]
				height := strm["height"]
				return fmt.Sprintf("%.0fx%.0f", width, height)
			}
		}
		return ""
	}()
	item := upnpav.Item{
		Object: obj,
		// Capacity: 1 for raw, 1 for icon, plus transcodes.
		Res: make([]upnpav.Resource, 0, 2+len(transcodes)),
	}
	item.Res = append(item.Res, upnpav.Resource{
		URL: (&url.URL{
			Scheme: "http",
			Host:   host,
			Path:   resPath,
			RawQuery: url.Values{
				"path": {entry.object.Path},
			}.Encode(),
		}).String(),
		ProtocolInfo: fmt.Sprintf("http-get:*:%s:%s", mimeType, dlna.ContentFeatures{
			SupportRange: true,
		}.String()),
		Bitrate:    nativeBitrate,
		Duration:   resDuration,
		Size:       uint64(entry.FileInfo.Size()),
		Resolution: resolution,
	})
	if mimeTypeType == "video" {
		if !me.NoTranscode {
			item.Res = append(item.Res, transcodeResources(host, entry.object.Path, resolution, resDuration)...)
		}
	}
	if mimeTypeType.IsMedia() {
		item.Res = append(item.Res, upnpav.Resource{
			URL: (&url.URL{
				Scheme: "http",
				Host:   host,
				Path:   iconPath,
				RawQuery: url.Values{
					"path": {entry.object.Path},
					"c":    {"jpeg"},
				}.Encode(),
			}).String(),
			ProtocolInfo: "http-get:*:image/jpeg:DLNA.ORG_PN=JPEG_TN",
		})
	}
	return item
}

// Returns all the upnpav objects in a directory.
func (me *contentDirectoryService) readContainer(o object, host, userAgent string) (ret []interface{}, err error) {
	sfis := sortableFileInfoSlice{
		// TODO(anacrolix): Dig up why this special cast was added.
		FoldersLast: strings.Contains(userAgent, `AwoX/1.1`),
	}
	sfis.fileInfoSlice, err = o.readDir()
	if err != nil {
		return
	}
	sort.Sort(sfis)
	for _, fi := range sfis.fileInfoSlice {
		obj := me.entryObject(cdsEntry{fi, object{path.Join(o.Path, fi.Name()), me.RootObjectPath}}, host, userAgent)
		if obj != nil {
			ret = append(ret, obj)
		}
	}
	return
}

type browse struct {
	ObjectID       string
	BrowseFlag     string
	Filter         string
	StartingIndex  int
	RequestedCount int
}

// ContentDirectory object from ObjectID.
func (me *contentDirectoryService) objectFromID(id string) (o object, err error) {
	o.Path, err = url.QueryUnescape(id)
	if err != nil {
		return
	}
	if o.Path == "0" {
		o.Path = "/"
	}
	o.Path = path.Clean(o.Path)
	if !path.IsAbs(o.Path) {
		err = errors.New("bad ObjectID")
		return
	}
	o.RootObjectPath = me.RootObjectPath
	return
}

func (me *contentDirectoryService) Handle(action string, argsXML []byte, r *http.Request) (map[string]string, *upnp.Error) {
	host := r.Host
	userAgent := r.UserAgent()
	switch action {
	case "GetSystemUpdateID":
		return map[string]string{
			"Id": fmt.Sprintf("%d", uint32(os.Getpid())),
		}, nil
	case "GetSortCapabilities":
		return map[string]string{
			"SortCaps": "dc:title",
		}, nil
	case "Browse":
		var browse browse
		if err := xml.Unmarshal([]byte(argsXML), &browse); err != nil {
			panic(err)
		}
		obj, err := me.objectFromID(browse.ObjectID)
		if err != nil {
			return nil, &upnp.Error{
				Code: upnpav.NoSuchObjectErrorCode,
				Desc: err.Error(),
			}
		}
		switch browse.BrowseFlag {
		case "BrowseDirectChildren":
			objs, err := me.readContainer(obj, host, userAgent)
			if err != nil {
				return nil, &upnp.Error{
					// readContainer can only fail due to bad ObjectID afaict
					Code: upnpav.NoSuchObjectErrorCode,
					Desc: err.Error(),
				}
			}
			totalMatches := len(objs)
			objs = objs[browse.StartingIndex:]
			if browse.RequestedCount != 0 && int(browse.RequestedCount) < len(objs) {
				objs = objs[:browse.RequestedCount]
			}
			result, err := xml.Marshal(objs)
			if err != nil {
				panic(err)
			}
			return map[string]string{
				"TotalMatches":   fmt.Sprint(totalMatches),
				"NumberReturned": fmt.Sprint(len(objs)),
				"Result":         didl_lite(string(result)),
				"UpdateID":       fmt.Sprintf("%d", uint32(time.Now().Unix())),
			}, nil
		case "BrowseMetadata":
			fileInfo, err := os.Stat(obj.FilePath())
			if err != nil {
				return nil, &upnp.Error{
					Code: upnpav.NoSuchObjectErrorCode,
					Desc: err.Error(),
				}
			}
			return map[string]string{
				"TotalMatches":   "1",
				"NumberReturned": "1",
				"Result": didl_lite(func() string {
					buf, err := xml.Marshal(me.entryObject(cdsEntry{
						FileInfo: fileInfo,
						object:   obj,
					}, host, userAgent))
					if err != nil {
						panic(err) // because aliens
					}
					return string(buf)
				}()),
				"UpdateID": fmt.Sprintf("%d", uint32(time.Now().Unix())),
			}, nil
		default:
			return nil, &upnp.Error{
				Code: upnp.ArgumentValueInvalidErrorCode,
				Desc: fmt.Sprint("unhandled browse flag:", browse.BrowseFlag),
			}
		}
	case "GetSearchCapabilities":
		return map[string]string{
			"SearchCaps": "",
		}, nil
	}
	return nil, &upnp.InvalidActionError
}

// Represents a ContentDirectory object.
type object struct {
	Path           string // The cleaned, absolute path for the object relative to the server.
	RootObjectPath string
}

// Returns the number of children this object has, such as for a container.
func (cds *contentDirectoryService) objectChildCount(me object, userAgent string) int {
	objs, err := cds.readContainer(me, "", userAgent)
	if err != nil {
		log.Printf("error reading container: %s", err)
	}
	return len(objs)
}

// Returns the actual local filesystem path for the object.
func (o *object) FilePath() string {
	return filepath.Join(o.RootObjectPath, filepath.FromSlash(o.Path))
}

// Returns the ObjectID for the object. This is used in various ContentDirectory actions.
func (o object) ID() string {
	if !path.IsAbs(o.Path) {
		panic(o.Path)
	}
	if len(o.Path) == 1 {
		return "0"
	}
	return url.QueryEscape(o.Path)
}

func (o *object) IsRoot() bool {
	return o.Path == "/"
}

// Returns the object's parent ObjectID. Fortunately it can be deduced from the
// ObjectID (for now).
func (o object) ParentID() string {
	if o.IsRoot() {
		return "-1"
	}
	o.Path = path.Dir(o.Path)
	return o.ID()
}

// This function exists rather than just calling os.(*File).Readdir because I
// want to stat(), not lstat() each entry.
func (o *object) readDir() (fis []os.FileInfo, err error) {
	dirPath := o.FilePath()
	dirFile, err := os.Open(dirPath)
	if err != nil {
		return
	}
	defer dirFile.Close()
	var dirContent []string
	dirContent, err = dirFile.Readdirnames(-1)
	if err != nil {
		return
	}
	fis = make([]os.FileInfo, 0, len(dirContent))
	for _, file := range dirContent {
		fi, err := os.Stat(filepath.Join(dirPath, file))
		if err != nil {
			continue
		}
		fis = append(fis, fi)
	}
	return
}

// Puts an object with it's previously obtained FileInfo.
type cdsEntry struct {
	os.FileInfo
	object
}

type sortableFileInfoSlice struct {
	fileInfoSlice []os.FileInfo
	FoldersLast   bool
}

func (me sortableFileInfoSlice) Len() int {
	return len(me.fileInfoSlice)
}

func (me sortableFileInfoSlice) Less(i, j int) bool {
	if me.fileInfoSlice[i].IsDir() && !me.fileInfoSlice[j].IsDir() {
		return !me.FoldersLast
	}
	if !me.fileInfoSlice[i].IsDir() && me.fileInfoSlice[j].IsDir() {
		return me.FoldersLast
	}
	return strings.ToLower(me.fileInfoSlice[i].Name()) < strings.ToLower(me.fileInfoSlice[j].Name())
}

func (me sortableFileInfoSlice) Swap(i, j int) {
	me.fileInfoSlice[i], me.fileInfoSlice[j] = me.fileInfoSlice[j], me.fileInfoSlice[i]
}
<|endoftext|>


<commit_before>package stellargregor

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"strings"

	"github.com/keybase/client/go/gregor"
	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/gregor1"
	"github.com/keybase/client/go/protocol/stellar1"
	"github.com/keybase/client/go/stellar"
)

type Handler struct {
	libkb.Contextified
	walletState *stellar.WalletState
}

var _ libkb.GregorInBandMessageHandler = (*Handler)(nil)

func New(g *libkb.GlobalContext, walletState *stellar.WalletState) *Handler {
	return &Handler{
		Contextified: libkb.NewContextified(g),
		walletState:  walletState,
	}
}

func (h *Handler) Create(ctx context.Context, cli gregor1.IncomingInterface, category string, item gregor.Item) (bool, error) {
	mctx := libkb.NewMetaContext(ctx, h.G()).WithLogTag("WGR")
	switch category {
	case stellar1.PushAutoClaim:
		return true, h.autoClaim(mctx, cli, category, item)
	case stellar1.PushPaymentStatus:
		return true, h.paymentStatus(mctx, cli, category, item)
	case stellar1.PushPaymentNotification:
		return true, h.paymentNotification(mctx, cli, category, item)
	case stellar1.PushRequestStatus:
		return true, h.requestStatus(mctx, cli, category, item)
	case stellar1.PushAccountChange:
		return true, h.accountChange(mctx, cli, category, item)
	default:
		if strings.HasPrefix(category, "stellar.") {
			return false, fmt.Errorf("unknown handler category: %q", category)
		}
		return false, nil
	}
}

func (h *Handler) Dismiss(ctx context.Context, cli gregor1.IncomingInterface, category string, item gregor.Item) (bool, error) {
	return false, nil
}

func (h *Handler) IsAlive() bool {
	return true
}

func (h *Handler) Name() string {
	return "stellarHandler"
}

// The server is telling the client to claim relay payments.
func (h *Handler) autoClaim(mctx libkb.MetaContext, cli gregor1.IncomingInterface, category string, item gregor.Item) error {
	mctx.CDebugf("%v: %v received", h.Name(), category)
	mctx.G().GetStellar().KickAutoClaimRunner(mctx, item.Metadata().MsgID())
	return nil
}

type accountChangeMsg struct {
	AccountID string `json:"account_id"`
	Reason    string `json:"reason"`
}

func (h *Handler) accountChange(mctx libkb.MetaContext, cli gregor1.IncomingInterface, category string, item gregor.Item) error {
	mctx.CDebugf("%v: %v received", h.Name(), category)

	if item.Body() == nil {
		return errors.New("stellar handler for account_change: nil message body")
	}
	var msgBody accountChangeMsg
	if err := json.Unmarshal(item.Body().Bytes(), &msgBody); err != nil {
		return err
	}

	if msgBody.AccountID != "" {
		account, err := stellar.WalletAccount(mctx, h.walletState, stellar1.AccountID(msgBody.AccountID))
		if err == nil {
			h.G().NotifyRouter.HandleWalletAccountDetailsUpdate(mctx.Ctx(), stellar1.AccountID(msgBody.AccountID), account)
		} else {
			h.G().Log.CDebugf(mctx.Ctx(), "failed to HandleWalletAccountDetailsUpdate")
		}
	} else {
		accounts, err := stellar.AllWalletAccounts(mctx, h.walletState)
		if err == nil {
			h.G().NotifyRouter.HandleWalletAccountsUpdate(mctx.Ctx(), accounts)
		} else {
			h.G().Log.CDebugf(mctx.Ctx(), "failed to HandleWalletAccountsUpdate")
		}
	}

	// We will locally dismiss for now so that each client only plays them once:
	if err := h.G().GregorState.LocalDismissItem(mctx.Ctx(), item.Metadata().MsgID()); err != nil {
		h.G().Log.CDebugf(mctx.Ctx(), "failed to local dismiss account_change: %s", err)
	}

	return nil
}

// paymentStatus is an old IBM and shouldn't happen anymore
func (h *Handler) paymentStatus(mctx libkb.MetaContext, cli gregor1.IncomingInterface, category string, item gregor.Item) error {
	mctx.CDebugf("%v: %v received IBM, ignoring it", h.Name(), category)

	// We will locally dismiss for now so that each client only plays them once:
	if err := h.G().GregorState.LocalDismissItem(mctx.Ctx(), item.Metadata().MsgID()); err != nil {
		h.G().Log.CDebugf(mctx.Ctx(), "failed to local dismiss payment_status: %s", err)
	}

	return nil
}

// paymentNotification is an old IBM and shouldn't happen anymore
func (h *Handler) paymentNotification(mctx libkb.MetaContext, cli gregor1.IncomingInterface, category string, item gregor.Item) error {
	mctx.CDebugf("%s: %s received IBM, ignoring it", h.Name(), category)

	// We will locally dismiss for now so that each client only plays them once:
	if err := h.G().GregorState.LocalDismissItem(mctx.Ctx(), item.Metadata().MsgID()); err != nil {
		h.G().Log.CDebugf(mctx.Ctx(), "failed to local dismiss payment_notification: %s", err)
	}

	return nil
}

// requestStatus is an old IBM and shouldn't happen anymore
func (h *Handler) requestStatus(mctx libkb.MetaContext, cli gregor1.IncomingInterface, category string, item gregor.Item) error {
	mctx.CDebugf("%v: %v received IBM, ignoring it", h.Name(), category)

	// We will locally dismiss for now so that each client only plays them once:
	if err := h.G().GregorState.LocalDismissItem(mctx.Ctx(), item.Metadata().MsgID()); err != nil {
		h.G().Log.CDebugf(mctx.Ctx(), "failed to local dismiss request_status: %s", err)
	}

	return nil
}
<commit_msg>Use goroutines for all IBM handlers (#16285)<commit_after>package stellargregor

import (
	"context"
	"encoding/json"
	"errors"
	"fmt"
	"strings"

	"github.com/keybase/client/go/gregor"
	"github.com/keybase/client/go/libkb"
	"github.com/keybase/client/go/protocol/gregor1"
	"github.com/keybase/client/go/protocol/stellar1"
	"github.com/keybase/client/go/stellar"
)

type Handler struct {
	libkb.Contextified
	walletState *stellar.WalletState
}

var _ libkb.GregorInBandMessageHandler = (*Handler)(nil)

func New(g *libkb.GlobalContext, walletState *stellar.WalletState) *Handler {
	return &Handler{
		Contextified: libkb.NewContextified(g),
		walletState:  walletState,
	}
}

func (h *Handler) Create(ctx context.Context, cli gregor1.IncomingInterface, category string, item gregor.Item) (bool, error) {
	mctx := libkb.NewMetaContextBackground(h.G()).WithLogTag("WAIBM")
	switch category {
	case stellar1.PushAutoClaim:
		go h.autoClaim(mctx, cli, category, item)
		return true, nil
	case stellar1.PushPaymentStatus:
		go h.paymentStatus(mctx, cli, category, item)
		return true, nil
	case stellar1.PushPaymentNotification:
		go h.paymentNotification(mctx, cli, category, item)
		return true, nil
	case stellar1.PushRequestStatus:
		go h.requestStatus(mctx, cli, category, item)
		return true, nil
	case stellar1.PushAccountChange:
		go h.accountChange(mctx, cli, category, item)
		return true, nil
	default:
		if strings.HasPrefix(category, "stellar.") {
			return false, fmt.Errorf("unknown handler category: %q", category)
		}
		return false, nil
	}
}

func (h *Handler) Dismiss(ctx context.Context, cli gregor1.IncomingInterface, category string, item gregor.Item) (bool, error) {
	return false, nil
}

func (h *Handler) IsAlive() bool {
	return true
}

func (h *Handler) Name() string {
	return "stellarHandler"
}

// The server is telling the client to claim relay payments.
func (h *Handler) autoClaim(mctx libkb.MetaContext, cli gregor1.IncomingInterface, category string, item gregor.Item) error {
	mctx.CDebugf("%v: %v received", h.Name(), category)
	mctx.G().GetStellar().KickAutoClaimRunner(mctx, item.Metadata().MsgID())
	return nil
}

type accountChangeMsg struct {
	AccountID string `json:"account_id"`
	Reason    string `json:"reason"`
}

func (h *Handler) accountChange(mctx libkb.MetaContext, cli gregor1.IncomingInterface, category string, item gregor.Item) error {
	mctx.CDebugf("%v: %v received", h.Name(), category)

	if item.Body() == nil {
		return errors.New("stellar handler for account_change: nil message body")
	}
	var msgBody accountChangeMsg
	if err := json.Unmarshal(item.Body().Bytes(), &msgBody); err != nil {
		return err
	}

	if msgBody.AccountID != "" {
		account, err := stellar.WalletAccount(mctx, h.walletState, stellar1.AccountID(msgBody.AccountID))
		if err == nil {
			h.G().NotifyRouter.HandleWalletAccountDetailsUpdate(mctx.Ctx(), stellar1.AccountID(msgBody.AccountID), account)
		} else {
			h.G().Log.CDebugf(mctx.Ctx(), "failed to HandleWalletAccountDetailsUpdate")
		}
	} else {
		accounts, err := stellar.AllWalletAccounts(mctx, h.walletState)
		if err == nil {
			h.G().NotifyRouter.HandleWalletAccountsUpdate(mctx.Ctx(), accounts)
		} else {
			h.G().Log.CDebugf(mctx.Ctx(), "failed to HandleWalletAccountsUpdate")
		}
	}

	// We will locally dismiss for now so that each client only plays them once:
	if err := h.G().GregorState.LocalDismissItem(mctx.Ctx(), item.Metadata().MsgID()); err != nil {
		h.G().Log.CDebugf(mctx.Ctx(), "failed to local dismiss account_change: %s", err)
	}

	return nil
}

// paymentStatus is an old IBM and shouldn't happen anymore
func (h *Handler) paymentStatus(mctx libkb.MetaContext, cli gregor1.IncomingInterface, category string, item gregor.Item) error {
	mctx.CDebugf("%v: %v received IBM, ignoring it", h.Name(), category)

	// We will locally dismiss for now so that each client only plays them once:
	if err := h.G().GregorState.LocalDismissItem(mctx.Ctx(), item.Metadata().MsgID()); err != nil {
		h.G().Log.CDebugf(mctx.Ctx(), "failed to local dismiss payment_status: %s", err)
	}

	return nil
}

// paymentNotification is an old IBM and shouldn't happen anymore
func (h *Handler) paymentNotification(mctx libkb.MetaContext, cli gregor1.IncomingInterface, category string, item gregor.Item) error {
	mctx.CDebugf("%s: %s received IBM, ignoring it", h.Name(), category)

	// We will locally dismiss for now so that each client only plays them once:
	if err := h.G().GregorState.LocalDismissItem(mctx.Ctx(), item.Metadata().MsgID()); err != nil {
		h.G().Log.CDebugf(mctx.Ctx(), "failed to local dismiss payment_notification: %s", err)
	}

	return nil
}

// requestStatus is an old IBM and shouldn't happen anymore
func (h *Handler) requestStatus(mctx libkb.MetaContext, cli gregor1.IncomingInterface, category string, item gregor.Item) error {
	mctx.CDebugf("%v: %v received IBM, ignoring it", h.Name(), category)

	// We will locally dismiss for now so that each client only plays them once:
	if err := h.G().GregorState.LocalDismissItem(mctx.Ctx(), item.Metadata().MsgID()); err != nil {
		h.G().Log.CDebugf(mctx.Ctx(), "failed to local dismiss request_status: %s", err)
	}

	return nil
}
<|endoftext|>


<commit_before>package trindex

// TODO:
// - Support both case-sensitive and case-insensitive
// - Support preprocessing of inputs (like removing special chars, renaming umlauts (ö -> oe), etc.)
// - 2 storage engines (provide an Sample() method to detect whether to use the short or the large storage
// [large storage will be used when avg(size(sample)) >= 500 bytes and uses a bitmap; short storage uses list of 8 bytes
// integer items]

import (
	"bufio"
	"container/heap"
	"encoding/binary"
	"fmt"
	"os"
	"sync"
	"sync/atomic"
	//"time"
)

const (
	writeCacheSize = 250000
	cacheDocIDSize = 1e7
	cacheSize      = 1024 * 1024 * 700 // 512 MiB, given in byte
	//sampleTresholdCount        = 50
	//sampleTresholdLargeStorage = 500 // in bytes, we change to a bitmap storage for large files (= huge amount of trigrams)
)

type storageType int

const (
	storageShort storageType = iota
	storageLong
)

type Index struct {
	filename     string
	sample_count int
	sample_size  int

	item_id      uint64
	item_db_lock sync.Mutex
	item_db      *os.File

	cache       map[uint64]uint32
	write_cache []uint64

	storageEngine storageType
	storage       IStorage
}

type Result struct {
	index      int
	ID         uint64
	Similarity float64
}

type ResultSet []*Result

func (r *Result) String() string {
	return fmt.Sprintf("<Result ID=%d Similarity=%.2f>", r.ID, r.Similarity)
}

/*
func (r *Result) Less(other llrb.Item) bool {
	return r.Similarity < other.(*Result).Similarity
}
*/

func (rs ResultSet) Less(i, j int) bool {
	return rs[i].Similarity > rs[j].Similarity
}

func (rs ResultSet) Len() int {
	return len(rs)
}

func (rs ResultSet) Swap(i, j int) {
	rs[i], rs[j] = rs[j], rs[i]
	rs[i].index = i
	rs[j].index = j
}

func (rs *ResultSet) Push(x interface{}) {
	n := len(*rs)
	item := x.(*Result)
	item.index = n
	*rs = append(*rs, item)
}

func (rs *ResultSet) Pop() interface{} {
	old := *rs
	n := len(old)
	item := old[n-1]
	item.index = -1
	*rs = old[0 : n-1]
	return item
}

func NewIndex(filename string) *Index {
	idx := &Index{
		filename:    filename,
		storage:     newListStorage(filename),
		cache:       make(map[uint64]uint32),
		write_cache: make([]uint64, 0, writeCacheSize),
	}

	fd, err := os.OpenFile(fmt.Sprintf("%s.docs", filename), os.O_RDWR|os.O_CREATE, 0600)
	if err != nil {
		panic(err)
	}
	idx.item_db = fd

	offset, err := fd.Seek(0, 2)
	if err != nil {
		panic(err)
	}
	if offset == 0 {
		// Write init item
		err = binary.Write(fd, binary.LittleEndian, uint64(0))
		if err != nil {
			panic(err)
		}
	} else {
		// Read last item id
		_, err = fd.Seek(0, 0)
		if err != nil {
			panic(err)
		}
		err = binary.Read(fd, binary.LittleEndian, &idx.item_id)
		if err != nil {
			panic(err)
		}

		// For the time being, read min(item_id, 5000000) IDs into cache.
		// We could skip these step but have it in the library for performance reasons.
		r := bufio.NewReader(fd)
		var tmp_id uint32
		for i := uint64(0); i < uint64(min(int(idx.item_id), 5000000)); i++ {
			err = binary.Read(r, binary.LittleEndian, &tmp_id)
			if err != nil {
				panic(err)
			}
			idx.cache[uint64(i+1)] = tmp_id
		}
	}

	return idx
}

// It's important to close the index to flush all writes to disk.
func (idx *Index) Close() {
	idx.storage.Close()

	idx.item_db_lock.Lock()
	_, err := idx.item_db.Seek(0, 0)
	if err != nil {
		panic(err)
	}
	err = binary.Write(idx.item_db, binary.LittleEndian, idx.item_id)
	if err != nil {
		panic(err)
	}
	idx.item_db_lock.Unlock()

	idx.flushWriteCache()

	if err = idx.item_db.Close(); err != nil {
		panic(err)
	}
}

// Inserts a document to the index. It is safe for concurrent use.
func (idx *Index) Insert(data string) uint64 {
	new_doc_id := atomic.AddUint64(&idx.item_id, 1)

	trigrams := trigramize(data)

	for _, trigram := range trigrams {
		idx.storage.AddItem(trigram, new_doc_id)
	}

	if len(idx.cache) > cacheDocIDSize {
		counter := 0
		treshold := int(cacheDocIDSize / 4)

		// Flush anything to disk before we delete something from cache
		idx.flushWriteCache()

		idx.item_db_lock.Lock()
		for doc_id, _ := range idx.cache {
			counter++
			delete(idx.cache, doc_id)

			if counter >= treshold {
				break
			}
		}
		idx.item_db_lock.Unlock()
	}

	if len(idx.write_cache) >= writeCacheSize {
		idx.flushWriteCache()
	}

	idx.item_db_lock.Lock()
	defer idx.item_db_lock.Unlock()

	idx.cache[new_doc_id] = uint32(len(trigrams))

	idx.write_cache = append(idx.write_cache, new_doc_id)

	return new_doc_id
}

func (idx *Index) getTotalTrigrams(doc_id uint64) int {
	idx.item_db_lock.Lock()
	defer idx.item_db_lock.Unlock()

	count, has := idx.cache[doc_id]
	if has {
		return int(count)
	}

	if doc_id <= 0 {
		panic("doc_id <= 0 not available")
	}

	_, err := idx.item_db.Seek(int64(8+(doc_id-1)*4), 0)
	if err != nil {
		panic(err)
	}
	var rtv uint32
	err = binary.Read(idx.item_db, binary.LittleEndian, &rtv)
	if err != nil {
		panic(err)
	}
	idx.cache[doc_id] = rtv

	// TODO: Put an cache invalidator here

	return int(rtv)
}

func (idx *Index) flushWriteCache() {
	idx.item_db_lock.Lock()
	defer idx.item_db_lock.Unlock()

	// write_cache
	for _, doc_id := range idx.write_cache {
		_, err := idx.item_db.Seek(int64(8+(doc_id-1)*4), 0)
		if err != nil {
			panic(err)
		}
		err = binary.Write(idx.item_db, binary.LittleEndian, idx.cache[doc_id])
		if err != nil {
			panic(err)
		}
	}

	idx.write_cache = make([]uint64, 0, writeCacheSize)
}

func (idx *Index) Query(query string, max_results int, skip float64) ResultSet {
	trigrams := trigramize(query)
	trigrams_len := float64(len(trigrams))

	//stime := time.Now()

	temp_map := make(map[uint64]int)
	for _, trigram := range trigrams {
		doc_ids := idx.storage.GetItems(trigram)

		for _, id := range doc_ids {
			c, has := temp_map[id]
			if has {
				temp_map[id] = c + 1
			} else {
				temp_map[id] = 1
			}
		}
	}

	/*
	etime := time.Now().Sub(stime)
	fmt.Printf("[%s] Time to get all document IDs per trigram took: %s\n", query, etime)
	stime = time.Now()
	*/

	heap_list := make(ResultSet, 0, max_results+1)
	heap.Init(&heap_list)

	for id, count := range temp_map {
		x := trigrams_len / float64(idx.getTotalTrigrams(id))
		if x > 1 {
			x = 1.0 / x
		}
		s := (float64(count) / trigrams_len) - (1.0 - x)

		if len(heap_list) > max_results && s < skip {
			continue
		}

		heap.Push(&heap_list, &Result{
			ID:         id,
			Similarity: s,
		})
	}

	//etime = time.Now().Sub(stime)
	//fmt.Printf("[%s] Time to calculate top X took: %s\n", query, etime)

	if len(heap_list) > 0 {
		item_count := min(len(heap_list), max_results)
		result_set := make(ResultSet, 0, item_count)

		for i := 0; i < item_count; i++ {
			result_set = append(result_set, heap.Pop(&heap_list).(*Result))
		}

		return result_set
	}

	return nil
}
<commit_msg>Respect provided score strictly<commit_after>package trindex

// TODO:
// - Support both case-sensitive and case-insensitive
// - Support preprocessing of inputs (like removing special chars, renaming umlauts (ö -> oe), etc.)
// - 2 storage engines (provide an Sample() method to detect whether to use the short or the large storage
// [large storage will be used when avg(size(sample)) >= 500 bytes and uses a bitmap; short storage uses list of 8 bytes
// integer items]

import (
	"bufio"
	"container/heap"
	"encoding/binary"
	"fmt"
	"os"
	"sync"
	"sync/atomic"
	//"time"
)

const (
	writeCacheSize = 250000
	cacheDocIDSize = 1e7
	cacheSize      = 1024 * 1024 * 700 // 512 MiB, given in byte
	//sampleTresholdCount        = 50
	//sampleTresholdLargeStorage = 500 // in bytes, we change to a bitmap storage for large files (= huge amount of trigrams)
)

type storageType int

const (
	storageShort storageType = iota
	storageLong
)

type Index struct {
	filename     string
	sample_count int
	sample_size  int

	item_id      uint64
	item_db_lock sync.Mutex
	item_db      *os.File

	cache       map[uint64]uint32
	write_cache []uint64

	storageEngine storageType
	storage       IStorage
}

type Result struct {
	index      int
	ID         uint64
	Similarity float64
}

type ResultSet []*Result

func (r *Result) String() string {
	return fmt.Sprintf("<Result ID=%d Similarity=%.2f>", r.ID, r.Similarity)
}

/*
func (r *Result) Less(other llrb.Item) bool {
	return r.Similarity < other.(*Result).Similarity
}
*/

func (rs ResultSet) Less(i, j int) bool {
	return rs[i].Similarity > rs[j].Similarity
}

func (rs ResultSet) Len() int {
	return len(rs)
}

func (rs ResultSet) Swap(i, j int) {
	rs[i], rs[j] = rs[j], rs[i]
	rs[i].index = i
	rs[j].index = j
}

func (rs *ResultSet) Push(x interface{}) {
	n := len(*rs)
	item := x.(*Result)
	item.index = n
	*rs = append(*rs, item)
}

func (rs *ResultSet) Pop() interface{} {
	old := *rs
	n := len(old)
	item := old[n-1]
	item.index = -1
	*rs = old[0 : n-1]
	return item
}

func NewIndex(filename string) *Index {
	idx := &Index{
		filename:    filename,
		storage:     newListStorage(filename),
		cache:       make(map[uint64]uint32),
		write_cache: make([]uint64, 0, writeCacheSize),
	}

	fd, err := os.OpenFile(fmt.Sprintf("%s.docs", filename), os.O_RDWR|os.O_CREATE, 0600)
	if err != nil {
		panic(err)
	}
	idx.item_db = fd

	offset, err := fd.Seek(0, 2)
	if err != nil {
		panic(err)
	}
	if offset == 0 {
		// Write init item
		err = binary.Write(fd, binary.LittleEndian, uint64(0))
		if err != nil {
			panic(err)
		}
	} else {
		// Read last item id
		_, err = fd.Seek(0, 0)
		if err != nil {
			panic(err)
		}
		err = binary.Read(fd, binary.LittleEndian, &idx.item_id)
		if err != nil {
			panic(err)
		}

		// For the time being, read min(item_id, 5000000) IDs into cache.
		// We could skip these step but have it in the library for performance reasons.
		r := bufio.NewReader(fd)
		var tmp_id uint32
		for i := uint64(0); i < uint64(min(int(idx.item_id), 5000000)); i++ {
			err = binary.Read(r, binary.LittleEndian, &tmp_id)
			if err != nil {
				panic(err)
			}
			idx.cache[uint64(i+1)] = tmp_id
		}
	}

	return idx
}

// It's important to close the index to flush all writes to disk.
func (idx *Index) Close() {
	idx.storage.Close()

	idx.item_db_lock.Lock()
	_, err := idx.item_db.Seek(0, 0)
	if err != nil {
		panic(err)
	}
	err = binary.Write(idx.item_db, binary.LittleEndian, idx.item_id)
	if err != nil {
		panic(err)
	}
	idx.item_db_lock.Unlock()

	idx.flushWriteCache()

	if err = idx.item_db.Close(); err != nil {
		panic(err)
	}
}

// Inserts a document to the index. It is safe for concurrent use.
func (idx *Index) Insert(data string) uint64 {
	new_doc_id := atomic.AddUint64(&idx.item_id, 1)

	trigrams := trigramize(data)

	for _, trigram := range trigrams {
		idx.storage.AddItem(trigram, new_doc_id)
	}

	if len(idx.cache) > cacheDocIDSize {
		counter := 0
		treshold := int(cacheDocIDSize / 4)

		// Flush anything to disk before we delete something from cache
		idx.flushWriteCache()

		idx.item_db_lock.Lock()
		for doc_id, _ := range idx.cache {
			counter++
			delete(idx.cache, doc_id)

			if counter >= treshold {
				break
			}
		}
		idx.item_db_lock.Unlock()
	}

	if len(idx.write_cache) >= writeCacheSize {
		idx.flushWriteCache()
	}

	idx.item_db_lock.Lock()
	defer idx.item_db_lock.Unlock()

	idx.cache[new_doc_id] = uint32(len(trigrams))

	idx.write_cache = append(idx.write_cache, new_doc_id)

	return new_doc_id
}

func (idx *Index) getTotalTrigrams(doc_id uint64) int {
	idx.item_db_lock.Lock()
	defer idx.item_db_lock.Unlock()

	count, has := idx.cache[doc_id]
	if has {
		return int(count)
	}

	if doc_id <= 0 {
		panic("doc_id <= 0 not available")
	}

	_, err := idx.item_db.Seek(int64(8+(doc_id-1)*4), 0)
	if err != nil {
		panic(err)
	}
	var rtv uint32
	err = binary.Read(idx.item_db, binary.LittleEndian, &rtv)
	if err != nil {
		panic(err)
	}
	idx.cache[doc_id] = rtv

	// TODO: Put an cache invalidator here

	return int(rtv)
}

func (idx *Index) flushWriteCache() {
	idx.item_db_lock.Lock()
	defer idx.item_db_lock.Unlock()

	// write_cache
	for _, doc_id := range idx.write_cache {
		_, err := idx.item_db.Seek(int64(8+(doc_id-1)*4), 0)
		if err != nil {
			panic(err)
		}
		err = binary.Write(idx.item_db, binary.LittleEndian, idx.cache[doc_id])
		if err != nil {
			panic(err)
		}
	}

	idx.write_cache = make([]uint64, 0, writeCacheSize)
}

func (idx *Index) Query(query string, max_results int, skip float64) ResultSet {
	trigrams := trigramize(query)
	trigrams_len := float64(len(trigrams))

	//stime := time.Now()

	temp_map := make(map[uint64]int)
	for _, trigram := range trigrams {
		doc_ids := idx.storage.GetItems(trigram)

		for _, id := range doc_ids {
			c, has := temp_map[id]
			if has {
				temp_map[id] = c + 1
			} else {
				temp_map[id] = 1
			}
		}
	}

	/*
	etime := time.Now().Sub(stime)
	fmt.Printf("[%s] Time to get all document IDs per trigram took: %s\n", query, etime)
	stime = time.Now()
	*/

	heap_list := make(ResultSet, 0, max_results+1)
	heap.Init(&heap_list)

	for id, count := range temp_map {
		x := trigrams_len / float64(idx.getTotalTrigrams(id))
		if x > 1 {
			x = 1.0 / x
		}
		s := (float64(count) / trigrams_len) - (1.0 - x)

		if s < skip {
			continue
		}

		heap.Push(&heap_list, &Result{
			ID:         id,
			Similarity: s,
		})
	}

	//etime = time.Now().Sub(stime)
	//fmt.Printf("[%s] Time to calculate top X took: %s\n", query, etime)

	if len(heap_list) > 0 {
		item_count := min(len(heap_list), max_results)
		result_set := make(ResultSet, 0, item_count)

		for i := 0; i < item_count; i++ {
			result_set = append(result_set, heap.Pop(&heap_list).(*Result))
		}

		return result_set
	}

	return nil
}
<|endoftext|>


<commit_before>package main

import (
	"github.com/dghubble/go-twitter/twitter"
	"github.com/dghubble/oauth1"
	"strings"
	"time"
)

var (
	twitterBot *TwitterBot
)

// TwitterBot ...
type TwitterBot struct {
	ID      string
	ImgPath string
	Client  *twitter.Client
	Follows map[string]string
}

// NewTwitterBot ...
func NewTwitterBot(cfg *TwitterConfig) *TwitterBot {
	config := oauth1.NewConfig(cfg.ConsumerKey, cfg.ConsumerSecret)
	token := oauth1.NewToken(cfg.AccessToken, cfg.AccessSecret)
	httpClient := config.Client(oauth1.NoContext, token)
	client := twitter.NewClient(httpClient)
	bot := &TwitterBot{
		ID:      cfg.IDSelf,
		ImgPath: cfg.ImgPath,
		Client:  client,
		Follows: map[string]string{
			"KanColle_STAFF": "294025417",
			"komatan":        "96604067",
			"maesanpicture":  "2381595966",
			"Strangestone":   "93332575",
			"kazuharukina":   "28787294",
		},
	}
	return bot
}

func hasHashTags(s string, tags []twitter.HashtagEntity) bool {
	for _, tag := range tags {
		if s == tag.Text {
			return true
		}
	}
	return false
}

func getMedias(tweet *twitter.Tweet) []twitter.MediaEntity {
	ee := tweet.ExtendedEntities
	if ee != nil {
		return ee.Media
	}
	return tweet.Entities.Media
}

func sendPics(medias []twitter.MediaEntity) {
	for _, media := range medias {
		switch media.Type {
		case "photo":
			go qqBot.SendPics(qqBot.SendGroupMsg, media.MediaURLHttps)
		}
	}
}

func logAllTrack(msg interface{}) {
	logger.Debug(msg)
}

func (t *TwitterBot) trackSendMedias(tweet *twitter.Tweet, withText bool) {
	medias := getMedias(tweet)
	if len(medias) == 0 {
		return
	}
	logger.Infof("(%s):{%s}", tweet.User.Name, strings.Replace(tweet.Text, "\n", " ", -1))
	if withText {
		qqBot.SendGroupMsg(tweet.Text)
	}
	sendPics(medias)
}

func (t *TwitterBot) trackTweet(tweet *twitter.Tweet) {
	if tweet.RetweetedStatus != nil {
		// logger.Debugf("ignore retweet (%s):{%s}", tweet.User.Name, tweet.Text)
		return
	}
	switch tweet.User.IDStr {
	case t.Follows["KanColle_STAFF"]:
		msg := tweet.Text
		if tweet.Truncated {
			msg = tweet.FullText
		}
		qqBot.SendGroupMsg(tweet.User.Name + "\n" + tweet.CreatedAt + "\n" + msg)
		t.trackSendMedias(tweet, false)
	case t.Follows["komatan"]:
		t.trackSendMedias(tweet, false)
	case t.Follows["maesanpicture"]:
		if !hasHashTags("毎日五月雨", tweet.Entities.Hashtags) {
			logger.Debugf("(%s):{%s}", tweet.User.Name, strings.Replace(tweet.Text, "\n", " ", -1))
			return
		}
		t.trackSendMedias(tweet, true)
	case t.Follows["Strangestone"]:
		if !strings.HasPrefix(tweet.Text, "月曜日のたわわ") {
			logger.Debugf("(%s):{%s}", tweet.User.Name, strings.Replace(tweet.Text, "\n", " ", -1))
			return
		}
		t.trackSendMedias(tweet, true)
	case t.Follows["kazuharukina"]:
		if !hasHashTags("和遥キナ毎日JK企画", tweet.Entities.Hashtags) {
			logger.Debugf("(%s):{%s}", tweet.User.Name, strings.Replace(tweet.Text, "\n", " ", -1))
			return
		}
		t.trackSendMedias(tweet, true)
	default:
		logger.Debugf("(%s):{%s}", tweet.User.Name, tweet.Text)
	}
}

func (t *TwitterBot) selfProceedPics(medias []twitter.MediaEntity, action int) {
	for _, media := range medias {
		switch media.Type {
		case "photo":
			switch action {
			case 1:
				downloadFile(media.MediaURLHttps, t.ImgPath)
				go qqBot.SendPics(qqBot.SendSelfMsg, media.MediaURLHttps)
			case -1:
				removeFile(media.MediaURLHttps, t.ImgPath)
			}
		}
	}
}

func (t *TwitterBot) selfEvent(event *twitter.Event) {
	if event.Source.IDStr != t.ID {
		logger.Debugf(
			"favorited: (%s):{%s}",
			event.Source.Name,
			strings.Replace(event.TargetObject.Text, "\n", " ", -1))
		return
	}
	switch event.Event {
	case "favorite":
		medias := getMedias(event.TargetObject)
		logger.Infof(
			"favorite: (%s):{%s} %d medias",
			event.TargetObject.User.Name,
			strings.Replace(event.TargetObject.Text, "\n", " ", -1),
			len(medias))
		go t.selfProceedPics(medias, 1)
	case "unfavorite":
		medias := getMedias(event.TargetObject)
		logger.Debugf(
			"unfavorite: (%s):{%s} %d medias",
			event.TargetObject.User.Name,
			strings.Replace(event.TargetObject.Text, "\n", " ", -1),
			len(medias))
		go t.selfProceedPics(medias, -1)
	default:
		logger.Debug(event.Event)
	}
}

func (t *TwitterBot) selfTweet(tweet *twitter.Tweet) {
	if qqBot.Config.NameGroup != "" {
		if hasHashTags(qqBot.Config.NameGroup, tweet.Entities.Hashtags) {
			if tweet.QuotedStatus != nil {
				logger.Infof("(%s):{%s}", qqBot.Config.NameGroup, strings.Replace(tweet.QuotedStatus.Text, "\n", " ", -1))
				sendPics(getMedias(tweet.QuotedStatus))
			} else {
				logger.Infof("(%s):{%s}", qqBot.Config.NameGroup, strings.Replace(tweet.Text, "\n", " ", -1))
				sendPics(getMedias(tweet))
			}
		}
	}
}

// Track ...
func (t *TwitterBot) Track() {
	follows := []string{}
	for _, value := range t.Follows {
		follows = append(follows, value)
	}
	for i := 1; ; i++ {
		demux := twitter.NewSwitchDemux()
		demux.Tweet = t.trackTweet
		filterParams := &twitter.StreamFilterParams{
			Follow: follows,
		}
		stream, err := t.Client.Streams.Filter(filterParams)
		if err != nil {
			logger.Error(err)
			time.Sleep(time.Duration(i) * time.Second)
		}
		demux.HandleChan(stream.Messages)
	}
}

// Self ...
func (t *TwitterBot) Self() {
	for i := 1; ; i++ {
		demux := twitter.NewSwitchDemux()
		demux.Event = t.selfEvent
		demux.Tweet = t.selfTweet
		userParams := &twitter.StreamUserParams{
			With: t.ID,
		}
		stream, err := t.Client.Streams.User(userParams)
		if err != nil {
			logger.Error(err)
			time.Sleep(time.Duration(i) * time.Second)
		}
		demux.HandleChan(stream.Messages)
	}
}
<commit_msg>fixes<commit_after>package main

import (
	"github.com/dghubble/go-twitter/twitter"
	"github.com/dghubble/oauth1"
	"strings"
	"time"
)

var (
	twitterBot *TwitterBot
)

// TwitterBot ...
type TwitterBot struct {
	ID      string
	ImgPath string
	Client  *twitter.Client
	Follows map[string]string
}

// NewTwitterBot ...
func NewTwitterBot(cfg *TwitterConfig) *TwitterBot {
	config := oauth1.NewConfig(cfg.ConsumerKey, cfg.ConsumerSecret)
	token := oauth1.NewToken(cfg.AccessToken, cfg.AccessSecret)
	httpClient := config.Client(oauth1.NoContext, token)
	client := twitter.NewClient(httpClient)
	bot := &TwitterBot{
		ID:      cfg.IDSelf,
		ImgPath: cfg.ImgPath,
		Client:  client,
		Follows: map[string]string{
			"KanColle_STAFF": "294025417",
			"komatan":        "96604067",
			"maesanpicture":  "2381595966",
			"Strangestone":   "93332575",
			"kazuharukina":   "28787294",
		},
	}
	return bot
}

func hasHashTags(s string, tags []twitter.HashtagEntity) bool {
	for _, tag := range tags {
		if s == tag.Text {
			return true
		}
	}
	return false
}

func getMedias(tweet *twitter.Tweet) []twitter.MediaEntity {
	ee := tweet.ExtendedEntities
	if ee != nil {
		return ee.Media
	}
	return tweet.Entities.Media
}

func sendPics(medias []twitter.MediaEntity) {
	for _, media := range medias {
		switch media.Type {
		case "photo":
			go qqBot.SendPics(qqBot.SendGroupMsg, media.MediaURLHttps)
		}
	}
}

func logAllTrack(msg interface{}) {
	logger.Debug(msg)
}

func (t *TwitterBot) trackTweet(tweet *twitter.Tweet) {
	if tweet.RetweetedStatus != nil {
		// logger.Debugf("ignore retweet (%s):{%s}", tweet.User.Name, tweet.Text)
		return
	}
	flattenedText := strings.Replace(tweet.Text, "\n", " ", -1)
	medias := getMedias(tweet)
	switch tweet.User.IDStr {
	case t.Follows["KanColle_STAFF"]:
		msg := tweet.Text
		if tweet.Truncated {
			msg = tweet.FullText
		}
		logger.Infof("(%s):{%s}", tweet.User.Name, flattenedText)
		qqBot.SendGroupMsg(tweet.User.Name + "\n" + tweet.CreatedAt + "\n" + msg)
		sendPics(medias)

	case t.Follows["komatan"]:
		sendPics(medias)

	case t.Follows["maesanpicture"]:
		if !hasHashTags("毎日五月雨", tweet.Entities.Hashtags) || (len(medias) == 0) {
			logger.Debugf("(%s):{%s}", tweet.User.Name, flattenedText)
			return
		}
		logger.Infof("(%s):{%s}", tweet.User.Name, flattenedText)
		qqBot.SendGroupMsg(tweet.Text)
		sendPics(medias)

	case t.Follows["Strangestone"]:
		if !strings.HasPrefix(tweet.Text, "月曜日のたわわ") || (len(medias) == 0) {
			logger.Debugf("(%s):{%s}", tweet.User.Name, flattenedText)
			return
		}
		logger.Infof("(%s):{%s}", tweet.User.Name, flattenedText)
		qqBot.SendGroupMsg(tweet.Text)
		sendPics(medias)

	case t.Follows["kazuharukina"]:
		if !hasHashTags("和遥キナ毎日JK企画", tweet.Entities.Hashtags) || (len(medias) == 0) {
			logger.Debugf("(%s):{%s}", tweet.User.Name, strings.Replace(tweet.Text, "\n", " ", -1))
			return
		}
		logger.Infof("(%s):{%s}", tweet.User.Name, flattenedText)
		sendPics(medias)

	default:
		logger.Debugf("(%s):{%s}", tweet.User.Name, strings.Replace(tweet.Text, "\n", " ", -1))
	}
}

func (t *TwitterBot) selfProceedPics(medias []twitter.MediaEntity, action int) {
	for _, media := range medias {
		switch media.Type {
		case "photo":
			switch action {
			case 1:
				downloadFile(media.MediaURLHttps, t.ImgPath)
				go qqBot.SendPics(qqBot.SendSelfMsg, media.MediaURLHttps)
			case -1:
				removeFile(media.MediaURLHttps, t.ImgPath)
			}
		}
	}
}

func (t *TwitterBot) selfEvent(event *twitter.Event) {
	if event.Source.IDStr != t.ID {
		logger.Debugf(
			"favorited: (%s):{%s}",
			event.Source.Name,
			strings.Replace(event.TargetObject.Text, "\n", " ", -1))
		return
	}
	switch event.Event {
	case "favorite":
		medias := getMedias(event.TargetObject)
		logger.Infof(
			"favorite: (%s):{%s} %d medias",
			event.TargetObject.User.Name,
			strings.Replace(event.TargetObject.Text, "\n", " ", -1),
			len(medias))
		go t.selfProceedPics(medias, 1)
	case "unfavorite":
		medias := getMedias(event.TargetObject)
		logger.Debugf(
			"unfavorite: (%s):{%s} %d medias",
			event.TargetObject.User.Name,
			strings.Replace(event.TargetObject.Text, "\n", " ", -1),
			len(medias))
		go t.selfProceedPics(medias, -1)
	default:
		logger.Debug(event.Event)
	}
}

func (t *TwitterBot) selfTweet(tweet *twitter.Tweet) {
	if qqBot.Config.NameGroup != "" {
		if hasHashTags(qqBot.Config.NameGroup, tweet.Entities.Hashtags) {
			if tweet.QuotedStatus != nil {
				logger.Infof("(%s):{%s}", qqBot.Config.NameGroup, strings.Replace(tweet.QuotedStatus.Text, "\n", " ", -1))
				sendPics(getMedias(tweet.QuotedStatus))
			} else {
				logger.Infof("(%s):{%s}", qqBot.Config.NameGroup, strings.Replace(tweet.Text, "\n", " ", -1))
				sendPics(getMedias(tweet))
			}
		}
	}
}

// Track ...
func (t *TwitterBot) Track() {
	follows := []string{}
	for _, value := range t.Follows {
		follows = append(follows, value)
	}
	for i := 1; ; i++ {
		demux := twitter.NewSwitchDemux()
		demux.Tweet = t.trackTweet
		filterParams := &twitter.StreamFilterParams{
			Follow: follows,
		}
		stream, err := t.Client.Streams.Filter(filterParams)
		if err != nil {
			logger.Error(err)
			time.Sleep(time.Duration(i) * time.Second)
		}
		demux.HandleChan(stream.Messages)
	}
}

// Self ...
func (t *TwitterBot) Self() {
	for i := 1; ; i++ {
		demux := twitter.NewSwitchDemux()
		demux.Event = t.selfEvent
		demux.Tweet = t.selfTweet
		userParams := &twitter.StreamUserParams{
			With: t.ID,
		}
		stream, err := t.Client.Streams.User(userParams)
		if err != nil {
			logger.Error(err)
			time.Sleep(time.Duration(i) * time.Second)
		}
		demux.HandleChan(stream.Messages)
	}
}
<|endoftext|>