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

// Calculator that takes a non-windowed AnnotatedRecordingCollection proto and
// produces a stream of sensor data. There will be 1 output stream per each type
// of sensor specified in the config with tag DATA_STREAM and index starting
// from 0, e.g. DATA_STREAM:0, DATA_STREAM:1, etc. The order of data streams
// will match that of the config, e.g. if the first specified sensor type is <2,
// 3> (SENSOR, ACCELEROMETER), then DATA_STREAM:0 will contain accelerometer
// data. For sensors that have SensorOptions.default_value set, the
// default_value will be prepended to the timeseries. This is used to have a
// value for packet_cloner at the first timestamp of the tick signal. Raises an
// error if default dimensions do not match the requested dimensions.
//
// The input can be passed as a side input packet or as an input stream with
// exactly one packet. If the input is passed through via input stream, its
// timestamp is not used for producing the output: the output timestamps are
// set based on the data within the annotated timeseries message.

#include <cstdint>
#include <vector>

#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/calculator_options.pb.h"
#include "absl/container/flat_hash_map.h"
#include "absl/status/status.h"
#include "absl/time/time.h"
#include "intent_recognition/annotated_recording_collection.pb.h"
#include "intent_recognition/annotated_recording_collection_sensor_options.pb.h"
#include "intent_recognition/annotated_recording_collection_utils.h"
#include "intent_recognition/processing/extract_sensor_data_from_annotated_recording_collection_calculator.pb.h"

namespace ambient_sensing {
namespace {
using ::mediapipe::CalculatorContext;
using ::mediapipe::CalculatorContract;

constexpr char kOutputDataStreamTag[] = "OUTPUT_DATA_STREAM";
constexpr char kInputAnnotatedRecordingCollectionTag[] =
    "INPUT_ANNOTATED_RECORDING_COLLECTION";

using SensorType = std::pair<std::string, std::string>;
}  // namespace

class ExtractSensorDataFromAnnotatedRecordingCollectionCalculator
    : public mediapipe::CalculatorBase {
 public:
  ExtractSensorDataFromAnnotatedRecordingCollectionCalculator() = default;

  static absl::Status GetContract(CalculatorContract* cc) {
    bool input_side_packets_has_tag =
        cc->InputSidePackets().HasTag(kInputAnnotatedRecordingCollectionTag);
    bool inputs_has_tag =
        cc->Inputs().HasTag(kInputAnnotatedRecordingCollectionTag);
    // TODO(b/149202382): factor out (copied from
    // FilterAnnotatedTimeseriesCalculator).
    if (inputs_has_tag && input_side_packets_has_tag) {
      return absl::InvalidArgumentError(
          "Input stream and input side packet can't be used simultaneously.");
    }
    if (!inputs_has_tag && !input_side_packets_has_tag) {
      return absl::InvalidArgumentError(
          "Input stream or input side packet must be specified.");
    }

    if (input_side_packets_has_tag) {
      cc->InputSidePackets()
          .Tag(kInputAnnotatedRecordingCollectionTag)
          .Set<AnnotatedRecordingCollection>();
    }
    if (inputs_has_tag) {
      cc->Inputs()
          .Tag(kInputAnnotatedRecordingCollectionTag)
          .Set<AnnotatedRecordingCollection>();
    }
    for (int i = 0; i < cc->Outputs().NumEntries(kOutputDataStreamTag); i++) {
      cc->Outputs().Get(kOutputDataStreamTag, i).Set<std::vector<float>>();
    }
    for (
        const auto& sensor_options :
        cc->Options()
            .GetExtension(
                ExtractSensorDataFromAnnotatedRecordingCollectionCalculatorOptions:: // NOLINT
                    ext)
            .sensor_options()) {
      bool required_dims_set = sensor_options.required_dims() > 0;
      bool default_value_provided = sensor_options.default_value_size() != 0;
      bool dims_mismatch =
          sensor_options.default_value_size() != sensor_options.required_dims();
      if (required_dims_set && default_value_provided && dims_mismatch) {
        return absl::InvalidArgumentError(
            absl::StrCat("Bad argument for default value: given ",
                         sensor_options.default_value_size(), ", but required ",
                         sensor_options.required_dims()));
      }
    }
    return absl::OkStatus();
  }

  absl::Status Open(CalculatorContext* cc) override {
    auto options = cc->Options().GetExtension(
        ExtractSensorDataFromAnnotatedRecordingCollectionCalculatorOptions::
            ext);
    return BuildSensorDataStreamsMap(options, cc);
  }

  absl::Status Process(CalculatorContext* cc) override {
    if (!cc->Inputs().HasTag(kInputAnnotatedRecordingCollectionTag))
      return mediapipe::tool::StatusStop();
    if (processed_) {
      return absl::InvalidArgumentError(
          absl::StrCat("Input stream must have exactly 1 value. Found a "
                       "second value @ ",
                       cc->InputTimestamp().Microseconds()));
    }
    processed_ = true;
    const auto& input = cc->Inputs()
                            .Tag(kInputAnnotatedRecordingCollectionTag)
                            .Get<AnnotatedRecordingCollection>();
    return ExtractSensorData(input, cc);
  }

  absl::Status Close(CalculatorContext* cc) override {
    if (cc->InputSidePackets().HasTag(kInputAnnotatedRecordingCollectionTag)) {
      const auto& annotated_recording_collection =
          cc->InputSidePackets()
              .Tag(kInputAnnotatedRecordingCollectionTag)
              .Get<AnnotatedRecordingCollection>();
      return ExtractSensorData(annotated_recording_collection, cc);
    }
    return absl::OkStatus();
  }

 private:
  absl::Status BuildSensorDataStreamsMap(
      const ExtractSensorDataFromAnnotatedRecordingCollectionCalculatorOptions&
          options,
      CalculatorContext* cc) {
    int current_index = 0;
    for (const auto& sensor_options : options.sensor_options()) {
      if (sensor_options.duplicate_timestamps_handling_strategy() ==
          RecordingCollectionSensorOptions::UNKNOWN) {
        return absl::InvalidArgumentError(absl::StrCat(
            "Bad configuration: duplicate_timestamps_handling_strategy must be "
            "set. ",
            sensor_options.DebugString(), " ", options.DebugString()));
      }
      SensorType sensor_type = {sensor_options.type(),
                                sensor_options.subtype()};
      auto emplace1 =
          sensor_data_streams_map_.emplace(sensor_type, current_index++);
      auto emplace2 = sensor_options_map_.emplace(sensor_type, sensor_options);

      if (!emplace1.second || !emplace2.second) {
        return absl::InvalidArgumentError(absl::StrCat(
            "Invalid options: repeated type/subtype combination ",
            sensor_options.DebugString(), " ", options.DebugString()));
      }
    }

    if (sensor_data_streams_map_.size() != cc->Outputs().NumEntries()) {
      return absl::InvalidArgumentError(absl::StrCat(
          "Bad configuration: number of output data streams does not match "
          "number of sensor data streams map: ",
          cc->Outputs().NumEntries(), " ", sensor_data_streams_map_.size()));
    }
    return absl::OkStatus();
  }

  // Adds the default value to the output stream at Timestamp::Min().
  absl::Status OutputDefault(
      const RecordingCollectionSensorOptions& sensor_options, int output_index,
      CalculatorContext* cc) {
    auto sensor_data = absl::make_unique<std::vector<float>>(
        sensor_options.default_value().cbegin(),
        sensor_options.default_value().cend());
    cc->Outputs()
        .Get(kOutputDataStreamTag, output_index)
        .Add(sensor_data.release(), mediapipe::Timestamp::Min());
    return absl::OkStatus();
  }

  // Adds a value to the output stream.
  // Raises an error if actual dimensions don't match the required dimensions.
  absl::Status OutputData(
      const RecordingCollectionSensorOptions& sensor_options, int output_index,
      const Datapoint& datapoint, CalculatorContext* cc) {
    absl::Duration datapoint_offset =
        ConvertProtoToDuration(datapoint.offset());
    if (sensor_options.required_dims() >= 0 &&
        datapoint.double_value().value_size() !=
            sensor_options.required_dims()) {
      return absl::InvalidArgumentError(absl::StrCat(
          "Bad argument at offset ", absl::FormatDuration(datapoint_offset),
          " - size ", datapoint.double_value().value_size(), ", but required ",
          sensor_options.required_dims()));
    }
    auto sensor_data = absl::make_unique<std::vector<float>>(
        datapoint.double_value().value().cbegin(),
        datapoint.double_value().value().cend());
    cc->Outputs()
        .Get(kOutputDataStreamTag, output_index)
        .Add(sensor_data.release(),
             mediapipe::Timestamp(absl::ToInt64Microseconds(datapoint_offset)));
    return absl::OkStatus();
  }

  absl::Status ExtractSensorData(
      const AnnotatedRecordingCollection& annotated_recording_collection,
      CalculatorContext* cc) {
    // Provide default value for sensors for which SensorOptions.default_value
    // is set at Timestamp::Min(), if requested in sensor_options.
    for (const auto& requested_sensor : sensor_options_map_) {
      const RecordingCollectionSensorOptions& sensor_options =
          requested_sensor.second;
      if (!sensor_options.default_value().empty()) {
        auto sensor_data_streams_map_it =
            sensor_data_streams_map_.find(requested_sensor.first);
        CHECK(sensor_data_streams_map_it != sensor_data_streams_map_.end());
        int32_t output_index = sensor_data_streams_map_it->second;
        absl::Status status = OutputDefault(sensor_options, output_index, cc);
        if (status != absl::OkStatus()) return status;
      }
    }

    // Pass measurements.
    for (const auto& sequence :
         annotated_recording_collection.recording_collection().sequence()) {
      SensorType sensor_type = {sequence.metadata().type(),
                                sequence.metadata().subtype()};
      auto sensor_data_streams_map_it =
          sensor_data_streams_map_.find(sensor_type);
      auto sensor_options_map_it = sensor_options_map_.find(sensor_type);
      if (sensor_data_streams_map_it == sensor_data_streams_map_.end() ||
          sensor_options_map_it == sensor_options_map_.end()) {
        cc->GetCounter(absl::StrCat("sensor-not-found-in-map-",
                                    sensor_type.first, " ", sensor_type.second))
            ->Increment();
        continue;
      }

      int32_t output_index = sensor_data_streams_map_it->second;
      const RecordingCollectionSensorOptions& sensor_options =
          sensor_options_map_it->second;

      for (int i = 0; i < sequence.repeated_datapoint().datapoint_size(); i++) {
        // Either last value in the stream, or the next value has different
        // timestamp.
        bool last_value =
            (i + 1 == sequence.repeated_datapoint().datapoint_size());
        absl::Duration datapoint_offset = ConvertProtoToDuration(
            sequence.repeated_datapoint().datapoint(i).offset());
        bool last_value_in_cluster;
        absl::Duration next_datapoint_offset;
        if (!last_value) {
          next_datapoint_offset = ConvertProtoToDuration(
              sequence.repeated_datapoint().datapoint(i + 1).offset());
          last_value_in_cluster =
              !last_value && (datapoint_offset != next_datapoint_offset);
        }
        if (last_value || last_value_in_cluster) {
          absl::Status status =
              OutputData(sensor_options, output_index,
                         sequence.repeated_datapoint().datapoint(i), cc);
          if (status != absl::OkStatus()) return status;
        } else {
          switch (sensor_options.duplicate_timestamps_handling_strategy()) {
            case RecordingCollectionSensorOptions::KEEP_LAST_VALUE:
              // This case already handled above; do nothing (i.e. drop the
              // current value since the one ahead of it has the same
              // timestamp).
              break;
            // Configuration is checked at startup, so this should not be
            // possible.
            case RecordingCollectionSensorOptions::UNKNOWN:
              return absl::InternalError("Internal error");
            case RecordingCollectionSensorOptions::RAISE_ERROR:
              return absl::InvalidArgumentError(
                  absl::StrCat("Duplicate timestamp for stream ",
                               sensor_options.DebugString(), " at offset ",
                               absl::FormatDuration(datapoint_offset)));
          }
        }
      }
    }
    return absl::OkStatus();
  }

  // Key: SensorType.
  // Value: output stream index.
  absl::flat_hash_map<SensorType, int32_t> sensor_data_streams_map_;
  // Key: SensorType.
  // Value: sensor options.
  absl::flat_hash_map<SensorType, RecordingCollectionSensorOptions>
      sensor_options_map_;
  AnnotatedRecordingCollection input_;
  bool processed_ = false;
};
REGISTER_CALCULATOR(
    ExtractSensorDataFromAnnotatedRecordingCollectionCalculator);

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

// Calculator that takes a non-windowed AnnotatedRecordingCollection proto and
// produces a stream of sensor data. There will be 1 output stream per each type
// of sensor specified in the config with tag DATA_STREAM and index starting
// from 0, e.g. DATA_STREAM:0, DATA_STREAM:1, etc. The order of data streams
// will match that of the config, e.g. if the first specified sensor type is <2,
// 3> (SENSOR, ACCELEROMETER), then DATA_STREAM:0 will contain accelerometer
// data. For sensors that have SensorOptions.default_value set, the
// default_value will be prepended to the timeseries. This is used to have a
// value for packet_cloner at the first timestamp of the tick signal. Raises an
// error if default dimensions do not match the requested dimensions.
//
// The input can be passed as a side input packet or as an input stream with
// exactly one packet. If the input is passed through via input stream, its
// timestamp is not used for producing the output: the output timestamps are
// set based on the data within the annotated timeseries message.

#include <cstdint>
#include <vector>

#include "net/google::protobuf/util/legacy_debug_string/legacy_unredacted_debug_string.h"
#include "mediapipe/framework/calculator_framework.h"
#include "mediapipe/framework/calculator_options.pb.h"
#include "absl/container/flat_hash_map.h"
#include "absl/status/status.h"
#include "absl/time/time.h"
#include "intent_recognition/annotated_recording_collection.pb.h"
#include "intent_recognition/annotated_recording_collection_sensor_options.pb.h"
#include "intent_recognition/annotated_recording_collection_utils.h"
#include "intent_recognition/processing/extract_sensor_data_from_annotated_recording_collection_calculator.pb.h"

namespace ambient_sensing {
namespace {
using ::mediapipe::CalculatorContext;
using ::mediapipe::CalculatorContract;
using ::google::protobuf::util::LegacyUnredactedDebugString;

constexpr char kOutputDataStreamTag[] = "OUTPUT_DATA_STREAM";
constexpr char kInputAnnotatedRecordingCollectionTag[] =
    "INPUT_ANNOTATED_RECORDING_COLLECTION";

using SensorType = std::pair<std::string, std::string>;
}  // namespace

class ExtractSensorDataFromAnnotatedRecordingCollectionCalculator
    : public mediapipe::CalculatorBase {
 public:
  ExtractSensorDataFromAnnotatedRecordingCollectionCalculator() = default;

  static absl::Status GetContract(CalculatorContract* cc) {
    bool input_side_packets_has_tag =
        cc->InputSidePackets().HasTag(kInputAnnotatedRecordingCollectionTag);
    bool inputs_has_tag =
        cc->Inputs().HasTag(kInputAnnotatedRecordingCollectionTag);
    // TODO(b/149202382): factor out (copied from
    // FilterAnnotatedTimeseriesCalculator).
    if (inputs_has_tag && input_side_packets_has_tag) {
      return absl::InvalidArgumentError(
          "Input stream and input side packet can't be used simultaneously.");
    }
    if (!inputs_has_tag && !input_side_packets_has_tag) {
      return absl::InvalidArgumentError(
          "Input stream or input side packet must be specified.");
    }

    if (input_side_packets_has_tag) {
      cc->InputSidePackets()
          .Tag(kInputAnnotatedRecordingCollectionTag)
          .Set<AnnotatedRecordingCollection>();
    }
    if (inputs_has_tag) {
      cc->Inputs()
          .Tag(kInputAnnotatedRecordingCollectionTag)
          .Set<AnnotatedRecordingCollection>();
    }
    for (int i = 0; i < cc->Outputs().NumEntries(kOutputDataStreamTag); i++) {
      cc->Outputs().Get(kOutputDataStreamTag, i).Set<std::vector<float>>();
    }
    for (
        const auto& sensor_options :
        cc->Options()
            .GetExtension(
                ExtractSensorDataFromAnnotatedRecordingCollectionCalculatorOptions:: // NOLINT
                    ext)
            .sensor_options()) {
      bool required_dims_set = sensor_options.required_dims() > 0;
      bool default_value_provided = sensor_options.default_value_size() != 0;
      bool dims_mismatch =
          sensor_options.default_value_size() != sensor_options.required_dims();
      if (required_dims_set && default_value_provided && dims_mismatch) {
        return absl::InvalidArgumentError(
            absl::StrCat("Bad argument for default value: given ",
                         sensor_options.default_value_size(), ", but required ",
                         sensor_options.required_dims()));
      }
    }
    return absl::OkStatus();
  }

  absl::Status Open(CalculatorContext* cc) override {
    auto options = cc->Options().GetExtension(
        ExtractSensorDataFromAnnotatedRecordingCollectionCalculatorOptions::
            ext);
    return BuildSensorDataStreamsMap(options, cc);
  }

  absl::Status Process(CalculatorContext* cc) override {
    if (!cc->Inputs().HasTag(kInputAnnotatedRecordingCollectionTag))
      return mediapipe::tool::StatusStop();
    if (processed_) {
      return absl::InvalidArgumentError(
          absl::StrCat("Input stream must have exactly 1 value. Found a "
                       "second value @ ",
                       cc->InputTimestamp().Microseconds()));
    }
    processed_ = true;
    const auto& input = cc->Inputs()
                            .Tag(kInputAnnotatedRecordingCollectionTag)
                            .Get<AnnotatedRecordingCollection>();
    return ExtractSensorData(input, cc);
  }

  absl::Status Close(CalculatorContext* cc) override {
    if (cc->InputSidePackets().HasTag(kInputAnnotatedRecordingCollectionTag)) {
      const auto& annotated_recording_collection =
          cc->InputSidePackets()
              .Tag(kInputAnnotatedRecordingCollectionTag)
              .Get<AnnotatedRecordingCollection>();
      return ExtractSensorData(annotated_recording_collection, cc);
    }
    return absl::OkStatus();
  }

 private:
  absl::Status BuildSensorDataStreamsMap(
      const ExtractSensorDataFromAnnotatedRecordingCollectionCalculatorOptions&
          options,
      CalculatorContext* cc) {
    int current_index = 0;
    for (const auto& sensor_options : options.sensor_options()) {
      if (sensor_options.duplicate_timestamps_handling_strategy() ==
          RecordingCollectionSensorOptions::UNKNOWN) {
        return absl::InvalidArgumentError(absl::StrCat(
            "Bad configuration: duplicate_timestamps_handling_strategy must be "
            "set. ",
            LegacyUnredactedDebugString(sensor_options), " ",
            LegacyUnredactedDebugString(options)));
      }
      SensorType sensor_type = {sensor_options.type(),
                                sensor_options.subtype()};
      auto emplace1 =
          sensor_data_streams_map_.emplace(sensor_type, current_index++);
      auto emplace2 = sensor_options_map_.emplace(sensor_type, sensor_options);

      if (!emplace1.second || !emplace2.second) {
        return absl::InvalidArgumentError(
            absl::StrCat("Invalid options: repeated type/subtype combination ",
                         LegacyUnredactedDebugString(sensor_options), " ",
                         LegacyUnredactedDebugString(options)));
      }
    }

    if (sensor_data_streams_map_.size() != cc->Outputs().NumEntries()) {
      return absl::InvalidArgumentError(absl::StrCat(
          "Bad configuration: number of output data streams does not match "
          "number of sensor data streams map: ",
          cc->Outputs().NumEntries(), " ", sensor_data_streams_map_.size()));
    }
    return absl::OkStatus();
  }

  // Adds the default value to the output stream at Timestamp::Min().
  absl::Status OutputDefault(
      const RecordingCollectionSensorOptions& sensor_options, int output_index,
      CalculatorContext* cc) {
    auto sensor_data = absl::make_unique<std::vector<float>>(
        sensor_options.default_value().cbegin(),
        sensor_options.default_value().cend());
    cc->Outputs()
        .Get(kOutputDataStreamTag, output_index)
        .Add(sensor_data.release(), mediapipe::Timestamp::Min());
    return absl::OkStatus();
  }

  // Adds a value to the output stream.
  // Raises an error if actual dimensions don't match the required dimensions.
  absl::Status OutputData(
      const RecordingCollectionSensorOptions& sensor_options, int output_index,
      const Datapoint& datapoint, CalculatorContext* cc) {
    absl::Duration datapoint_offset =
        ConvertProtoToDuration(datapoint.offset());
    if (sensor_options.required_dims() >= 0 &&
        datapoint.double_value().value_size() !=
            sensor_options.required_dims()) {
      return absl::InvalidArgumentError(absl::StrCat(
          "Bad argument at offset ", absl::FormatDuration(datapoint_offset),
          " - size ", datapoint.double_value().value_size(), ", but required ",
          sensor_options.required_dims()));
    }
    auto sensor_data = absl::make_unique<std::vector<float>>(
        datapoint.double_value().value().cbegin(),
        datapoint.double_value().value().cend());
    cc->Outputs()
        .Get(kOutputDataStreamTag, output_index)
        .Add(sensor_data.release(),
             mediapipe::Timestamp(absl::ToInt64Microseconds(datapoint_offset)));
    return absl::OkStatus();
  }

  absl::Status ExtractSensorData(
      const AnnotatedRecordingCollection& annotated_recording_collection,
      CalculatorContext* cc) {
    // Provide default value for sensors for which SensorOptions.default_value
    // is set at Timestamp::Min(), if requested in sensor_options.
    for (const auto& requested_sensor : sensor_options_map_) {
      const RecordingCollectionSensorOptions& sensor_options =
          requested_sensor.second;
      if (!sensor_options.default_value().empty()) {
        auto sensor_data_streams_map_it =
            sensor_data_streams_map_.find(requested_sensor.first);
        CHECK(sensor_data_streams_map_it != sensor_data_streams_map_.end());
        int32_t output_index = sensor_data_streams_map_it->second;
        absl::Status status = OutputDefault(sensor_options, output_index, cc);
        if (status != absl::OkStatus()) return status;
      }
    }

    // Pass measurements.
    for (const auto& sequence :
         annotated_recording_collection.recording_collection().sequence()) {
      SensorType sensor_type = {sequence.metadata().type(),
                                sequence.metadata().subtype()};
      auto sensor_data_streams_map_it =
          sensor_data_streams_map_.find(sensor_type);
      auto sensor_options_map_it = sensor_options_map_.find(sensor_type);
      if (sensor_data_streams_map_it == sensor_data_streams_map_.end() ||
          sensor_options_map_it == sensor_options_map_.end()) {
        cc->GetCounter(absl::StrCat("sensor-not-found-in-map-",
                                    sensor_type.first, " ", sensor_type.second))
            ->Increment();
        continue;
      }

      int32_t output_index = sensor_data_streams_map_it->second;
      const RecordingCollectionSensorOptions& sensor_options =
          sensor_options_map_it->second;

      for (int i = 0; i < sequence.repeated_datapoint().datapoint_size(); i++) {
        // Either last value in the stream, or the next value has different
        // timestamp.
        bool last_value =
            (i + 1 == sequence.repeated_datapoint().datapoint_size());
        absl::Duration datapoint_offset = ConvertProtoToDuration(
            sequence.repeated_datapoint().datapoint(i).offset());
        bool last_value_in_cluster;
        absl::Duration next_datapoint_offset;
        if (!last_value) {
          next_datapoint_offset = ConvertProtoToDuration(
              sequence.repeated_datapoint().datapoint(i + 1).offset());
          last_value_in_cluster =
              !last_value && (datapoint_offset != next_datapoint_offset);
        }
        if (last_value || last_value_in_cluster) {
          absl::Status status =
              OutputData(sensor_options, output_index,
                         sequence.repeated_datapoint().datapoint(i), cc);
          if (status != absl::OkStatus()) return status;
        } else {
          switch (sensor_options.duplicate_timestamps_handling_strategy()) {
            case RecordingCollectionSensorOptions::KEEP_LAST_VALUE:
              // This case already handled above; do nothing (i.e. drop the
              // current value since the one ahead of it has the same
              // timestamp).
              break;
            // Configuration is checked at startup, so this should not be
            // possible.
            case RecordingCollectionSensorOptions::UNKNOWN:
              return absl::InternalError("Internal error");
            case RecordingCollectionSensorOptions::RAISE_ERROR:
              return absl::InvalidArgumentError(absl::StrCat(
                  "Duplicate timestamp for stream ",
                  LegacyUnredactedDebugString(sensor_options), " at offset ",
                  absl::FormatDuration(datapoint_offset)));
          }
        }
      }
    }
    return absl::OkStatus();
  }

  // Key: SensorType.
  // Value: output stream index.
  absl::flat_hash_map<SensorType, int32_t> sensor_data_streams_map_;
  // Key: SensorType.
  // Value: sensor options.
  absl::flat_hash_map<SensorType, RecordingCollectionSensorOptions>
      sensor_options_map_;
  AnnotatedRecordingCollection input_;
  bool processed_ = false;
};
REGISTER_CALCULATOR(
    ExtractSensorDataFromAnnotatedRecordingCollectionCalculator);

}  // namespace ambient_sensing
<|endoftext|>


<commit_before>/*
   For more information, please see: http://software.sci.utah.edu

   The MIT License

   Copyright (c) 2020 Scientific Computing and Imaging Institute,
   University of Utah.

   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.
*/

#include "ArcBall.hpp"
#include <glm/gtx/vec_swizzle.hpp>

namespace spire {

//------------------------------------------------------------------------------
  ArcBall::ArcBall(const glm::vec3& center, float radius, bool inverted, const glm::mat4& screenToTCS) :
    mScreenToTCS(screenToTCS),
    mCenter(center),
    mRadius(radius),
    invertHemisphere(inverted)
{
  // glm uses the following format for quaternions: w,x,y,z.
  //        w,    x,    y,    z
  glm::quat qOne(1.0, 0.0, 0.0, 0.0);
  glm::vec3 vZero(0.0, 0.0, 0.0);

  mQDown       = qOne;
  mQNow        = qOne;
  mVSphereDown = mouseOnSphere(vZero);
}

//------------------------------------------------------------------------------
glm::vec3 ArcBall::mouseOnSphere(const glm::vec3& tscMouse)
{
  glm::vec3 ballMouse;

  // (m - C) / R
  ballMouse.x = (tscMouse.x - mCenter.x) / mRadius;
  ballMouse.y = (tscMouse.y - mCenter.y) / mRadius;

  float mag_sq = glm::dot(ballMouse, ballMouse);
  if (mag_sq > 1.0)
  {
    // Since we are outside of the sphere, map to the visible boundary of the sphere.
    ballMouse *= 1.0 / sqrtf(mag_sq);
  }
  else
  {
    // Essentially, we are normalizing the vector by adding the missing z component.
    ballMouse.z = sqrtf(1.0 - mag_sq);
  }

  if(invertHemisphere)
    ballMouse.z = -ballMouse.z;

  return ballMouse;
}

//------------------------------------------------------------------------------
void ArcBall::beginDrag(const glm::vec2& msc)
{
  mQDown       = mQNow;
  mVSphereDown = mouseOnSphere(xyz(mScreenToTCS * glm::vec4(msc, 0.0f, 1.0f)));
}

//------------------------------------------------------------------------------
void ArcBall::drag(const glm::vec2& msc)
{
  glm::vec3 mVSphereNow = mouseOnSphere(xyz(mScreenToTCS * glm::vec4(msc, 0.0f, 1.0f)));

  // Construct a quaternion from two points on the unit sphere.
  glm::quat mQDrag = quatFromUnitSphere(mVSphereDown, mVSphereNow);
  mQNow = mQDrag * mQDown;
  if(glm::dot(mVSphereDown, mVSphereNow) < 0.0)
    beginDrag(msc);
}

//------------------------------------------------------------------------------
void ArcBall::setLocationOnSphere(glm::vec3 location, glm::vec3 up)
{
  glm::mat4 mMatNow = glm::lookAt(location, glm::vec3(0.0f), up);
  mQNow = glm::quat_cast(mMatNow);
}

//------------------------------------------------------------------------------
glm::quat ArcBall::quatFromUnitSphere(const glm::vec3& from, const glm::vec3& to)
{
  //TODO: check if cross is 0 before normalize. Crashes on Windows
  glm::vec3 axis = glm::normalize(glm::cross(from, to));

  // Give arbitrary non-zero vector because no rotation
  if (std::isnan(axis[0]))
    axis = from;

  float angle = std::acos(glm::dot(from, to));

  if(angle <= 0.00001 || std::isnan(angle))
    return glm::quat(1.0, 0.0, 0.0, 0.0);

  return glm::angleAxis(angle, axis);
}

//------------------------------------------------------------------------------
glm::mat4 ArcBall::getTransformation() const
{
  return glm::mat4_cast(mQNow);
}

} // namespace spire
<commit_msg>Fixed camera rotation<commit_after>/*
   For more information, please see: http://software.sci.utah.edu

   The MIT License

   Copyright (c) 2020 Scientific Computing and Imaging Institute,
   University of Utah.

   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.
*/

#include "ArcBall.hpp"
#include <glm/gtx/vec_swizzle.hpp>

namespace spire {

//------------------------------------------------------------------------------
  ArcBall::ArcBall(const glm::vec3& center, float radius, bool inverted, const glm::mat4& screenToTCS) :
    mScreenToTCS(screenToTCS),
    mCenter(center),
    mRadius(radius),
    invertHemisphere(inverted)
{
  // glm uses the following format for quaternions: w,x,y,z.
  //        w,    x,    y,    z
  glm::quat qOne(1.0, 0.0, 0.0, 0.0);
  glm::vec3 vZero(0.0, 0.0, 0.0);

  mQDown       = qOne;
  mQNow        = qOne;
  mVSphereDown = mouseOnSphere(vZero);
}

//------------------------------------------------------------------------------
glm::vec3 ArcBall::mouseOnSphere(const glm::vec3& tscMouse)
{
  glm::vec3 ballMouse((tscMouse - mCenter) / mRadius);

  float mag_sq = glm::dot(ballMouse, ballMouse);
  if (mag_sq > 1.0)
  {
    // Since we are outside of the sphere, map to the visible boundary of the sphere.
    ballMouse *= 1.0 / sqrtf(mag_sq);
  }
  else
  {
    // Essentially, we are normalizing the vector by adding the missing z component.
    ballMouse.z = sqrtf(1.0 - mag_sq);
  }

  if(invertHemisphere)
    ballMouse.z = -ballMouse.z;

  return ballMouse;
}

//------------------------------------------------------------------------------
void ArcBall::beginDrag(const glm::vec2& msc)
{
  mQDown       = mQNow;
  mVSphereDown = mouseOnSphere(xyz(mScreenToTCS * glm::vec4(msc, 0.0f, 1.0f)));
}

//------------------------------------------------------------------------------
void ArcBall::drag(const glm::vec2& msc)
{
  glm::vec3 mVSphereNow = mouseOnSphere(xyz(mScreenToTCS * glm::vec4(msc, 0.0f, 1.0f)));

  // Construct a quaternion from two points on the unit sphere.
  glm::quat mQDrag = quatFromUnitSphere(mVSphereDown, mVSphereNow);
  mQNow = mQDrag * mQDown;
  if(glm::dot(mVSphereDown, mVSphereNow) < 0.0)
    beginDrag(msc);
}

//------------------------------------------------------------------------------
void ArcBall::setLocationOnSphere(glm::vec3 location, glm::vec3 up)
{
  glm::mat4 mMatNow = glm::lookAt(location, glm::vec3(0.0f), up);
  mQNow = glm::quat_cast(mMatNow);
}

//------------------------------------------------------------------------------
glm::quat ArcBall::quatFromUnitSphere(const glm::vec3& from, const glm::vec3& to)
{
  //TODO: check if cross is 0 before normalize. Crashes on Windows
  glm::vec3 axis = glm::normalize(glm::cross(from, to));

  // Give arbitrary non-zero vector because no rotation
  if (std::isnan(axis[0]))
    axis = from;

  float angle = std::acos(glm::dot(from, to));

  if(angle <= 0.00001 || std::isnan(angle))
    return glm::quat(1.0, 0.0, 0.0, 0.0);

  return glm::angleAxis(angle, axis);
}

//------------------------------------------------------------------------------
glm::mat4 ArcBall::getTransformation() const
{
  return glm::mat4_cast(mQNow);
}

} // namespace spire
<|endoftext|>


<commit_before>#pragma once

#include "attribute.hpp"
#include "filter.hpp"

#define DECLARE_KEYWORD_IMPL(Name, Scope, T) \
    namespace keyword { \
    namespace tag { \
        struct Name##_t { \
            typedef T type; \
            static const char* name() { return #Name; } \
        }; \
    } \
    static inline ::blackhole::keyword::keyword_t<T, tag::Name##_t, ::blackhole::log::attribute::scope::Scope>& Name() { \
        static ::blackhole::keyword::keyword_t<T, tag::Name##_t, ::blackhole::log::attribute::scope::Scope> self; \
        return self; \
    } \
    }

#define DECLARE_EVENT_KEYWORD(Name, T) \
    DECLARE_KEYWORD_IMPL(Name, event, T)

#define DECLARE_KEYWORD(Name, T) \
    DECLARE_KEYWORD_IMPL(Name, global, T)

#define DECLARE_THREAD_KEYWORD(Name, T) \
    DECLARE_KEYWORD_IMPL(Name, thread, T)

#define DECLARE_UNIVERSE_KEYWORD(Name, T) \
    DECLARE_KEYWORD_IMPL(Name, universe, T)

#include "blackhole/expression/helper.hpp"

namespace blackhole {

namespace keyword {

template<typename T, typename NameProvider, log::attribute::scope Scope = log::attribute::DEFAULT_SCOPE>
struct keyword_t {
    typedef T type;
    static_assert(log::attribute::is_supported<
        typename blackhole::aux::underlying_type<T>::type
    >::value, "invalid attribute type");

    static const char* name() {
        return NameProvider::name();
    }

    struct extracter_t {
        typedef typename blackhole::aux::underlying_type<T>::type result_type;

        result_type operator()(const log::attributes_t& attributes) const {
            return static_cast<result_type>(attribute::traits<T>::extract(attributes, name()));
        }
    };

    log::attribute_pair_t operator=(const T& value) const {
        return attribute::make(name(), attribute::traits<T>::pack(value), Scope);
    }

    log::attribute_pair_t operator=(T&& value) const {
        return attribute::make(name(), attribute::traits<T>::pack(std::forward<T>(value)), Scope);
    }

    template<template<typename> class Operator>
    inline
    Operator<extracter_t> operation(const T& other) const {
        typedef typename blackhole::aux::underlying_type<T>::type result_type;
        return Operator<extracter_t>({ extracter_t(), static_cast<result_type>(other) });
    }

    typename expression::aux::Less<extracter_t> operator<(const T& other) const {
        return operation<expression::aux::Less>(other);
    }

    typename expression::aux::LessEq<extracter_t> operator<=(const T& other) const {
        return operation<expression::aux::LessEq>(other);
    }

    typename expression::aux::Gt<extracter_t> operator>(const T& other) const {
        return operation<expression::aux::Gt>(other);
    }

    typename expression::aux::GtEq<extracter_t> operator>=(const T& other) const {
        return operation<expression::aux::GtEq>(other);
    }

    typename expression::aux::Eq<extracter_t> operator==(const T& other) const {
        return operation<expression::aux::Eq>(other);
    }
};

} // namespace keyword

} // namespace blackhole
<commit_msg>[API] Added DECLARE_LOCAL_KEYWORD<commit_after>#pragma once

#include "attribute.hpp"
#include "filter.hpp"

#define DECLARE_KEYWORD_IMPL(Name, Scope, T) \
    namespace keyword { \
    namespace tag { \
        struct Name##_t { \
            typedef T type; \
            static const char* name() { return #Name; } \
        }; \
    } \
    static inline ::blackhole::keyword::keyword_t<T, tag::Name##_t, ::blackhole::log::attribute::scope::Scope>& Name() { \
        static ::blackhole::keyword::keyword_t<T, tag::Name##_t, ::blackhole::log::attribute::scope::Scope> self; \
        return self; \
    } \
    }

#define DECLARE_LOCAL_KEYWORD(Name, T) \
    DECLARE_KEYWORD_IMPL(Name, local, T)

#define DECLARE_EVENT_KEYWORD(Name, T) \
    DECLARE_KEYWORD_IMPL(Name, event, T)

#define DECLARE_KEYWORD(Name, T) \
    DECLARE_KEYWORD_IMPL(Name, global, T)

#define DECLARE_THREAD_KEYWORD(Name, T) \
    DECLARE_KEYWORD_IMPL(Name, thread, T)

#define DECLARE_UNIVERSE_KEYWORD(Name, T) \
    DECLARE_KEYWORD_IMPL(Name, universe, T)

#include "blackhole/expression/helper.hpp"

namespace blackhole {

namespace keyword {

template<typename T, typename NameProvider, log::attribute::scope Scope = log::attribute::DEFAULT_SCOPE>
struct keyword_t {
    typedef T type;
    static_assert(log::attribute::is_supported<
        typename blackhole::aux::underlying_type<T>::type
    >::value, "invalid attribute type");

    static const char* name() {
        return NameProvider::name();
    }

    struct extracter_t {
        typedef typename blackhole::aux::underlying_type<T>::type result_type;

        result_type operator()(const log::attributes_t& attributes) const {
            return static_cast<result_type>(attribute::traits<T>::extract(attributes, name()));
        }
    };

    log::attribute_pair_t operator=(const T& value) const {
        return attribute::make(name(), attribute::traits<T>::pack(value), Scope);
    }

    log::attribute_pair_t operator=(T&& value) const {
        return attribute::make(name(), attribute::traits<T>::pack(std::forward<T>(value)), Scope);
    }

    template<template<typename> class Operator>
    inline
    Operator<extracter_t> operation(const T& other) const {
        typedef typename blackhole::aux::underlying_type<T>::type result_type;
        return Operator<extracter_t>({ extracter_t(), static_cast<result_type>(other) });
    }

    typename expression::aux::Less<extracter_t> operator<(const T& other) const {
        return operation<expression::aux::Less>(other);
    }

    typename expression::aux::LessEq<extracter_t> operator<=(const T& other) const {
        return operation<expression::aux::LessEq>(other);
    }

    typename expression::aux::Gt<extracter_t> operator>(const T& other) const {
        return operation<expression::aux::Gt>(other);
    }

    typename expression::aux::GtEq<extracter_t> operator>=(const T& other) const {
        return operation<expression::aux::GtEq>(other);
    }

    typename expression::aux::Eq<extracter_t> operator==(const T& other) const {
        return operation<expression::aux::Eq>(other);
    }
};

} // namespace keyword

} // namespace blackhole
<|endoftext|>


<commit_before>/* <x0/plugins/proxy.cpp>
 *
 * This file is part of the x0 web server project and is released under LGPL-3.
 * http://www.xzero.ws/
 *
 * (c) 2009-2010 Christian Parpart <trapni@gentoo.org>
 */

#include <x0/http/HttpPlugin.h>
#include <x0/http/HttpServer.h>
#include <x0/http/HttpRequest.h>
#include <x0/io/BufferSource.h>
#include <x0/SocketSpec.h>
#include <x0/strutils.h>
#include <x0/Url.h>
#include <x0/Types.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>

/* {{{ -- configuration proposal:
 *
 * handler setup {
 * }
 *
 * handler main {
 *     proxy.reverse 'http://127.0.0.1:3000';
 * }
 *
 * --------------------------------------------------------------------------
 * possible tweaks:
 *  - bufsize (0 = unbuffered)
 *  - timeout.connect
 *  - timeout.write
 *  - timeout.read
 *  - ignore_clientabort
 * };
 *
 *
 */ // }}}

#if 0
#	define TRACE(msg...) DEBUG("proxy: " msg)
#else
#	define TRACE(msg...) /*!*/
#endif

// {{{ ProxyConnection API
class ProxyConnection :
	public x0::HttpMessageProcessor
{
private:
	int refCount_;

	x0::HttpRequest *request_;		//!< client's request
	x0::Socket* backend_;			//!< connection to backend app
	bool cloak_;					//!< to cloak or not to cloak the "Server" response header

	int connectTimeout_;
	int readTimeout_;
	int writeTimeout_;

	x0::Buffer writeBuffer_;
	size_t writeOffset_;
	size_t writeProgress_;

	x0::Buffer readBuffer_;
	bool processingDone_;

private:
	void ref();
	void unref();
	inline void close();
	inline void readSome();
	inline void writeSome();

	void onConnected(x0::Socket* s, int revents);
	void io(x0::Socket* s, int revents);
	void onRequestChunk(const x0::BufferRef& chunk);

	static void onAbort(void *p);
	void onWriteComplete();

	// response (HttpMessageProcessor)
	virtual bool onMessageBegin(int versionMajor, int versionMinor, int code, const x0::BufferRef& text);
	virtual bool onMessageHeader(const x0::BufferRef& name, const x0::BufferRef& value);
	virtual bool onMessageContent(const x0::BufferRef& chunk);
	virtual bool onMessageEnd();

public:
	inline ProxyConnection();
	~ProxyConnection();

	void start(x0::HttpRequest* in, x0::Socket* backend, bool cloak);
};
// }}}

// {{{ ProxyConnection impl
ProxyConnection::ProxyConnection() :
	x0::HttpMessageProcessor(x0::HttpMessageProcessor::RESPONSE),
	refCount_(1),
	request_(nullptr),
	backend_(nullptr),
	cloak_(false),

	connectTimeout_(0),
	readTimeout_(0),
	writeTimeout_(0),
	writeBuffer_(),
	writeOffset_(0),
	writeProgress_(0),
	readBuffer_(),
	processingDone_(false)
{
	TRACE("ProxyConnection()");
}

ProxyConnection::~ProxyConnection()
{
	TRACE("~ProxyConnection()");

	if (backend_) {
		backend_->close();

		delete backend_;
	}

	if (request_) {
		if (request_->status == x0::HttpError::Undefined)
			request_->status = x0::HttpError::ServiceUnavailable;

		request_->finish();
	}
}

void ProxyConnection::ref()
{
	++refCount_;
}

void ProxyConnection::unref()
{
	assert(refCount_ > 0);

	--refCount_;

	if (refCount_ == 0) {
		delete this;
	}
}

void ProxyConnection::close()
{
	unref(); // the one from the constructor
}

void ProxyConnection::onAbort(void *p)
{
	ProxyConnection *self = reinterpret_cast<ProxyConnection *>(p);
	self->close();
}

void ProxyConnection::start(x0::HttpRequest* in, x0::Socket* backend, bool cloak)
{
	request_ = in;
	request_->setAbortHandler(&ProxyConnection::onAbort, this);
	backend_ = backend;
	cloak_ = cloak_;

	// request line
	writeBuffer_.push_back(request_->method);
	writeBuffer_.push_back(' ');
	writeBuffer_.push_back(request_->uri);
	writeBuffer_.push_back(" HTTP/1.1\r\n");

	// request headers
	for (auto& header: request_->requestHeaders) {
		if (iequals(header.name, "Content-Transfer")
				|| iequals(header.name, "Expect")
				|| iequals(header.name, "Connection")
				|| iequals(header.name, "X-Forwarded-For")
				|| iequals(header.name, "X-Forwarded-Proto")) {
			TRACE("skip requestHeader(%s: %s)", header.name.str().c_str(), header.value.str().c_str());
			continue;
		}

		TRACE("pass requestHeader(%s: %s)", header.name.str().c_str(), header.value.str().c_str());
		writeBuffer_.push_back(header.name);
		writeBuffer_.push_back(": ");
		writeBuffer_.push_back(header.value);
		writeBuffer_.push_back("\r\n");
	}

	// additional headers to add
	writeBuffer_.push_back("Connection: closed\r\n");

	writeBuffer_.push_back("X-Forwarded-For: ");
	writeBuffer_.push_back(request_->connection.remoteIP());
	writeBuffer_.push_back("\r\n");

#if defined(WITH_SSL)
	if (request_->connection.isSecure())
		writeBuffer_.push_back("X-Forwarded-Proto: https\r\n");
	else
		writeBuffer_.push_back("X-Forwarded-Proto: http\r\n");
#endif

	// request headers terminator
	writeBuffer_.push_back("\r\n");

	if (request_->contentAvailable()) {
		TRACE("start: request content available: reading.");
		request_->setBodyCallback<ProxyConnection, &ProxyConnection::onRequestChunk>(this);
	}

	if (backend_->state() == x0::Socket::Connecting) {
		TRACE("start: connect in progress");
		backend_->setReadyCallback<ProxyConnection, &ProxyConnection::onConnected>(this);
	} else { // connected
		TRACE("start: flushing");
		backend_->setReadyCallback<ProxyConnection, &ProxyConnection::io>(this);
		backend_->setMode(x0::Socket::ReadWrite);
	}
}

void ProxyConnection::onConnected(x0::Socket* s, int revents)
{
	TRACE("onConnected: content? %d", request_->contentAvailable());
	//TRACE("onConnected.pending:\n%s\n", writeBuffer_.c_str());

	if (backend_->state() == x0::Socket::Operational) {
		TRACE("onConnected: flushing");
		backend_->setReadyCallback<ProxyConnection, &ProxyConnection::io>(this);
		backend_->setMode(x0::Socket::ReadWrite); // flush already serialized request
	} else {
		close();
	}
}

/** transferres a request body chunk to the origin server.  */
void ProxyConnection::onRequestChunk(const x0::BufferRef& chunk)
{
	TRACE("onRequestChunk(nb:%ld)", chunk.size());
	writeBuffer_.push_back(chunk);

	if (backend_->state() == x0::Socket::Operational) {
		backend_->setMode(x0::Socket::ReadWrite);
	}
}

inline bool validateResponseHeader(const x0::BufferRef& name)
{
	// XXX do not allow origin's connection-level response headers to be passed to the client.
	if (iequals(name, "Connection"))
		return false;

	if (iequals(name, "Transfer-Encoding"))
		return false;

	return true;
}

/** callback, invoked when the origin server has passed us the response status line.
 *
 * We will use the status code only.
 * However, we could pass the text field, too - once x0 core supports it.
 */
bool ProxyConnection::onMessageBegin(int major, int minor, int code, const x0::BufferRef& text)
{
	TRACE("ProxyConnection(%p).status(HTTP/%d.%d, %d, '%s')", (void*)this, major, minor, code, text.str().c_str());

	request_->status = static_cast<x0::HttpError>(code);
	TRACE("status: %d", (int)request_->status);
	return true;
}

/** callback, invoked on every successfully parsed response header.
 *
 * We will pass this header directly to the client's response,
 * if that is NOT a connection-level header.
 */
bool ProxyConnection::onMessageHeader(const x0::BufferRef& name, const x0::BufferRef& value)
{
	TRACE("ProxyConnection(%p).onHeader('%s', '%s')", (void*)this, name.str().c_str(), value.str().c_str());

	if (!validateResponseHeader(name))
		goto done;

	if (cloak_ && iequals(name, "Server"))
		goto done;

	request_->responseHeaders.push_back(name.str(), value.str());

done:
	return true;
}

/** callback, invoked on a new response content chunk. */
bool ProxyConnection::onMessageContent(const x0::BufferRef& chunk)
{
	TRACE("messageContent(nb:%lu) state:%s", chunk.size(), backend_->state_str());

	// stop watching for more input
	backend_->setMode(x0::Socket::None);

	// transfer response-body chunk to client
	request_->write<x0::BufferSource>(chunk);

	// start listening on backend I/O when chunk has been fully transmitted
	ref();
	request_->writeCallback<ProxyConnection, &ProxyConnection::onWriteComplete>(this);

	return true;
}

void ProxyConnection::onWriteComplete()
{
	TRACE("chunk write complete: %s", state_str());
	backend_->setMode(x0::Socket::Read);
	unref();
}

bool ProxyConnection::onMessageEnd()
{
	TRACE("messageEnd() backend-state:%s", backend_->state_str());
	processingDone_ = true;
	return false;
}

void ProxyConnection::io(x0::Socket* s, int revents)
{
	TRACE("io(0x%04x)", revents);

	if (revents & x0::Socket::Read)
		readSome();

	if (revents & x0::Socket::Write)
		writeSome();
}

void ProxyConnection::writeSome()
{
	TRACE("writeSome() - %s (%d)", state_str(), request_->contentAvailable());

	ssize_t rv = backend_->write(writeBuffer_.data() + writeOffset_, writeBuffer_.size() - writeOffset_);

	if (rv > 0) {
		TRACE("write request: %ld (of %ld) bytes", rv, writeBuffer_.size() - writeOffset_);

		writeOffset_ += rv;
		writeProgress_ += rv;

		if (writeOffset_ == writeBuffer_.size()) {
			TRACE("writeOffset == writeBuffser.size (%ld) p:%ld, ca: %d, clr:%ld", writeOffset_,
				writeProgress_, request_->contentAvailable(), request_->connection.contentLength());

			writeOffset_ = 0;
			writeBuffer_.clear();
			backend_->setMode(x0::Socket::Read);
		}
	} else {
		TRACE("write request failed(%ld): %s", rv, strerror(errno));
		close();
	}
}

void ProxyConnection::readSome()
{
	TRACE("readSome() - %s", state_str());

	std::size_t lower_bound = readBuffer_.size();

	if (lower_bound == readBuffer_.capacity())
		readBuffer_.setCapacity(lower_bound + 4096);

	ssize_t rv = backend_->read(readBuffer_);

	if (rv > 0) {
		TRACE("read response: %ld bytes", rv);
		std::size_t np = process(readBuffer_.ref(lower_bound, rv));
		TRACE("readSome(): process(): %ld / %ld", np, rv);

		if (processingDone_ || state() == SYNTAX_ERROR) {
			close();
		} else {
			TRACE("resume with io:%d, state:%s", backend_->mode(), backend_->state_str());
			backend_->setMode(x0::Socket::Read);
		}
	} else if (rv == 0) {
		TRACE("http server connection closed");
		close();
	} else {
		TRACE("read response failed(%ld): %s", rv, strerror(errno));

		switch (errno) {
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
		case EWOULDBLOCK:
#endif
		case EAGAIN:
		case EINTR:
			backend_->setMode(x0::Socket::Read);
			break;
		default:
			close();
			break;
		}
	}
}

// }}}

// {{{ plugin class
/**
 * \ingroup plugins
 * \brief proxy content generator plugin
 */
class proxy_plugin :
	public x0::HttpPlugin
{
private:
	bool cloak_;

public:
	proxy_plugin(x0::HttpServer& srv, const std::string& name) :
		x0::HttpPlugin(srv, name),
		cloak_(true)
	{
		registerHandler<proxy_plugin, &proxy_plugin::proxy_reverse>("proxy.reverse");
		registerSetupProperty<proxy_plugin, &proxy_plugin::proxy_cloak>("proxy.cloak", x0::FlowValue::BOOLEAN);
	}

	~proxy_plugin()
	{
	}

private:
	void proxy_cloak(const x0::FlowParams& args, x0::FlowValue& result)
	{
		if (args.size() && (args[0].isBool() || args[0].isNumber())) {
			cloak_ = args[0].toBool();
			printf("proxy cloak: %s\n", cloak_ ? "true" : "false");
		}

		result.set(cloak_);
	}

	bool proxy_reverse(x0::HttpRequest *in, const x0::FlowParams& args)
	{
		// TODO: reuse already spawned proxy connections instead of recreating each time.
		x0::SocketSpec spec;
		spec << args;
		if (!spec.isValid() || spec.backlog >= 0) {
			in->log(x0::Severity::error, "Invalid socket spec passed.");
			return false;
		}

		x0::Socket* backend = new x0::Socket(in->connection.worker().loop());
		if (spec.isLocal()) {
			TRACE("unix socket: '%s'", s.c_str());
			backend->openUnix(spec.local);
		} else {
			backend->openTcp(spec.address.str(), spec.port);
		}

		if (backend->isOpen()) {
			TRACE("in.content? %d", in->contentAvailable());
			if (ProxyConnection* pc = new ProxyConnection()) {
				pc->start(in, backend, cloak_);
				return true;
			}
		}

		in->status = x0::HttpError::ServiceUnavailable;
		in->finish();

		return true;
	}
};
// }}}

X0_EXPORT_PLUGIN(proxy)
<commit_msg>[plugins] proxy: potentially fixes a SEGFAULT.<commit_after>/* <x0/plugins/proxy.cpp>
 *
 * This file is part of the x0 web server project and is released under LGPL-3.
 * http://www.xzero.ws/
 *
 * (c) 2009-2010 Christian Parpart <trapni@gentoo.org>
 */

#include <x0/http/HttpPlugin.h>
#include <x0/http/HttpServer.h>
#include <x0/http/HttpRequest.h>
#include <x0/io/BufferSource.h>
#include <x0/SocketSpec.h>
#include <x0/strutils.h>
#include <x0/Url.h>
#include <x0/Types.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>
#include <fcntl.h>
#include <netdb.h>

/* {{{ -- configuration proposal:
 *
 * handler setup {
 * }
 *
 * handler main {
 *     proxy.reverse 'http://127.0.0.1:3000';
 * }
 *
 * --------------------------------------------------------------------------
 * possible tweaks:
 *  - bufsize (0 = unbuffered)
 *  - timeout.connect
 *  - timeout.write
 *  - timeout.read
 *  - ignore_clientabort
 * };
 *
 *
 */ // }}}

#if 0
#	define TRACE(msg...) DEBUG("proxy: " msg)
#else
#	define TRACE(msg...) /*!*/
#endif

// {{{ ProxyConnection API
class ProxyConnection :
	public x0::HttpMessageProcessor
{
private:
	int refCount_;

	x0::HttpRequest *request_;		//!< client's request
	x0::Socket* backend_;			//!< connection to backend app
	bool cloak_;					//!< to cloak or not to cloak the "Server" response header

	int connectTimeout_;
	int readTimeout_;
	int writeTimeout_;

	x0::Buffer writeBuffer_;
	size_t writeOffset_;
	size_t writeProgress_;

	x0::Buffer readBuffer_;
	bool processingDone_;

private:
	void ref();
	void unref();
	inline void close();
	inline void readSome();
	inline void writeSome();

	void onConnected(x0::Socket* s, int revents);
	void io(x0::Socket* s, int revents);
	void onRequestChunk(const x0::BufferRef& chunk);

	static void onAbort(void *p);
	void onWriteComplete();

	// response (HttpMessageProcessor)
	virtual bool onMessageBegin(int versionMajor, int versionMinor, int code, const x0::BufferRef& text);
	virtual bool onMessageHeader(const x0::BufferRef& name, const x0::BufferRef& value);
	virtual bool onMessageContent(const x0::BufferRef& chunk);
	virtual bool onMessageEnd();

public:
	inline ProxyConnection();
	~ProxyConnection();

	void start(x0::HttpRequest* in, x0::Socket* backend, bool cloak);
};
// }}}

// {{{ ProxyConnection impl
ProxyConnection::ProxyConnection() :
	x0::HttpMessageProcessor(x0::HttpMessageProcessor::RESPONSE),
	refCount_(1),
	request_(nullptr),
	backend_(nullptr),
	cloak_(false),

	connectTimeout_(0),
	readTimeout_(0),
	writeTimeout_(0),
	writeBuffer_(),
	writeOffset_(0),
	writeProgress_(0),
	readBuffer_(),
	processingDone_(false)
{
	TRACE("ProxyConnection()");
}

ProxyConnection::~ProxyConnection()
{
	TRACE("~ProxyConnection()");

	if (backend_) {
		backend_->close();

		delete backend_;
	}

	if (request_) {
		if (request_->status == x0::HttpError::Undefined)
			request_->status = x0::HttpError::ServiceUnavailable;

		request_->finish();
	}
}

void ProxyConnection::ref()
{
	++refCount_;
}

void ProxyConnection::unref()
{
	assert(refCount_ > 0);

	--refCount_;

	if (refCount_ == 0) {
		delete this;
	}
}

void ProxyConnection::close()
{
	if (backend_)
		// stop watching on any backend I/O events, if active
		backend_->close();

	unref(); // the one from the constructor
}

void ProxyConnection::onAbort(void *p)
{
	ProxyConnection *self = reinterpret_cast<ProxyConnection *>(p);
	self->close();
}

void ProxyConnection::start(x0::HttpRequest* in, x0::Socket* backend, bool cloak)
{
	request_ = in;
	request_->setAbortHandler(&ProxyConnection::onAbort, this);
	backend_ = backend;
	cloak_ = cloak_;

	// request line
	writeBuffer_.push_back(request_->method);
	writeBuffer_.push_back(' ');
	writeBuffer_.push_back(request_->uri);
	writeBuffer_.push_back(" HTTP/1.1\r\n");

	// request headers
	for (auto& header: request_->requestHeaders) {
		if (iequals(header.name, "Content-Transfer")
				|| iequals(header.name, "Expect")
				|| iequals(header.name, "Connection")
				|| iequals(header.name, "X-Forwarded-For")
				|| iequals(header.name, "X-Forwarded-Proto")) {
			TRACE("skip requestHeader(%s: %s)", header.name.str().c_str(), header.value.str().c_str());
			continue;
		}

		TRACE("pass requestHeader(%s: %s)", header.name.str().c_str(), header.value.str().c_str());
		writeBuffer_.push_back(header.name);
		writeBuffer_.push_back(": ");
		writeBuffer_.push_back(header.value);
		writeBuffer_.push_back("\r\n");
	}

	// additional headers to add
	writeBuffer_.push_back("Connection: closed\r\n");

	writeBuffer_.push_back("X-Forwarded-For: ");
	writeBuffer_.push_back(request_->connection.remoteIP());
	writeBuffer_.push_back("\r\n");

#if defined(WITH_SSL)
	if (request_->connection.isSecure())
		writeBuffer_.push_back("X-Forwarded-Proto: https\r\n");
	else
		writeBuffer_.push_back("X-Forwarded-Proto: http\r\n");
#endif

	// request headers terminator
	writeBuffer_.push_back("\r\n");

	if (request_->contentAvailable()) {
		TRACE("start: request content available: reading.");
		request_->setBodyCallback<ProxyConnection, &ProxyConnection::onRequestChunk>(this);
	}

	if (backend_->state() == x0::Socket::Connecting) {
		TRACE("start: connect in progress");
		backend_->setReadyCallback<ProxyConnection, &ProxyConnection::onConnected>(this);
	} else { // connected
		TRACE("start: flushing");
		backend_->setReadyCallback<ProxyConnection, &ProxyConnection::io>(this);
		backend_->setMode(x0::Socket::ReadWrite);
	}
}

void ProxyConnection::onConnected(x0::Socket* s, int revents)
{
	TRACE("onConnected: content? %d", request_->contentAvailable());
	//TRACE("onConnected.pending:\n%s\n", writeBuffer_.c_str());

	if (backend_->state() == x0::Socket::Operational) {
		TRACE("onConnected: flushing");
		backend_->setReadyCallback<ProxyConnection, &ProxyConnection::io>(this);
		backend_->setMode(x0::Socket::ReadWrite); // flush already serialized request
	} else {
		close();
	}
}

/** transferres a request body chunk to the origin server.  */
void ProxyConnection::onRequestChunk(const x0::BufferRef& chunk)
{
	TRACE("onRequestChunk(nb:%ld)", chunk.size());
	writeBuffer_.push_back(chunk);

	if (backend_->state() == x0::Socket::Operational) {
		backend_->setMode(x0::Socket::ReadWrite);
	}
}

inline bool validateResponseHeader(const x0::BufferRef& name)
{
	// XXX do not allow origin's connection-level response headers to be passed to the client.
	if (iequals(name, "Connection"))
		return false;

	if (iequals(name, "Transfer-Encoding"))
		return false;

	return true;
}

/** callback, invoked when the origin server has passed us the response status line.
 *
 * We will use the status code only.
 * However, we could pass the text field, too - once x0 core supports it.
 */
bool ProxyConnection::onMessageBegin(int major, int minor, int code, const x0::BufferRef& text)
{
	TRACE("ProxyConnection(%p).status(HTTP/%d.%d, %d, '%s')", (void*)this, major, minor, code, text.str().c_str());

	request_->status = static_cast<x0::HttpError>(code);
	TRACE("status: %d", (int)request_->status);
	return true;
}

/** callback, invoked on every successfully parsed response header.
 *
 * We will pass this header directly to the client's response,
 * if that is NOT a connection-level header.
 */
bool ProxyConnection::onMessageHeader(const x0::BufferRef& name, const x0::BufferRef& value)
{
	TRACE("ProxyConnection(%p).onHeader('%s', '%s')", (void*)this, name.str().c_str(), value.str().c_str());

	if (!validateResponseHeader(name))
		goto done;

	if (cloak_ && iequals(name, "Server"))
		goto done;

	request_->responseHeaders.push_back(name.str(), value.str());

done:
	return true;
}

/** callback, invoked on a new response content chunk. */
bool ProxyConnection::onMessageContent(const x0::BufferRef& chunk)
{
	TRACE("messageContent(nb:%lu) state:%s", chunk.size(), backend_->state_str());

	// stop watching for more input
	backend_->setMode(x0::Socket::None);

	// transfer response-body chunk to client
	request_->write<x0::BufferSource>(chunk);

	// start listening on backend I/O when chunk has been fully transmitted
	ref();
	request_->writeCallback<ProxyConnection, &ProxyConnection::onWriteComplete>(this);

	return true;
}

void ProxyConnection::onWriteComplete()
{
	TRACE("chunk write complete: %s", state_str());
	backend_->setMode(x0::Socket::Read);
	unref();
}

bool ProxyConnection::onMessageEnd()
{
	TRACE("messageEnd() backend-state:%s", backend_->state_str());
	processingDone_ = true;
	return false;
}

void ProxyConnection::io(x0::Socket* s, int revents)
{
	TRACE("io(0x%04x)", revents);

	if (revents & x0::Socket::Read)
		readSome();

	if (revents & x0::Socket::Write)
		writeSome();
}

void ProxyConnection::writeSome()
{
	TRACE("writeSome() - %s (%d)", state_str(), request_->contentAvailable());

	ssize_t rv = backend_->write(writeBuffer_.data() + writeOffset_, writeBuffer_.size() - writeOffset_);

	if (rv > 0) {
		TRACE("write request: %ld (of %ld) bytes", rv, writeBuffer_.size() - writeOffset_);

		writeOffset_ += rv;
		writeProgress_ += rv;

		if (writeOffset_ == writeBuffer_.size()) {
			TRACE("writeOffset == writeBuffser.size (%ld) p:%ld, ca: %d, clr:%ld", writeOffset_,
				writeProgress_, request_->contentAvailable(), request_->connection.contentLength());

			writeOffset_ = 0;
			writeBuffer_.clear();
			backend_->setMode(x0::Socket::Read);
		}
	} else {
		TRACE("write request failed(%ld): %s", rv, strerror(errno));
		close();
	}
}

void ProxyConnection::readSome()
{
	TRACE("readSome() - %s", state_str());

	std::size_t lower_bound = readBuffer_.size();

	if (lower_bound == readBuffer_.capacity())
		readBuffer_.setCapacity(lower_bound + 4096);

	ssize_t rv = backend_->read(readBuffer_);

	if (rv > 0) {
		TRACE("read response: %ld bytes", rv);
		std::size_t np = process(readBuffer_.ref(lower_bound, rv));
		TRACE("readSome(): process(): %ld / %ld", np, rv);

		if (processingDone_ || state() == SYNTAX_ERROR) {
			close();
		} else {
			TRACE("resume with io:%d, state:%s", backend_->mode(), backend_->state_str());
			backend_->setMode(x0::Socket::Read);
		}
	} else if (rv == 0) {
		TRACE("http server connection closed");
		close();
	} else {
		TRACE("read response failed(%ld): %s", rv, strerror(errno));

		switch (errno) {
#if defined(EWOULDBLOCK) && (EWOULDBLOCK != EAGAIN)
		case EWOULDBLOCK:
#endif
		case EAGAIN:
		case EINTR:
			backend_->setMode(x0::Socket::Read);
			break;
		default:
			close();
			break;
		}
	}
}

// }}}

// {{{ plugin class
/**
 * \ingroup plugins
 * \brief proxy content generator plugin
 */
class proxy_plugin :
	public x0::HttpPlugin
{
private:
	bool cloak_;

public:
	proxy_plugin(x0::HttpServer& srv, const std::string& name) :
		x0::HttpPlugin(srv, name),
		cloak_(true)
	{
		registerHandler<proxy_plugin, &proxy_plugin::proxy_reverse>("proxy.reverse");
		registerSetupProperty<proxy_plugin, &proxy_plugin::proxy_cloak>("proxy.cloak", x0::FlowValue::BOOLEAN);
	}

	~proxy_plugin()
	{
	}

private:
	void proxy_cloak(const x0::FlowParams& args, x0::FlowValue& result)
	{
		if (args.size() && (args[0].isBool() || args[0].isNumber())) {
			cloak_ = args[0].toBool();
			printf("proxy cloak: %s\n", cloak_ ? "true" : "false");
		}

		result.set(cloak_);
	}

	bool proxy_reverse(x0::HttpRequest *in, const x0::FlowParams& args)
	{
		// TODO: reuse already spawned proxy connections instead of recreating each time.
		x0::SocketSpec spec;
		spec << args;
		if (!spec.isValid() || spec.backlog >= 0) {
			in->log(x0::Severity::error, "Invalid socket spec passed.");
			return false;
		}

		x0::Socket* backend = new x0::Socket(in->connection.worker().loop());
		if (spec.isLocal()) {
			TRACE("unix socket: '%s'", s.c_str());
			backend->openUnix(spec.local);
		} else {
			backend->openTcp(spec.address.str(), spec.port);
		}

		if (backend->isOpen()) {
			TRACE("in.content? %d", in->contentAvailable());
			if (ProxyConnection* pc = new ProxyConnection()) {
				pc->start(in, backend, cloak_);
				return true;
			}
		}

		in->status = x0::HttpError::ServiceUnavailable;
		in->finish();

		return true;
	}
};
// }}}

X0_EXPORT_PLUGIN(proxy)
<|endoftext|>


<commit_before>/***************************************************************************
 *   Copyright (c) 2014 Luke Parry <l.parry@warwick.ac.uk>                 *
 *   Copyright (c) Jürgen Riegel          (juergen.riegel@web.de) 2002     *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  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 Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/

#include "PreCompiled.h"

#ifndef _PreComp_
  #include <sstream>
  #include <QDomDocument>
  #include <QFile>
#endif

#include <QXmlQuery>
#include <QXmlResultItems>
#include "QDomNodeModel.h"

#include <Base/Exception.h>
#include <Base/Console.h>
#include <Base/Interpreter.h>
#include <Base/FileInfo.h>
#include <Base/PyObjectBase.h>
#include <Base/Quantity.h>

#include <App/Application.h>

#include <boost/regex.hpp>
#include <boost/graph/graph_concepts.hpp>
#include <iostream>
#include <iterator>

#include <QDebug>

#include "DrawPage.h"
#include "DrawSVGTemplate.h"

#include <Mod/TechDraw/App/DrawSVGTemplatePy.h>

using namespace TechDraw;
using namespace std;

PROPERTY_SOURCE(TechDraw::DrawSVGTemplate, TechDraw::DrawTemplate)

DrawSVGTemplate::DrawSVGTemplate()
{
    static const char *group = "Template";

    //TODO: Do we need PageResult anymore?  -wf Yes!
    // PageResult points to a temporary file in tmp/FreeCAD-AB-CD-EF-.../myTemplate.svg
    // which is really copy of original Template with EditableFields replaced
    // When restoring saved document, Template is redundant/incorrect/not present - PageResult is the correct info.  -wf-
    ADD_PROPERTY_TYPE(PageResult, (0),  group, App::Prop_Output,    "Current SVG code for template");
    ADD_PROPERTY_TYPE(Template,   (""), group, App::Prop_Transient, "Template for the page");             //sb TemplateFileName???

    // Width and Height properties shouldn't be set by the user
    Height.setStatus(App::Property::ReadOnly,true);
    Width.setStatus(App::Property::ReadOnly,true);
    Orientation.setStatus(App::Property::ReadOnly,true);
}

DrawSVGTemplate::~DrawSVGTemplate()
{
}

PyObject *DrawSVGTemplate::getPyObject(void)
{
    if (PythonObject.is(Py::_None())) {
        // ref counter is set to 1
        PythonObject = Py::Object(new DrawSVGTemplatePy(this),true);
    }
    return Py::new_reference_to(PythonObject);
}

unsigned int DrawSVGTemplate::getMemSize(void) const
{
    return 0;
}

short DrawSVGTemplate::mustExecute() const
{
    return TechDraw::DrawTemplate::mustExecute();
}

void DrawSVGTemplate::onChanged(const App::Property* prop)
{
    bool updatePage = false;

    if (prop == &PageResult) {
        if (isRestoring()) {

            //original template has been stored in fcstd file
            Template.setValue(PageResult.getValue());
        }
    } else if (prop == &Template) {            //fileName has changed
        if (!isRestoring()) {
            EditableTexts.setValues(getEditableTextsFromTemplate());
            updatePage = true;
        }
    } else if (prop == &EditableTexts) {
        if (!isRestoring()) {
            updatePage = true;
        }
    }

    if (updatePage) {
        execute();
    }

    TechDraw::DrawTemplate::onChanged(prop);
}

App::DocumentObjectExecReturn * DrawSVGTemplate::execute(void)
{
    std::string templateFilename = Template.getValue();
    if (templateFilename.empty())
        return App::DocumentObject::StdReturn;

    Base::FileInfo fi(templateFilename);
    if (!fi.isReadable()) {
        // non-empty template value, but can't read file
        // if there is a old absolute template file set use a redirect
        fi.setFile(App::Application::getResourceDir() + "Mod/Drawing/Templates/" + fi.fileName());
        // try the redirect
        if (!fi.isReadable()) {
            Base::Console().Log("DrawPage::execute() not able to open %s!\n", Template.getValue());
            std::string error = std::string("Cannot open file ") + Template.getValue();
            return new App::DocumentObjectExecReturn(error);
        }
    }

    if (std::string(PageResult.getValue()).empty())                    //first time through?
        PageResult.setValue(fi.filePath().c_str());

    QFile templateFile(QString::fromUtf8(fi.filePath().c_str()));
    if (!templateFile.open(QIODevice::ReadOnly)) {
        Base::Console().Log("DrawPage::execute() can't read template %s!\n", Template.getValue());
        std::string error = std::string("Cannot read file ") + Template.getValue();
        return new App::DocumentObjectExecReturn(error);
    }

    QDomDocument templateDocument;
    if (!templateDocument.setContent(&templateFile)) {
        Base::Console().Message("DrawPage::execute() - failed to parse file: %s\n",
                                Template.getValue());
        std::string error = std::string("Cannot parse file ") + Template.getValue();
        return new App::DocumentObjectExecReturn(error);
    }

    QXmlQuery query(QXmlQuery::XQuery10);
    QDomNodeModel model(query.namePool(), templateDocument);
    query.setFocus(QXmlItem(model.fromDomNode(templateDocument.documentElement())));

    // XPath query to select all <tspan> nodes whose <text> parent
    // has "freecad:editable" attribute
    query.setQuery(QString::fromUtf8(
        "declare default element namespace \"" SVG_NS_URI "\"; "
        "declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; "
        "//text[@freecad:editable]/tspan"));

    QXmlResultItems queryResult;
    query.evaluateTo(&queryResult);

    std::map<std::string, std::string> substitutions = EditableTexts.getValues();
    while (!queryResult.next().isNull())
    {
        QDomElement tspan = model.toDomNode(queryResult.current().toNodeModelIndex()).toElement();

        // Replace the editable text spans with new nodes holding actual values
        std::map<std::string, std::string>::iterator item =
            substitutions.find(tspan.parentNode().toElement()
                               .attribute(QString::fromUtf8("freecad:editable")).toStdString());
        if (item != substitutions.end()) {
            // Keep all spaces in the text node
            tspan.setAttribute(QString::fromUtf8("xml:space"), QString::fromUtf8("preserve"));

            // Remove all child nodes and append text node with editable replacement as the only descendant
            while (!tspan.lastChild().isNull()) {
                tspan.removeChild(tspan.lastChild());
            }
            tspan.appendChild(templateDocument.createTextNode(QString::fromUtf8(item->second.c_str())));
        }
    }

    string pageResultFilename = PageResult.getExchangeTempFile();
    QFile pageResult(QString::fromUtf8(pageResultFilename.c_str()));
    if (pageResult.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QTextStream stream(&pageResult);
        stream << templateDocument.toString();
        pageResult.close();
        PageResult.setValue(pageResultFilename.c_str());
    }
    else {
        Base::Console().Message("DrawPage::execute() - failed to open file for writing: %s\n",
                                pageResultFilename.c_str());
    }

    // Calculate the dimensions of the page and store for retrieval
    // Obtain the size of the SVG document by reading the document attributes
    QDomElement docElement = templateDocument.documentElement();
    Base::Quantity quantity;

    // Obtain the width
    QString str = docElement.attribute(QString::fromLatin1("width"));
    quantity = Base::Quantity::parse(str);
    quantity.setUnit(Base::Unit::Length);

    Width.setValue(quantity.getValue());

    str = docElement.attribute(QString::fromLatin1("height"));
    quantity = Base::Quantity::parse(str);
    quantity.setUnit(Base::Unit::Length);

    Height.setValue(quantity.getValue());

    bool isLandscape = getWidth() / getHeight() >= 1.;

    Orientation.setValue(isLandscape ? 1 : 0);

    return TechDraw::DrawTemplate::execute();
}

double DrawSVGTemplate::getWidth() const
{
    return Width.getValue();
}

double DrawSVGTemplate::getHeight() const
{
    return Height.getValue();
}

std::map<std::string, std::string> DrawSVGTemplate::getEditableTextsFromTemplate()
{
    std::map<std::string, std::string> editables;

    std::string templateFilename = Template.getValue();
    if (templateFilename.empty()) {
        return editables;
    }

    Base::FileInfo tfi(templateFilename);
    if (!tfi.isReadable()) {
        // if there is a old absolute template file set use a redirect
        tfi.setFile(App::Application::getResourceDir() + "Mod/Drawing/Templates/" + tfi.fileName());
        // try the redirect
        if (!tfi.isReadable()) {
            Base::Console().Log("DrawPage::getEditableTextsFromTemplate() not able to open %s!\n", Template.getValue());
            return editables;
        }
    }

    QFile templateFile(QString::fromUtf8(tfi.filePath().c_str()));
    if (!templateFile.open(QIODevice::ReadOnly)) {
        Base::Console().Log("DrawPage::getEditableTextsFromTemplate() can't read template %s!\n", Template.getValue());
        return editables;
    }

    QDomDocument templateDocument;
    if (!templateDocument.setContent(&templateFile)) {
        Base::Console().Message("DrawPage::getEditableTextsFromTemplate() - failed to parse file: %s\n",
                                Template.getValue());
        return editables;
    }

    QXmlQuery query(QXmlQuery::XQuery10);
    QDomNodeModel model(query.namePool(), templateDocument, true);
    query.setFocus(QXmlItem(model.fromDomNode(templateDocument.documentElement())));

    // XPath query to select all <tspan> nodes whose <text> parent
    // has "freecad:editable" attribute
    query.setQuery(QString::fromUtf8(
        "declare default element namespace \"" SVG_NS_URI "\"; "
        "declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; "
        "//text[@freecad:editable]/tspan"));

    QXmlResultItems queryResult;
    query.evaluateTo(&queryResult);

    while (!queryResult.next().isNull())
    {
        QDomElement tspan = model.toDomNode(queryResult.current().toNodeModelIndex()).toElement();

        // Takeover the names stored as attributes and the values stored as text items
        editables[tspan.parentNode().toElement().attribute(QString::fromUtf8("freecad:editable"))
                  .toStdString()] = tspan.firstChild().nodeValue().toStdString();
    }

    return editables;
}

// Python Template feature ---------------------------------------------------------
namespace App {
/// @cond DOXERR
PROPERTY_SOURCE_TEMPLATE(TechDraw::DrawSVGTemplatePython, TechDraw::DrawSVGTemplate)
template<> const char* TechDraw::DrawSVGTemplatePython::getViewProviderName(void) const {
    return "TechDrawGui::ViewProviderPython";
}
/// @endcond

// explicit template instantiation
template class TechDrawExport FeaturePythonT<TechDraw::DrawSVGTemplate>;
}
<commit_msg>TechDraw: SVG export refactoring - Fix russian templates rendering<commit_after>/***************************************************************************
 *   Copyright (c) 2014 Luke Parry <l.parry@warwick.ac.uk>                 *
 *   Copyright (c) Jürgen Riegel          (juergen.riegel@web.de) 2002     *
 *                                                                         *
 *   This file is part of the FreeCAD CAx development system.              *
 *                                                                         *
 *   This library is free software; you can redistribute it and/or         *
 *   modify it under the terms of the GNU Library General Public           *
 *   License as published by the Free Software Foundation; either          *
 *   version 2 of the License, or (at your option) any later version.      *
 *                                                                         *
 *   This library  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 Library General Public License for more details.                  *
 *                                                                         *
 *   You should have received a copy of the GNU Library General Public     *
 *   License along with this library; see the file COPYING.LIB. If not,    *
 *   write to the Free Software Foundation, Inc., 59 Temple Place,         *
 *   Suite 330, Boston, MA  02111-1307, USA                                *
 *                                                                         *
 ***************************************************************************/

#include "PreCompiled.h"

#ifndef _PreComp_
  #include <sstream>
  #include <QDomDocument>
  #include <QFile>
#endif

#include <QXmlQuery>
#include <QXmlResultItems>
#include "QDomNodeModel.h"

#include <Base/Exception.h>
#include <Base/Console.h>
#include <Base/Interpreter.h>
#include <Base/FileInfo.h>
#include <Base/PyObjectBase.h>
#include <Base/Quantity.h>

#include <App/Application.h>

#include <boost/regex.hpp>
#include <boost/graph/graph_concepts.hpp>
#include <iostream>
#include <iterator>

#include <QDebug>

#include "DrawPage.h"
#include "DrawSVGTemplate.h"

#include <Mod/TechDraw/App/DrawSVGTemplatePy.h>

using namespace TechDraw;
using namespace std;

PROPERTY_SOURCE(TechDraw::DrawSVGTemplate, TechDraw::DrawTemplate)

DrawSVGTemplate::DrawSVGTemplate()
{
    static const char *group = "Template";

    //TODO: Do we need PageResult anymore?  -wf Yes!
    // PageResult points to a temporary file in tmp/FreeCAD-AB-CD-EF-.../myTemplate.svg
    // which is really copy of original Template with EditableFields replaced
    // When restoring saved document, Template is redundant/incorrect/not present - PageResult is the correct info.  -wf-
    ADD_PROPERTY_TYPE(PageResult, (0),  group, App::Prop_Output,    "Current SVG code for template");
    ADD_PROPERTY_TYPE(Template,   (""), group, App::Prop_Transient, "Template for the page");             //sb TemplateFileName???

    // Width and Height properties shouldn't be set by the user
    Height.setStatus(App::Property::ReadOnly,true);
    Width.setStatus(App::Property::ReadOnly,true);
    Orientation.setStatus(App::Property::ReadOnly,true);
}

DrawSVGTemplate::~DrawSVGTemplate()
{
}

PyObject *DrawSVGTemplate::getPyObject(void)
{
    if (PythonObject.is(Py::_None())) {
        // ref counter is set to 1
        PythonObject = Py::Object(new DrawSVGTemplatePy(this),true);
    }
    return Py::new_reference_to(PythonObject);
}

unsigned int DrawSVGTemplate::getMemSize(void) const
{
    return 0;
}

short DrawSVGTemplate::mustExecute() const
{
    return TechDraw::DrawTemplate::mustExecute();
}

void DrawSVGTemplate::onChanged(const App::Property* prop)
{
    bool updatePage = false;

    if (prop == &PageResult) {
        if (isRestoring()) {

            //original template has been stored in fcstd file
            Template.setValue(PageResult.getValue());
        }
    } else if (prop == &Template) {            //fileName has changed
        if (!isRestoring()) {
            EditableTexts.setValues(getEditableTextsFromTemplate());
            updatePage = true;
        }
    } else if (prop == &EditableTexts) {
        if (!isRestoring()) {
            updatePage = true;
        }
    }

    if (updatePage) {
        execute();
    }

    TechDraw::DrawTemplate::onChanged(prop);
}

App::DocumentObjectExecReturn * DrawSVGTemplate::execute(void)
{
    std::string templateFilename = Template.getValue();
    if (templateFilename.empty())
        return App::DocumentObject::StdReturn;

    Base::FileInfo fi(templateFilename);
    if (!fi.isReadable()) {
        // non-empty template value, but can't read file
        // if there is a old absolute template file set use a redirect
        fi.setFile(App::Application::getResourceDir() + "Mod/Drawing/Templates/" + fi.fileName());
        // try the redirect
        if (!fi.isReadable()) {
            Base::Console().Log("DrawPage::execute() not able to open %s!\n", Template.getValue());
            std::string error = std::string("Cannot open file ") + Template.getValue();
            return new App::DocumentObjectExecReturn(error);
        }
    }

    if (std::string(PageResult.getValue()).empty())                    //first time through?
        PageResult.setValue(fi.filePath().c_str());

    QFile templateFile(QString::fromUtf8(fi.filePath().c_str()));
    if (!templateFile.open(QIODevice::ReadOnly)) {
        Base::Console().Log("DrawPage::execute() can't read template %s!\n", Template.getValue());
        std::string error = std::string("Cannot read file ") + Template.getValue();
        return new App::DocumentObjectExecReturn(error);
    }

    QDomDocument templateDocument;
    if (!templateDocument.setContent(&templateFile)) {
        Base::Console().Message("DrawPage::execute() - failed to parse file: %s\n",
                                Template.getValue());
        std::string error = std::string("Cannot parse file ") + Template.getValue();
        return new App::DocumentObjectExecReturn(error);
    }

    QXmlQuery query(QXmlQuery::XQuery10);
    QDomNodeModel model(query.namePool(), templateDocument);
    query.setFocus(QXmlItem(model.fromDomNode(templateDocument.documentElement())));

    // XPath query to select all <tspan> nodes whose <text> parent
    // has "freecad:editable" attribute
    query.setQuery(QString::fromUtf8(
        "declare default element namespace \"" SVG_NS_URI "\"; "
        "declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; "
        "//text[@freecad:editable]/tspan"));

    QXmlResultItems queryResult;
    query.evaluateTo(&queryResult);

    std::map<std::string, std::string> substitutions = EditableTexts.getValues();
    while (!queryResult.next().isNull())
    {
        QDomElement tspan = model.toDomNode(queryResult.current().toNodeModelIndex()).toElement();

        // Replace the editable text spans with new nodes holding actual values
        QString editableName = tspan.parentNode().toElement().attribute(QString::fromUtf8("freecad:editable"));
        std::map<std::string, std::string>::iterator item =
            substitutions.find(std::string(editableName.toUtf8().constData()));
        if (item != substitutions.end()) {
            // Keep all spaces in the text node
            tspan.setAttribute(QString::fromUtf8("xml:space"), QString::fromUtf8("preserve"));

            // Remove all child nodes and append text node with editable replacement as the only descendant
            while (!tspan.lastChild().isNull()) {
                tspan.removeChild(tspan.lastChild());
            }
            tspan.appendChild(templateDocument.createTextNode(QString::fromUtf8(item->second.c_str())));
        }
    }

    string pageResultFilename = PageResult.getExchangeTempFile();
    QFile pageResult(QString::fromUtf8(pageResultFilename.c_str()));
    if (pageResult.open(QIODevice::WriteOnly | QIODevice::Text)) {
        QTextStream stream(&pageResult);
        stream << templateDocument.toString();
        pageResult.close();
        PageResult.setValue(pageResultFilename.c_str());
    }
    else {
        Base::Console().Message("DrawPage::execute() - failed to open file for writing: %s\n",
                                pageResultFilename.c_str());
    }

    // Calculate the dimensions of the page and store for retrieval
    // Obtain the size of the SVG document by reading the document attributes
    QDomElement docElement = templateDocument.documentElement();
    Base::Quantity quantity;

    // Obtain the width
    QString str = docElement.attribute(QString::fromLatin1("width"));
    quantity = Base::Quantity::parse(str);
    quantity.setUnit(Base::Unit::Length);

    Width.setValue(quantity.getValue());

    str = docElement.attribute(QString::fromLatin1("height"));
    quantity = Base::Quantity::parse(str);
    quantity.setUnit(Base::Unit::Length);

    Height.setValue(quantity.getValue());

    bool isLandscape = getWidth() / getHeight() >= 1.;

    Orientation.setValue(isLandscape ? 1 : 0);

    return TechDraw::DrawTemplate::execute();
}

double DrawSVGTemplate::getWidth() const
{
    return Width.getValue();
}

double DrawSVGTemplate::getHeight() const
{
    return Height.getValue();
}

std::map<std::string, std::string> DrawSVGTemplate::getEditableTextsFromTemplate()
{
    std::map<std::string, std::string> editables;

    std::string templateFilename = Template.getValue();
    if (templateFilename.empty()) {
        return editables;
    }

    Base::FileInfo tfi(templateFilename);
    if (!tfi.isReadable()) {
        // if there is a old absolute template file set use a redirect
        tfi.setFile(App::Application::getResourceDir() + "Mod/Drawing/Templates/" + tfi.fileName());
        // try the redirect
        if (!tfi.isReadable()) {
            Base::Console().Log("DrawPage::getEditableTextsFromTemplate() not able to open %s!\n", Template.getValue());
            return editables;
        }
    }

    QFile templateFile(QString::fromUtf8(tfi.filePath().c_str()));
    if (!templateFile.open(QIODevice::ReadOnly)) {
        Base::Console().Log("DrawPage::getEditableTextsFromTemplate() can't read template %s!\n", Template.getValue());
        return editables;
    }

    QDomDocument templateDocument;
    if (!templateDocument.setContent(&templateFile)) {
        Base::Console().Message("DrawPage::getEditableTextsFromTemplate() - failed to parse file: %s\n",
                                Template.getValue());
        return editables;
    }

    QXmlQuery query(QXmlQuery::XQuery10);
    QDomNodeModel model(query.namePool(), templateDocument, true);
    query.setFocus(QXmlItem(model.fromDomNode(templateDocument.documentElement())));

    // XPath query to select all <tspan> nodes whose <text> parent
    // has "freecad:editable" attribute
    query.setQuery(QString::fromUtf8(
        "declare default element namespace \"" SVG_NS_URI "\"; "
        "declare namespace freecad=\"" FREECAD_SVG_NS_URI "\"; "
        "//text[@freecad:editable]/tspan"));

    QXmlResultItems queryResult;
    query.evaluateTo(&queryResult);

    while (!queryResult.next().isNull()) {
        QDomElement tspan = model.toDomNode(queryResult.current().toNodeModelIndex()).toElement();

        QString editableName = tspan.parentNode().toElement().attribute(QString::fromUtf8("freecad:editable"));
        QString editableValue = tspan.firstChild().nodeValue();

        editables[std::string(editableName.toUtf8().constData())] =
            std::string(editableValue.toUtf8().constData());
    }

    return editables;
}

// Python Template feature ---------------------------------------------------------
namespace App {
/// @cond DOXERR
PROPERTY_SOURCE_TEMPLATE(TechDraw::DrawSVGTemplatePython, TechDraw::DrawSVGTemplate)
template<> const char* TechDraw::DrawSVGTemplatePython::getViewProviderName(void) const {
    return "TechDrawGui::ViewProviderPython";
}
/// @endcond

// explicit template instantiation
template class TechDrawExport FeaturePythonT<TechDraw::DrawSVGTemplate>;
}
<|endoftext|>