<commit_before>//===--- ToolChain.cpp - Collections of tools for one platform ------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
/// \file This file defines the base implementation of the ToolChain class.
/// The platform-specific subclasses are implemented in ToolChains.cpp.
/// For organizational purposes, the platform-independent logic for
/// constructing job invocations is also located in ToolChains.cpp.
//
//===----------------------------------------------------------------------===//

#include "swift/Driver/ToolChain.h"
#include "swift/Driver/Compilation.h"
#include "swift/Driver/Driver.h"
#include "swift/Driver/Job.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Program.h"
#include "llvm/ADT/STLExtras.h"

using namespace swift;
using namespace swift::driver;
using namespace llvm::opt;

ToolChain::JobContext::JobContext(Compilation &C,
                                  ArrayRef<const Job *> Inputs,
                                  ArrayRef<const Action *> InputActions,
                                  const CommandOutput &Output,
                                  const OutputInfo &OI)
  : C(C), Inputs(Inputs), InputActions(InputActions), Output(Output),
    OI(OI), Args(C.getArgs()) {}

ArrayRef<InputPair> ToolChain::JobContext::getTopLevelInputFiles() const {
  return C.getInputFiles();
}
const char *ToolChain::JobContext::getAllSourcesPath() const {
  return C.getAllSourcesPath();
}

const char *
ToolChain::JobContext::getTemporaryFilePath(const llvm::Twine &name,
                                            StringRef suffix) const {
  SmallString<128> buffer;
  std::error_code EC =
      llvm::sys::fs::createTemporaryFile(name, suffix, buffer);
  if (EC) {
    // FIXME: This should not take down the entire process.
    llvm::report_fatal_error("unable to create temporary file for filelist");
  }

  C.addTemporaryFile(buffer.str(), PreserveOnSignal::Yes);
  // We can't just reference the data in the TemporaryFiles vector because
  // that could theoretically get copied to a new address.
  return C.getArgs().MakeArgString(buffer.str());
}

std::unique_ptr<Job>
ToolChain::constructJob(const JobAction &JA,
                        Compilation &C,
                        SmallVectorImpl<const Job *> &&inputs,
                        ArrayRef<const Action *> inputActions,
                        std::unique_ptr<CommandOutput> output,
                        const OutputInfo &OI) const {
  JobContext context{C, inputs, inputActions, *output, OI};

  auto invocationInfo = [&]() -> InvocationInfo {
    switch (JA.getKind()) {
  #define CASE(K) case Action::K: \
      return constructInvocation(cast<K##Action>(JA), context);
    CASE(CompileJob)
    CASE(InterpretJob)
    CASE(BackendJob)
    CASE(MergeModuleJob)
    CASE(ModuleWrapJob)
    CASE(LinkJob)
    CASE(GenerateDSYMJob)
    CASE(VerifyDebugInfoJob)
    CASE(GeneratePCHJob)
    CASE(AutolinkExtractJob)
    CASE(REPLJob)
#undef CASE
    case Action::Input:
      llvm_unreachable("not a JobAction");
    }

    // Work around MSVC warning: not all control paths return a value
    llvm_unreachable("All switch cases are covered");
  }();

  // Special-case the Swift frontend.
  const char *executablePath = nullptr;
  if (StringRef(SWIFT_EXECUTABLE_NAME) == invocationInfo.ExecutableName) {
    executablePath = getDriver().getSwiftProgramPath().c_str();
  } else {
    std::string relativePath =
        findProgramRelativeToSwift(invocationInfo.ExecutableName);
    if (!relativePath.empty()) {
      executablePath = C.getArgs().MakeArgString(relativePath);
    } else {
      auto systemPath =
          llvm::sys::findProgramByName(invocationInfo.ExecutableName);
      if (systemPath) {
        executablePath = C.getArgs().MakeArgString(systemPath.get());
      } else {
        // For debugging purposes.
        executablePath = invocationInfo.ExecutableName;
      }
    }
  }

  return llvm::make_unique<Job>(JA, std::move(inputs), std::move(output),
                                executablePath,
                                std::move(invocationInfo.Arguments),
                                std::move(invocationInfo.ExtraEnvironment),
                                std::move(invocationInfo.FilelistInfos));
}

std::string
ToolChain::findProgramRelativeToSwift(StringRef executableName) const {
  auto insertionResult =
      ProgramLookupCache.insert(std::make_pair(executableName, ""));
  if (insertionResult.second) {
    std::string path = findProgramRelativeToSwiftImpl(executableName);
    insertionResult.first->setValue(std::move(path));
  }
  return insertionResult.first->getValue();
}

std::string
ToolChain::findProgramRelativeToSwiftImpl(StringRef executableName) const {
  StringRef swiftPath = getDriver().getSwiftProgramPath();
  StringRef swiftBinDir = llvm::sys::path::parent_path(swiftPath);

  auto result = llvm::sys::findProgramByName(executableName, {swiftBinDir});
  if (result)
    return result.get();
  return {};
}

types::ID ToolChain::lookupTypeForExtension(StringRef Ext) const {
  return types::lookupTypeForExtension(Ext);
}

/// Return a _single_ TY_Swift InputAction, if one exists;
/// if 0 or >1 such inputs exist, return nullptr.
static const InputAction*
findSingleSwiftInput(const CompileJobAction *CJA) {
  auto Inputs = CJA->getInputs();
  const InputAction *IA = nullptr;
  for (auto const *I : Inputs) {
    if (auto const *S = dyn_cast<InputAction>(I)) {
      if (S->getType() == types::TY_Swift) {
        if (IA == nullptr) {
          IA = S;
        } else {
          // Already found one, two is too many.
          return nullptr;
        }
      }
    }
  }
  return IA;
}

static bool
jobsHaveSameExecutableNames(const Job *A, const Job *B) {
  // Jobs that get here (that are derived from CompileJobActions) should always
  // have the same executable name -- it should always be SWIFT_EXECUTABLE_NAME
  // -- but we check here just to be sure / fail gracefully in non-assert
  // builds.
  assert(strcmp(A->getExecutable(), B->getExecutable()) == 0);
  if (strcmp(A->getExecutable(), B->getExecutable()) != 0) {
    return false;
  }
  return true;
}

static bool
jobsHaveSameOutputTypes(const Job *A, const Job *B) {
  if (A->getOutput().getPrimaryOutputType() !=
      B->getOutput().getPrimaryOutputType())
    return false;
  return A->getOutput().hasSameAdditionalOutputTypes(B->getOutput());
}

static bool
jobsHaveSameEnvironment(const Job *A, const Job *B) {
  auto AEnv = A->getExtraEnvironment();
  auto BEnv = B->getExtraEnvironment();
  if (AEnv.size() != BEnv.size())
    return false;
  for (size_t i = 0; i < AEnv.size(); ++i) {
    if (strcmp(AEnv[i].first, BEnv[i].first) != 0)
      return false;
    if (strcmp(AEnv[i].second, BEnv[i].second) != 0)
      return false;
  }
  return true;
}

bool
ToolChain::jobIsBatchable(const Compilation &C, const Job *A) const {
  // FIXME: There might be a tighter criterion to use here?
  if (C.getOutputInfo().CompilerMode != OutputInfo::Mode::StandardCompile)
    return false;
  auto const *CJActA = dyn_cast<const CompileJobAction>(&A->getSource());
  if (!CJActA)
    return false;
  return findSingleSwiftInput(CJActA) != nullptr;
}

bool
ToolChain::jobsAreBatchCombinable(const Compilation &C,
                                  const Job *A, const Job *B) const {
  assert(jobIsBatchable(C, A));
  assert(jobIsBatchable(C, B));
  return (jobsHaveSameExecutableNames(A, B) &&
          jobsHaveSameOutputTypes(A, B) &&
          jobsHaveSameEnvironment(A, B));
}

/// Form a synthetic \c CommandOutput for a \c BatchJob by merging together the
/// \c CommandOutputs of all the jobs passed.
static std::unique_ptr<CommandOutput>
makeBatchCommandOutput(ArrayRef<const Job *> jobs, Compilation &C,
                       types::ID outputType) {
  auto output =
      llvm::make_unique<CommandOutput>(outputType, C.getDerivedOutputFileMap());
  for (auto const *J : jobs) {
    output->addOutputs(J->getOutput());
  }
  return output;
}

/// Set-union the \c Inputs and \c InputActions from each \c Job in \p jobs into
/// the provided \p inputJobs and \p inputActions vectors, further adding all \c
/// Actions in the \p jobs -- InputActions or otherwise -- to \p batchCJA. Do
/// set-union rather than concatenation here to avoid mentioning the same input
/// multiple times.
static bool
mergeBatchInputs(ArrayRef<const Job *> jobs,
                 llvm::SmallSetVector<const Job *, 16> &inputJobs,
                 llvm::SmallSetVector<const Action *, 16> &inputActions,
                 CompileJobAction *batchCJA) {

  llvm::SmallSetVector<const Action *, 16> allActions;

  for (auto const *J : jobs) {
    for (auto const *I : J->getInputs()) {
      inputJobs.insert(I);
    }
    auto const *CJA = dyn_cast<CompileJobAction>(&J->getSource());
    if (!CJA)
      return true;
    for (auto const *I : CJA->getInputs()) {
      // Capture _all_ input actions -- whether or not they are InputActions --
      // in allActions, to set as the inputs for batchCJA below.
      allActions.insert(I);
      // Only collect input actions that _are InputActions_ in the inputActions
      // array, to load into the JobContext in our caller.
      if (auto const *IA = dyn_cast<InputAction>(I)) {
        inputActions.insert(IA);
      }
    }
  }

  for (auto const *I : allActions) {
    batchCJA->addInput(I);
  }
  return false;
}

/// Construct a \c BatchJob by merging the constituent \p jobs' CommandOutput,
/// input \c Job and \c Action members. Call through to \c constructInvocation
/// on \p BatchJob, to build the \c InvocationInfo.
std::unique_ptr<Job>
ToolChain::constructBatchJob(ArrayRef<const Job *> jobs,
                             Compilation &C) const
{
#ifndef NDEBUG
  // Verify that the equivalence relation on the jobs also holds pairwise.
  for (auto *A : jobs) {
    for (auto *B : jobs) {
      assert(jobsAreBatchCombinable(C, A, B));
    }
  }
#endif
  if (jobs.empty())
    return nullptr;

  // Synthetic OutputInfo is a slightly-modified version of the initial
  // compilation's OI.
  auto OI = C.getOutputInfo();
  OI.CompilerMode = OutputInfo::Mode::BatchModeCompile;

  auto const *executablePath = jobs[0]->getExecutable();
  auto outputType = jobs[0]->getOutput().getPrimaryOutputType();
  auto output = makeBatchCommandOutput(jobs, C, outputType);

  llvm::SmallSetVector<const Job *, 16> inputJobs;
  llvm::SmallSetVector<const Action *, 16> inputActions;
  auto *batchCJA = C.createAction<CompileJobAction>(outputType);
  if (mergeBatchInputs(jobs, inputJobs, inputActions, batchCJA))
    return nullptr;

  JobContext context{C, inputJobs.getArrayRef(), inputActions.getArrayRef(),
                     *output, OI};
  auto invocationInfo = constructInvocation(*batchCJA, context);
  return llvm::make_unique<BatchJob>(*batchCJA,
                                     inputJobs.takeVector(),
                                     std::move(output),
                                     executablePath,
                                     std::move(invocationInfo.Arguments),
                                     std::move(invocationInfo.ExtraEnvironment),
                                     std::move(invocationInfo.FilelistInfos),
                                     jobs);
}

bool
ToolChain::sanitizerRuntimeLibExists(const ArgList &args,
                                     StringRef sanitizerName,
                                     bool shared) const {
  // Assume no sanitizers are supported by default.
  // This method should be overriden by a platform-specific subclass.
  return false;
}
<commit_msg>[BatchMode] Remove a low-value quadratic assertion.<commit_after>//===--- ToolChain.cpp - Collections of tools for one platform ------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
//
/// \file This file defines the base implementation of the ToolChain class.
/// The platform-specific subclasses are implemented in ToolChains.cpp.
/// For organizational purposes, the platform-independent logic for
/// constructing job invocations is also located in ToolChains.cpp.
//
//===----------------------------------------------------------------------===//

#include "swift/Driver/ToolChain.h"
#include "swift/Driver/Compilation.h"
#include "swift/Driver/Driver.h"
#include "swift/Driver/Job.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/Option/ArgList.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
#include "llvm/Support/Program.h"
#include "llvm/ADT/STLExtras.h"

using namespace swift;
using namespace swift::driver;
using namespace llvm::opt;

ToolChain::JobContext::JobContext(Compilation &C,
                                  ArrayRef<const Job *> Inputs,
                                  ArrayRef<const Action *> InputActions,
                                  const CommandOutput &Output,
                                  const OutputInfo &OI)
  : C(C), Inputs(Inputs), InputActions(InputActions), Output(Output),
    OI(OI), Args(C.getArgs()) {}

ArrayRef<InputPair> ToolChain::JobContext::getTopLevelInputFiles() const {
  return C.getInputFiles();
}
const char *ToolChain::JobContext::getAllSourcesPath() const {
  return C.getAllSourcesPath();
}

const char *
ToolChain::JobContext::getTemporaryFilePath(const llvm::Twine &name,
                                            StringRef suffix) const {
  SmallString<128> buffer;
  std::error_code EC =
      llvm::sys::fs::createTemporaryFile(name, suffix, buffer);
  if (EC) {
    // FIXME: This should not take down the entire process.
    llvm::report_fatal_error("unable to create temporary file for filelist");
  }

  C.addTemporaryFile(buffer.str(), PreserveOnSignal::Yes);
  // We can't just reference the data in the TemporaryFiles vector because
  // that could theoretically get copied to a new address.
  return C.getArgs().MakeArgString(buffer.str());
}

std::unique_ptr<Job>
ToolChain::constructJob(const JobAction &JA,
                        Compilation &C,
                        SmallVectorImpl<const Job *> &&inputs,
                        ArrayRef<const Action *> inputActions,
                        std::unique_ptr<CommandOutput> output,
                        const OutputInfo &OI) const {
  JobContext context{C, inputs, inputActions, *output, OI};

  auto invocationInfo = [&]() -> InvocationInfo {
    switch (JA.getKind()) {
  #define CASE(K) case Action::K: \
      return constructInvocation(cast<K##Action>(JA), context);
    CASE(CompileJob)
    CASE(InterpretJob)
    CASE(BackendJob)
    CASE(MergeModuleJob)
    CASE(ModuleWrapJob)
    CASE(LinkJob)
    CASE(GenerateDSYMJob)
    CASE(VerifyDebugInfoJob)
    CASE(GeneratePCHJob)
    CASE(AutolinkExtractJob)
    CASE(REPLJob)
#undef CASE
    case Action::Input:
      llvm_unreachable("not a JobAction");
    }

    // Work around MSVC warning: not all control paths return a value
    llvm_unreachable("All switch cases are covered");
  }();

  // Special-case the Swift frontend.
  const char *executablePath = nullptr;
  if (StringRef(SWIFT_EXECUTABLE_NAME) == invocationInfo.ExecutableName) {
    executablePath = getDriver().getSwiftProgramPath().c_str();
  } else {
    std::string relativePath =
        findProgramRelativeToSwift(invocationInfo.ExecutableName);
    if (!relativePath.empty()) {
      executablePath = C.getArgs().MakeArgString(relativePath);
    } else {
      auto systemPath =
          llvm::sys::findProgramByName(invocationInfo.ExecutableName);
      if (systemPath) {
        executablePath = C.getArgs().MakeArgString(systemPath.get());
      } else {
        // For debugging purposes.
        executablePath = invocationInfo.ExecutableName;
      }
    }
  }

  return llvm::make_unique<Job>(JA, std::move(inputs), std::move(output),
                                executablePath,
                                std::move(invocationInfo.Arguments),
                                std::move(invocationInfo.ExtraEnvironment),
                                std::move(invocationInfo.FilelistInfos));
}

std::string
ToolChain::findProgramRelativeToSwift(StringRef executableName) const {
  auto insertionResult =
      ProgramLookupCache.insert(std::make_pair(executableName, ""));
  if (insertionResult.second) {
    std::string path = findProgramRelativeToSwiftImpl(executableName);
    insertionResult.first->setValue(std::move(path));
  }
  return insertionResult.first->getValue();
}

std::string
ToolChain::findProgramRelativeToSwiftImpl(StringRef executableName) const {
  StringRef swiftPath = getDriver().getSwiftProgramPath();
  StringRef swiftBinDir = llvm::sys::path::parent_path(swiftPath);

  auto result = llvm::sys::findProgramByName(executableName, {swiftBinDir});
  if (result)
    return result.get();
  return {};
}

types::ID ToolChain::lookupTypeForExtension(StringRef Ext) const {
  return types::lookupTypeForExtension(Ext);
}

/// Return a _single_ TY_Swift InputAction, if one exists;
/// if 0 or >1 such inputs exist, return nullptr.
static const InputAction*
findSingleSwiftInput(const CompileJobAction *CJA) {
  auto Inputs = CJA->getInputs();
  const InputAction *IA = nullptr;
  for (auto const *I : Inputs) {
    if (auto const *S = dyn_cast<InputAction>(I)) {
      if (S->getType() == types::TY_Swift) {
        if (IA == nullptr) {
          IA = S;
        } else {
          // Already found one, two is too many.
          return nullptr;
        }
      }
    }
  }
  return IA;
}

static bool
jobsHaveSameExecutableNames(const Job *A, const Job *B) {
  // Jobs that get here (that are derived from CompileJobActions) should always
  // have the same executable name -- it should always be SWIFT_EXECUTABLE_NAME
  // -- but we check here just to be sure / fail gracefully in non-assert
  // builds.
  assert(strcmp(A->getExecutable(), B->getExecutable()) == 0);
  if (strcmp(A->getExecutable(), B->getExecutable()) != 0) {
    return false;
  }
  return true;
}

static bool
jobsHaveSameOutputTypes(const Job *A, const Job *B) {
  if (A->getOutput().getPrimaryOutputType() !=
      B->getOutput().getPrimaryOutputType())
    return false;
  return A->getOutput().hasSameAdditionalOutputTypes(B->getOutput());
}

static bool
jobsHaveSameEnvironment(const Job *A, const Job *B) {
  auto AEnv = A->getExtraEnvironment();
  auto BEnv = B->getExtraEnvironment();
  if (AEnv.size() != BEnv.size())
    return false;
  for (size_t i = 0; i < AEnv.size(); ++i) {
    if (strcmp(AEnv[i].first, BEnv[i].first) != 0)
      return false;
    if (strcmp(AEnv[i].second, BEnv[i].second) != 0)
      return false;
  }
  return true;
}

bool
ToolChain::jobIsBatchable(const Compilation &C, const Job *A) const {
  // FIXME: There might be a tighter criterion to use here?
  if (C.getOutputInfo().CompilerMode != OutputInfo::Mode::StandardCompile)
    return false;
  auto const *CJActA = dyn_cast<const CompileJobAction>(&A->getSource());
  if (!CJActA)
    return false;
  return findSingleSwiftInput(CJActA) != nullptr;
}

bool
ToolChain::jobsAreBatchCombinable(const Compilation &C,
                                  const Job *A, const Job *B) const {
  assert(jobIsBatchable(C, A));
  assert(jobIsBatchable(C, B));
  return (jobsHaveSameExecutableNames(A, B) &&
          jobsHaveSameOutputTypes(A, B) &&
          jobsHaveSameEnvironment(A, B));
}

/// Form a synthetic \c CommandOutput for a \c BatchJob by merging together the
/// \c CommandOutputs of all the jobs passed.
static std::unique_ptr<CommandOutput>
makeBatchCommandOutput(ArrayRef<const Job *> jobs, Compilation &C,
                       types::ID outputType) {
  auto output =
      llvm::make_unique<CommandOutput>(outputType, C.getDerivedOutputFileMap());
  for (auto const *J : jobs) {
    output->addOutputs(J->getOutput());
  }
  return output;
}

/// Set-union the \c Inputs and \c InputActions from each \c Job in \p jobs into
/// the provided \p inputJobs and \p inputActions vectors, further adding all \c
/// Actions in the \p jobs -- InputActions or otherwise -- to \p batchCJA. Do
/// set-union rather than concatenation here to avoid mentioning the same input
/// multiple times.
static bool
mergeBatchInputs(ArrayRef<const Job *> jobs,
                 llvm::SmallSetVector<const Job *, 16> &inputJobs,
                 llvm::SmallSetVector<const Action *, 16> &inputActions,
                 CompileJobAction *batchCJA) {

  llvm::SmallSetVector<const Action *, 16> allActions;

  for (auto const *J : jobs) {
    for (auto const *I : J->getInputs()) {
      inputJobs.insert(I);
    }
    auto const *CJA = dyn_cast<CompileJobAction>(&J->getSource());
    if (!CJA)
      return true;
    for (auto const *I : CJA->getInputs()) {
      // Capture _all_ input actions -- whether or not they are InputActions --
      // in allActions, to set as the inputs for batchCJA below.
      allActions.insert(I);
      // Only collect input actions that _are InputActions_ in the inputActions
      // array, to load into the JobContext in our caller.
      if (auto const *IA = dyn_cast<InputAction>(I)) {
        inputActions.insert(IA);
      }
    }
  }

  for (auto const *I : allActions) {
    batchCJA->addInput(I);
  }
  return false;
}

/// Construct a \c BatchJob by merging the constituent \p jobs' CommandOutput,
/// input \c Job and \c Action members. Call through to \c constructInvocation
/// on \p BatchJob, to build the \c InvocationInfo.
std::unique_ptr<Job>
ToolChain::constructBatchJob(ArrayRef<const Job *> jobs,
                             Compilation &C) const
{
  if (jobs.empty())
    return nullptr;

  // Synthetic OutputInfo is a slightly-modified version of the initial
  // compilation's OI.
  auto OI = C.getOutputInfo();
  OI.CompilerMode = OutputInfo::Mode::BatchModeCompile;

  auto const *executablePath = jobs[0]->getExecutable();
  auto outputType = jobs[0]->getOutput().getPrimaryOutputType();
  auto output = makeBatchCommandOutput(jobs, C, outputType);

  llvm::SmallSetVector<const Job *, 16> inputJobs;
  llvm::SmallSetVector<const Action *, 16> inputActions;
  auto *batchCJA = C.createAction<CompileJobAction>(outputType);
  if (mergeBatchInputs(jobs, inputJobs, inputActions, batchCJA))
    return nullptr;

  JobContext context{C, inputJobs.getArrayRef(), inputActions.getArrayRef(),
                     *output, OI};
  auto invocationInfo = constructInvocation(*batchCJA, context);
  return llvm::make_unique<BatchJob>(*batchCJA,
                                     inputJobs.takeVector(),
                                     std::move(output),
                                     executablePath,
                                     std::move(invocationInfo.Arguments),
                                     std::move(invocationInfo.ExtraEnvironment),
                                     std::move(invocationInfo.FilelistInfos),
                                     jobs);
}

bool
ToolChain::sanitizerRuntimeLibExists(const ArgList &args,
                                     StringRef sanitizerName,
                                     bool shared) const {
  // Assume no sanitizers are supported by default.
  // This method should be overriden by a platform-specific subclass.
  return false;
}
<|endoftext|>


<commit_before>/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company.  For licensing terms and
** conditions see http://www.qt.io/terms-conditions.  For further information
** use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/

#include "navigatorview.h"
#include "navigatortreemodel.h"
#include "navigatorwidget.h"
#include "nameitemdelegate.h"
#include "iconcheckboxitemdelegate.h"

#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icore.h>

#include <designmodecontext.h>
#include <nodeproperty.h>
#include <nodelistproperty.h>
#include <variantproperty.h>
#include <QHeaderView>
#include <qmlitemnode.h>

static inline void setScenePos(const QmlDesigner::ModelNode &modelNode,const QPointF &pos)
{
    if (modelNode.hasParentProperty() && QmlDesigner::QmlItemNode::isValidQmlItemNode(modelNode.parentProperty().parentModelNode())) {
        QmlDesigner::QmlItemNode parentNode = modelNode.parentProperty().parentQmlObjectNode().toQmlItemNode();

        if (!parentNode.modelNode().metaInfo().isLayoutable()) {
            QPointF localPos = parentNode.instanceSceneTransform().inverted().map(pos);
            modelNode.variantProperty("x").setValue(localPos.toPoint().x());
            modelNode.variantProperty("y").setValue(localPos.toPoint().y());
        } else { //Items in Layouts do not have a position
            modelNode.removeProperty("x");
            modelNode.removeProperty("y");
        }
    }
}

namespace QmlDesigner {

NavigatorView::NavigatorView(QObject* parent) :
        AbstractView(parent),
        m_blockSelectionChangedSignal(false),
        m_widget(new NavigatorWidget(this)),
        m_treeModel(new NavigatorTreeModel(this))
{
    Internal::NavigatorContext *navigatorContext = new Internal::NavigatorContext(m_widget.data());
    Core::ICore::addContextObject(navigatorContext);

    m_widget->setTreeModel(m_treeModel.data());

    connect(treeWidget()->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(changeSelection(QItemSelection,QItemSelection)));

    connect(m_widget.data(), SIGNAL(leftButtonClicked()), this, SLOT(leftButtonClicked()));
    connect(m_widget.data(), SIGNAL(rightButtonClicked()), this, SLOT(rightButtonClicked()));
    connect(m_widget.data(), SIGNAL(downButtonClicked()), this, SLOT(downButtonClicked()));
    connect(m_widget.data(), SIGNAL(upButtonClicked()), this, SLOT(upButtonClicked()));

    treeWidget()->setIndentation(treeWidget()->indentation() * 0.5);

    NameItemDelegate *idDelegate = new NameItemDelegate(this,
                                                        m_treeModel.data());
    IconCheckboxItemDelegate *showDelegate = new IconCheckboxItemDelegate(this,
                                                                          ":/qmldesigner/images/eye_open.png",
                                                                          ":/qmldesigner/images/placeholder.png",
                                                                          m_treeModel.data());

#ifdef _LOCK_ITEMS_
    IconCheckboxItemDelegate *lockDelegate = new IconCheckboxItemDelegate(this,":/qmldesigner/images/lock.png",
                                                          ":/qmldesigner/images/hole.png",m_treeModel.data());
#endif

    treeWidget()->setItemDelegateForColumn(0,idDelegate);
#ifdef _LOCK_ITEMS_
    treeWidget()->setItemDelegateForColumn(1,lockDelegate);
    treeWidget()->setItemDelegateForColumn(2,showDelegate);
#else
    treeWidget()->setItemDelegateForColumn(1,showDelegate);
#endif

}

NavigatorView::~NavigatorView()
{
    if (m_widget && !m_widget->parent())
        delete m_widget.data();
}

bool NavigatorView::hasWidget() const
{
    return true;
}

WidgetInfo NavigatorView::widgetInfo()
{
    return createWidgetInfo(m_widget.data(),
                            new WidgetInfo::ToolBarWidgetDefaultFactory<NavigatorWidget>(m_widget.data()),
                            QStringLiteral("Navigator"),
                            WidgetInfo::LeftPane,
                            0);
}

void NavigatorView::modelAttached(Model *model)
{
    AbstractView::modelAttached(model);

    m_treeModel->setView(this);

    QTreeView *treeView = treeWidget();
    treeView->expandAll();

    treeView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
    treeView->header()->resizeSection(1,26);
    treeView->setRootIsDecorated(false);
    treeView->setIndentation(20);
#ifdef _LOCK_ITEMS_
    treeView->header()->resizeSection(2,20);
#endif
}

void NavigatorView::modelAboutToBeDetached(Model *model)
{
    m_treeModel->removeSubTree(rootModelNode());
    m_treeModel->clearView();
    AbstractView::modelAboutToBeDetached(model);
}

void NavigatorView::importsChanged(const QList<Import> &/*addedImports*/, const QList<Import> &/*removedImports*/)
{
    treeWidget()->update();
}

void NavigatorView::nodeAboutToBeRemoved(const ModelNode &removedNode)
{
    m_treeModel->removeSubTree(removedNode);
}

void NavigatorView::nodeReparented(const ModelNode &node, const NodeAbstractProperty & /*newPropertyParent*/, const NodeAbstractProperty & /*oldPropertyParent*/, AbstractView::PropertyChangeFlags /*propertyChange*/)
{
    bool blocked = blockSelectionChangedSignal(true);

    m_treeModel->removeSubTree(node);

    if (node.isInHierarchy())
        m_treeModel->addSubTree(node);

    // make sure selection is in sync again
    updateItemSelection();

    blockSelectionChangedSignal(blocked);
}

void NavigatorView::nodeIdChanged(const ModelNode& node, const QString & /*newId*/, const QString & /*oldId*/)
{
    if (m_treeModel->isInTree(node))
        m_treeModel->updateItemRow(node);
}

void NavigatorView::propertiesAboutToBeRemoved(const QList<AbstractProperty>& propertyList)
{
    foreach (const AbstractProperty &property, propertyList) {
        if (property.isNodeAbstractProperty()) {
            NodeAbstractProperty nodeAbstractProperty(property.toNodeListProperty());
            foreach (const ModelNode &childNode, nodeAbstractProperty.directSubNodes()) {
                m_treeModel->removeSubTree(childNode);
            }
        }
    }
}

void NavigatorView::rootNodeTypeChanged(const QString & /*type*/, int /*majorVersion*/, int /*minorVersion*/)
{
    if (m_treeModel->isInTree(rootModelNode()))
        m_treeModel->updateItemRow(rootModelNode());
}

void NavigatorView::auxiliaryDataChanged(const ModelNode &modelNode, const PropertyName & name, const QVariant & /*data*/)
{
    if (name == "invisible" && m_treeModel->isInTree(modelNode))
    {
        // update model
        m_treeModel->updateItemRow(modelNode);

        // repaint row (id and icon)
        foreach (const ModelNode &currentModelNode, modelNode.allSubModelNodesAndThisNode()) {
            QModelIndex index = m_treeModel->indexForNode(currentModelNode);
            treeWidget()->update(index);
            treeWidget()->update(index.sibling(index.row(),index.column()+1));
        }
    }
}

void NavigatorView::nodeOrderChanged(const NodeListProperty &listProperty, const ModelNode &node, int /*oldIndex*/)
{
    if (m_treeModel->isInTree(node)) {
        m_treeModel->removeSubTree(listProperty.parentModelNode());

        if (node.isInHierarchy())
            m_treeModel->addSubTree(listProperty.parentModelNode());

    }
}

void NavigatorView::changeToComponent(const QModelIndex &index)
{
    if (index.isValid() && m_treeModel->data(index, Qt::UserRole).isValid()) {
        ModelNode doubleClickNode = m_treeModel->nodeForIndex(index);
        if (doubleClickNode.metaInfo().isFileComponent())
            Core::EditorManager::openEditor(doubleClickNode.metaInfo().componentFileName(),
                                            Core::Id(), Core::EditorManager::DoNotMakeVisible);
    }
}

void NavigatorView::leftButtonClicked()
{
    if (selectedModelNodes().count() > 1)
        return; //Semantics are unclear for multi selection.

    bool blocked = blockSelectionChangedSignal(true);

    foreach (const ModelNode &node, selectedModelNodes()) {
        if (!node.isRootNode() && !node.parentProperty().parentModelNode().isRootNode()) {
            if (QmlItemNode::isValidQmlItemNode(node)) {
                QPointF scenePos = QmlItemNode(node).instanceScenePosition();
                node.parentProperty().parentProperty().reparentHere(node);
                if (!scenePos.isNull())
                    setScenePos(node, scenePos);
            } else {
                node.parentProperty().parentProperty().reparentHere(node);
            }
        }
    }
    updateItemSelection();
    blockSelectionChangedSignal(blocked);
}

void NavigatorView::rightButtonClicked()
{
    if (selectedModelNodes().count() > 1)
        return; //Semantics are unclear for multi selection.

    bool blocked = blockSelectionChangedSignal(true);
    foreach (const ModelNode &node, selectedModelNodes()) {
        if (!node.isRootNode() && node.parentProperty().isNodeListProperty() && node.parentProperty().count() > 1) {
            int index = node.parentProperty().indexOf(node);
            index--;
            if (index >= 0) { //for the first node the semantics are not clear enough. Wrapping would be irritating.
                ModelNode newParent = node.parentProperty().toNodeListProperty().at(index);

                if (QmlItemNode::isValidQmlItemNode(node)
                        && QmlItemNode::isValidQmlItemNode(newParent)
                        && !newParent.metaInfo().defaultPropertyIsComponent()) {
                    QPointF scenePos = QmlItemNode(node).instanceScenePosition();
                    newParent.nodeAbstractProperty(newParent.metaInfo().defaultPropertyName()).reparentHere(node);
                    if (!scenePos.isNull())
                        setScenePos(node, scenePos);
                } else {
                    if (newParent.metaInfo().isValid() && !newParent.metaInfo().defaultPropertyIsComponent())
                        newParent.nodeAbstractProperty(newParent.metaInfo().defaultPropertyName()).reparentHere(node);
                }
            }
        }
    }
    updateItemSelection();
    blockSelectionChangedSignal(blocked);
}

void NavigatorView::upButtonClicked()
{
    bool blocked = blockSelectionChangedSignal(true);
    foreach (const ModelNode &node, selectedModelNodes()) {
        if (!node.isRootNode() && node.parentProperty().isNodeListProperty()) {
            int oldIndex = node.parentProperty().indexOf(node);
            int index = oldIndex;
            index--;
            if (index < 0)
                index = node.parentProperty().count() - 1; //wrap around
            node.parentProperty().toNodeListProperty().slide(oldIndex, index);
        }
    }
    updateItemSelection();
    blockSelectionChangedSignal(blocked);
}

void NavigatorView::downButtonClicked()
{
    bool blocked = blockSelectionChangedSignal(true);
    foreach (const ModelNode &node, selectedModelNodes()) {
        if (!node.isRootNode() && node.parentProperty().isNodeListProperty()) {
            int oldIndex = node.parentProperty().indexOf(node);
            int index = oldIndex;
            index++;
            if (index >= node.parentProperty().count())
                index = 0; //wrap around
            node.parentProperty().toNodeListProperty().slide(oldIndex, index);
        }
    }
    updateItemSelection();
    blockSelectionChangedSignal(blocked);
}

void NavigatorView::changeSelection(const QItemSelection & /*newSelection*/, const QItemSelection &/*deselected*/)
{
    if (m_blockSelectionChangedSignal)
        return;
    QSet<ModelNode> nodeSet;
    foreach (const QModelIndex &index, treeWidget()->selectionModel()->selectedIndexes()) {
        if (m_treeModel->data(index, Qt::UserRole).isValid())
            nodeSet.insert(m_treeModel->nodeForIndex(index));
    }

    bool blocked = blockSelectionChangedSignal(true);
    setSelectedModelNodes(nodeSet.toList());
    blockSelectionChangedSignal(blocked);
}

void NavigatorView::selectedNodesChanged(const QList<ModelNode> &/*selectedNodeList*/, const QList<ModelNode> &/*lastSelectedNodeList*/)
{
    updateItemSelection();
}

void NavigatorView::updateItemSelection()
{
    QItemSelection itemSelection;
    foreach (const ModelNode &node, selectedModelNodes()) {
        const QModelIndex index = m_treeModel->indexForNode(node);
        if (index.isValid()) {
            const QModelIndex beginIndex(m_treeModel->index(index.row(), 0, index.parent()));
            const QModelIndex endIndex(m_treeModel->index(index.row(), m_treeModel->columnCount(index.parent()) - 1, index.parent()));
            if (beginIndex.isValid() && endIndex.isValid())
                itemSelection.select(beginIndex, endIndex);
        }
    }

    bool blocked = blockSelectionChangedSignal(true);
    treeWidget()->selectionModel()->select(itemSelection, QItemSelectionModel::ClearAndSelect);
    blockSelectionChangedSignal(blocked);

    if (!selectedModelNodes().isEmpty())
        treeWidget()->scrollTo(m_treeModel->indexForNode(selectedModelNodes().first()));

    // make sure selected nodes a visible
    foreach (const QModelIndex &selectedIndex, itemSelection.indexes()) {
        if (selectedIndex.column() == 0)
            expandRecursively(selectedIndex);
    }
}

QTreeView *NavigatorView::treeWidget()
{
    if (m_widget)
        return m_widget->treeView();
    return 0;
}

NavigatorTreeModel *NavigatorView::treeModel()
{
    return m_treeModel.data();
}

// along the lines of QObject::blockSignals
bool NavigatorView::blockSelectionChangedSignal(bool block)
{
    bool oldValue = m_blockSelectionChangedSignal;
    m_blockSelectionChangedSignal = block;
    return oldValue;
}

void NavigatorView::expandRecursively(const QModelIndex &index)
{
    QModelIndex currentIndex = index;
    while (currentIndex.isValid()) {
        if (!treeWidget()->isExpanded(currentIndex))
            treeWidget()->expand(currentIndex);
        currentIndex = currentIndex.parent();
    }
}

} // namespace QmlDesigner
<commit_msg>QmlDesigner: Fix for the navigator<commit_after>/****************************************************************************
**
** Copyright (C) 2015 The Qt Company Ltd.
** Contact: http://www.qt.io/licensing
**
** This file is part of Qt Creator.
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company.  For licensing terms and
** conditions see http://www.qt.io/terms-conditions.  For further information
** use the contact form at http://www.qt.io/contact-us.
**
** GNU Lesser General Public License Usage
** Alternatively, this file may be used under the terms of the GNU Lesser
** General Public License version 2.1 or version 3 as published by the Free
** Software Foundation and appearing in the file LICENSE.LGPLv21 and
** LICENSE.LGPLv3 included in the packaging of this file.  Please review the
** following information to ensure the GNU Lesser General Public License
** requirements will be met: https://www.gnu.org/licenses/lgpl.html and
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
**
** In addition, as a special exception, The Qt Company gives you certain additional
** rights.  These rights are described in The Qt Company LGPL Exception
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
**
****************************************************************************/

#include "navigatorview.h"
#include "navigatortreemodel.h"
#include "navigatorwidget.h"
#include "nameitemdelegate.h"
#include "iconcheckboxitemdelegate.h"

#include <coreplugin/editormanager/editormanager.h>
#include <coreplugin/icore.h>

#include <designmodecontext.h>
#include <nodeproperty.h>
#include <nodelistproperty.h>
#include <variantproperty.h>
#include <QHeaderView>
#include <qmlitemnode.h>

static inline void setScenePos(const QmlDesigner::ModelNode &modelNode,const QPointF &pos)
{
    if (modelNode.hasParentProperty() && QmlDesigner::QmlItemNode::isValidQmlItemNode(modelNode.parentProperty().parentModelNode())) {
        QmlDesigner::QmlItemNode parentNode = modelNode.parentProperty().parentQmlObjectNode().toQmlItemNode();

        if (!parentNode.modelNode().metaInfo().isLayoutable()) {
            QPointF localPos = parentNode.instanceSceneTransform().inverted().map(pos);
            modelNode.variantProperty("x").setValue(localPos.toPoint().x());
            modelNode.variantProperty("y").setValue(localPos.toPoint().y());
        } else { //Items in Layouts do not have a position
            modelNode.removeProperty("x");
            modelNode.removeProperty("y");
        }
    }
}

namespace QmlDesigner {

NavigatorView::NavigatorView(QObject* parent) :
        AbstractView(parent),
        m_blockSelectionChangedSignal(false),
        m_widget(new NavigatorWidget(this)),
        m_treeModel(new NavigatorTreeModel(this))
{
    Internal::NavigatorContext *navigatorContext = new Internal::NavigatorContext(m_widget.data());
    Core::ICore::addContextObject(navigatorContext);

    m_widget->setTreeModel(m_treeModel.data());

    connect(treeWidget()->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(changeSelection(QItemSelection,QItemSelection)));

    connect(m_widget.data(), SIGNAL(leftButtonClicked()), this, SLOT(leftButtonClicked()));
    connect(m_widget.data(), SIGNAL(rightButtonClicked()), this, SLOT(rightButtonClicked()));
    connect(m_widget.data(), SIGNAL(downButtonClicked()), this, SLOT(downButtonClicked()));
    connect(m_widget.data(), SIGNAL(upButtonClicked()), this, SLOT(upButtonClicked()));

    treeWidget()->setIndentation(treeWidget()->indentation() * 0.5);

    NameItemDelegate *idDelegate = new NameItemDelegate(this,
                                                        m_treeModel.data());
    IconCheckboxItemDelegate *showDelegate = new IconCheckboxItemDelegate(this,
                                                                          ":/qmldesigner/images/eye_open.png",
                                                                          ":/qmldesigner/images/placeholder.png",
                                                                          m_treeModel.data());

#ifdef _LOCK_ITEMS_
    IconCheckboxItemDelegate *lockDelegate = new IconCheckboxItemDelegate(this,":/qmldesigner/images/lock.png",
                                                          ":/qmldesigner/images/hole.png",m_treeModel.data());
#endif

    treeWidget()->setItemDelegateForColumn(0,idDelegate);
#ifdef _LOCK_ITEMS_
    treeWidget()->setItemDelegateForColumn(1,lockDelegate);
    treeWidget()->setItemDelegateForColumn(2,showDelegate);
#else
    treeWidget()->setItemDelegateForColumn(1,showDelegate);
#endif

}

NavigatorView::~NavigatorView()
{
    if (m_widget && !m_widget->parent())
        delete m_widget.data();
}

bool NavigatorView::hasWidget() const
{
    return true;
}

WidgetInfo NavigatorView::widgetInfo()
{
    return createWidgetInfo(m_widget.data(),
                            new WidgetInfo::ToolBarWidgetDefaultFactory<NavigatorWidget>(m_widget.data()),
                            QStringLiteral("Navigator"),
                            WidgetInfo::LeftPane,
                            0);
}

void NavigatorView::modelAttached(Model *model)
{
    AbstractView::modelAttached(model);

    m_treeModel->setView(this);

    QTreeView *treeView = treeWidget();
    treeView->expandAll();

    treeView->header()->setSectionResizeMode(0, QHeaderView::Stretch);
    treeView->header()->resizeSection(1,26);
    treeView->setRootIsDecorated(false);
    treeView->setIndentation(20);
#ifdef _LOCK_ITEMS_
    treeView->header()->resizeSection(2,20);
#endif
}

void NavigatorView::modelAboutToBeDetached(Model *model)
{
    m_treeModel->removeSubTree(rootModelNode());
    m_treeModel->clearView();
    AbstractView::modelAboutToBeDetached(model);
}

void NavigatorView::importsChanged(const QList<Import> &/*addedImports*/, const QList<Import> &/*removedImports*/)
{
    treeWidget()->update();
}

void NavigatorView::nodeAboutToBeRemoved(const ModelNode &removedNode)
{
    m_treeModel->removeSubTree(removedNode);
}

void NavigatorView::nodeReparented(const ModelNode &node, const NodeAbstractProperty & newPropertyParent, const NodeAbstractProperty & /*oldPropertyParent*/, AbstractView::PropertyChangeFlags /*propertyChange*/)
{
    bool blocked = blockSelectionChangedSignal(true);

    m_treeModel->removeSubTree(node);

    if (node.isInHierarchy())
        m_treeModel->addSubTree(node);

    // make sure selection is in sync again
    updateItemSelection();

    if (newPropertyParent.parentModelNode().isValid()) {
        QModelIndex index = m_treeModel->indexForNode(newPropertyParent.parentModelNode());
        treeWidget()->expand(index);
    }

    blockSelectionChangedSignal(blocked);
}

void NavigatorView::nodeIdChanged(const ModelNode& node, const QString & /*newId*/, const QString & /*oldId*/)
{
    if (m_treeModel->isInTree(node))
        m_treeModel->updateItemRow(node);
}

void NavigatorView::propertiesAboutToBeRemoved(const QList<AbstractProperty>& propertyList)
{
    foreach (const AbstractProperty &property, propertyList) {
        if (property.isNodeAbstractProperty()) {
            NodeAbstractProperty nodeAbstractProperty(property.toNodeListProperty());
            foreach (const ModelNode &childNode, nodeAbstractProperty.directSubNodes()) {
                m_treeModel->removeSubTree(childNode);
            }
        }
    }
}

void NavigatorView::rootNodeTypeChanged(const QString & /*type*/, int /*majorVersion*/, int /*minorVersion*/)
{
    if (m_treeModel->isInTree(rootModelNode()))
        m_treeModel->updateItemRow(rootModelNode());
}

void NavigatorView::auxiliaryDataChanged(const ModelNode &modelNode, const PropertyName & name, const QVariant & /*data*/)
{
    if (name == "invisible" && m_treeModel->isInTree(modelNode))
    {
        // update model
        m_treeModel->updateItemRow(modelNode);

        // repaint row (id and icon)
        foreach (const ModelNode &currentModelNode, modelNode.allSubModelNodesAndThisNode()) {
            QModelIndex index = m_treeModel->indexForNode(currentModelNode);
            treeWidget()->update(index);
            treeWidget()->update(index.sibling(index.row(),index.column()+1));
        }
    }
}

void NavigatorView::nodeOrderChanged(const NodeListProperty &listProperty, const ModelNode &node, int /*oldIndex*/)
{
    if (m_treeModel->isInTree(node)) {
        m_treeModel->removeSubTree(listProperty.parentModelNode());

        if (node.isInHierarchy())
            m_treeModel->addSubTree(listProperty.parentModelNode());

        if (listProperty.parentModelNode().isValid()) {
            QModelIndex index = m_treeModel->indexForNode(listProperty.parentModelNode());
            treeWidget()->expand(index);
        }
    }
}

void NavigatorView::changeToComponent(const QModelIndex &index)
{
    if (index.isValid() && m_treeModel->data(index, Qt::UserRole).isValid()) {
        ModelNode doubleClickNode = m_treeModel->nodeForIndex(index);
        if (doubleClickNode.metaInfo().isFileComponent())
            Core::EditorManager::openEditor(doubleClickNode.metaInfo().componentFileName(),
                                            Core::Id(), Core::EditorManager::DoNotMakeVisible);
    }
}

void NavigatorView::leftButtonClicked()
{
    if (selectedModelNodes().count() > 1)
        return; //Semantics are unclear for multi selection.

    bool blocked = blockSelectionChangedSignal(true);

    foreach (const ModelNode &node, selectedModelNodes()) {
        if (!node.isRootNode() && !node.parentProperty().parentModelNode().isRootNode()) {
            if (QmlItemNode::isValidQmlItemNode(node)) {
                QPointF scenePos = QmlItemNode(node).instanceScenePosition();
                node.parentProperty().parentProperty().reparentHere(node);
                if (!scenePos.isNull())
                    setScenePos(node, scenePos);
            } else {
                node.parentProperty().parentProperty().reparentHere(node);
            }
        }
    }
    updateItemSelection();
    blockSelectionChangedSignal(blocked);
}

void NavigatorView::rightButtonClicked()
{
    if (selectedModelNodes().count() > 1)
        return; //Semantics are unclear for multi selection.

    bool blocked = blockSelectionChangedSignal(true);
    foreach (const ModelNode &node, selectedModelNodes()) {
        if (!node.isRootNode() && node.parentProperty().isNodeListProperty() && node.parentProperty().count() > 1) {
            int index = node.parentProperty().indexOf(node);
            index--;
            if (index >= 0) { //for the first node the semantics are not clear enough. Wrapping would be irritating.
                ModelNode newParent = node.parentProperty().toNodeListProperty().at(index);

                if (QmlItemNode::isValidQmlItemNode(node)
                        && QmlItemNode::isValidQmlItemNode(newParent)
                        && !newParent.metaInfo().defaultPropertyIsComponent()) {
                    QPointF scenePos = QmlItemNode(node).instanceScenePosition();
                    newParent.nodeAbstractProperty(newParent.metaInfo().defaultPropertyName()).reparentHere(node);
                    if (!scenePos.isNull())
                        setScenePos(node, scenePos);
                } else {
                    if (newParent.metaInfo().isValid() && !newParent.metaInfo().defaultPropertyIsComponent())
                        newParent.nodeAbstractProperty(newParent.metaInfo().defaultPropertyName()).reparentHere(node);
                }
            }
        }
    }
    updateItemSelection();
    blockSelectionChangedSignal(blocked);
}

void NavigatorView::upButtonClicked()
{
    bool blocked = blockSelectionChangedSignal(true);
    foreach (const ModelNode &node, selectedModelNodes()) {
        if (!node.isRootNode() && node.parentProperty().isNodeListProperty()) {
            int oldIndex = node.parentProperty().indexOf(node);
            int index = oldIndex;
            index--;
            if (index < 0)
                index = node.parentProperty().count() - 1; //wrap around
            node.parentProperty().toNodeListProperty().slide(oldIndex, index);
        }
    }
    updateItemSelection();
    blockSelectionChangedSignal(blocked);
}

void NavigatorView::downButtonClicked()
{
    bool blocked = blockSelectionChangedSignal(true);
    foreach (const ModelNode &node, selectedModelNodes()) {
        if (!node.isRootNode() && node.parentProperty().isNodeListProperty()) {
            int oldIndex = node.parentProperty().indexOf(node);
            int index = oldIndex;
            index++;
            if (index >= node.parentProperty().count())
                index = 0; //wrap around
            node.parentProperty().toNodeListProperty().slide(oldIndex, index);
        }
    }
    updateItemSelection();
    blockSelectionChangedSignal(blocked);
}

void NavigatorView::changeSelection(const QItemSelection & /*newSelection*/, const QItemSelection &/*deselected*/)
{
    if (m_blockSelectionChangedSignal)
        return;
    QSet<ModelNode> nodeSet;
    foreach (const QModelIndex &index, treeWidget()->selectionModel()->selectedIndexes()) {
        if (m_treeModel->data(index, Qt::UserRole).isValid())
            nodeSet.insert(m_treeModel->nodeForIndex(index));
    }

    bool blocked = blockSelectionChangedSignal(true);
    setSelectedModelNodes(nodeSet.toList());
    blockSelectionChangedSignal(blocked);
}

void NavigatorView::selectedNodesChanged(const QList<ModelNode> &/*selectedNodeList*/, const QList<ModelNode> &/*lastSelectedNodeList*/)
{
    updateItemSelection();
}

void NavigatorView::updateItemSelection()
{
    QItemSelection itemSelection;
    foreach (const ModelNode &node, selectedModelNodes()) {
        const QModelIndex index = m_treeModel->indexForNode(node);
        if (index.isValid()) {
            const QModelIndex beginIndex(m_treeModel->index(index.row(), 0, index.parent()));
            const QModelIndex endIndex(m_treeModel->index(index.row(), m_treeModel->columnCount(index.parent()) - 1, index.parent()));
            if (beginIndex.isValid() && endIndex.isValid())
                itemSelection.select(beginIndex, endIndex);
        }
    }

    bool blocked = blockSelectionChangedSignal(true);
    treeWidget()->selectionModel()->select(itemSelection, QItemSelectionModel::ClearAndSelect);
    blockSelectionChangedSignal(blocked);

    if (!selectedModelNodes().isEmpty())
        treeWidget()->scrollTo(m_treeModel->indexForNode(selectedModelNodes().first()));

    // make sure selected nodes a visible
    foreach (const QModelIndex &selectedIndex, itemSelection.indexes()) {
        if (selectedIndex.column() == 0)
            expandRecursively(selectedIndex);
    }
}

QTreeView *NavigatorView::treeWidget()
{
    if (m_widget)
        return m_widget->treeView();
    return 0;
}

NavigatorTreeModel *NavigatorView::treeModel()
{
    return m_treeModel.data();
}

// along the lines of QObject::blockSignals
bool NavigatorView::blockSelectionChangedSignal(bool block)
{
    bool oldValue = m_blockSelectionChangedSignal;
    m_blockSelectionChangedSignal = block;
    return oldValue;
}

void NavigatorView::expandRecursively(const QModelIndex &index)
{
    QModelIndex currentIndex = index;
    while (currentIndex.isValid()) {
        if (!treeWidget()->isExpanded(currentIndex))
            treeWidget()->expand(currentIndex);
        currentIndex = currentIndex.parent();
    }
}

} // namespace QmlDesigner
<|endoftext|>


<commit_before>#include "MotorEncoderDriver2013.h"
#include <sstream>
#include <iostream>
#include <common/utils/StringUtils.hpp>
#include <common/logger/logger.h>
#include <algorithm>

using namespace std;

MotorEncoderDriver2013::MotorEncoderDriver2013()
 : _arduino("/dev/igvc_motor_arduino", 9600)
{
    qRegisterMetaType<MotorCommand>("MotorCommand");
    _leftVel = 0;
    _rightVel = 0;
    _maxVel = 2.0;
    //writeVelocities();
    if(_arduino.isConnected())
    {
        cout << "Waiting for motor arduino.";
        cout.flush();
        string line;
        while((line = _arduino.readln()).compare("Ready"))
        {
            //cout << line << endl;
            cout << ".";
            cout.flush();
            usleep(250);
        }
        cout << endl;
        Logger::Log(LogLevel::Info, "Motor arduino connected.");
    }
    else
    {
        Logger::Log(LogLevel::Warning, "Motor arduino not connected. Commands will be ignored.");
    }
    _thread = boost::thread(boost::bind(&MotorEncoderDriver2013::run, this));
}

void MotorEncoderDriver2013::run()
{
    while(true)
    {
        try
        {
            boost::this_thread::interruption_point();
        }
        catch (boost::thread_interrupted)
        {
            return;
        }
        writeVelocities();
        usleep(10000);
    }
}

double MotorEncoderDriver2013::getLeftSetVelocity()
{
    return _leftVel;
}

double MotorEncoderDriver2013::getRightSetVelocity()
{
    return _rightVel;
}

double MotorEncoderDriver2013::getLeftCurrentVelocity()
{
    return _leftCurrVel;
}

double MotorEncoderDriver2013::getRightCurrentVelocity()
{
    return _rightCurrVel;
}

void MotorEncoderDriver2013::stop()
{
    _leftVel = 0;
    _rightVel = 0;
}

void MotorEncoderDriver2013::setVelocities(double left, double right)
{
    _leftVel = left;
    _rightVel = right;
}

void MotorEncoderDriver2013::setLeftVelocity(double vel)
{
    _leftVel = vel;
}

void MotorEncoderDriver2013::setRightVelocity(double vel)
{
    _rightVel = vel;
}

void MotorEncoderDriver2013::writeVelocities()
{
    if(_arduino.isConnected())
    {
        std::ostringstream msg;

        /*
         * The newline character below must be there.
         * In the Arduino code, Serial.parseFloat is used.
         * This command will slow down significantly if
         * you do not follow the number with a non-numeric
         * character. Here, the newline does this for us.
         *
         * Checking the E-Stop status prevents the PID
         * controller from ramping up to full PWM when
         * the E-Stop is causing all PWMs to map to
         * 0 m/s.
         */
        //msg << '$' << ( _isEnabled ? _leftVel : 0 ) << ',' << ( _isEnabled ? _rightVel : 0 ) << '\n';
        msg << '$' << _leftVel << ',' << _rightVel << '\n';

        _portLock.lock();
        _arduino.write(msg.str());
        std::string ret = _arduino.readln();
        try {
            if(!ret.empty())
            {
                size_t dollar = ret.find('$');
                size_t comma = ret.find(',');
                size_t end = ret.find('\n');
                std::string leftStr = ret.substr(dollar+1, comma-dollar-1);
                std::string rightStr = ret.substr(comma+1, end-comma-1);
                _leftCurrVel = atof(leftStr.c_str());
                _rightCurrVel = atof(rightStr.c_str());
                newCurrentVelocities(_leftCurrVel, _rightCurrVel);
            }
        } catch (std::out_of_range) { }
        _portLock.unlock();
    }
}

void MotorEncoderDriver2013::setMotorCommand(MotorCommand cmd)
{
    setVelocities(cmd.leftVel, cmd.rightVel);
}

bool MotorEncoderDriver2013::isOpen()
{
    return _arduino.isConnected();
}

MotorEncoderDriver2013::~MotorEncoderDriver2013()
{
    stop();
    _arduino.close();
}

void MotorEncoderDriver2013::onEStopStatusChanged(bool isEnabled)
{
    _isEnabled = isEnabled;
}
<commit_msg>Fixes bug in motor driver that was causing crash on close.<commit_after>#include "MotorEncoderDriver2013.h"
#include <sstream>
#include <iostream>
#include <common/utils/StringUtils.hpp>
#include <common/logger/logger.h>
#include <algorithm>

using namespace std;

MotorEncoderDriver2013::MotorEncoderDriver2013()
 : _arduino("/dev/igvc_motor_arduino", 9600)
{
    qRegisterMetaType<MotorCommand>("MotorCommand");
    _leftVel = 0;
    _rightVel = 0;
    _maxVel = 2.0;
    if(_arduino.isConnected())
    {
        cout << "Waiting for motor arduino.";
        cout.flush();
        string line;
        while((line = _arduino.readln()).compare("Ready"))
        {
            cout << ".";
            cout.flush();
            usleep(250);
        }
        cout << endl;
        Logger::Log(LogLevel::Info, "Motor arduino connected.");
        _thread = boost::thread(boost::bind(&MotorEncoderDriver2013::run, this));
    }
    else
    {
        Logger::Log(LogLevel::Warning, "Motor arduino not connected. Commands will be ignored.");
    }
}

void MotorEncoderDriver2013::run()
{
    while(true)
    {
        try
        {
            boost::this_thread::interruption_point();
        }
        catch (boost::thread_interrupted)
        {
            return;
        }
        writeVelocities();
        usleep(10000);
    }
}

double MotorEncoderDriver2013::getLeftSetVelocity()
{
    return _leftVel;
}

double MotorEncoderDriver2013::getRightSetVelocity()
{
    return _rightVel;
}

double MotorEncoderDriver2013::getLeftCurrentVelocity()
{
    return _leftCurrVel;
}

double MotorEncoderDriver2013::getRightCurrentVelocity()
{
    return _rightCurrVel;
}

void MotorEncoderDriver2013::stop()
{
    _leftVel = 0;
    _rightVel = 0;
}

void MotorEncoderDriver2013::setVelocities(double left, double right)
{
    _leftVel = left;
    _rightVel = right;
}

void MotorEncoderDriver2013::setLeftVelocity(double vel)
{
    _leftVel = vel;
}

void MotorEncoderDriver2013::setRightVelocity(double vel)
{
    _rightVel = vel;
}

void MotorEncoderDriver2013::writeVelocities()
{
    if(_arduino.isConnected())
    {
        std::ostringstream msg;

        /*
         * The newline character below must be there.
         * In the Arduino code, Serial.parseFloat is used.
         * This command will slow down significantly if
         * you do not follow the number with a non-numeric
         * character. Here, the newline does this for us.
         *
         * Checking the E-Stop status prevents the PID
         * controller from ramping up to full PWM when
         * the E-Stop is causing all PWMs to map to
         * 0 m/s.
         */
        //msg << '$' << ( _isEnabled ? _leftVel : 0 ) << ',' << ( _isEnabled ? _rightVel : 0 ) << '\n';
        msg << '$' << _leftVel << ',' << _rightVel << '\n';

        _portLock.lock();
        try{
            _arduino.write(msg.str());
        }catch(...){
            Logger::Log(LogLevel::Error, "Error writing velocities to motors.");
        }

        std::string ret = _arduino.readln();
        try {
            if(!ret.empty())
            {
                size_t dollar = ret.find('$');
                size_t comma = ret.find(',');
                size_t end = ret.find('\n');
                std::string leftStr = ret.substr(dollar+1, comma-dollar-1);
                std::string rightStr = ret.substr(comma+1, end-comma-1);
                _leftCurrVel = atof(leftStr.c_str());
                _rightCurrVel = atof(rightStr.c_str());
                newCurrentVelocities(_leftCurrVel, _rightCurrVel);
            }
        } catch (std::out_of_range) { }
        _portLock.unlock();
    }
}

void MotorEncoderDriver2013::setMotorCommand(MotorCommand cmd)
{
    setVelocities(cmd.leftVel, cmd.rightVel);
}

bool MotorEncoderDriver2013::isOpen()
{
    return _arduino.isConnected();
}

MotorEncoderDriver2013::~MotorEncoderDriver2013()
{
    stop();
    _arduino.close();
}

void MotorEncoderDriver2013::onEStopStatusChanged(bool isEnabled)
{
    _isEnabled = isEnabled;
}
<|endoftext|>


<commit_before>/*
 * Copyright (C) 2009 Toni Gundogdu.
 *
 * This file is part of cclive.
 *
 * cclive is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * cclive 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.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <sstream>
#include <vector>
#include <iomanip>

#include <pcrecpp.h>

#include "except.h"
#include "log.h"
#include "opts.h"
#include "util.h"
#include "quvi.h"

QuviMgr::QuviMgr()
    : quvi(NULL)
{
}

// Keeps -Weffc++ happy.
QuviMgr::QuviMgr(const QuviMgr&)
    : quvi(NULL)
{
}

// Ditto.
QuviMgr&
QuviMgr::operator=(const QuviMgr&) {
    return *this;
}

QuviMgr::~QuviMgr() {
    quvi_close(&quvi);
}

static void
handle_error(QUVIcode rc) {

    switch (rc) {
    case QUVI_NOSUPPORT:
        throw NoSupportException(s.str());
    case QUVI_PCRE:
        throw ParseException(s.str());
    default:
        break;
    }

    std::stringstream s;

    s << "quvi: "
      << quvi_strerror(quvimgr.handle(),rc);

    throw QuviException(s.str());
}

static int
status_callback(long param, void *data) {
    quvi_word status = quvi_loword(param);
    quvi_word type   = quvi_hiword(param);

    switch (status) {
    case QUVIS_FETCH:
        switch (type) {
        default:
            logmgr.cout()
                << "fetch "
                << static_cast<char *>(data)
                << " ...";
            break;
        case QUVIST_CONFIG:
            logmgr.cout()
                << "fetch config ...";
            break;
        case QUVIST_PLAYLIST:
            logmgr.cout()
                << "fetch playlist ...";
            break;
        case QUVIST_DONE:
            logmgr.cout()
                << "done."
                << std::endl;
            break;
        }
        break;

    case QUVIS_VERIFY:
        switch (type) {
        default:
            logmgr.cout()
                << "verify video link ...";
            break;
        case QUVIST_DONE:
            logmgr.cout()
                << "done."
                << std::endl;
            break;
        }
        break;
    }

    logmgr.cout() << std::flush;

    return 0;
}

void
QuviMgr::init() {
    QUVIcode rc = quvi_init(&quvi);
    if (rc != QUVI_OK)
        handle_error(rc);

    quvi_setopt(quvi,
        QUVIOPT_STATUSFUNCTION, status_callback);
}

quvi_t
QuviMgr::handle() const {
    return quvi;
}

void
QuviMgr::curlHandle(CURL **curl) {
    assert(curl != 0);
    quvi_getinfo(quvi, QUVII_CURL, curl);
}

// QuviVideo

QuviVideo::QuviVideo()
    : length    (0),
      pageLink  (""),
      id        (""),
      title     (""),
      link      (""),
      suffix    (""),
      contentType(""),
      hostId    (""),
      initial   (0),
      filename  ("")
{
}

QuviVideo::QuviVideo(const std::string& url)
    : length    (0),
      pageLink  (url),
      id        (""),
      title     (""),
      link      (""),
      suffix    (""),
      contentType(""),
      hostId    (""),
      initial   (0),
      filename  ("")
{
}

QuviVideo::QuviVideo(const QuviVideo& o)
    : length    (o.length),
      pageLink  (o.pageLink),
      id        (o.id),
      title     (o.title),
      link      (o.link),
      suffix    (o.suffix),
      contentType(o.contentType),
      hostId    (o.hostId),
      initial   (o.initial),
      filename  (o.filename)
{
}

QuviVideo&
QuviVideo::operator=(const QuviVideo& o) {
    length  = o.length;
    pageLink= o.pageLink;
    id      = o.id;
    title   = o.title;
    link    = o.link;
    suffix  = o.suffix;
    contentType = o.contentType;
    hostId  = o.hostId;
    initial = o.initial;
    filename= o.filename;
    return *this;
}

QuviVideo::~QuviVideo() {
}

void
QuviVideo::parse(std::string url /*=""*/) {

    if (url.empty())
        url = pageLink;

    assert(!url.empty());

    const Options opts =
        optsmgr.getOptions();

    if (opts.format_given) {
        quvi_setopt(quvimgr.handle(),
            QUVIOPT_FORMAT, opts.format_arg);
    }

    CURL *curl = 0;
    quvimgr.curlHandle(&curl);
    assert(curl != 0);

    curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT,
        opts.connect_timeout_arg);

    curl_easy_setopt(curl, CURLOPT_TIMEOUT,
        opts.connect_timeout_socks_arg);

    quvi_video_t video;

    QUVIcode rc =
        quvi_parse(quvimgr.handle(),
            const_cast<char*>(url.c_str()), &video);

    curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 0L);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0L);

    if (rc != QUVI_OK)
        handle_error(rc);

    quvi_getprop(video, QUVIP_LENGTH, &length);

#define _getprop(id,dst) \
    do { quvi_getprop(video, id, &s); dst = s; } while (0)

    char *s; // _getprop uses this.
    _getprop(QUVIP_PAGELINK, pageLink);
    _getprop(QUVIP_ID, id);
    _getprop(QUVIP_TITLE, title);
    _getprop(QUVIP_LINK, link);
    _getprop(QUVIP_SUFFIX, suffix);
    _getprop(QUVIP_CONTENTTYPE, contentType);
    _getprop(QUVIP_HOSTID, hostId);
#undef _getstr
    quvi_parse_close(&video);

    formatOutputFilename();
}

static int video_num = 0;

void
QuviVideo::formatOutputFilename() {

    const Options opts = optsmgr.getOptions();

    if (!opts.output_video_given) {
        std::stringstream b;

        if (opts.number_videos_given) {
            b << std::setw(4)
              << std::setfill('0')
              << ++video_num
              << "_";
        }

        customOutputFilenameFormatter(b);

        filename = b.str();

        typedef unsigned int _uint;

        for (register _uint i=1;
            i<INT_MAX && !opts.overwrite_given; ++i) {

            initial = Util::fileExists(filename);

            if (initial == 0)
                break;
            else if (initial >= length)
                throw NothingToDoException();
            else {
                if (opts.continue_given)
                    break;
            }

            std::stringstream tmp;
            tmp << b.str() << "." << i;
            filename = tmp.str();
        }
    }
    else {
        initial = Util::fileExists(opts.output_video_arg);

        if (initial >= length)
            throw NothingToDoException();

        if (opts.overwrite_given)
            initial = 0;

        filename = opts.output_video_arg;
    }

    if (!opts.continue_given)
        initial = 0;
}

void
QuviVideo::customOutputFilenameFormatter(
    std::stringstream& b)
{
    const Options opts  = optsmgr.getOptions();
    std::string fmt     = opts.filename_format_arg;

    std::string _id = this->id;
    Util::subStrReplace(_id, "-", "_");

    std::string _title = title;

    // Apply --regexp.
    if (opts.regexp_given)
        applyTitleRegexp(_title);

    // Remove leading and trailing whitespace.
    pcrecpp::RE("^[\\s]+", pcrecpp::UTF8())
        .Replace("", &_title);
    pcrecpp::RE("\\s+$", pcrecpp::UTF8())
        .Replace("", &_title);

    // Replace format specifiers.
    Util::subStrReplace(fmt, "%t", _title.empty() ? _id : _title);
    Util::subStrReplace(fmt, "%i", _id);
    Util::subStrReplace(fmt, "%h", hostId);
    Util::subStrReplace(fmt, "%s", suffix);

    b << fmt;
}

void
QuviVideo::applyTitleRegexp(std::string& src) {

    const Options opts =
        optsmgr.getOptions();

    if (opts.find_all_given) {
        pcrecpp::StringPiece sp(src);
        pcrecpp::RE re(opts.regexp_arg, pcrecpp::UTF8());

        src.clear();

        std::string s;
        while (re.FindAndConsume(&sp, &s))
            src += s;
    }
    else {
        std::string tmp = src;
        src.clear();

        pcrecpp::RE(opts.regexp_arg, pcrecpp::UTF8())
            .PartialMatch(tmp, &src);
    }
}

const double&
QuviVideo::getLength() const {
    return length;
}

const std::string&
QuviVideo::getPageLink() const {
    return pageLink;
}

const std::string&
QuviVideo::getId() const {
    return id;
}

const std::string&
QuviVideo::getTitle() const {
    return title;
}

const std::string&
QuviVideo::getLink() const {
    return link;
}

const std::string&
QuviVideo::getSuffix() const {
    return suffix;
}

const std::string&
QuviVideo::getContentType() const {
    return contentType;
}

const std::string&
QuviVideo::getHostId() const {
    return hostId;
}

const double&
QuviVideo::getInitial() const {
    return initial;
}

const std::string&
QuviVideo::getFilename() const {
    return filename;
}

void
QuviVideo::updateInitial() {
    initial = Util::fileExists(filename);

    if (initial >= length)
        throw NothingToDoException();
}

<commit_msg>Revert "quvi.cpp: cleanup handler_error."<commit_after>/*
 * Copyright (C) 2009 Toni Gundogdu.
 *
 * This file is part of cclive.
 *
 * cclive is free software: you can redistribute it and/or modify it under the
 * terms of the GNU General Public License as published by the Free Software
 * Foundation, either version 3 of the License, or (at your option) any later
 * version.
 *
 * cclive 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.  See the GNU General Public License for more
 * details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "config.h"

#include <sstream>
#include <vector>
#include <iomanip>

#include <pcrecpp.h>

#include "except.h"
#include "log.h"
#include "opts.h"
#include "util.h"
#include "quvi.h"

QuviMgr::QuviMgr()
    : quvi(NULL)
{
}

// Keeps -Weffc++ happy.
QuviMgr::QuviMgr(const QuviMgr&)
    : quvi(NULL)
{
}

// Ditto.
QuviMgr&
QuviMgr::operator=(const QuviMgr&) {
    return *this;
}

QuviMgr::~QuviMgr() {
    quvi_close(&quvi);
}

static void
handle_error(QUVIcode rc) {
    std::stringstream s;

    s << "quvi: "
      << quvi_strerror(quvimgr.handle(),rc);

    switch (rc) {
    case QUVI_NOSUPPORT:
        throw NoSupportException(s.str());
    case QUVI_PCRE:
        throw ParseException(s.str());
    default:
        break;
    }
    throw QuviException(s.str());
}

static int
status_callback(long param, void *data) {
    quvi_word status = quvi_loword(param);
    quvi_word type   = quvi_hiword(param);

    switch (status) {
    case QUVIS_FETCH:
        switch (type) {
        default:
            logmgr.cout()
                << "fetch "
                << static_cast<char *>(data)
                << " ...";
            break;
        case QUVIST_CONFIG:
            logmgr.cout()
                << "fetch config ...";
            break;
        case QUVIST_PLAYLIST:
            logmgr.cout()
                << "fetch playlist ...";
            break;
        case QUVIST_DONE:
            logmgr.cout()
                << "done."
                << std::endl;
            break;
        }
        break;

    case QUVIS_VERIFY:
        switch (type) {
        default:
            logmgr.cout()
                << "verify video link ...";
            break;
        case QUVIST_DONE:
            logmgr.cout()
                << "done."
                << std::endl;
            break;
        }
        break;
    }

    logmgr.cout() << std::flush;

    return 0;
}

void
QuviMgr::init() {
    QUVIcode rc = quvi_init(&quvi);
    if (rc != QUVI_OK)
        handle_error(rc);

    quvi_setopt(quvi,
        QUVIOPT_STATUSFUNCTION, status_callback);
}

quvi_t
QuviMgr::handle() const {
    return quvi;
}

void
QuviMgr::curlHandle(CURL **curl) {
    assert(curl != 0);
    quvi_getinfo(quvi, QUVII_CURL, curl);
}

// QuviVideo

QuviVideo::QuviVideo()
    : length    (0),
      pageLink  (""),
      id        (""),
      title     (""),
      link      (""),
      suffix    (""),
      contentType(""),
      hostId    (""),
      initial   (0),
      filename  ("")
{
}

QuviVideo::QuviVideo(const std::string& url)
    : length    (0),
      pageLink  (url),
      id        (""),
      title     (""),
      link      (""),
      suffix    (""),
      contentType(""),
      hostId    (""),
      initial   (0),
      filename  ("")
{
}

QuviVideo::QuviVideo(const QuviVideo& o)
    : length    (o.length),
      pageLink  (o.pageLink),
      id        (o.id),
      title     (o.title),
      link      (o.link),
      suffix    (o.suffix),
      contentType(o.contentType),
      hostId    (o.hostId),
      initial   (o.initial),
      filename  (o.filename)
{
}

QuviVideo&
QuviVideo::operator=(const QuviVideo& o) {
    length  = o.length;
    pageLink= o.pageLink;
    id      = o.id;
    title   = o.title;
    link    = o.link;
    suffix  = o.suffix;
    contentType = o.contentType;
    hostId  = o.hostId;
    initial = o.initial;
    filename= o.filename;
    return *this;
}

QuviVideo::~QuviVideo() {
}

void
QuviVideo::parse(std::string url /*=""*/) {

    if (url.empty())
        url = pageLink;

    assert(!url.empty());

    const Options opts =
        optsmgr.getOptions();

    if (opts.format_given) {
        quvi_setopt(quvimgr.handle(),
            QUVIOPT_FORMAT, opts.format_arg);
    }

    CURL *curl = 0;
    quvimgr.curlHandle(&curl);
    assert(curl != 0);

    curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT,
        opts.connect_timeout_arg);

    curl_easy_setopt(curl, CURLOPT_TIMEOUT,
        opts.connect_timeout_socks_arg);

    quvi_video_t video;

    QUVIcode rc =
        quvi_parse(quvimgr.handle(),
            const_cast<char*>(url.c_str()), &video);

    curl_easy_setopt(curl, CURLOPT_CONNECTTIMEOUT, 0L);
    curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0L);

    if (rc != QUVI_OK)
        handle_error(rc);

    quvi_getprop(video, QUVIP_LENGTH, &length);

#define _getprop(id,dst) \
    do { quvi_getprop(video, id, &s); dst = s; } while (0)

    char *s; // _getprop uses this.
    _getprop(QUVIP_PAGELINK, pageLink);
    _getprop(QUVIP_ID, id);
    _getprop(QUVIP_TITLE, title);
    _getprop(QUVIP_LINK, link);
    _getprop(QUVIP_SUFFIX, suffix);
    _getprop(QUVIP_CONTENTTYPE, contentType);
    _getprop(QUVIP_HOSTID, hostId);
#undef _getstr
    quvi_parse_close(&video);

    formatOutputFilename();
}

static int video_num = 0;

void
QuviVideo::formatOutputFilename() {

    const Options opts = optsmgr.getOptions();

    if (!opts.output_video_given) {
        std::stringstream b;

        if (opts.number_videos_given) {
            b << std::setw(4)
              << std::setfill('0')
              << ++video_num
              << "_";
        }

        customOutputFilenameFormatter(b);

        filename = b.str();

        typedef unsigned int _uint;

        for (register _uint i=1;
            i<INT_MAX && !opts.overwrite_given; ++i) {

            initial = Util::fileExists(filename);

            if (initial == 0)
                break;
            else if (initial >= length)
                throw NothingToDoException();
            else {
                if (opts.continue_given)
                    break;
            }

            std::stringstream tmp;
            tmp << b.str() << "." << i;
            filename = tmp.str();
        }
    }
    else {
        initial = Util::fileExists(opts.output_video_arg);

        if (initial >= length)
            throw NothingToDoException();

        if (opts.overwrite_given)
            initial = 0;

        filename = opts.output_video_arg;
    }

    if (!opts.continue_given)
        initial = 0;
}

void
QuviVideo::customOutputFilenameFormatter(
    std::stringstream& b)
{
    const Options opts  = optsmgr.getOptions();
    std::string fmt     = opts.filename_format_arg;

    std::string _id = this->id;
    Util::subStrReplace(_id, "-", "_");

    std::string _title = title;

    // Apply --regexp.
    if (opts.regexp_given)
        applyTitleRegexp(_title);

    // Remove leading and trailing whitespace.
    pcrecpp::RE("^[\\s]+", pcrecpp::UTF8())
        .Replace("", &_title);
    pcrecpp::RE("\\s+$", pcrecpp::UTF8())
        .Replace("", &_title);

    // Replace format specifiers.
    Util::subStrReplace(fmt, "%t", _title.empty() ? _id : _title);
    Util::subStrReplace(fmt, "%i", _id);
    Util::subStrReplace(fmt, "%h", hostId);
    Util::subStrReplace(fmt, "%s", suffix);

    b << fmt;
}

void
QuviVideo::applyTitleRegexp(std::string& src) {

    const Options opts =
        optsmgr.getOptions();

    if (opts.find_all_given) {
        pcrecpp::StringPiece sp(src);
        pcrecpp::RE re(opts.regexp_arg, pcrecpp::UTF8());

        src.clear();

        std::string s;
        while (re.FindAndConsume(&sp, &s))
            src += s;
    }
    else {
        std::string tmp = src;
        src.clear();

        pcrecpp::RE(opts.regexp_arg, pcrecpp::UTF8())
            .PartialMatch(tmp, &src);
    }
}

const double&
QuviVideo::getLength() const {
    return length;
}

const std::string&
QuviVideo::getPageLink() const {
    return pageLink;
}

const std::string&
QuviVideo::getId() const {
    return id;
}

const std::string&
QuviVideo::getTitle() const {
    return title;
}

const std::string&
QuviVideo::getLink() const {
    return link;
}

const std::string&
QuviVideo::getSuffix() const {
    return suffix;
}

const std::string&
QuviVideo::getContentType() const {
    return contentType;
}

const std::string&
QuviVideo::getHostId() const {
    return hostId;
}

const double&
QuviVideo::getInitial() const {
    return initial;
}

const std::string&
QuviVideo::getFilename() const {
    return filename;
}

void
QuviVideo::updateInitial() {
    initial = Util::fileExists(filename);

    if (initial >= length)
        throw NothingToDoException();
}

<|endoftext|>


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

#ifndef CATS_CORECAT_SYSTEM_ENVIRONMENT_HPP
#define CATS_CORECAT_SYSTEM_ENVIRONMENT_HPP

#include "OS.hpp"
#include "../Data/Array.hpp"
#include "../Text/String.hpp"
#include "../Util/Exception.hpp"

#if defined(CORECAT_OS_WINDOWS)
#   include "../Win32/Windows.hpp"
#elif defined(CORECAT_OS_LINUX) || defined(CORECAT_OS_MACOS)
#   include <limits.h>
#   include <unistd.h>
#   if defined(CORECAT_OS_MACOS)
#       include <crt_externs.h>
#       define environ (*_NSGetEnviron())
#   endif
#else
#   error Unknown OS
#endif

namespace Cats {
namespace Corecat {
inline namespace System {

class Environment {

public:

    static Array<String8> getEnvironmentVariable() {
#if defined(CORECAT_OS_WINDOWS)
        std::size_t count = 0;
        auto env = ::GetEnvironmentStringsW();
        if(!env) return {};
        for(auto p = env; *p; ++count, ++p)
            while(*++p);
        Array<String8> data;
        data.reserve(count);
        for(auto p = env; *p; ++p) {

            data.push(WString(p));
            while(*++p);

        }
        ::FreeEnvironmentStringsW(env);
        return data;
#else
        std::size_t count = 0;
        for(auto p = environ; *p; ++count, ++p);
        Array<String8> data;
        data.reserve(count);
        for(auto p = environ; *p; ++p)
            data.push(WString(*p));
        return data;
#endif
    }

    static String8 getEnvironmentVariable(StringView8 name) {
#if defined(CORECAT_OS_WINDOWS)
#else
#endif
    }

    static void setEnvironmentVariable(StringView8 name, StringView8 value) {
#if defined(CORECAT_OS_WINDOWS)
#else
#endif
    }

    static String8 getCurrentDirectory() {
#if defined(CORECAT_OS_WINDOWS)
        WString path;
        path.setLength(::GetCurrentDirectoryW(0, nullptr));
        ::GetCurrentDirectoryW(DWORD(path.getLength() + 1), path.getData());
        return String8(path);
#else
        char path[PATH_MAX];
        if(!::getcwd(path, sizeof(path)))
            throw SystemException("::getcwd failed");
        return path;
#endif
    }

};

}
}
}

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

#ifndef CATS_CORECAT_SYSTEM_ENVIRONMENT_HPP
#define CATS_CORECAT_SYSTEM_ENVIRONMENT_HPP

#include "OS.hpp"
#include "../Data/Array.hpp"
#include "../Text/String.hpp"
#include "../Util/Exception.hpp"

#if defined(CORECAT_OS_WINDOWS)
#   include "../Win32/Windows.hpp"
#elif defined(CORECAT_OS_LINUX) || defined(CORECAT_OS_MACOS)
#   include <limits.h>
#   include <unistd.h>
#   if defined(CORECAT_OS_MACOS)
#       include <crt_externs.h>
#       define environ (*_NSGetEnviron())
#   endif
#else
#   error Unknown OS
#endif

namespace Cats {
namespace Corecat {
inline namespace System {

class Environment {

public:

    static Array<String8> getEnvironmentVariable() {
#if defined(CORECAT_OS_WINDOWS)
        std::size_t count = 0;
        auto env = ::GetEnvironmentStringsW();
        if(!env) return {};
        for(auto p = env; *p; ++count, ++p)
            while(*++p);
        Array<String8> data;
        data.reserve(count);
        for(auto p = env; *p; ++p) {

            data.push(WString(p));
            while(*++p);

        }
        ::FreeEnvironmentStringsW(env);
        return data;
#else
        std::size_t count = 0;
        for(auto p = environ; *p; ++count, ++p);
        Array<String8> data;
        data.reserve(count);
        for(auto p = environ; *p; ++p)
            data.push(WString(*p));
        return data;
#endif
    }

    static String8 getEnvironmentVariable(StringView8 name) {
#if defined(CORECAT_OS_WINDOWS)
        WString n(name);
        DWORD length = ::GetEnvironmentVariableW(n.getData(), nullptr, 0);
        if(!length) return {};
        WString v;
        v.setLength(length - 1);
        ::GetEnvironmentVariableW(n.getData(), v.getData(),length);
        return String8(v);
#else
#endif
    }

    static void setEnvironmentVariable(StringView8 name, StringView8 value) {
#if defined(CORECAT_OS_WINDOWS)
#else
#endif
    }

    static String8 getCurrentDirectory() {
#if defined(CORECAT_OS_WINDOWS)
        WString path;
        path.setLength(::GetCurrentDirectoryW(0, nullptr));
        ::GetCurrentDirectoryW(DWORD(path.getLength() + 1), path.getData());
        return String8(path);
#else
        char path[PATH_MAX];
        if(!::getcwd(path, sizeof(path)))
            throw SystemException("::getcwd failed");
        return path;
#endif
    }

};

}
}
}

#endif
<|endoftext|>


<commit_before>#include "derivations.hh"
#include "globals.hh"
#include "shared.hh"
#include "store-api.hh"
#include <sys/utsname.h>
#include <algorithm>
#include <iostream>
#include <fstream>
#include <sys/mman.h>
#include <fcntl.h>
#include <mach-o/loader.h>
#include <mach-o/swap.h>

#define DO_SWAP(x, y) ((x) ? OSSwapInt32(y) : (y))

using namespace nix;

static auto cacheDir = Path{};

Path resolveCacheFile(Path lib)
{
    std::replace(lib.begin(), lib.end(), '/', '%');
    return cacheDir + "/" + lib;
}

std::set<string> readCacheFile(const Path & file)
{
    return tokenizeString<set<string>>(readFile(file), "\n");
}

std::set<std::string> runResolver(const Path & filename)
{
    AutoCloseFD fd = open(filename.c_str(), O_RDONLY);
    if (!fd)
        throw SysError("opening '%s'", filename);

    struct stat st;
    if (fstat(fd.get(), &st))
        throw SysError("statting '%s'", filename);

    if (!S_ISREG(st.st_mode)) {
        logError({
            .name = "Regular MACH file",
            .hint = hintfmt("file '%s' is not a regular file", filename)
        });
        return {};
    }

    if (st.st_size < sizeof(mach_header_64)) {
        logError({
            .name = "File too short",
            .hint = hintfmt("file '%s' is too short for a MACH binary", filename)
        });
        return {};
    }

    char* obj = (char*) mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd.get(), 0);
    if (!obj)
        throw SysError("mmapping '%s'", filename);

    ptrdiff_t mach64_offset = 0;

    uint32_t magic = ((mach_header_64*) obj)->magic;
    if (magic == FAT_CIGAM || magic == FAT_MAGIC) {
        bool should_swap = magic == FAT_CIGAM;
        uint32_t narches = DO_SWAP(should_swap, ((fat_header *) obj)->nfat_arch);
        for (uint32_t i = 0; i < narches; i++) {
            fat_arch* arch = (fat_arch*) (obj + sizeof(fat_header) + sizeof(fat_arch) * i);
            if (DO_SWAP(should_swap, arch->cputype) == CPU_TYPE_X86_64) {
                mach64_offset = (ptrdiff_t) DO_SWAP(should_swap, arch->offset);
                break;
            }
        }
        if (mach64_offset == 0) {
            logError({
                .name = "No mach64 blobs",
                .hint = hintfmt("Could not find any mach64 blobs in file '%1%', continuing...", filename)
            });
            return {};
        }
    } else if (magic == MH_MAGIC_64 || magic == MH_CIGAM_64) {
        mach64_offset = 0;
    } else {
        logError({
            .name = "Magic number",
            .hint = hintfmt("Object file has unknown magic number '%1%', skipping it...", magic)
        });
        return {};
    }

    mach_header_64 * m_header = (mach_header_64 *) (obj + mach64_offset);

    bool should_swap = magic == MH_CIGAM_64;
    ptrdiff_t cmd_offset = mach64_offset + sizeof(mach_header_64);

    std::set<string> libs;
    for (uint32_t i = 0; i < DO_SWAP(should_swap, m_header->ncmds); i++) {
        load_command * cmd = (load_command *) (obj + cmd_offset);
        switch(DO_SWAP(should_swap, cmd->cmd)) {
            case LC_LOAD_UPWARD_DYLIB:
            case LC_LOAD_DYLIB:
            case LC_REEXPORT_DYLIB:
                libs.insert(std::string((char *) cmd + ((dylib_command*) cmd)->dylib.name.offset));
                break;
        }
        cmd_offset += DO_SWAP(should_swap, cmd->cmdsize);
    }

    return libs;
}

bool isSymlink(const Path & path)
{
    return S_ISLNK(lstat(path).st_mode);
}

Path resolveSymlink(const Path & path)
{
    auto target = readLink(path);
    return hasPrefix(target, "/")
        ? target
        : dirOf(path) + "/" + target;
}

std::set<string> resolveTree(const Path & path, PathSet & deps)
{
    std::set<string> results;
    if (!deps.insert(path).second) return {};
    for (auto & lib : runResolver(path)) {
        results.insert(lib);
        for (auto & p : resolveTree(lib, deps)) {
            results.insert(p);
        }
    }
    return results;
}

std::set<string> getPath(const Path & path)
{
    if (hasPrefix(path, "/dev")) return {};

    Path cacheFile = resolveCacheFile(path);
    if (pathExists(cacheFile))
        return readCacheFile(cacheFile);

    std::set<string> deps, paths;
    paths.insert(path);

    Path nextPath(path);
    while (isSymlink(nextPath)) {
        nextPath = resolveSymlink(nextPath);
        paths.insert(nextPath);
    }

    for (auto & t : resolveTree(nextPath, deps))
        paths.insert(t);

    writeFile(cacheFile, concatStringsSep("\n", paths));

    return paths;
}

int main(int argc, char ** argv)
{
    return handleExceptions(argv[0], [&]() {
        initNix();

        struct utsname _uname;

        uname(&_uname);

        auto cacheParentDir = (format("%1%/dependency-maps") % settings.nixStateDir).str();

        cacheDir = (format("%1%/%2%-%3%-%4%")
                % cacheParentDir
                % _uname.machine
                % _uname.sysname
                % _uname.release).str();

        mkdir(cacheParentDir.c_str(), 0755);
        mkdir(cacheDir.c_str(), 0755);

        auto store = openStore();

        StringSet impurePaths;

        if (std::string(argv[1]) == "--test")
            impurePaths.insert(argv[2]);
        else {
            auto drv = store->derivationFromPath(store->parseStorePath(argv[1]));
            impurePaths = tokenizeString<StringSet>(get(drv.env, "__impureHostDeps").value_or(""));
            impurePaths.insert("/usr/lib/libSystem.dylib");
        }

        std::set<string> allPaths;

        for (auto & path : impurePaths)
            for (auto & p : getPath(path))
                allPaths.insert(p);

        std::cout << "extra-chroot-dirs" << std::endl;
        for (auto & path : allPaths)
            std::cout << path << std::endl;
        std::cout << std::endl;
    });
}
<commit_msg>Fix macOS build<commit_after>#include "derivations.hh"
#include "globals.hh"
#include "shared.hh"
#include "store-api.hh"
#include <sys/utsname.h>
#include <algorithm>
#include <iostream>
#include <fstream>
#include <sys/mman.h>
#include <fcntl.h>
#include <mach-o/loader.h>
#include <mach-o/swap.h>

#define DO_SWAP(x, y) ((x) ? OSSwapInt32(y) : (y))

using namespace nix;

static auto cacheDir = Path{};

Path resolveCacheFile(Path lib)
{
    std::replace(lib.begin(), lib.end(), '/', '%');
    return cacheDir + "/" + lib;
}

std::set<string> readCacheFile(const Path & file)
{
    return tokenizeString<set<string>>(readFile(file), "\n");
}

std::set<std::string> runResolver(const Path & filename)
{
    AutoCloseFD fd = open(filename.c_str(), O_RDONLY);
    if (!fd)
        throw SysError("opening '%s'", filename);

    struct stat st;
    if (fstat(fd.get(), &st))
        throw SysError("statting '%s'", filename);

    if (!S_ISREG(st.st_mode)) {
        printError("file '%s' is not a regular MACH binary", filename);
        return {};
    }

    if (st.st_size < sizeof(mach_header_64)) {
        printError("file '%s' is too short for a MACH binary", filename);
        return {};
    }

    char* obj = (char*) mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, fd.get(), 0);
    if (!obj)
        throw SysError("mmapping '%s'", filename);

    ptrdiff_t mach64_offset = 0;

    uint32_t magic = ((mach_header_64*) obj)->magic;
    if (magic == FAT_CIGAM || magic == FAT_MAGIC) {
        bool should_swap = magic == FAT_CIGAM;
        uint32_t narches = DO_SWAP(should_swap, ((fat_header *) obj)->nfat_arch);
        for (uint32_t i = 0; i < narches; i++) {
            fat_arch* arch = (fat_arch*) (obj + sizeof(fat_header) + sizeof(fat_arch) * i);
            if (DO_SWAP(should_swap, arch->cputype) == CPU_TYPE_X86_64) {
                mach64_offset = (ptrdiff_t) DO_SWAP(should_swap, arch->offset);
                break;
            }
        }
        if (mach64_offset == 0) {
            printError("could not find any mach64 blobs in file '%1%', continuing...", filename);
            return {};
        }
    } else if (magic == MH_MAGIC_64 || magic == MH_CIGAM_64) {
        mach64_offset = 0;
    } else {
        printError("Object file has unknown magic number '%1%', skipping it...", magic);
        return {};
    }

    mach_header_64 * m_header = (mach_header_64 *) (obj + mach64_offset);

    bool should_swap = magic == MH_CIGAM_64;
    ptrdiff_t cmd_offset = mach64_offset + sizeof(mach_header_64);

    std::set<string> libs;
    for (uint32_t i = 0; i < DO_SWAP(should_swap, m_header->ncmds); i++) {
        load_command * cmd = (load_command *) (obj + cmd_offset);
        switch(DO_SWAP(should_swap, cmd->cmd)) {
            case LC_LOAD_UPWARD_DYLIB:
            case LC_LOAD_DYLIB:
            case LC_REEXPORT_DYLIB:
                libs.insert(std::string((char *) cmd + ((dylib_command*) cmd)->dylib.name.offset));
                break;
        }
        cmd_offset += DO_SWAP(should_swap, cmd->cmdsize);
    }

    return libs;
}

bool isSymlink(const Path & path)
{
    return S_ISLNK(lstat(path).st_mode);
}

Path resolveSymlink(const Path & path)
{
    auto target = readLink(path);
    return hasPrefix(target, "/")
        ? target
        : dirOf(path) + "/" + target;
}

std::set<string> resolveTree(const Path & path, PathSet & deps)
{
    std::set<string> results;
    if (!deps.insert(path).second) return {};
    for (auto & lib : runResolver(path)) {
        results.insert(lib);
        for (auto & p : resolveTree(lib, deps)) {
            results.insert(p);
        }
    }
    return results;
}

std::set<string> getPath(const Path & path)
{
    if (hasPrefix(path, "/dev")) return {};

    Path cacheFile = resolveCacheFile(path);
    if (pathExists(cacheFile))
        return readCacheFile(cacheFile);

    std::set<string> deps, paths;
    paths.insert(path);

    Path nextPath(path);
    while (isSymlink(nextPath)) {
        nextPath = resolveSymlink(nextPath);
        paths.insert(nextPath);
    }

    for (auto & t : resolveTree(nextPath, deps))
        paths.insert(t);

    writeFile(cacheFile, concatStringsSep("\n", paths));

    return paths;
}

int main(int argc, char ** argv)
{
    return handleExceptions(argv[0], [&]() {
        initNix();

        struct utsname _uname;

        uname(&_uname);

        auto cacheParentDir = (format("%1%/dependency-maps") % settings.nixStateDir).str();

        cacheDir = (format("%1%/%2%-%3%-%4%")
                % cacheParentDir
                % _uname.machine
                % _uname.sysname
                % _uname.release).str();

        mkdir(cacheParentDir.c_str(), 0755);
        mkdir(cacheDir.c_str(), 0755);

        auto store = openStore();

        StringSet impurePaths;

        if (std::string(argv[1]) == "--test")
            impurePaths.insert(argv[2]);
        else {
            auto drv = store->derivationFromPath(store->parseStorePath(argv[1]));
            impurePaths = tokenizeString<StringSet>(get(drv.env, "__impureHostDeps").value_or(""));
            impurePaths.insert("/usr/lib/libSystem.dylib");
        }

        std::set<string> allPaths;

        for (auto & path : impurePaths)
            for (auto & p : getPath(path))
                allPaths.insert(p);

        std::cout << "extra-chroot-dirs" << std::endl;
        for (auto & path : allPaths)
            std::cout << path << std::endl;
        std::cout << std::endl;
    });
}
<|endoftext|>