<commit_before>/* Copyright 2013-present Barefoot Networks, Inc.
 *
 * 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.
 */

/*
 * Antonin Bas (antonin@barefootnetworks.com)
 *
 */

#include "packet_io_mgr.h"

#include <algorithm>  // for std::fill, std::copy
#include <string>
#include <unordered_map>
#include <vector>

#include "google/rpc/code.pb.h"

namespace p4v1 = ::p4::v1;
namespace p4configv1 = ::p4::config::v1;

namespace pi {

namespace fe {

namespace proto {

using Code = ::google::rpc::Code;

namespace {

using p4configv1::ControllerPacketMetadata;

size_t compute_nbytes(const ControllerPacketMetadata &metadata_hdr) {
  size_t nbits = 0;
  for (const auto &metadata : metadata_hdr.metadata())
    nbits += metadata.bitwidth();
  return (nbits + 7) / 8;
}

// generic_extract and generic_deparse taken from the behavioral-model code

void generic_extract(const char *data, int bit_offset, int bitwidth,
                     char *dst) {
  int nbytes = (bitwidth + 7) / 8;

  if (bit_offset == 0 && bitwidth % 8 == 0) {
    memcpy(dst, data, nbytes);
    return;
  }

  int dst_offset = (nbytes << 3) - bitwidth;
  int i;

  // necessary to ensure correct behavior when shifting right (no sign
  // extension)
  auto udata = reinterpret_cast<const unsigned char *>(data);

  int offset = bit_offset - dst_offset;
  if (offset == 0) {
    memcpy(dst, udata, nbytes);
    dst[0] &= (0xFF >> dst_offset);
  } else if (offset > 0) {  // shift left
    for (i = 0; i < nbytes - 1; i++) {
      dst[i] = (udata[i] << offset) | (udata[i + 1] >> (8 - offset));
    }
    dst[0] &= (0xFF >> dst_offset);
    dst[i] = udata[i] << offset;
    if ((bit_offset + bitwidth) > (nbytes << 3)) {
      dst[i] |= (udata[i + 1] >> (8 - offset));
    }
  } else {  // shift right
    offset = -offset;
    dst[0] = udata[0] >> offset;
    dst[0] &= (0xFF >> dst_offset);
    for (i = 1; i < nbytes; i++) {
      dst[i] = (udata[i - 1] << (8 - offset)) | (udata[i] >> offset);
    }
  }
}

void generic_deparse(const char *data, int bitwidth, char *dst,
                     int hdr_offset) {
  if (bitwidth == 0) return;

  int nbytes = (bitwidth + 7) / 8;

  if (hdr_offset == 0 && bitwidth % 8 == 0) {
    memcpy(dst, data, nbytes);
    return;
  }

  int field_offset = (nbytes << 3) - bitwidth;
  int hdr_bytes = (hdr_offset + bitwidth + 7) / 8;

  int i;

  // necessary to ensure correct behavior when shifting right (no sign
  // extension)
  auto udata = reinterpret_cast<const unsigned char *>(data);

  // zero out bits we are going to write in dst[0]
  dst[0] &= (~(0xFF >> hdr_offset));

  int offset = field_offset - hdr_offset;
  if (offset == 0) {
    std::copy(data + 1, data + hdr_bytes, dst + 1);
    dst[0] |= udata[0];
  } else if (offset > 0) {  // shift left
    // don't know if this is very efficient, we memset the remaining bytes to 0
    // so we can use |= and preserve what was originally in dst[0]
    std::fill(&dst[1], &dst[hdr_bytes], 0);
    for (i = 0; i < hdr_bytes - 1; i++) {
      dst[i] |= (udata[i] << offset) | (udata[i + 1] >> (8 - offset));
    }
    dst[i] |= udata[i] << offset;
  } else {  // shift right
    offset = -offset;
    dst[0] |= (udata[0] >> offset);
    if (nbytes == 1) {
      // dst[1] is always valid, otherwise we would not need to shift the field
      // to the right
      dst[1] = udata[0] << (8 - offset);
      return;
    }
    for (i = 1; i < hdr_bytes - 1; i++) {
      dst[i] = (udata[i - 1] << (8 - offset)) | (udata[i] >> offset);
    }
    int tail_offset = (hdr_bytes << 3) - (hdr_offset + bitwidth);
    dst[i] &= ((1 << tail_offset) - 1);
    dst[i] |= (udata[i - 1] << (8 - offset));
  }
}

}  // namespace

class PacketInMutate {
 public:
  static constexpr const char name[] = "packet_in";

  explicit PacketInMutate(const ControllerPacketMetadata &metadata_hdr)
      : metadata_hdr(metadata_hdr) {
    nbytes = compute_nbytes(metadata_hdr);
  }

  bool operator ()(const char *pkt, size_t size,
                   p4v1::PacketIn *packet_in) const {
    if (size < nbytes) return false;
    packet_in->set_payload(pkt + nbytes, size - nbytes);
    int bit_offset = 0;
    std::vector<char> buffer(32);
    for (const auto &metadata_info : metadata_hdr.metadata()) {
      auto metadata = packet_in->add_metadata();
      metadata->set_metadata_id(metadata_info.id());
      auto bitwidth = metadata_info.bitwidth();
      buffer.resize((bitwidth + 7) / 8);
      buffer[0] = 0;
      generic_extract(pkt, bit_offset, bitwidth, buffer.data());
      bit_offset += (bitwidth % 8);
      pkt += (bitwidth / 8);
      metadata->set_value(buffer.data(), buffer.size());
    }
    return true;
  }

 private:
  ControllerPacketMetadata metadata_hdr;
  size_t nbytes{0};
};

constexpr const char PacketInMutate::name[];

namespace {

class Id2Offset {
 public:
  struct Offset {
    int byte_offset;
    int bit_offset;
    int bitwidth;
  };

  explicit Id2Offset(const ControllerPacketMetadata &metadata_hdr) {
    int nbits = 0;
    for (const auto &metadata : metadata_hdr.metadata()) {
      auto id = metadata.id();
      auto bitwidth = metadata.bitwidth();
      offsets.emplace(id, Offset{nbits / 8, nbits % 8, bitwidth});
      nbits += bitwidth;
    }
  }

  const Offset &at(uint32_t id) const { return offsets.at(id); }

 private:
  std::unordered_map<uint32_t, Offset> offsets{};
};

}  // namespace

class PacketOutMutate {
 public:
  static constexpr const char name[] = "packet_out";

  explicit PacketOutMutate(const ControllerPacketMetadata &metadata_hdr)
      : metadata_hdr(metadata_hdr), id2offset(metadata_hdr) {
    nbytes = compute_nbytes(metadata_hdr);
  }

  bool operator ()(const p4v1::PacketOut &packet_out, std::string *pkt) const {
    pkt->clear();
    const auto &payload = packet_out.payload();
    pkt->reserve(nbytes + payload.size());
    pkt->append(nbytes, 0);
    for (const auto &metadata : packet_out.metadata()) {
      const auto &offset = id2offset.at(metadata.metadata_id());
      generic_deparse(metadata.value().data(), offset.bitwidth,
                      &(*pkt)[offset.byte_offset], offset.bit_offset);
    }
    pkt->append(payload);
    return true;
  }

 private:
  ControllerPacketMetadata metadata_hdr;
  size_t nbytes{0};
  Id2Offset id2offset;
};

constexpr const char PacketOutMutate::name[];

using Status = PacketIOMgr::Status;

PacketIOMgr::PacketIOMgr(device_id_t device_id)
    : device_id(device_id), packet_in_mutate(nullptr),
      packet_out_mutate(nullptr) { }

PacketIOMgr::~PacketIOMgr() = default;

void
PacketIOMgr::p4_change(const p4configv1::P4Info &p4info) {
  PacketInMutate *packet_in_mutate_new = nullptr;
  PacketOutMutate *packet_out_mutate_new = nullptr;
  for (const auto &metadata_hdr : p4info.controller_packet_metadata()) {
    const auto &name = metadata_hdr.preamble().name();
    if (name == PacketInMutate::name)
      packet_in_mutate_new = new PacketInMutate(metadata_hdr);
    else if (name == PacketOutMutate::name)
      packet_out_mutate_new = new PacketOutMutate(metadata_hdr);
  }
  Lock lock(mutex);
  packet_in_mutate.reset(packet_in_mutate_new);
  packet_out_mutate.reset(packet_out_mutate_new);
}

Status
PacketIOMgr::packet_out_send(const p4v1::PacketOut &packet) const {
    Status status;
    pi_status_t pi_status = PI_STATUS_SUCCESS;
    if (packet_out_mutate) {
      std::string raw_packet;
      auto success = (*packet_out_mutate)(packet, &raw_packet);
      if (!success) {
        status.set_code(Code::UNKNOWN);
        return status;
      }
      pi_status = pi_packetout_send(device_id, raw_packet.data(),
                                    raw_packet.size());
    } else {
      const auto &payload = packet.payload();
      pi_status = pi_packetout_send(device_id, payload.data(),
                                    payload.size());
    }
    if (pi_status != PI_STATUS_SUCCESS)
      status.set_code(Code::UNKNOWN);
    else
      status.set_code(Code::OK);
    return status;
}

void
PacketIOMgr::packet_in_register_cb(PacketInCb cb, void *cookie) {
  cb_ = std::move(cb);
  cookie_ = cookie;
  pi_packetin_register_cb(device_id, &PacketIOMgr::packet_in_cb,
                          static_cast<void *>(this));
}

void
PacketIOMgr::packet_in_cb(pi_dev_id_t dev_id, const char *pkt, size_t size,
                          void *cookie) {
  auto mgr = static_cast<PacketIOMgr *>(cookie);
  assert(dev_id == mgr->device_id);
  p4v1::PacketIn packet_in;
  if (mgr->packet_in_mutate) {
    Lock lock(mgr->mutex);
    auto success = (*mgr->packet_in_mutate)(pkt, size, &packet_in);
    if (!success) return;
  } else {
    packet_in.set_payload(pkt, size);
  }
  mgr->cb_(mgr->device_id, &packet_in, mgr->cookie_);
}

}  // namespace proto

}  // namespace fe

}  // namespace pi
<commit_msg>Fix extract for sub-byte fields that need to be shifted left<commit_after>/* Copyright 2013-present Barefoot Networks, Inc.
 *
 * 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.
 */

/*
 * Antonin Bas (antonin@barefootnetworks.com)
 *
 */

#include "packet_io_mgr.h"

#include <algorithm>  // for std::fill, std::copy
#include <string>
#include <unordered_map>
#include <vector>

#include "google/rpc/code.pb.h"

namespace p4v1 = ::p4::v1;
namespace p4configv1 = ::p4::config::v1;

namespace pi {

namespace fe {

namespace proto {

using Code = ::google::rpc::Code;

namespace {

using p4configv1::ControllerPacketMetadata;

size_t compute_nbytes(const ControllerPacketMetadata &metadata_hdr) {
  size_t nbits = 0;
  for (const auto &metadata : metadata_hdr.metadata())
    nbits += metadata.bitwidth();
  return (nbits + 7) / 8;
}

// generic_extract and generic_deparse taken from the behavioral-model code

void generic_extract(const char *data, int bit_offset, int bitwidth,
                     char *dst) {
  int nbytes = (bitwidth + 7) / 8;

  if (bit_offset == 0 && bitwidth % 8 == 0) {
    memcpy(dst, data, nbytes);
    return;
  }

  int dst_offset = (nbytes << 3) - bitwidth;
  int i;

  // necessary to ensure correct behavior when shifting right (no sign
  // extension)
  auto udata = reinterpret_cast<const unsigned char *>(data);

  int offset = bit_offset - dst_offset;
  if (offset == 0) {
    memcpy(dst, udata, nbytes);
    dst[0] &= (0xFF >> dst_offset);
  } else if (offset > 0) {  // shift left
    for (i = 0; i < nbytes - 1; i++) {
      dst[i] = (udata[i] << offset) | (udata[i + 1] >> (8 - offset));
    }
    dst[i] = udata[i] << offset;
    dst[0] &= (0xFF >> dst_offset);
    if ((bit_offset + bitwidth) > (nbytes << 3)) {
      dst[i] |= (udata[i + 1] >> (8 - offset));
    }
  } else {  // shift right
    offset = -offset;
    dst[0] = udata[0] >> offset;
    dst[0] &= (0xFF >> dst_offset);
    for (i = 1; i < nbytes; i++) {
      dst[i] = (udata[i - 1] << (8 - offset)) | (udata[i] >> offset);
    }
  }
}

void generic_deparse(const char *data, int bitwidth, char *dst,
                     int hdr_offset) {
  if (bitwidth == 0) return;

  int nbytes = (bitwidth + 7) / 8;

  if (hdr_offset == 0 && bitwidth % 8 == 0) {
    memcpy(dst, data, nbytes);
    return;
  }

  int field_offset = (nbytes << 3) - bitwidth;
  int hdr_bytes = (hdr_offset + bitwidth + 7) / 8;

  int i;

  // necessary to ensure correct behavior when shifting right (no sign
  // extension)
  auto udata = reinterpret_cast<const unsigned char *>(data);

  // zero out bits we are going to write in dst[0]
  dst[0] &= (~(0xFF >> hdr_offset));

  int offset = field_offset - hdr_offset;
  if (offset == 0) {
    std::copy(data + 1, data + hdr_bytes, dst + 1);
    dst[0] |= udata[0];
  } else if (offset > 0) {  // shift left
    // don't know if this is very efficient, we memset the remaining bytes to 0
    // so we can use |= and preserve what was originally in dst[0]
    std::fill(&dst[1], &dst[hdr_bytes], 0);
    for (i = 0; i < hdr_bytes - 1; i++) {
      dst[i] |= (udata[i] << offset) | (udata[i + 1] >> (8 - offset));
    }
    dst[i] |= udata[i] << offset;
  } else {  // shift right
    offset = -offset;
    dst[0] |= (udata[0] >> offset);
    if (nbytes == 1) {
      // dst[1] is always valid, otherwise we would not need to shift the field
      // to the right
      dst[1] = udata[0] << (8 - offset);
      return;
    }
    for (i = 1; i < hdr_bytes - 1; i++) {
      dst[i] = (udata[i - 1] << (8 - offset)) | (udata[i] >> offset);
    }
    int tail_offset = (hdr_bytes << 3) - (hdr_offset + bitwidth);
    dst[i] &= ((1 << tail_offset) - 1);
    dst[i] |= (udata[i - 1] << (8 - offset));
  }
}

}  // namespace

class PacketInMutate {
 public:
  static constexpr const char name[] = "packet_in";

  explicit PacketInMutate(const ControllerPacketMetadata &metadata_hdr)
      : metadata_hdr(metadata_hdr) {
    nbytes = compute_nbytes(metadata_hdr);
  }

  bool operator ()(const char *pkt, size_t size,
                   p4v1::PacketIn *packet_in) const {
    if (size < nbytes) return false;
    packet_in->set_payload(pkt + nbytes, size - nbytes);
    int bit_offset = 0;
    std::vector<char> buffer(32);
    for (const auto &metadata_info : metadata_hdr.metadata()) {
      auto metadata = packet_in->add_metadata();
      metadata->set_metadata_id(metadata_info.id());
      auto bitwidth = metadata_info.bitwidth();
      buffer.resize((bitwidth + 7) / 8);
      buffer[0] = 0;
      generic_extract(pkt, bit_offset, bitwidth, buffer.data());
      bit_offset += (bitwidth % 8);
      pkt += (bitwidth / 8);
      metadata->set_value(buffer.data(), buffer.size());
    }
    return true;
  }

 private:
  ControllerPacketMetadata metadata_hdr;
  size_t nbytes{0};
};

constexpr const char PacketInMutate::name[];

namespace {

class Id2Offset {
 public:
  struct Offset {
    int byte_offset;
    int bit_offset;
    int bitwidth;
  };

  explicit Id2Offset(const ControllerPacketMetadata &metadata_hdr) {
    int nbits = 0;
    for (const auto &metadata : metadata_hdr.metadata()) {
      auto id = metadata.id();
      auto bitwidth = metadata.bitwidth();
      offsets.emplace(id, Offset{nbits / 8, nbits % 8, bitwidth});
      nbits += bitwidth;
    }
  }

  const Offset &at(uint32_t id) const { return offsets.at(id); }

 private:
  std::unordered_map<uint32_t, Offset> offsets{};
};

}  // namespace

class PacketOutMutate {
 public:
  static constexpr const char name[] = "packet_out";

  explicit PacketOutMutate(const ControllerPacketMetadata &metadata_hdr)
      : metadata_hdr(metadata_hdr), id2offset(metadata_hdr) {
    nbytes = compute_nbytes(metadata_hdr);
  }

  bool operator ()(const p4v1::PacketOut &packet_out, std::string *pkt) const {
    pkt->clear();
    const auto &payload = packet_out.payload();
    pkt->reserve(nbytes + payload.size());
    pkt->append(nbytes, 0);
    for (const auto &metadata : packet_out.metadata()) {
      const auto &offset = id2offset.at(metadata.metadata_id());
      generic_deparse(metadata.value().data(), offset.bitwidth,
                      &(*pkt)[offset.byte_offset], offset.bit_offset);
    }
    pkt->append(payload);
    return true;
  }

 private:
  ControllerPacketMetadata metadata_hdr;
  size_t nbytes{0};
  Id2Offset id2offset;
};

constexpr const char PacketOutMutate::name[];

using Status = PacketIOMgr::Status;

PacketIOMgr::PacketIOMgr(device_id_t device_id)
    : device_id(device_id), packet_in_mutate(nullptr),
      packet_out_mutate(nullptr) { }

PacketIOMgr::~PacketIOMgr() = default;

void
PacketIOMgr::p4_change(const p4configv1::P4Info &p4info) {
  PacketInMutate *packet_in_mutate_new = nullptr;
  PacketOutMutate *packet_out_mutate_new = nullptr;
  for (const auto &metadata_hdr : p4info.controller_packet_metadata()) {
    const auto &name = metadata_hdr.preamble().name();
    if (name == PacketInMutate::name)
      packet_in_mutate_new = new PacketInMutate(metadata_hdr);
    else if (name == PacketOutMutate::name)
      packet_out_mutate_new = new PacketOutMutate(metadata_hdr);
  }
  Lock lock(mutex);
  packet_in_mutate.reset(packet_in_mutate_new);
  packet_out_mutate.reset(packet_out_mutate_new);
}

Status
PacketIOMgr::packet_out_send(const p4v1::PacketOut &packet) const {
    Status status;
    pi_status_t pi_status = PI_STATUS_SUCCESS;
    if (packet_out_mutate) {
      std::string raw_packet;
      auto success = (*packet_out_mutate)(packet, &raw_packet);
      if (!success) {
        status.set_code(Code::UNKNOWN);
        return status;
      }
      pi_status = pi_packetout_send(device_id, raw_packet.data(),
                                    raw_packet.size());
    } else {
      const auto &payload = packet.payload();
      pi_status = pi_packetout_send(device_id, payload.data(),
                                    payload.size());
    }
    if (pi_status != PI_STATUS_SUCCESS)
      status.set_code(Code::UNKNOWN);
    else
      status.set_code(Code::OK);
    return status;
}

void
PacketIOMgr::packet_in_register_cb(PacketInCb cb, void *cookie) {
  cb_ = std::move(cb);
  cookie_ = cookie;
  pi_packetin_register_cb(device_id, &PacketIOMgr::packet_in_cb,
                          static_cast<void *>(this));
}

void
PacketIOMgr::packet_in_cb(pi_dev_id_t dev_id, const char *pkt, size_t size,
                          void *cookie) {
  auto mgr = static_cast<PacketIOMgr *>(cookie);
  assert(dev_id == mgr->device_id);
  p4v1::PacketIn packet_in;
  if (mgr->packet_in_mutate) {
    Lock lock(mgr->mutex);
    auto success = (*mgr->packet_in_mutate)(pkt, size, &packet_in);
    if (!success) return;
  } else {
    packet_in.set_payload(pkt, size);
  }
  mgr->cb_(mgr->device_id, &packet_in, mgr->cookie_);
}

}  // namespace proto

}  // namespace fe

}  // namespace pi
<|endoftext|>


<commit_before>#include "PrecompiledHeader.h"
#include "DirectStorageFileReadWorkQueue.h"
#include "DirectXContext.h"
#include "SearchInstructions.h"
#include "SearchResultReporter.h"
#include "StringSearch/StringSearcher.h"
#include "Utilities/ScopedStackAllocator.h"

DirectStorageFileReadWorkQueue::DirectStorageFileReadWorkQueue(const StringSearcher& stringSearcher, const SearchInstructions& searchInstructions, SearchResultReporter& searchResultReporter) :
    m_SearchResultReporter(searchResultReporter),
    m_StringSearcher(stringSearcher),
    m_ReadBufferSize(0),
    m_FenceEvent(false),
    m_FenceValue(0),
    m_IsTerminating(false),
    m_FreeReadSlotCount(ARRAYSIZE(m_FileReadSlots))
{
    if (searchInstructions.SearchInFileContents())
        m_ReadBufferSize = kFileReadBufferBaseSize + std::max(searchInstructions.utf8SearchString.length(), searchInstructions.searchString.length() * sizeof(wchar_t));
}

DirectStorageFileReadWorkQueue::~DirectStorageFileReadWorkQueue()
{
    if (m_DStorageQueue != nullptr)
    {
        m_DStorageQueue->Close();
        m_DStorageQueue = nullptr;
    }
}

void DirectStorageFileReadWorkQueue::Initialize()
{
    m_FileReadBuffers.reset(new uint8_t[m_ReadBufferSize * kFileReadSlotCount]);
    m_WaitableTimer = CreateWaitableTimerExW(nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);

    memset(m_FreeReadSlots, 0xFF, sizeof(m_FreeReadSlots));

    DSTORAGE_QUEUE_DESC queueDesc = {};
    queueDesc.SourceType = DSTORAGE_REQUEST_SOURCE_FILE;
    queueDesc.Capacity = DSTORAGE_MAX_QUEUE_CAPACITY;
    queueDesc.Priority = DSTORAGE_PRIORITY_NORMAL;

    auto hr = DirectXContext::GetDStorageFactory()->CreateQueue(&queueDesc, __uuidof(m_DStorageQueue), &m_DStorageQueue);
    Assert(SUCCEEDED(hr));

    hr = DirectXContext::GetD3D12Device()->CreateFence(0, D3D12_FENCE_FLAG_NONE, __uuidof(m_Fence), &m_Fence);
    Assert(SUCCEEDED(hr));

    m_FenceEvent.Initialize();

    SYSTEM_INFO systemInfo;
    GetNativeSystemInfo(&systemInfo);

    MyFileReadBase::Initialize();
    m_FileOpenWorkQueue.Initialize<&DirectStorageFileReadWorkQueue::FileOpenThread>(this, systemInfo.dwNumberOfProcessors - 2);
    m_SearchWorkQueue.Initialize<&DirectStorageFileReadWorkQueue::ContentsSearchThread>(this, systemInfo.dwNumberOfProcessors - 1);
    MySearchResultBase::Initialize<&DirectStorageFileReadWorkQueue::FileReadThread>(this, 1);
}

void DirectStorageFileReadWorkQueue::DrainWorkQueue()
{
    m_IsTerminating = true;
    m_FileOpenWorkQueue.DrainWorkQueue();
    MyFileReadBase::DrainWorkQueue();
    m_SearchWorkQueue.DrainWorkQueue();
    MySearchResultBase::DrainWorkQueue();
}

void DirectStorageFileReadWorkQueue::CompleteAllWork()
{
    m_FileOpenWorkQueue.CompleteAllWork();

    ReleaseSemaphore(MyFileReadBase::GetWorkSemaphore(), 1, nullptr);
    WaitForSingleObject(m_FileReadsCompletedEvent, INFINITE);

    m_SearchWorkQueue.CompleteAllWork();
    MySearchResultBase::CompleteAllWork();
}

void DirectStorageFileReadWorkQueue::FileOpenThread()
{
    SetThreadDescription(GetCurrentThread(), L"FSS File Open Thread");

    m_FileOpenWorkQueue.DoWork([this](FileOpenData& searchData)
    {
        if (m_IsTerminating)
            return;

        FileReadData readData(std::move(searchData));
        auto hr = DirectXContext::GetDStorageFactory()->OpenFile(readData.filePath.c_str(), __uuidof(readData.file), &readData.file);
        if (FAILED(hr))
        {
            m_SearchResultReporter.AddToScannedFileCount();
            m_SearchResultReporter.AddToScannedFileSize(readData.fileSize);
            return;
        }

        MyFileReadBase::PushWorkItem(std::move(readData));
    });
}

void DirectStorageFileReadWorkQueue::FileReadThread()
{
    bool readSubmissionCompleted = false;
    bool fileContentSearchCompleted = false;
    SetThreadDescription(GetCurrentThread(), L"FSS Read Submission Thread");

    for (;;)
    {
        uint32_t handleCount = 0;
        HANDLE waitHandles[4];

        if (!readSubmissionCompleted)
            waitHandles[handleCount++] = MyFileReadBase::GetWorkSemaphore();

        if (!fileContentSearchCompleted)
            waitHandles[handleCount++] = MySearchResultBase::GetWorkSemaphore();

        if (!m_CurrentBatch.slots.empty())
        {
            LARGE_INTEGER timer;
            timer.QuadPart = -10000; // 1 ms
            auto result = SetWaitableTimer(m_WaitableTimer, &timer, 0, nullptr, nullptr, FALSE);
            Assert(result);

            waitHandles[handleCount++] = m_WaitableTimer;
        }

        if (!m_SubmittedBatches.empty())
            waitHandles[handleCount++] = m_FenceEvent;

        if (handleCount == 0)
            break;

        auto waitResult = WaitForMultipleObjects(handleCount, waitHandles, FALSE, INFINITE);
        Assert(waitResult >= WAIT_OBJECT_0 && waitResult < WAIT_OBJECT_0 + handleCount);

        if (waitResult < WAIT_OBJECT_0 || waitResult >= WAIT_OBJECT_0 + handleCount)
            break;

        if (m_IsTerminating)
        {
            DrainWorkQueue();
            break;
        }

        auto signaledHandle = waitHandles[waitResult - WAIT_OBJECT_0];
        if (signaledHandle == MyFileReadBase::GetWorkSemaphore())
        {
            auto workEntry = MyFileReadBase::PopWorkEntry();
            if (workEntry != nullptr)
            {
                m_FilesToRead.push_back(std::move(workEntry->workItem));
                QueueFileReads();

                MyFileReadBase::DeleteWorkEntry(workEntry);
            }
            else
            {
                readSubmissionCompleted = true;
            }
        }
        else if (signaledHandle == MySearchResultBase::GetWorkSemaphore())
        {
            auto workEntry = MySearchResultBase::PopWorkEntry();
            if (workEntry != nullptr)
            {
                ProcessSearchCompletion(workEntry->workItem);
                MySearchResultBase::DeleteWorkEntry(workEntry);
            }
            else
            {
                fileContentSearchCompleted = true;
            }

            if (readSubmissionCompleted && m_CurrentBatch.slots.empty() && m_FreeReadSlotCount == ARRAYSIZE(m_FileReadSlots))
                m_FileReadsCompletedEvent.Set();
        }
        else if (signaledHandle == m_WaitableTimer)
        {
            // No requests for 1 ms, submit outstanding work
            SubmitReadRequests();
        }
        else
        {
            ProcessReadCompletion();
        }
    }
}

static uint32_t GetChunkCount(const FileReadStateData& file)
{
    const auto fileSize = file.fileSize;
    auto chunkCount = file.fileSize / DirectStorageFileReadWorkQueue::kFileReadBufferBaseSize;
    if (file.fileSize % DirectStorageFileReadWorkQueue::kFileReadBufferBaseSize)
        chunkCount++;

    Assert(chunkCount < std::numeric_limits<uint32_t>::max());
    return static_cast<uint32_t>(chunkCount);
}

void DirectStorageFileReadWorkQueue::QueueFileReads()
{
    while (true)
    {
        uint16_t slot = std::numeric_limits<uint16_t>::max();

        // TO DO: use a trie if this is too slow
        for (uint16_t i = 0; i < ARRAYSIZE(m_FreeReadSlots); i++)
        {
            DWORD index;
            if (_BitScanForward64(&index, m_FreeReadSlots[i]))
            {
                slot = 64 * i + static_cast<uint16_t>(index);
                break;
            }
        }

        if (slot == std::numeric_limits<uint16_t>::max())
            return;

        if (m_FilesWithReadProgress.empty() || m_FilesWithReadProgress.back().chunksRead == GetChunkCount(m_FilesWithReadProgress.back()))
        {
            if (m_FilesToRead.empty())
                return;

            m_FilesWithReadProgress.push_back(std::move(m_FilesToRead.back()));
            m_FilesToRead.pop_back();
        }

        uint32_t fileIndex = static_cast<uint32_t>(m_FilesWithReadProgress.size() - 1);
        auto& file = m_FilesWithReadProgress[fileIndex];
        file.readsInProgress++;

        Assert(slot < kFileReadSlotCount);
        m_FileReadSlots[slot] = fileIndex;
        m_FreeReadSlots[slot / 64] &= ~(1ULL << (slot % 64));
        m_FreeReadSlotCount--;

        const auto fileOffset = (file.chunksRead++) * kFileReadBufferBaseSize;
        uint32_t bytesToRead = static_cast<uint32_t>(std::min(file.fileSize - fileOffset, m_ReadBufferSize));

        DSTORAGE_REQUEST request = {};
        request.Options.SourceType = DSTORAGE_REQUEST_SOURCE_FILE;
        request.Options.DestinationType = DSTORAGE_REQUEST_DESTINATION_MEMORY;
        request.Source.File.Source = file.file.Get();
        request.Source.File.Offset = fileOffset;
        request.Source.File.Size = bytesToRead;
        request.Destination.Memory.Buffer = m_FileReadBuffers.get() + slot * m_ReadBufferSize;
        request.Destination.Memory.Size = bytesToRead;
        request.UncompressedSize = bytesToRead;

        m_DStorageQueue->EnqueueRequest(&request);
        m_CurrentBatch.slots.emplace_back(slot, bytesToRead);

        if (m_CurrentBatch.slots.size() >= ARRAYSIZE(m_FileReadSlots) / 2)
            SubmitReadRequests();
    }
}

void DirectStorageFileReadWorkQueue::SubmitReadRequests()
{
    Assert(m_CurrentBatch.slots.size() > 0);
    m_CurrentBatch.fenceValue = m_FenceValue++;

    m_DStorageQueue->EnqueueSignal(m_Fence.Get(), m_CurrentBatch.fenceValue);
    m_DStorageQueue->Submit();

    if (m_SubmittedBatches.empty())
        m_Fence->SetEventOnCompletion(m_CurrentBatch.fenceValue, m_FenceEvent);

    m_SubmittedBatches.push_back(std::move(m_CurrentBatch));
    m_CurrentBatch = m_BatchPool.GetNewObject();
}

void DirectStorageFileReadWorkQueue::ProcessReadCompletion()
{
    uint32_t batchIndex = 0;
    for (; batchIndex < m_SubmittedBatches.size() && m_SubmittedBatches[batchIndex].fenceValue <= m_Fence->GetCompletedValue(); batchIndex++)
    {
        auto& batch = m_SubmittedBatches[batchIndex];

        for (auto slot : batch.slots)
            m_SearchWorkQueue.PushWorkItem(slot);

        batch.slots.clear();
        m_BatchPool.PoolObject(std::move(batch));
    }

    m_SubmittedBatches.erase(m_SubmittedBatches.begin(), m_SubmittedBatches.begin() + batchIndex);
    if (!m_SubmittedBatches.empty())
        m_Fence->SetEventOnCompletion(m_SubmittedBatches.front().fenceValue, m_FenceEvent);
}

void DirectStorageFileReadWorkQueue::ContentsSearchThread()
{
    ScopedStackAllocator allocator;
    SetThreadDescription(GetCurrentThread(), L"FSS Content Search Thread");

    m_SearchWorkQueue.DoWork([this, &allocator](SlotSearchData& searchData)
    {
        searchData.found = m_StringSearcher.PerformFileContentSearch(m_FileReadBuffers.get() + searchData.slot * m_ReadBufferSize, searchData.size, allocator);
        MySearchResultBase::PushWorkItem(searchData);
    });
}

void DirectStorageFileReadWorkQueue::ProcessSearchCompletion(SlotSearchData searchData)
{
    auto& fileIndex = m_FileReadSlots[searchData.slot];
    auto& file = m_FilesWithReadProgress[fileIndex];

    Assert(file.readsInProgress > 0);
    file.readsInProgress--;
    m_FreeReadSlots[searchData.slot / 64] |= 1ULL << (searchData.slot % 64);
    m_FreeReadSlotCount++;

    if (!file.dispatchedToResults)
    {
        if (searchData.found)
        {
            m_SearchResultReporter.AddToScannedFileCount();
            m_SearchResultReporter.AddToScannedFileSize(file.fileSize - file.totalScannedSize);
            m_SearchResultReporter.DispatchSearchResult(file.fileFindData, std::move(file.filePath));

            file.totalScannedSize = file.fileSize;
            file.chunksRead = GetChunkCount(file);
            file.dispatchedToResults = true;
        }
        else if (file.readsInProgress == 0 && file.chunksRead == GetChunkCount(file))
        {
            m_SearchResultReporter.AddToScannedFileCount();
            m_SearchResultReporter.AddToScannedFileSize(file.fileSize - file.totalScannedSize);
            file.totalScannedSize = file.fileSize;
        }
        else
        {
            m_SearchResultReporter.AddToScannedFileSize(kFileReadBufferBaseSize);
            file.totalScannedSize += kFileReadBufferBaseSize;
        }
    }

    while (!m_FilesWithReadProgress.empty())
    {
        auto& lastFile = m_FilesWithReadProgress.back();

        if (lastFile.chunksRead == GetChunkCount(lastFile) && lastFile.readsInProgress == 0)
        {
            m_FilesWithReadProgress.pop_back();
        }
        else
        {
            break;
        }
    }

    QueueFileReads();
}
<commit_msg>Fix off by one error in fence value, causing search to proceed before reads are complete.<commit_after>#include "PrecompiledHeader.h"
#include "DirectStorageFileReadWorkQueue.h"
#include "DirectXContext.h"
#include "SearchInstructions.h"
#include "SearchResultReporter.h"
#include "StringSearch/StringSearcher.h"
#include "Utilities/ScopedStackAllocator.h"

DirectStorageFileReadWorkQueue::DirectStorageFileReadWorkQueue(const StringSearcher& stringSearcher, const SearchInstructions& searchInstructions, SearchResultReporter& searchResultReporter) :
    m_SearchResultReporter(searchResultReporter),
    m_StringSearcher(stringSearcher),
    m_ReadBufferSize(0),
    m_FenceEvent(false),
    m_FenceValue(0),
    m_IsTerminating(false),
    m_FreeReadSlotCount(ARRAYSIZE(m_FileReadSlots))
{
    if (searchInstructions.SearchInFileContents())
        m_ReadBufferSize = kFileReadBufferBaseSize + std::max(searchInstructions.utf8SearchString.length(), searchInstructions.searchString.length() * sizeof(wchar_t));
}

DirectStorageFileReadWorkQueue::~DirectStorageFileReadWorkQueue()
{
    if (m_DStorageQueue != nullptr)
    {
        m_DStorageQueue->Close();
        m_DStorageQueue = nullptr;
    }
}

void DirectStorageFileReadWorkQueue::Initialize()
{
    m_FileReadBuffers.reset(new uint8_t[m_ReadBufferSize * kFileReadSlotCount]);
    m_WaitableTimer = CreateWaitableTimerExW(nullptr, nullptr, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);

    memset(m_FreeReadSlots, 0xFF, sizeof(m_FreeReadSlots));

    DSTORAGE_QUEUE_DESC queueDesc = {};
    queueDesc.SourceType = DSTORAGE_REQUEST_SOURCE_FILE;
    queueDesc.Capacity = DSTORAGE_MAX_QUEUE_CAPACITY;
    queueDesc.Priority = DSTORAGE_PRIORITY_NORMAL;

    auto hr = DirectXContext::GetDStorageFactory()->CreateQueue(&queueDesc, __uuidof(m_DStorageQueue), &m_DStorageQueue);
    Assert(SUCCEEDED(hr));

    hr = DirectXContext::GetD3D12Device()->CreateFence(0, D3D12_FENCE_FLAG_NONE, __uuidof(m_Fence), &m_Fence);
    Assert(SUCCEEDED(hr));

    m_FenceEvent.Initialize();

    SYSTEM_INFO systemInfo;
    GetNativeSystemInfo(&systemInfo);

    MyFileReadBase::Initialize();
    m_FileOpenWorkQueue.Initialize<&DirectStorageFileReadWorkQueue::FileOpenThread>(this, systemInfo.dwNumberOfProcessors - 2);
    m_SearchWorkQueue.Initialize<&DirectStorageFileReadWorkQueue::ContentsSearchThread>(this, systemInfo.dwNumberOfProcessors - 1);
    MySearchResultBase::Initialize<&DirectStorageFileReadWorkQueue::FileReadThread>(this, 1);
}

void DirectStorageFileReadWorkQueue::DrainWorkQueue()
{
    m_IsTerminating = true;
    m_FileOpenWorkQueue.DrainWorkQueue();
    MyFileReadBase::DrainWorkQueue();
    m_SearchWorkQueue.DrainWorkQueue();
    MySearchResultBase::DrainWorkQueue();
}

void DirectStorageFileReadWorkQueue::CompleteAllWork()
{
    m_FileOpenWorkQueue.CompleteAllWork();

    ReleaseSemaphore(MyFileReadBase::GetWorkSemaphore(), 1, nullptr);
    WaitForSingleObject(m_FileReadsCompletedEvent, INFINITE);

    m_SearchWorkQueue.CompleteAllWork();
    MySearchResultBase::CompleteAllWork();
}

void DirectStorageFileReadWorkQueue::FileOpenThread()
{
    SetThreadDescription(GetCurrentThread(), L"FSS File Open Thread");

    m_FileOpenWorkQueue.DoWork([this](FileOpenData& searchData)
    {
        if (m_IsTerminating)
            return;

        FileReadData readData(std::move(searchData));
        auto hr = DirectXContext::GetDStorageFactory()->OpenFile(readData.filePath.c_str(), __uuidof(readData.file), &readData.file);
        if (FAILED(hr))
        {
            m_SearchResultReporter.AddToScannedFileCount();
            m_SearchResultReporter.AddToScannedFileSize(readData.fileSize);
            return;
        }

        MyFileReadBase::PushWorkItem(std::move(readData));
    });
}

void DirectStorageFileReadWorkQueue::FileReadThread()
{
    bool readSubmissionCompleted = false;
    bool fileContentSearchCompleted = false;
    SetThreadDescription(GetCurrentThread(), L"FSS Read Submission Thread");

    for (;;)
    {
        uint32_t handleCount = 0;
        HANDLE waitHandles[4];

        if (!readSubmissionCompleted)
            waitHandles[handleCount++] = MyFileReadBase::GetWorkSemaphore();

        if (!fileContentSearchCompleted)
            waitHandles[handleCount++] = MySearchResultBase::GetWorkSemaphore();

        if (!m_CurrentBatch.slots.empty())
        {
            LARGE_INTEGER timer;
            timer.QuadPart = -10000; // 1 ms
            auto result = SetWaitableTimer(m_WaitableTimer, &timer, 0, nullptr, nullptr, FALSE);
            Assert(result);

            waitHandles[handleCount++] = m_WaitableTimer;
        }

        if (!m_SubmittedBatches.empty())
            waitHandles[handleCount++] = m_FenceEvent;

        if (handleCount == 0)
            break;

        auto waitResult = WaitForMultipleObjects(handleCount, waitHandles, FALSE, INFINITE);
        Assert(waitResult >= WAIT_OBJECT_0 && waitResult < WAIT_OBJECT_0 + handleCount);

        if (waitResult < WAIT_OBJECT_0 || waitResult >= WAIT_OBJECT_0 + handleCount)
            break;

        if (m_IsTerminating)
        {
            DrainWorkQueue();
            break;
        }

        auto signaledHandle = waitHandles[waitResult - WAIT_OBJECT_0];
        if (signaledHandle == MyFileReadBase::GetWorkSemaphore())
        {
            auto workEntry = MyFileReadBase::PopWorkEntry();
            if (workEntry != nullptr)
            {
                m_FilesToRead.push_back(std::move(workEntry->workItem));
                QueueFileReads();

                MyFileReadBase::DeleteWorkEntry(workEntry);
            }
            else
            {
                readSubmissionCompleted = true;
            }
        }
        else if (signaledHandle == MySearchResultBase::GetWorkSemaphore())
        {
            auto workEntry = MySearchResultBase::PopWorkEntry();
            if (workEntry != nullptr)
            {
                ProcessSearchCompletion(workEntry->workItem);
                MySearchResultBase::DeleteWorkEntry(workEntry);
            }
            else
            {
                fileContentSearchCompleted = true;
            }

            if (readSubmissionCompleted && m_CurrentBatch.slots.empty() && m_FreeReadSlotCount == ARRAYSIZE(m_FileReadSlots))
                m_FileReadsCompletedEvent.Set();
        }
        else if (signaledHandle == m_WaitableTimer)
        {
            // No requests for 1 ms, submit outstanding work
            SubmitReadRequests();
        }
        else
        {
            ProcessReadCompletion();
        }
    }
}

static uint32_t GetChunkCount(const FileReadStateData& file)
{
    const auto fileSize = file.fileSize;
    auto chunkCount = file.fileSize / DirectStorageFileReadWorkQueue::kFileReadBufferBaseSize;
    if (file.fileSize % DirectStorageFileReadWorkQueue::kFileReadBufferBaseSize)
        chunkCount++;

    Assert(chunkCount < std::numeric_limits<uint32_t>::max());
    return static_cast<uint32_t>(chunkCount);
}

void DirectStorageFileReadWorkQueue::QueueFileReads()
{
    while (true)
    {
        uint16_t slot = std::numeric_limits<uint16_t>::max();

        // TO DO: use a trie if this is too slow
        for (uint16_t i = 0; i < ARRAYSIZE(m_FreeReadSlots); i++)
        {
            DWORD index;
            if (_BitScanForward64(&index, m_FreeReadSlots[i]))
            {
                slot = 64 * i + static_cast<uint16_t>(index);
                break;
            }
        }

        if (slot == std::numeric_limits<uint16_t>::max())
            return;

        if (m_FilesWithReadProgress.empty() || m_FilesWithReadProgress.back().chunksRead == GetChunkCount(m_FilesWithReadProgress.back()))
        {
            if (m_FilesToRead.empty())
                return;

            m_FilesWithReadProgress.push_back(std::move(m_FilesToRead.back()));
            m_FilesToRead.pop_back();
        }

        uint32_t fileIndex = static_cast<uint32_t>(m_FilesWithReadProgress.size() - 1);
        auto& file = m_FilesWithReadProgress[fileIndex];
        file.readsInProgress++;

        Assert(slot < kFileReadSlotCount);
        m_FileReadSlots[slot] = fileIndex;
        m_FreeReadSlots[slot / 64] &= ~(1ULL << (slot % 64));
        m_FreeReadSlotCount--;

        const auto fileOffset = (file.chunksRead++) * kFileReadBufferBaseSize;
        uint32_t bytesToRead = static_cast<uint32_t>(std::min(file.fileSize - fileOffset, m_ReadBufferSize));

        DSTORAGE_REQUEST request = {};
        request.Options.SourceType = DSTORAGE_REQUEST_SOURCE_FILE;
        request.Options.DestinationType = DSTORAGE_REQUEST_DESTINATION_MEMORY;
        request.Source.File.Source = file.file.Get();
        request.Source.File.Offset = fileOffset;
        request.Source.File.Size = bytesToRead;
        request.Destination.Memory.Buffer = m_FileReadBuffers.get() + slot * m_ReadBufferSize;
        request.Destination.Memory.Size = bytesToRead;
        request.UncompressedSize = bytesToRead;

        m_DStorageQueue->EnqueueRequest(&request);
        m_CurrentBatch.slots.emplace_back(slot, bytesToRead);

        if (m_CurrentBatch.slots.size() >= ARRAYSIZE(m_FileReadSlots) / 2)
            SubmitReadRequests();
    }
}

void DirectStorageFileReadWorkQueue::SubmitReadRequests()
{
    Assert(m_CurrentBatch.slots.size() > 0);
    m_CurrentBatch.fenceValue = ++m_FenceValue;

    m_DStorageQueue->EnqueueSignal(m_Fence.Get(), m_CurrentBatch.fenceValue);
    m_DStorageQueue->Submit();

    if (m_SubmittedBatches.empty())
        m_Fence->SetEventOnCompletion(m_CurrentBatch.fenceValue, m_FenceEvent);

    m_SubmittedBatches.push_back(std::move(m_CurrentBatch));
    m_CurrentBatch = m_BatchPool.GetNewObject();
}

void DirectStorageFileReadWorkQueue::ProcessReadCompletion()
{
    uint32_t batchIndex = 0;
    for (; batchIndex < m_SubmittedBatches.size() && m_SubmittedBatches[batchIndex].fenceValue <= m_Fence->GetCompletedValue(); batchIndex++)
    {
        auto& batch = m_SubmittedBatches[batchIndex];

        for (auto slot : batch.slots)
            m_SearchWorkQueue.PushWorkItem(slot);

        batch.slots.clear();
        m_BatchPool.PoolObject(std::move(batch));
    }

    m_SubmittedBatches.erase(m_SubmittedBatches.begin(), m_SubmittedBatches.begin() + batchIndex);
    if (!m_SubmittedBatches.empty())
        m_Fence->SetEventOnCompletion(m_SubmittedBatches.front().fenceValue, m_FenceEvent);
}

void DirectStorageFileReadWorkQueue::ContentsSearchThread()
{
    ScopedStackAllocator allocator;
    SetThreadDescription(GetCurrentThread(), L"FSS Content Search Thread");

    m_SearchWorkQueue.DoWork([this, &allocator](SlotSearchData& searchData)
    {
        searchData.found = m_StringSearcher.PerformFileContentSearch(m_FileReadBuffers.get() + searchData.slot * m_ReadBufferSize, searchData.size, allocator);
        MySearchResultBase::PushWorkItem(searchData);
    });
}

void DirectStorageFileReadWorkQueue::ProcessSearchCompletion(SlotSearchData searchData)
{
    auto& fileIndex = m_FileReadSlots[searchData.slot];
    auto& file = m_FilesWithReadProgress[fileIndex];

    Assert(file.readsInProgress > 0);
    file.readsInProgress--;
    m_FreeReadSlots[searchData.slot / 64] |= 1ULL << (searchData.slot % 64);
    m_FreeReadSlotCount++;

    if (!file.dispatchedToResults)
    {
        if (searchData.found)
        {
            m_SearchResultReporter.AddToScannedFileCount();
            m_SearchResultReporter.AddToScannedFileSize(file.fileSize - file.totalScannedSize);
            m_SearchResultReporter.DispatchSearchResult(file.fileFindData, std::move(file.filePath));

            file.totalScannedSize = file.fileSize;
            file.chunksRead = GetChunkCount(file);
            file.dispatchedToResults = true;
        }
        else if (file.readsInProgress == 0 && file.chunksRead == GetChunkCount(file))
        {
            m_SearchResultReporter.AddToScannedFileCount();
            m_SearchResultReporter.AddToScannedFileSize(file.fileSize - file.totalScannedSize);
            file.totalScannedSize = file.fileSize;
        }
        else
        {
            m_SearchResultReporter.AddToScannedFileSize(kFileReadBufferBaseSize);
            file.totalScannedSize += kFileReadBufferBaseSize;
        }
    }

    while (!m_FilesWithReadProgress.empty())
    {
        auto& lastFile = m_FilesWithReadProgress.back();

        if (lastFile.chunksRead == GetChunkCount(lastFile) && lastFile.readsInProgress == 0)
        {
            m_FilesWithReadProgress.pop_back();
        }
        else
        {
            break;
        }
    }

    QueueFileReads();
}
<|endoftext|>


<commit_before>#include "transactionview.h"

#include "transactionfilterproxy.h"
#include "transactionrecord.h"
#include "walletmodel.h"
#include "addresstablemodel.h"
#include "transactiontablemodel.h"
#include "bitcoinunits.h"
#include "csvmodelwriter.h"
#include "transactiondescdialog.h"
#include "editaddressdialog.h"
#include "optionsmodel.h"
#include "guiutil.h"

#include <QScrollBar>
#include <QComboBox>
#include <QDoubleValidator>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLineEdit>
#include <QTableView>
#include <QHeaderView>
#include <QPushButton>
#include <QMessageBox>
#include <QPoint>
#include <QMenu>
#include <QApplication>
#include <QClipboard>
#include <QLabel>
#include <QDateTimeEdit>

TransactionView::TransactionView(QWidget *parent) :
    QWidget(parent), model(0), transactionProxyModel(0),
    transactionView(0)
{
    // Build filter row
    setContentsMargins(0,0,0,0);

    QHBoxLayout *hlayout = new QHBoxLayout();
    hlayout->setContentsMargins(0,0,0,0);
#ifdef Q_OS_MAC
    hlayout->setSpacing(5);
    hlayout->addSpacing(26);
#else
    hlayout->setSpacing(0);
    hlayout->addSpacing(23);
#endif

    dateWidget = new QComboBox(this);
#ifdef Q_OS_MAC
    dateWidget->setFixedWidth(121);
#else
    dateWidget->setFixedWidth(120);
#endif
    dateWidget->addItem(tr("All"), All);
    dateWidget->addItem(tr("Today"), Today);
    dateWidget->addItem(tr("This week"), ThisWeek);
    dateWidget->addItem(tr("This month"), ThisMonth);
    dateWidget->addItem(tr("Last month"), LastMonth);
    dateWidget->addItem(tr("This year"), ThisYear);
    dateWidget->addItem(tr("Range..."), Range);
    hlayout->addWidget(dateWidget);

    typeWidget = new QComboBox(this);
#ifdef Q_OS_MAC
    typeWidget->setFixedWidth(121);
#else
    typeWidget->setFixedWidth(120);
#endif

    typeWidget->addItem(tr("All"), TransactionFilterProxy::ALL_TYPES);
    typeWidget->addItem(tr("Received with"), TransactionFilterProxy::TYPE(TransactionRecord::RecvWithAddress) |
                                        TransactionFilterProxy::TYPE(TransactionRecord::RecvFromOther));
    typeWidget->addItem(tr("Sent to"), TransactionFilterProxy::TYPE(TransactionRecord::SendToAddress) |
                                  TransactionFilterProxy::TYPE(TransactionRecord::SendToOther));
    typeWidget->addItem(tr("To yourself"), TransactionFilterProxy::TYPE(TransactionRecord::SendToSelf));
    typeWidget->addItem(tr("Mined"), TransactionFilterProxy::TYPE(TransactionRecord::StakeMint));
    typeWidget->addItem(tr("Mined"), TransactionFilterProxy::TYPE(TransactionRecord::Generated));
    typeWidget->addItem(tr("Other"), TransactionFilterProxy::TYPE(TransactionRecord::Other));

    hlayout->addWidget(typeWidget);

    addressWidget = new QLineEdit(this);
#if QT_VERSION >= 0x040700
    /* Do not move this to the XML file, Qt before 4.7 will choke on it */
    addressWidget->setPlaceholderText(tr("Enter address or label to search"));
#endif
    hlayout->addWidget(addressWidget);

    amountWidget = new QLineEdit(this);
#if QT_VERSION >= 0x040700
    /* Do not move this to the XML file, Qt before 4.7 will choke on it */
    amountWidget->setPlaceholderText(tr("Min amount"));
#endif
#ifdef Q_OS_MAC
    amountWidget->setFixedWidth(97);
#else
    amountWidget->setFixedWidth(100);
#endif
    amountWidget->setValidator(new QDoubleValidator(0, 1e20, 8, this));
    hlayout->addWidget(amountWidget);

    QVBoxLayout *vlayout = new QVBoxLayout(this);
    vlayout->setContentsMargins(0,0,0,0);
    vlayout->setSpacing(0);

    QTableView *view = new QTableView(this);
    vlayout->addLayout(hlayout);
    vlayout->addWidget(createDateRangeWidget());
    vlayout->addWidget(view);
    vlayout->setSpacing(0);
    int width = view->verticalScrollBar()->sizeHint().width();
    // Cover scroll bar width with spacing
#ifdef Q_OS_MAC
    hlayout->addSpacing(width+2);
#else
    hlayout->addSpacing(width);
#endif
    // Always show scroll bar
    view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
    view->setTabKeyNavigation(false);
    view->setContextMenuPolicy(Qt::CustomContextMenu);

    transactionView = view;

    // Actions
    QAction *copyAddressAction = new QAction(tr("Copy address"), this);
    QAction *copyLabelAction = new QAction(tr("Copy label"), this);
    QAction *copyAmountAction = new QAction(tr("Copy amount"), this);
    QAction *editLabelAction = new QAction(tr("Edit label"), this);
    QAction *showDetailsAction = new QAction(tr("Show transaction details"), this);

    contextMenu = new QMenu();
    contextMenu->addAction(copyAddressAction);
    contextMenu->addAction(copyLabelAction);
    contextMenu->addAction(copyAmountAction);
    contextMenu->addAction(editLabelAction);
    contextMenu->addAction(showDetailsAction);

    // Connect actions
    connect(dateWidget, SIGNAL(activated(int)), this, SLOT(chooseDate(int)));
    connect(typeWidget, SIGNAL(activated(int)), this, SLOT(chooseType(int)));
    connect(addressWidget, SIGNAL(textChanged(QString)), this, SLOT(changedPrefix(QString)));
    connect(amountWidget, SIGNAL(textChanged(QString)), this, SLOT(changedAmount(QString)));

    connect(view, SIGNAL(doubleClicked(QModelIndex)), this, SIGNAL(doubleClicked(QModelIndex)));
    connect(view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextualMenu(QPoint)));

    connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress()));
    connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel()));
    connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount()));
    connect(editLabelAction, SIGNAL(triggered()), this, SLOT(editLabel()));
    connect(showDetailsAction, SIGNAL(triggered()), this, SLOT(showDetails()));
}

void TransactionView::setModel(WalletModel *model)
{
    this->model = model;
    if(model)
    {
        transactionProxyModel = new TransactionFilterProxy(this);
        transactionProxyModel->setSourceModel(model->getTransactionTableModel());
        transactionProxyModel->setDynamicSortFilter(true);
        transactionProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
        transactionProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);

        transactionProxyModel->setSortRole(Qt::EditRole);

        transactionView->setModel(transactionProxyModel);
        transactionView->setAlternatingRowColors(true);
        transactionView->setSelectionBehavior(QAbstractItemView::SelectRows);
        transactionView->setSelectionMode(QAbstractItemView::ExtendedSelection);
        transactionView->setSortingEnabled(true);
        transactionView->sortByColumn(TransactionTableModel::Status, Qt::DescendingOrder);
        transactionView->verticalHeader()->hide();

        transactionView->horizontalHeader()->resizeSection(TransactionTableModel::Status, 23);
        transactionView->horizontalHeader()->resizeSection(TransactionTableModel::Date, 120);
        transactionView->horizontalHeader()->resizeSection(TransactionTableModel::Type, 120);
#if QT_VERSION < 0x050000
        transactionView->horizontalHeader()->setResizeMode(TransactionTableModel::ToAddress, QHeaderView::Stretch);
#else
        transactionView->horizontalHeader()->setSectionResizeMode(TransactionTableModel::ToAddress, QHeaderView::Stretch);
#endif
        transactionView->horizontalHeader()->resizeSection( TransactionTableModel::Amount, 100);
    }
}

void TransactionView::chooseDate(int idx)
{
    if(!transactionProxyModel)
        return;
    QDate current = QDate::currentDate();
    dateRangeWidget->setVisible(false);
    switch(dateWidget->itemData(idx).toInt())
    {
    case All:
        transactionProxyModel->setDateRange(
                TransactionFilterProxy::MIN_DATE,
                TransactionFilterProxy::MAX_DATE);
        break;
    case Today:
        transactionProxyModel->setDateRange(
                QDateTime(current),
                TransactionFilterProxy::MAX_DATE);
        break;
    case ThisWeek: {
        // Find last Monday
        QDate startOfWeek = current.addDays(-(current.dayOfWeek()-1));
        transactionProxyModel->setDateRange(
                QDateTime(startOfWeek),
                TransactionFilterProxy::MAX_DATE);

        } break;
    case ThisMonth:
        transactionProxyModel->setDateRange(
                QDateTime(QDate(current.year(), current.month(), 1)),
                TransactionFilterProxy::MAX_DATE);
        break;
    case LastMonth:
        transactionProxyModel->setDateRange(
                QDateTime(QDate(current.year(), current.month()-1, 1)),
                QDateTime(QDate(current.year(), current.month(), 1)));
        break;
    case ThisYear:
        transactionProxyModel->setDateRange(
                QDateTime(QDate(current.year(), 1, 1)),
                TransactionFilterProxy::MAX_DATE);
        break;
    case Range:
        dateRangeWidget->setVisible(true);
        dateRangeChanged();
        break;
    }
}

void TransactionView::chooseType(int idx)
{
    if(!transactionProxyModel)
        return;
    transactionProxyModel->setTypeFilter(
        typeWidget->itemData(idx).toInt());
}

void TransactionView::changedPrefix(const QString &prefix)
{
    if(!transactionProxyModel)
        return;
    transactionProxyModel->setAddressPrefix(prefix);
}

void TransactionView::changedAmount(const QString &amount)
{
    if(!transactionProxyModel)
        return;
    qint64 amount_parsed = 0;
    if(BitcoinUnits::parse(model->getOptionsModel()->getDisplayUnit(), amount, &amount_parsed))
    {
        transactionProxyModel->setMinAmount(amount_parsed);
    }
    else
    {
        transactionProxyModel->setMinAmount(0);
    }
}

void TransactionView::exportClicked()
{
    // CSV is currently the only supported format
    QString filename = GUIUtil::getSaveFileName(
            this,
            tr("Export Transaction Data"), QString(),
            tr("Comma separated file (*.csv)"));

    if (filename.isNull()) return;

    CSVModelWriter writer(filename);

    // name, column, role
    writer.setModel(transactionProxyModel);
    writer.addColumn(tr("Confirmed"), 0, TransactionTableModel::ConfirmedRole);
    writer.addColumn(tr("Date"), 0, TransactionTableModel::DateRole);
    writer.addColumn(tr("Type"), TransactionTableModel::Type, Qt::EditRole);
    writer.addColumn(tr("Label"), 0, TransactionTableModel::LabelRole);
    writer.addColumn(tr("Address"), 0, TransactionTableModel::AddressRole);
    writer.addColumn(tr("Amount"), 0, TransactionTableModel::FormattedAmountRole);
    writer.addColumn(tr("ID"), 0, TransactionTableModel::TxIDRole);

    if(!writer.write())
    {
        QMessageBox::critical(this, tr("Error exporting"), tr("Could not write to file %1.").arg(filename),
                              QMessageBox::Abort, QMessageBox::Abort);
    }
}

void TransactionView::contextualMenu(const QPoint &point)
{
    QModelIndex index = transactionView->indexAt(point);
    if(index.isValid())
    {
        contextMenu->exec(QCursor::pos());
    }
}

void TransactionView::copyAddress()
{
    GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::AddressRole);
}

void TransactionView::copyLabel()
{
    GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::LabelRole);
}

void TransactionView::copyAmount()
{
    GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::FormattedAmountRole);
}

void TransactionView::editLabel()
{
    if(!transactionView->selectionModel() ||!model)
        return;
    QModelIndexList selection = transactionView->selectionModel()->selectedRows();
    if(!selection.isEmpty())
    {
        AddressTableModel *addressBook = model->getAddressTableModel();
        if(!addressBook)
            return;
        QString address = selection.at(0).data(TransactionTableModel::AddressRole).toString();
        if(address.isEmpty())
        {
            // If this transaction has no associated address, exit
            return;
        }
        // Is address in address book? Address book can miss address when a transaction is
        // sent from outside the UI.
        int idx = addressBook->lookupAddress(address);
        if(idx != -1)
        {
            // Edit sending / receiving address
            QModelIndex modelIdx = addressBook->index(idx, 0, QModelIndex());
            // Determine type of address, launch appropriate editor dialog type
            QString type = modelIdx.data(AddressTableModel::TypeRole).toString();

            EditAddressDialog dlg(type==AddressTableModel::Receive
                                         ? EditAddressDialog::EditReceivingAddress
                                         : EditAddressDialog::EditSendingAddress,
                                  this);
            dlg.setModel(addressBook);
            dlg.loadRow(idx);
            dlg.exec();
        }
        else
        {
            // Add sending address
            EditAddressDialog dlg(EditAddressDialog::NewSendingAddress,
                                  this);
            dlg.setModel(addressBook);
            dlg.setAddress(address);
            dlg.exec();
        }
    }
}

void TransactionView::showDetails()
{
    if(!transactionView->selectionModel())
        return;
    QModelIndexList selection = transactionView->selectionModel()->selectedRows();
    if(!selection.isEmpty())
    {
        TransactionDescDialog dlg(selection.at(0));
        dlg.exec();
    }
}

QWidget *TransactionView::createDateRangeWidget()
{
    dateRangeWidget = new QFrame();
    dateRangeWidget->setFrameStyle(QFrame::Panel | QFrame::Raised);
    dateRangeWidget->setContentsMargins(1,1,1,1);
    QHBoxLayout *layout = new QHBoxLayout(dateRangeWidget);
    layout->setContentsMargins(0,0,0,0);
    layout->addSpacing(23);
    layout->addWidget(new QLabel(tr("Range:")));

    dateFrom = new QDateTimeEdit(this);
    dateFrom->setDisplayFormat("dd/MM/yy");
    dateFrom->setCalendarPopup(true);
    dateFrom->setMinimumWidth(100);
    dateFrom->setDate(QDate::currentDate().addDays(-7));
    layout->addWidget(dateFrom);
    layout->addWidget(new QLabel(tr("to")));

    dateTo = new QDateTimeEdit(this);
    dateTo->setDisplayFormat("dd/MM/yy");
    dateTo->setCalendarPopup(true);
    dateTo->setMinimumWidth(100);
    dateTo->setDate(QDate::currentDate());
    layout->addWidget(dateTo);
    layout->addStretch();

    // Hide by default
    dateRangeWidget->setVisible(false);

    // Notify on change
    connect(dateFrom, SIGNAL(dateChanged(QDate)), this, SLOT(dateRangeChanged()));
    connect(dateTo, SIGNAL(dateChanged(QDate)), this, SLOT(dateRangeChanged()));

    return dateRangeWidget;
}

void TransactionView::dateRangeChanged()
{
    if(!transactionProxyModel)
        return;
    transactionProxyModel->setDateRange(
            QDateTime(dateFrom->date()),
            QDateTime(dateTo->date()).addDays(1));
}

void TransactionView::focusTransaction(const QModelIndex &idx)
{
    if(!transactionProxyModel)
        return;
    QModelIndex targetIdx = transactionProxyModel->mapFromSource(idx);
    transactionView->scrollTo(targetIdx);
    transactionView->setCurrentIndex(targetIdx);
    transactionView->setFocus();
}
<commit_msg>Fix for #349<commit_after>#include "transactionview.h"

#include "transactionfilterproxy.h"
#include "transactionrecord.h"
#include "walletmodel.h"
#include "addresstablemodel.h"
#include "transactiontablemodel.h"
#include "bitcoinunits.h"
#include "csvmodelwriter.h"
#include "transactiondescdialog.h"
#include "editaddressdialog.h"
#include "optionsmodel.h"
#include "guiutil.h"

#include <QScrollBar>
#include <QComboBox>
#include <QDoubleValidator>
#include <QHBoxLayout>
#include <QVBoxLayout>
#include <QLineEdit>
#include <QTableView>
#include <QHeaderView>
#include <QPushButton>
#include <QMessageBox>
#include <QPoint>
#include <QMenu>
#include <QApplication>
#include <QClipboard>
#include <QLabel>
#include <QDateTimeEdit>

TransactionView::TransactionView(QWidget *parent) :
    QWidget(parent), model(0), transactionProxyModel(0),
    transactionView(0)
{
    // Build filter row
    setContentsMargins(0,0,0,0);

    QHBoxLayout *hlayout = new QHBoxLayout();
    hlayout->setContentsMargins(0,0,0,0);
#ifdef Q_OS_MAC
    hlayout->setSpacing(5);
    hlayout->addSpacing(26);
#else
    hlayout->setSpacing(0);
    hlayout->addSpacing(23);
#endif

    dateWidget = new QComboBox(this);
#ifdef Q_OS_MAC
    dateWidget->setFixedWidth(121);
#else
    dateWidget->setFixedWidth(120);
#endif
    dateWidget->addItem(tr("All"), All);
    dateWidget->addItem(tr("Today"), Today);
    dateWidget->addItem(tr("This week"), ThisWeek);
    dateWidget->addItem(tr("This month"), ThisMonth);
    dateWidget->addItem(tr("Last month"), LastMonth);
    dateWidget->addItem(tr("This year"), ThisYear);
    dateWidget->addItem(tr("Range..."), Range);
    hlayout->addWidget(dateWidget);

    typeWidget = new QComboBox(this);
#ifdef Q_OS_MAC
    typeWidget->setFixedWidth(121);
#else
    typeWidget->setFixedWidth(120);
#endif

    typeWidget->addItem(tr("All"), TransactionFilterProxy::ALL_TYPES);
    typeWidget->addItem(tr("Received with"), TransactionFilterProxy::TYPE(TransactionRecord::RecvWithAddress) |
                                        TransactionFilterProxy::TYPE(TransactionRecord::RecvFromOther));
    typeWidget->addItem(tr("Sent to"), TransactionFilterProxy::TYPE(TransactionRecord::SendToAddress) |
                                  TransactionFilterProxy::TYPE(TransactionRecord::SendToOther));
    typeWidget->addItem(tr("To yourself"), TransactionFilterProxy::TYPE(TransactionRecord::SendToSelf));
    typeWidget->addItem(tr("Mined"), TransactionFilterProxy::TYPE(TransactionRecord::Generated));
    typeWidget->addItem(tr("Other"), TransactionFilterProxy::TYPE(TransactionRecord::Other));

    hlayout->addWidget(typeWidget);

    addressWidget = new QLineEdit(this);
#if QT_VERSION >= 0x040700
    /* Do not move this to the XML file, Qt before 4.7 will choke on it */
    addressWidget->setPlaceholderText(tr("Enter address or label to search"));
#endif
    hlayout->addWidget(addressWidget);

    amountWidget = new QLineEdit(this);
#if QT_VERSION >= 0x040700
    /* Do not move this to the XML file, Qt before 4.7 will choke on it */
    amountWidget->setPlaceholderText(tr("Min amount"));
#endif
#ifdef Q_OS_MAC
    amountWidget->setFixedWidth(97);
#else
    amountWidget->setFixedWidth(100);
#endif
    amountWidget->setValidator(new QDoubleValidator(0, 1e20, 8, this));
    hlayout->addWidget(amountWidget);

    QVBoxLayout *vlayout = new QVBoxLayout(this);
    vlayout->setContentsMargins(0,0,0,0);
    vlayout->setSpacing(0);

    QTableView *view = new QTableView(this);
    vlayout->addLayout(hlayout);
    vlayout->addWidget(createDateRangeWidget());
    vlayout->addWidget(view);
    vlayout->setSpacing(0);
    int width = view->verticalScrollBar()->sizeHint().width();
    // Cover scroll bar width with spacing
#ifdef Q_OS_MAC
    hlayout->addSpacing(width+2);
#else
    hlayout->addSpacing(width);
#endif
    // Always show scroll bar
    view->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
    view->setTabKeyNavigation(false);
    view->setContextMenuPolicy(Qt::CustomContextMenu);

    transactionView = view;

    // Actions
    QAction *copyAddressAction = new QAction(tr("Copy address"), this);
    QAction *copyLabelAction = new QAction(tr("Copy label"), this);
    QAction *copyAmountAction = new QAction(tr("Copy amount"), this);
    QAction *editLabelAction = new QAction(tr("Edit label"), this);
    QAction *showDetailsAction = new QAction(tr("Show transaction details"), this);

    contextMenu = new QMenu();
    contextMenu->addAction(copyAddressAction);
    contextMenu->addAction(copyLabelAction);
    contextMenu->addAction(copyAmountAction);
    contextMenu->addAction(editLabelAction);
    contextMenu->addAction(showDetailsAction);

    // Connect actions
    connect(dateWidget, SIGNAL(activated(int)), this, SLOT(chooseDate(int)));
    connect(typeWidget, SIGNAL(activated(int)), this, SLOT(chooseType(int)));
    connect(addressWidget, SIGNAL(textChanged(QString)), this, SLOT(changedPrefix(QString)));
    connect(amountWidget, SIGNAL(textChanged(QString)), this, SLOT(changedAmount(QString)));

    connect(view, SIGNAL(doubleClicked(QModelIndex)), this, SIGNAL(doubleClicked(QModelIndex)));
    connect(view, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(contextualMenu(QPoint)));

    connect(copyAddressAction, SIGNAL(triggered()), this, SLOT(copyAddress()));
    connect(copyLabelAction, SIGNAL(triggered()), this, SLOT(copyLabel()));
    connect(copyAmountAction, SIGNAL(triggered()), this, SLOT(copyAmount()));
    connect(editLabelAction, SIGNAL(triggered()), this, SLOT(editLabel()));
    connect(showDetailsAction, SIGNAL(triggered()), this, SLOT(showDetails()));
}

void TransactionView::setModel(WalletModel *model)
{
    this->model = model;
    if(model)
    {
        transactionProxyModel = new TransactionFilterProxy(this);
        transactionProxyModel->setSourceModel(model->getTransactionTableModel());
        transactionProxyModel->setDynamicSortFilter(true);
        transactionProxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);
        transactionProxyModel->setFilterCaseSensitivity(Qt::CaseInsensitive);

        transactionProxyModel->setSortRole(Qt::EditRole);

        transactionView->setModel(transactionProxyModel);
        transactionView->setAlternatingRowColors(true);
        transactionView->setSelectionBehavior(QAbstractItemView::SelectRows);
        transactionView->setSelectionMode(QAbstractItemView::ExtendedSelection);
        transactionView->setSortingEnabled(true);
        transactionView->sortByColumn(TransactionTableModel::Status, Qt::DescendingOrder);
        transactionView->verticalHeader()->hide();

        transactionView->horizontalHeader()->resizeSection(TransactionTableModel::Status, 23);
        transactionView->horizontalHeader()->resizeSection(TransactionTableModel::Date, 120);
        transactionView->horizontalHeader()->resizeSection(TransactionTableModel::Type, 120);
#if QT_VERSION < 0x050000
        transactionView->horizontalHeader()->setResizeMode(TransactionTableModel::ToAddress, QHeaderView::Stretch);
#else
        transactionView->horizontalHeader()->setSectionResizeMode(TransactionTableModel::ToAddress, QHeaderView::Stretch);
#endif
        transactionView->horizontalHeader()->resizeSection( TransactionTableModel::Amount, 100);
    }
}

void TransactionView::chooseDate(int idx)
{
    if(!transactionProxyModel)
        return;
    QDate current = QDate::currentDate();
    dateRangeWidget->setVisible(false);
    switch(dateWidget->itemData(idx).toInt())
    {
    case All:
        transactionProxyModel->setDateRange(
                TransactionFilterProxy::MIN_DATE,
                TransactionFilterProxy::MAX_DATE);
        break;
    case Today:
        transactionProxyModel->setDateRange(
                QDateTime(current),
                TransactionFilterProxy::MAX_DATE);
        break;
    case ThisWeek: {
        // Find last Monday
        QDate startOfWeek = current.addDays(-(current.dayOfWeek()-1));
        transactionProxyModel->setDateRange(
                QDateTime(startOfWeek),
                TransactionFilterProxy::MAX_DATE);

        } break;
    case ThisMonth:
        transactionProxyModel->setDateRange(
                QDateTime(QDate(current.year(), current.month(), 1)),
                TransactionFilterProxy::MAX_DATE);
        break;
    case LastMonth:
        transactionProxyModel->setDateRange(
                QDateTime(QDate(current.year(), current.month()-1, 1)),
                QDateTime(QDate(current.year(), current.month(), 1)));
        break;
    case ThisYear:
        transactionProxyModel->setDateRange(
                QDateTime(QDate(current.year(), 1, 1)),
                TransactionFilterProxy::MAX_DATE);
        break;
    case Range:
        dateRangeWidget->setVisible(true);
        dateRangeChanged();
        break;
    }
}

void TransactionView::chooseType(int idx)
{
    if(!transactionProxyModel)
        return;
    transactionProxyModel->setTypeFilter(
        typeWidget->itemData(idx).toInt());
}

void TransactionView::changedPrefix(const QString &prefix)
{
    if(!transactionProxyModel)
        return;
    transactionProxyModel->setAddressPrefix(prefix);
}

void TransactionView::changedAmount(const QString &amount)
{
    if(!transactionProxyModel)
        return;
    qint64 amount_parsed = 0;
    if(BitcoinUnits::parse(model->getOptionsModel()->getDisplayUnit(), amount, &amount_parsed))
    {
        transactionProxyModel->setMinAmount(amount_parsed);
    }
    else
    {
        transactionProxyModel->setMinAmount(0);
    }
}

void TransactionView::exportClicked()
{
    // CSV is currently the only supported format
    QString filename = GUIUtil::getSaveFileName(
            this,
            tr("Export Transaction Data"), QString(),
            tr("Comma separated file (*.csv)"));

    if (filename.isNull()) return;

    CSVModelWriter writer(filename);

    // name, column, role
    writer.setModel(transactionProxyModel);
    writer.addColumn(tr("Confirmed"), 0, TransactionTableModel::ConfirmedRole);
    writer.addColumn(tr("Date"), 0, TransactionTableModel::DateRole);
    writer.addColumn(tr("Type"), TransactionTableModel::Type, Qt::EditRole);
    writer.addColumn(tr("Label"), 0, TransactionTableModel::LabelRole);
    writer.addColumn(tr("Address"), 0, TransactionTableModel::AddressRole);
    writer.addColumn(tr("Amount"), 0, TransactionTableModel::FormattedAmountRole);
    writer.addColumn(tr("ID"), 0, TransactionTableModel::TxIDRole);

    if(!writer.write())
    {
        QMessageBox::critical(this, tr("Error exporting"), tr("Could not write to file %1.").arg(filename),
                              QMessageBox::Abort, QMessageBox::Abort);
    }
}

void TransactionView::contextualMenu(const QPoint &point)
{
    QModelIndex index = transactionView->indexAt(point);
    if(index.isValid())
    {
        contextMenu->exec(QCursor::pos());
    }
}

void TransactionView::copyAddress()
{
    GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::AddressRole);
}

void TransactionView::copyLabel()
{
    GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::LabelRole);
}

void TransactionView::copyAmount()
{
    GUIUtil::copyEntryData(transactionView, 0, TransactionTableModel::FormattedAmountRole);
}

void TransactionView::editLabel()
{
    if(!transactionView->selectionModel() ||!model)
        return;
    QModelIndexList selection = transactionView->selectionModel()->selectedRows();
    if(!selection.isEmpty())
    {
        AddressTableModel *addressBook = model->getAddressTableModel();
        if(!addressBook)
            return;
        QString address = selection.at(0).data(TransactionTableModel::AddressRole).toString();
        if(address.isEmpty())
        {
            // If this transaction has no associated address, exit
            return;
        }
        // Is address in address book? Address book can miss address when a transaction is
        // sent from outside the UI.
        int idx = addressBook->lookupAddress(address);
        if(idx != -1)
        {
            // Edit sending / receiving address
            QModelIndex modelIdx = addressBook->index(idx, 0, QModelIndex());
            // Determine type of address, launch appropriate editor dialog type
            QString type = modelIdx.data(AddressTableModel::TypeRole).toString();

            EditAddressDialog dlg(type==AddressTableModel::Receive
                                         ? EditAddressDialog::EditReceivingAddress
                                         : EditAddressDialog::EditSendingAddress,
                                  this);
            dlg.setModel(addressBook);
            dlg.loadRow(idx);
            dlg.exec();
        }
        else
        {
            // Add sending address
            EditAddressDialog dlg(EditAddressDialog::NewSendingAddress,
                                  this);
            dlg.setModel(addressBook);
            dlg.setAddress(address);
            dlg.exec();
        }
    }
}

void TransactionView::showDetails()
{
    if(!transactionView->selectionModel())
        return;
    QModelIndexList selection = transactionView->selectionModel()->selectedRows();
    if(!selection.isEmpty())
    {
        TransactionDescDialog dlg(selection.at(0));
        dlg.exec();
    }
}

QWidget *TransactionView::createDateRangeWidget()
{
    dateRangeWidget = new QFrame();
    dateRangeWidget->setFrameStyle(QFrame::Panel | QFrame::Raised);
    dateRangeWidget->setContentsMargins(1,1,1,1);
    QHBoxLayout *layout = new QHBoxLayout(dateRangeWidget);
    layout->setContentsMargins(0,0,0,0);
    layout->addSpacing(23);
    layout->addWidget(new QLabel(tr("Range:")));

    dateFrom = new QDateTimeEdit(this);
    dateFrom->setDisplayFormat("dd/MM/yy");
    dateFrom->setCalendarPopup(true);
    dateFrom->setMinimumWidth(100);
    dateFrom->setDate(QDate::currentDate().addDays(-7));
    layout->addWidget(dateFrom);
    layout->addWidget(new QLabel(tr("to")));

    dateTo = new QDateTimeEdit(this);
    dateTo->setDisplayFormat("dd/MM/yy");
    dateTo->setCalendarPopup(true);
    dateTo->setMinimumWidth(100);
    dateTo->setDate(QDate::currentDate());
    layout->addWidget(dateTo);
    layout->addStretch();

    // Hide by default
    dateRangeWidget->setVisible(false);

    // Notify on change
    connect(dateFrom, SIGNAL(dateChanged(QDate)), this, SLOT(dateRangeChanged()));
    connect(dateTo, SIGNAL(dateChanged(QDate)), this, SLOT(dateRangeChanged()));

    return dateRangeWidget;
}

void TransactionView::dateRangeChanged()
{
    if(!transactionProxyModel)
        return;
    transactionProxyModel->setDateRange(
            QDateTime(dateFrom->date()),
            QDateTime(dateTo->date()).addDays(1));
}

void TransactionView::focusTransaction(const QModelIndex &idx)
{
    if(!transactionProxyModel)
        return;
    QModelIndex targetIdx = transactionProxyModel->mapFromSource(idx);
    transactionView->scrollTo(targetIdx);
    transactionView->setCurrentIndex(targetIdx);
    transactionView->setFocus();
}
<|endoftext|>


<commit_before>//===--- swift-demangle.cpp - Swift Demangler app -------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 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
//
//===----------------------------------------------------------------------===//
//
// This is the entry point.
//
//===----------------------------------------------------------------------===//

#include "swift/Basic/DemangleWrappers.h"
#include "swift/Basic/ManglingMacros.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/PrettyStackTrace.h"
#include "llvm/Support/Regex.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/raw_ostream.h"

#include <cstdlib>
#include <string>
#if !defined(_MSC_VER) && !defined(__MINGW32__)
#include <unistd.h>
#else
#include <io.h>
#endif

static llvm::cl::opt<bool>
ExpandMode("expand",
               llvm::cl::desc("Expand mode (show node structure of the demangling)"));

static llvm::cl::opt<bool>
CompactMode("compact",
          llvm::cl::desc("Compact mode (only emit the demangled names)"));

static llvm::cl::opt<bool>
TreeOnly("tree-only",
           llvm::cl::desc("Tree-only mode (do not show the demangled string)"));

static llvm::cl::opt<bool>
RemangleMode("test-remangle",
           llvm::cl::desc("Remangle test mode (show the remangled string)"));

static llvm::cl::opt<bool>
DisableSugar("no-sugar",
           llvm::cl::desc("No sugar mode (disable common language idioms such as ? and [] from the output)"));

static llvm::cl::opt<bool>
Simplified("simplified",
           llvm::cl::desc("Don't display module names or implicit self types"));

static llvm::cl::list<std::string>
InputNames(llvm::cl::Positional, llvm::cl::desc("[mangled name...]"),
               llvm::cl::ZeroOrMore);

static llvm::StringRef substrBefore(llvm::StringRef whole,
                                    llvm::StringRef part) {
  return whole.slice(0, part.data() - whole.data());
}

static llvm::StringRef substrAfter(llvm::StringRef whole,
                                   llvm::StringRef part) {
  return whole.substr((part.data() - whole.data()) + part.size());
}

static void demangle(llvm::raw_ostream &os, llvm::StringRef name,
                     const swift::Demangle::DemangleOptions &options) {
  bool hadLeadingUnderscore = false;
  if (name.startswith("__")) {
    hadLeadingUnderscore = true;
    name = name.substr(1);
  }
  swift::Demangle::NodePointer pointer =
      swift::demangle_wrappers::demangleSymbolAsNode(name);
  if (ExpandMode || TreeOnly) {
    llvm::outs() << "Demangling for " << name << '\n';
    swift::demangle_wrappers::NodeDumper(pointer).print(llvm::outs());
  }
  if (RemangleMode) {
    if (hadLeadingUnderscore) llvm::outs() << '_';
    // Just reprint the original mangled name if it didn't demangle.
    // This makes it easier to share the same database between the
    // mangling and demangling tests.
    if (!pointer) {
      llvm::outs() << name;
    } else {
      llvm::outs() << swift::Demangle::mangleNode(pointer);
    }
    return;
  }
  if (!TreeOnly) {
    std::string string = swift::Demangle::nodeToString(pointer, options);
    if (!CompactMode)
      llvm::outs() << name << " ---> ";
    llvm::outs() << (string.empty() ? name : llvm::StringRef(string));
  }
}

static int demangleSTDIN(const swift::Demangle::DemangleOptions &options) {
  // This doesn't handle Unicode symbols, but maybe that's okay.
  llvm::Regex maybeSymbol("(_T|" MANGLING_PREFIX_STR ")[_a-zA-Z0-9$]+");

  while (true) {
    char *inputLine = NULL;
    size_t size;
    if (getline(&inputLine, &size, stdin) == -1 || size <= 0) {
      if (errno == 0) {
        break;
      }

      return EXIT_FAILURE;
    }

    llvm::StringRef inputContents(inputLine);
    llvm::SmallVector<llvm::StringRef, 1> matches;
    while (maybeSymbol.match(inputContents, &matches)) {
      llvm::outs() << substrBefore(inputContents, matches.front());
      demangle(llvm::outs(), matches.front(), options);
      inputContents = substrAfter(inputContents, matches.front());
    }

    llvm::outs() << inputContents;
    free(inputLine);
  }

  return EXIT_SUCCESS;
}

int main(int argc, char **argv) {
#if defined(__CYGWIN__)
  // Cygwin clang 3.5.2 with '-O3' generates CRASHING BINARY,
  // if main()'s first function call is passing argv[0].
  std::rand();
#endif
  llvm::cl::ParseCommandLineOptions(argc, argv);

  swift::Demangle::DemangleOptions options;
  options.SynthesizeSugarOnTypes = !DisableSugar;
  if (Simplified)
    options = swift::Demangle::DemangleOptions::SimplifiedUIDemangleOptions();

  if (InputNames.empty()) {
    CompactMode = true;
    return demangleSTDIN(options);
  } else {
    for (llvm::StringRef name : InputNames) {
      demangle(llvm::outs(), name, options);
      llvm::outs() << '\n';
    }

    return EXIT_SUCCESS;
  }
}
<commit_msg>swift-demangle: Abort with an error if the demangling-test fails for a symbol.<commit_after>//===--- swift-demangle.cpp - Swift Demangler app -------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2014 - 2016 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
//
//===----------------------------------------------------------------------===//
//
// This is the entry point.
//
//===----------------------------------------------------------------------===//

#include "swift/Basic/DemangleWrappers.h"
#include "swift/Basic/ManglingMacros.h"
#include "llvm/ADT/SmallString.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/MemoryBuffer.h"
#include "llvm/Support/PrettyStackTrace.h"
#include "llvm/Support/Regex.h"
#include "llvm/Support/Signals.h"
#include "llvm/Support/raw_ostream.h"

#include <cstdlib>
#include <string>
#if !defined(_MSC_VER) && !defined(__MINGW32__)
#include <unistd.h>
#else
#include <io.h>
#endif

static llvm::cl::opt<bool>
ExpandMode("expand",
               llvm::cl::desc("Expand mode (show node structure of the demangling)"));

static llvm::cl::opt<bool>
CompactMode("compact",
          llvm::cl::desc("Compact mode (only emit the demangled names)"));

static llvm::cl::opt<bool>
TreeOnly("tree-only",
           llvm::cl::desc("Tree-only mode (do not show the demangled string)"));

static llvm::cl::opt<bool>
RemangleMode("test-remangle",
           llvm::cl::desc("Remangle test mode (show the remangled string)"));

static llvm::cl::opt<bool>
DisableSugar("no-sugar",
           llvm::cl::desc("No sugar mode (disable common language idioms such as ? and [] from the output)"));

static llvm::cl::opt<bool>
Simplified("simplified",
           llvm::cl::desc("Don't display module names or implicit self types"));

static llvm::cl::list<std::string>
InputNames(llvm::cl::Positional, llvm::cl::desc("[mangled name...]"),
               llvm::cl::ZeroOrMore);

static llvm::StringRef substrBefore(llvm::StringRef whole,
                                    llvm::StringRef part) {
  return whole.slice(0, part.data() - whole.data());
}

static llvm::StringRef substrAfter(llvm::StringRef whole,
                                   llvm::StringRef part) {
  return whole.substr((part.data() - whole.data()) + part.size());
}

static void demangle(llvm::raw_ostream &os, llvm::StringRef name,
                     const swift::Demangle::DemangleOptions &options) {
  bool hadLeadingUnderscore = false;
  if (name.startswith("__")) {
    hadLeadingUnderscore = true;
    name = name.substr(1);
  }
  swift::Demangle::NodePointer pointer =
      swift::demangle_wrappers::demangleSymbolAsNode(name);
  if (ExpandMode || TreeOnly) {
    llvm::outs() << "Demangling for " << name << '\n';
    swift::demangle_wrappers::NodeDumper(pointer).print(llvm::outs());
  }
  if (RemangleMode) {
    std::string remangled;
    if (!pointer) {
      // Just reprint the original mangled name if it didn't demangle.
      // This makes it easier to share the same database between the
      // mangling and demangling tests.
      remangled = name;
    } else {
      remangled = swift::Demangle::mangleNode(pointer,
                                        /*NewMangling*/ name.startswith("_S"));
      if (name != remangled) {
        llvm::errs() << "\nError: re-mangled name \n  " << remangled
                     << "\ndoes not match original name\n  " << name << '\n';
        exit(1);
      }
    }
    if (hadLeadingUnderscore) llvm::outs() << '_';
    llvm::outs() << remangled;
    return;
  }
  if (!TreeOnly) {
    std::string string = swift::Demangle::nodeToString(pointer, options);
    if (!CompactMode)
      llvm::outs() << name << " ---> ";
    llvm::outs() << (string.empty() ? name : llvm::StringRef(string));
  }
}

static int demangleSTDIN(const swift::Demangle::DemangleOptions &options) {
  // This doesn't handle Unicode symbols, but maybe that's okay.
  llvm::Regex maybeSymbol("(_T|" MANGLING_PREFIX_STR ")[_a-zA-Z0-9$]+");

  while (true) {
    char *inputLine = NULL;
    size_t size;
    if (getline(&inputLine, &size, stdin) == -1 || size <= 0) {
      if (errno == 0) {
        break;
      }

      return EXIT_FAILURE;
    }

    llvm::StringRef inputContents(inputLine);
    llvm::SmallVector<llvm::StringRef, 1> matches;
    while (maybeSymbol.match(inputContents, &matches)) {
      llvm::outs() << substrBefore(inputContents, matches.front());
      demangle(llvm::outs(), matches.front(), options);
      inputContents = substrAfter(inputContents, matches.front());
    }

    llvm::outs() << inputContents;
    free(inputLine);
  }

  return EXIT_SUCCESS;
}

int main(int argc, char **argv) {
#if defined(__CYGWIN__)
  // Cygwin clang 3.5.2 with '-O3' generates CRASHING BINARY,
  // if main()'s first function call is passing argv[0].
  std::rand();
#endif
  llvm::cl::ParseCommandLineOptions(argc, argv);

  swift::Demangle::DemangleOptions options;
  options.SynthesizeSugarOnTypes = !DisableSugar;
  if (Simplified)
    options = swift::Demangle::DemangleOptions::SimplifiedUIDemangleOptions();

  if (InputNames.empty()) {
    CompactMode = true;
    return demangleSTDIN(options);
  } else {
    for (llvm::StringRef name : InputNames) {
      demangle(llvm::outs(), name, options);
      llvm::outs() << '\n';
    }

    return EXIT_SUCCESS;
  }
}
<|endoftext|>