<commit_before>/*
 *
 * Copyright 2015 gRPC 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.
 *
 */

#include "src/cpp/client/secure_credentials.h"
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
#include <grpcpp/channel.h>
#include <grpcpp/impl/grpc_library.h>
#include <grpcpp/support/channel_arguments.h>
#include "src/core/lib/security/transport/auth_filters.h"
#include "src/cpp/client/create_channel_internal.h"
#include "src/cpp/common/secure_auth_context.h"

namespace grpc_impl {

static grpc::internal::GrpcLibraryInitializer g_gli_initializer;
SecureChannelCredentials::SecureChannelCredentials(
    grpc_channel_credentials* c_creds)
    : c_creds_(c_creds) {
  g_gli_initializer.summon();
}

std::shared_ptr<grpc::Channel> SecureChannelCredentials::CreateChannelImpl(
    const grpc::string& target, const grpc::ChannelArguments& args) {
  return CreateChannelWithInterceptors(
      target, args,
      std::vector<std::unique_ptr<
          grpc::experimental::ClientInterceptorFactoryInterface>>());
}

std::shared_ptr<grpc::Channel>
SecureChannelCredentials::CreateChannelWithInterceptors(
    const grpc::string& target, const grpc::ChannelArguments& args,
    std::vector<
        std::unique_ptr<grpc::experimental::ClientInterceptorFactoryInterface>>
        interceptor_creators) {
  grpc_channel_args channel_args;
  args.SetChannelArgs(&channel_args);
  return ::grpc::CreateChannelInternal(
      args.GetSslTargetNameOverride(),
      grpc_secure_channel_create(c_creds_, target.c_str(), &channel_args,
                                 nullptr),
      std::move(interceptor_creators));
}

SecureCallCredentials::SecureCallCredentials(grpc_call_credentials* c_creds)
    : c_creds_(c_creds) {
  g_gli_initializer.summon();
}

bool SecureCallCredentials::ApplyToCall(grpc_call* call) {
  return grpc_call_set_credentials(call, c_creds_) == GRPC_CALL_OK;
}

namespace {
std::shared_ptr<ChannelCredentials> WrapChannelCredentials(
    grpc_channel_credentials* creds) {
  return creds == nullptr ? nullptr
                          : std::shared_ptr<ChannelCredentials>(
                                new SecureChannelCredentials(creds));
}

std::shared_ptr<CallCredentials> WrapCallCredentials(
    grpc_call_credentials* creds) {
  return creds == nullptr ? nullptr
                          : std::shared_ptr<CallCredentials>(
                                new SecureCallCredentials(creds));
}
}  // namespace

std::shared_ptr<ChannelCredentials> GoogleDefaultCredentials() {
  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
  return WrapChannelCredentials(grpc_google_default_credentials_create());
}

// Builds SSL Credentials given SSL specific options
std::shared_ptr<ChannelCredentials> SslCredentials(
    const SslCredentialsOptions& options) {
  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
  grpc_ssl_pem_key_cert_pair pem_key_cert_pair = {
      options.pem_private_key.c_str(), options.pem_cert_chain.c_str()};

  grpc_channel_credentials* c_creds = grpc_ssl_credentials_create(
      options.pem_root_certs.empty() ? nullptr : options.pem_root_certs.c_str(),
      options.pem_private_key.empty() ? nullptr : &pem_key_cert_pair, nullptr,
      nullptr);
  return WrapChannelCredentials(c_creds);
}

namespace experimental {

// Builds ALTS Credentials given ALTS specific options
std::shared_ptr<ChannelCredentials> AltsCredentials(
    const AltsCredentialsOptions& options) {
  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
  grpc_alts_credentials_options* c_options =
      grpc_alts_credentials_client_options_create();
  for (auto service_account = options.target_service_accounts.begin();
       service_account != options.target_service_accounts.end();
       service_account++) {
    grpc_alts_credentials_client_options_add_target_service_account(
        c_options, service_account->c_str());
  }
  grpc_channel_credentials* c_creds = grpc_alts_credentials_create(c_options);
  grpc_alts_credentials_options_destroy(c_options);
  return WrapChannelCredentials(c_creds);
}

// Builds Local Credentials
std::shared_ptr<ChannelCredentials> LocalCredentials(
    grpc_local_connect_type type) {
  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
  return WrapChannelCredentials(grpc_local_credentials_create(type));
}

}  // namespace experimental

// Builds credentials for use when running in GCE
std::shared_ptr<CallCredentials> GoogleComputeEngineCredentials() {
  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
  return WrapCallCredentials(
      grpc_google_compute_engine_credentials_create(nullptr));
}

// Builds JWT credentials.
std::shared_ptr<CallCredentials> ServiceAccountJWTAccessCredentials(
    const grpc::string& json_key, long token_lifetime_seconds) {
  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
  if (token_lifetime_seconds <= 0) {
    gpr_log(GPR_ERROR,
            "Trying to create JWTCredentials with non-positive lifetime");
    return WrapCallCredentials(nullptr);
  }
  gpr_timespec lifetime =
      gpr_time_from_seconds(token_lifetime_seconds, GPR_TIMESPAN);
  return WrapCallCredentials(grpc_service_account_jwt_access_credentials_create(
      json_key.c_str(), lifetime, nullptr));
}

// Builds refresh token credentials.
std::shared_ptr<CallCredentials> GoogleRefreshTokenCredentials(
    const grpc::string& json_refresh_token) {
  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
  return WrapCallCredentials(grpc_google_refresh_token_credentials_create(
      json_refresh_token.c_str(), nullptr));
}

// Builds access token credentials.
std::shared_ptr<CallCredentials> AccessTokenCredentials(
    const grpc::string& access_token) {
  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
  return WrapCallCredentials(
      grpc_access_token_credentials_create(access_token.c_str(), nullptr));
}

// Builds IAM credentials.
std::shared_ptr<CallCredentials> GoogleIAMCredentials(
    const grpc::string& authorization_token,
    const grpc::string& authority_selector) {
  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
  return WrapCallCredentials(grpc_google_iam_credentials_create(
      authorization_token.c_str(), authority_selector.c_str(), nullptr));
}

// Combines one channel credentials and one call credentials into a channel
// composite credentials.
std::shared_ptr<ChannelCredentials> CompositeChannelCredentials(
    const std::shared_ptr<ChannelCredentials>& channel_creds,
    const std::shared_ptr<CallCredentials>& call_creds) {
  // Note that we are not saving shared_ptrs to the two credentials passed in
  // here. This is OK because the underlying C objects (i.e., channel_creds and
  // call_creds) into grpc_composite_credentials_create will see their refcounts
  // incremented.
  SecureChannelCredentials* s_channel_creds =
      channel_creds->AsSecureCredentials();
  SecureCallCredentials* s_call_creds = call_creds->AsSecureCredentials();
  if (s_channel_creds && s_call_creds) {
    return WrapChannelCredentials(grpc_composite_channel_credentials_create(
        s_channel_creds->GetRawCreds(), s_call_creds->GetRawCreds(), nullptr));
  }
  return nullptr;
}

std::shared_ptr<CallCredentials> CompositeCallCredentials(
    const std::shared_ptr<CallCredentials>& creds1,
    const std::shared_ptr<CallCredentials>& creds2) {
  SecureCallCredentials* s_creds1 = creds1->AsSecureCredentials();
  SecureCallCredentials* s_creds2 = creds2->AsSecureCredentials();
  if (s_creds1 != nullptr && s_creds2 != nullptr) {
    return WrapCallCredentials(grpc_composite_call_credentials_create(
        s_creds1->GetRawCreds(), s_creds2->GetRawCreds(), nullptr));
  }
  return nullptr;
}

std::shared_ptr<grpc_impl::CallCredentials> MetadataCredentialsFromPlugin(
    std::unique_ptr<MetadataCredentialsPlugin> plugin) {
  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
  const char* type = plugin->GetType();
  grpc::MetadataCredentialsPluginWrapper* wrapper =
      new grpc::MetadataCredentialsPluginWrapper(std::move(plugin));
  grpc_metadata_credentials_plugin c_plugin = {
      grpc::MetadataCredentialsPluginWrapper::GetMetadata,
      grpc::MetadataCredentialsPluginWrapper::Destroy, wrapper, type};
  return WrapCallCredentials(
      grpc_metadata_credentials_create_from_plugin(c_plugin, nullptr));
}

}  // namespace grpc_impl

namespace grpc {

void MetadataCredentialsPluginWrapper::Destroy(void* wrapper) {
  if (wrapper == nullptr) return;
  MetadataCredentialsPluginWrapper* w =
      static_cast<MetadataCredentialsPluginWrapper*>(wrapper);
  delete w;
}

int MetadataCredentialsPluginWrapper::GetMetadata(
    void* wrapper, grpc_auth_metadata_context context,
    grpc_credentials_plugin_metadata_cb cb, void* user_data,
    grpc_metadata creds_md[GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX],
    size_t* num_creds_md, grpc_status_code* status,
    const char** error_details) {
  GPR_ASSERT(wrapper);
  MetadataCredentialsPluginWrapper* w =
      static_cast<MetadataCredentialsPluginWrapper*>(wrapper);
  if (!w->plugin_) {
    *num_creds_md = 0;
    *status = GRPC_STATUS_OK;
    *error_details = nullptr;
    return true;
  }
  if (w->plugin_->IsBlocking()) {
    grpc_auth_metadata_context context_copy = grpc_auth_metadata_context();
    grpc_auth_metadata_context_copy(&context, &context_copy);
    // Asynchronous return.
    w->thread_pool_->Add([w, context_copy, cb, user_data]() mutable {
      w->MetadataCredentialsPluginWrapper::InvokePlugin(
          context_copy, cb, user_data, nullptr, nullptr, nullptr, nullptr);
      grpc_auth_metadata_context_reset(&context_copy);
    });
    return 0;
  } else {
    // Synchronous return.
    w->InvokePlugin(context, cb, user_data, creds_md, num_creds_md, status,
                    error_details);
    return 1;
  }
}

namespace {

void UnrefMetadata(const std::vector<grpc_metadata>& md) {
  for (auto it = md.begin(); it != md.end(); ++it) {
    grpc_slice_unref(it->key);
    grpc_slice_unref(it->value);
  }
}

}  // namespace

void MetadataCredentialsPluginWrapper::InvokePlugin(
    grpc_auth_metadata_context context, grpc_credentials_plugin_metadata_cb cb,
    void* user_data, grpc_metadata creds_md[4], size_t* num_creds_md,
    grpc_status_code* status_code, const char** error_details) {
  std::multimap<grpc::string, grpc::string> metadata;

  // const_cast is safe since the SecureAuthContext only inc/dec the refcount
  // and the object is passed as a const ref to plugin_->GetMetadata.
  SecureAuthContext cpp_channel_auth_context(
      const_cast<grpc_auth_context*>(context.channel_auth_context));

  Status status = plugin_->GetMetadata(context.service_url, context.method_name,
                                       cpp_channel_auth_context, &metadata);
  std::vector<grpc_metadata> md;
  for (auto it = metadata.begin(); it != metadata.end(); ++it) {
    grpc_metadata md_entry;
    md_entry.key = SliceFromCopiedString(it->first);
    md_entry.value = SliceFromCopiedString(it->second);
    md_entry.flags = 0;
    md.push_back(md_entry);
  }
  if (creds_md != nullptr) {
    // Synchronous return.
    if (md.size() > GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX) {
      *num_creds_md = 0;
      *status_code = GRPC_STATUS_INTERNAL;
      *error_details = gpr_strdup(
          "blocking plugin credentials returned too many metadata keys");
      UnrefMetadata(md);
    } else {
      for (const auto& elem : md) {
        creds_md[*num_creds_md].key = elem.key;
        creds_md[*num_creds_md].value = elem.value;
        creds_md[*num_creds_md].flags = elem.flags;
        ++(*num_creds_md);
      }
      *status_code = static_cast<grpc_status_code>(status.error_code());
      *error_details =
          status.ok() ? nullptr : gpr_strdup(status.error_message().c_str());
    }
  } else {
    // Asynchronous return.
    cb(user_data, md.empty() ? nullptr : &md[0], md.size(),
       static_cast<grpc_status_code>(status.error_code()),
       status.error_message().c_str());
    UnrefMetadata(md);
  }
}

MetadataCredentialsPluginWrapper::MetadataCredentialsPluginWrapper(
    std::unique_ptr<MetadataCredentialsPlugin> plugin)
    : thread_pool_(CreateDefaultThreadPool()), plugin_(std::move(plugin)) {}

}  // namespace grpc
<commit_msg>Delete wrapper in executor thread to avoid self-joining deadlock<commit_after>/*
 *
 * Copyright 2015 gRPC 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.
 *
 */

#include "src/cpp/client/secure_credentials.h"
#include <grpc/support/log.h>
#include <grpc/support/string_util.h>
#include <grpcpp/channel.h>
#include <grpcpp/impl/grpc_library.h>
#include <grpcpp/support/channel_arguments.h>
#include "src/core/lib/iomgr/executor.h"
#include "src/core/lib/security/transport/auth_filters.h"
#include "src/cpp/client/create_channel_internal.h"
#include "src/cpp/common/secure_auth_context.h"

namespace grpc_impl {

static grpc::internal::GrpcLibraryInitializer g_gli_initializer;
SecureChannelCredentials::SecureChannelCredentials(
    grpc_channel_credentials* c_creds)
    : c_creds_(c_creds) {
  g_gli_initializer.summon();
}

std::shared_ptr<grpc::Channel> SecureChannelCredentials::CreateChannelImpl(
    const grpc::string& target, const grpc::ChannelArguments& args) {
  return CreateChannelWithInterceptors(
      target, args,
      std::vector<std::unique_ptr<
          grpc::experimental::ClientInterceptorFactoryInterface>>());
}

std::shared_ptr<grpc::Channel>
SecureChannelCredentials::CreateChannelWithInterceptors(
    const grpc::string& target, const grpc::ChannelArguments& args,
    std::vector<
        std::unique_ptr<grpc::experimental::ClientInterceptorFactoryInterface>>
        interceptor_creators) {
  grpc_channel_args channel_args;
  args.SetChannelArgs(&channel_args);
  return ::grpc::CreateChannelInternal(
      args.GetSslTargetNameOverride(),
      grpc_secure_channel_create(c_creds_, target.c_str(), &channel_args,
                                 nullptr),
      std::move(interceptor_creators));
}

SecureCallCredentials::SecureCallCredentials(grpc_call_credentials* c_creds)
    : c_creds_(c_creds) {
  g_gli_initializer.summon();
}

bool SecureCallCredentials::ApplyToCall(grpc_call* call) {
  return grpc_call_set_credentials(call, c_creds_) == GRPC_CALL_OK;
}

namespace {
std::shared_ptr<ChannelCredentials> WrapChannelCredentials(
    grpc_channel_credentials* creds) {
  return creds == nullptr ? nullptr
                          : std::shared_ptr<ChannelCredentials>(
                                new SecureChannelCredentials(creds));
}

std::shared_ptr<CallCredentials> WrapCallCredentials(
    grpc_call_credentials* creds) {
  return creds == nullptr ? nullptr
                          : std::shared_ptr<CallCredentials>(
                                new SecureCallCredentials(creds));
}
}  // namespace

std::shared_ptr<ChannelCredentials> GoogleDefaultCredentials() {
  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
  return WrapChannelCredentials(grpc_google_default_credentials_create());
}

// Builds SSL Credentials given SSL specific options
std::shared_ptr<ChannelCredentials> SslCredentials(
    const SslCredentialsOptions& options) {
  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
  grpc_ssl_pem_key_cert_pair pem_key_cert_pair = {
      options.pem_private_key.c_str(), options.pem_cert_chain.c_str()};

  grpc_channel_credentials* c_creds = grpc_ssl_credentials_create(
      options.pem_root_certs.empty() ? nullptr : options.pem_root_certs.c_str(),
      options.pem_private_key.empty() ? nullptr : &pem_key_cert_pair, nullptr,
      nullptr);
  return WrapChannelCredentials(c_creds);
}

namespace experimental {

// Builds ALTS Credentials given ALTS specific options
std::shared_ptr<ChannelCredentials> AltsCredentials(
    const AltsCredentialsOptions& options) {
  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
  grpc_alts_credentials_options* c_options =
      grpc_alts_credentials_client_options_create();
  for (auto service_account = options.target_service_accounts.begin();
       service_account != options.target_service_accounts.end();
       service_account++) {
    grpc_alts_credentials_client_options_add_target_service_account(
        c_options, service_account->c_str());
  }
  grpc_channel_credentials* c_creds = grpc_alts_credentials_create(c_options);
  grpc_alts_credentials_options_destroy(c_options);
  return WrapChannelCredentials(c_creds);
}

// Builds Local Credentials
std::shared_ptr<ChannelCredentials> LocalCredentials(
    grpc_local_connect_type type) {
  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
  return WrapChannelCredentials(grpc_local_credentials_create(type));
}

}  // namespace experimental

// Builds credentials for use when running in GCE
std::shared_ptr<CallCredentials> GoogleComputeEngineCredentials() {
  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
  return WrapCallCredentials(
      grpc_google_compute_engine_credentials_create(nullptr));
}

// Builds JWT credentials.
std::shared_ptr<CallCredentials> ServiceAccountJWTAccessCredentials(
    const grpc::string& json_key, long token_lifetime_seconds) {
  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
  if (token_lifetime_seconds <= 0) {
    gpr_log(GPR_ERROR,
            "Trying to create JWTCredentials with non-positive lifetime");
    return WrapCallCredentials(nullptr);
  }
  gpr_timespec lifetime =
      gpr_time_from_seconds(token_lifetime_seconds, GPR_TIMESPAN);
  return WrapCallCredentials(grpc_service_account_jwt_access_credentials_create(
      json_key.c_str(), lifetime, nullptr));
}

// Builds refresh token credentials.
std::shared_ptr<CallCredentials> GoogleRefreshTokenCredentials(
    const grpc::string& json_refresh_token) {
  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
  return WrapCallCredentials(grpc_google_refresh_token_credentials_create(
      json_refresh_token.c_str(), nullptr));
}

// Builds access token credentials.
std::shared_ptr<CallCredentials> AccessTokenCredentials(
    const grpc::string& access_token) {
  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
  return WrapCallCredentials(
      grpc_access_token_credentials_create(access_token.c_str(), nullptr));
}

// Builds IAM credentials.
std::shared_ptr<CallCredentials> GoogleIAMCredentials(
    const grpc::string& authorization_token,
    const grpc::string& authority_selector) {
  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
  return WrapCallCredentials(grpc_google_iam_credentials_create(
      authorization_token.c_str(), authority_selector.c_str(), nullptr));
}

// Combines one channel credentials and one call credentials into a channel
// composite credentials.
std::shared_ptr<ChannelCredentials> CompositeChannelCredentials(
    const std::shared_ptr<ChannelCredentials>& channel_creds,
    const std::shared_ptr<CallCredentials>& call_creds) {
  // Note that we are not saving shared_ptrs to the two credentials passed in
  // here. This is OK because the underlying C objects (i.e., channel_creds and
  // call_creds) into grpc_composite_credentials_create will see their refcounts
  // incremented.
  SecureChannelCredentials* s_channel_creds =
      channel_creds->AsSecureCredentials();
  SecureCallCredentials* s_call_creds = call_creds->AsSecureCredentials();
  if (s_channel_creds && s_call_creds) {
    return WrapChannelCredentials(grpc_composite_channel_credentials_create(
        s_channel_creds->GetRawCreds(), s_call_creds->GetRawCreds(), nullptr));
  }
  return nullptr;
}

std::shared_ptr<CallCredentials> CompositeCallCredentials(
    const std::shared_ptr<CallCredentials>& creds1,
    const std::shared_ptr<CallCredentials>& creds2) {
  SecureCallCredentials* s_creds1 = creds1->AsSecureCredentials();
  SecureCallCredentials* s_creds2 = creds2->AsSecureCredentials();
  if (s_creds1 != nullptr && s_creds2 != nullptr) {
    return WrapCallCredentials(grpc_composite_call_credentials_create(
        s_creds1->GetRawCreds(), s_creds2->GetRawCreds(), nullptr));
  }
  return nullptr;
}

std::shared_ptr<grpc_impl::CallCredentials> MetadataCredentialsFromPlugin(
    std::unique_ptr<MetadataCredentialsPlugin> plugin) {
  grpc::GrpcLibraryCodegen init;  // To call grpc_init().
  const char* type = plugin->GetType();
  grpc::MetadataCredentialsPluginWrapper* wrapper =
      new grpc::MetadataCredentialsPluginWrapper(std::move(plugin));
  grpc_metadata_credentials_plugin c_plugin = {
      grpc::MetadataCredentialsPluginWrapper::GetMetadata,
      grpc::MetadataCredentialsPluginWrapper::Destroy, wrapper, type};
  return WrapCallCredentials(
      grpc_metadata_credentials_create_from_plugin(c_plugin, nullptr));
}

}  // namespace grpc_impl

namespace grpc {
namespace {
void DeleteWrapper(void* wrapper, grpc_error* ignored) {
  MetadataCredentialsPluginWrapper* w =
      static_cast<MetadataCredentialsPluginWrapper*>(wrapper);
  delete w;
}
}  // namespace

void MetadataCredentialsPluginWrapper::Destroy(void* wrapper) {
  if (wrapper == nullptr) return;
  grpc_core::ApplicationCallbackExecCtx callback_exec_ctx;
  grpc_core::ExecCtx exec_ctx;
  GRPC_CLOSURE_RUN(GRPC_CLOSURE_CREATE(DeleteWrapper, wrapper,
                                       grpc_core::Executor::Scheduler(
                                           grpc_core::ExecutorJobType::SHORT)),
                   GRPC_ERROR_NONE);
}

int MetadataCredentialsPluginWrapper::GetMetadata(
    void* wrapper, grpc_auth_metadata_context context,
    grpc_credentials_plugin_metadata_cb cb, void* user_data,
    grpc_metadata creds_md[GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX],
    size_t* num_creds_md, grpc_status_code* status,
    const char** error_details) {
  GPR_ASSERT(wrapper);
  MetadataCredentialsPluginWrapper* w =
      static_cast<MetadataCredentialsPluginWrapper*>(wrapper);
  if (!w->plugin_) {
    *num_creds_md = 0;
    *status = GRPC_STATUS_OK;
    *error_details = nullptr;
    return true;
  }
  if (w->plugin_->IsBlocking()) {
    grpc_auth_metadata_context context_copy = grpc_auth_metadata_context();
    grpc_auth_metadata_context_copy(&context, &context_copy);
    // Asynchronous return.
    w->thread_pool_->Add([w, context_copy, cb, user_data]() mutable {
      w->MetadataCredentialsPluginWrapper::InvokePlugin(
          context_copy, cb, user_data, nullptr, nullptr, nullptr, nullptr);
      grpc_auth_metadata_context_reset(&context_copy);
    });
    return 0;
  } else {
    // Synchronous return.
    w->InvokePlugin(context, cb, user_data, creds_md, num_creds_md, status,
                    error_details);
    return 1;
  }
}

namespace {

void UnrefMetadata(const std::vector<grpc_metadata>& md) {
  for (auto it = md.begin(); it != md.end(); ++it) {
    grpc_slice_unref(it->key);
    grpc_slice_unref(it->value);
  }
}

}  // namespace

void MetadataCredentialsPluginWrapper::InvokePlugin(
    grpc_auth_metadata_context context, grpc_credentials_plugin_metadata_cb cb,
    void* user_data, grpc_metadata creds_md[4], size_t* num_creds_md,
    grpc_status_code* status_code, const char** error_details) {
  std::multimap<grpc::string, grpc::string> metadata;

  // const_cast is safe since the SecureAuthContext only inc/dec the refcount
  // and the object is passed as a const ref to plugin_->GetMetadata.
  SecureAuthContext cpp_channel_auth_context(
      const_cast<grpc_auth_context*>(context.channel_auth_context));

  Status status = plugin_->GetMetadata(context.service_url, context.method_name,
                                       cpp_channel_auth_context, &metadata);
  std::vector<grpc_metadata> md;
  for (auto it = metadata.begin(); it != metadata.end(); ++it) {
    grpc_metadata md_entry;
    md_entry.key = SliceFromCopiedString(it->first);
    md_entry.value = SliceFromCopiedString(it->second);
    md_entry.flags = 0;
    md.push_back(md_entry);
  }
  if (creds_md != nullptr) {
    // Synchronous return.
    if (md.size() > GRPC_METADATA_CREDENTIALS_PLUGIN_SYNC_MAX) {
      *num_creds_md = 0;
      *status_code = GRPC_STATUS_INTERNAL;
      *error_details = gpr_strdup(
          "blocking plugin credentials returned too many metadata keys");
      UnrefMetadata(md);
    } else {
      for (const auto& elem : md) {
        creds_md[*num_creds_md].key = elem.key;
        creds_md[*num_creds_md].value = elem.value;
        creds_md[*num_creds_md].flags = elem.flags;
        ++(*num_creds_md);
      }
      *status_code = static_cast<grpc_status_code>(status.error_code());
      *error_details =
          status.ok() ? nullptr : gpr_strdup(status.error_message().c_str());
    }
  } else {
    // Asynchronous return.
    cb(user_data, md.empty() ? nullptr : &md[0], md.size(),
       static_cast<grpc_status_code>(status.error_code()),
       status.error_message().c_str());
    UnrefMetadata(md);
  }
}

MetadataCredentialsPluginWrapper::MetadataCredentialsPluginWrapper(
    std::unique_ptr<MetadataCredentialsPlugin> plugin)
    : thread_pool_(CreateDefaultThreadPool()), plugin_(std::move(plugin)) {}

}  // namespace grpc
<|endoftext|>


<commit_before>// [WriteFile Name=ExportTiles, Category=Layers]
// [Legal]
// Copyright 2016 Esri.

// 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.
// [Legal]

#include "ExportTiles.h"

#include <QUrl>

#include "Map.h"
#include "MapQuickView.h"
#include "Basemap.h"
#include "ExportTiles.h"
#include "ArcGISTiledLayer.h"
#include "ExportTileCacheTask.h"
#include "Envelope.h"
#include "GeometryEngine.h"
#include "SpatialReference.h"
#include "TileCache.h"

using namespace Esri::ArcGISRuntime;

ExportTiles::ExportTiles(QQuickItem* parent) :
    QQuickItem(parent),
    m_map(nullptr),
    m_mapView(nullptr),
    m_serviceUrl("http://sampleserver6.arcgisonline.com/arcgis/rest/services/World_Street_Map/MapServer"),
    m_exportTileCacheTask(nullptr)
{
}

ExportTiles::~ExportTiles()
{
}

void ExportTiles::componentComplete()
{
    QQuickItem::componentComplete();

    // find QML MapView component
    m_mapView = findChild<MapQuickView*>("mapView");

    // create a tiled basemap
    ArcGISTiledLayer* tiledLayer = new ArcGISTiledLayer(m_serviceUrl);
    Basemap* basemap = new Basemap(tiledLayer, this);

    // create a new map instance
    m_map = new Map(basemap, this);

    // set an initial viewpoint
    Envelope env(12362601, 936021, 10187678, 2567213, SpatialReference(3857));
    Viewpoint viewpoint(env);
    m_map->setInitialViewpoint(viewpoint);

    // set map on the map view
    m_mapView->setMap(m_map);

    // create the task from the tiled layer's map service info once it is loaded
    connect(tiledLayer, &ArcGISTiledLayer::doneLoading, [this, tiledLayer](Error)
    {
      m_exportTileCacheTask = new ExportTileCacheTask(tiledLayer->mapServiceInfo(), this);
      connect(m_exportTileCacheTask, &ExportTileCacheTask::doneLoading, [this](Error error)
      {
        if (!error.isEmpty())
        {
          emit updateStatus("ExportTileCacheTask failed to load.");
          emit hideWindow(5000, false);
        }
      });

      m_exportTileCacheTask->load();
    });
}

void ExportTiles::exportTileCacheFromCorners(double xCorner1, double yCorner1, double xCorner2, double yCorner2, QString dataPath)
{
    // create an envelope from the QML rectangle corners
    auto corner1 = m_mapView->screenToLocation(xCorner1, yCorner1);
    auto corner2 = m_mapView->screenToLocation(xCorner2, yCorner2);
    auto extent = Envelope(corner1, corner2);
    auto tileCacheExtent = GeometryEngine::project(extent, SpatialReference::webMercator());

    // generate parameters
    auto params = m_exportTileCacheTask->createDefaultExportTileCacheParameters(tileCacheExtent, m_mapView->mapScale(), m_exportTileCacheTask->mapServiceInfo().maxScale());

    // execute the task and obtain the job
    auto exportJob = m_exportTileCacheTask->exportTileCache(params, dataPath);

    // check if there is a valid job
    if (exportJob)
    {
        // connect to the job's status changed signal
        connect(exportJob, &ExportTileCacheJob::jobStatusChanged, [this, exportJob]()
        {
            // connect to the job's status changed signal to know once it is done
            switch (exportJob->jobStatus()) {
            case JobStatus::Failed:
                emit updateStatus("Export failed");
                emit hideWindow(5000, false);
                break;
            case JobStatus::NotStarted:
                emit updateStatus("Job not started");
                break;
            case JobStatus::Paused:
                emit updateStatus("Job paused");
                break;
            case JobStatus::Started:
                emit updateStatus("In progress...");
                break;
            case JobStatus::Succeeded:
                emit updateStatus("Adding TPK...");
                emit hideWindow(1500, true);
                displayOutputTileCache(exportJob->result());
                break;
            default:
                break;
            }
        });

        // start the export job
        exportJob->start();
    }
    else
    {
        emit updateStatus("Export failed");
        emit hideWindow(5000, false);
    }
}

// display the tile cache once the task is complete
void ExportTiles::displayOutputTileCache(TileCache* tileCache)
{
    // create a new tiled layer from the output tile cache
    auto tiledLayer = new ArcGISTiledLayer(tileCache, this);

    // add the new layer to a basemap
    auto basemap = new Basemap(tiledLayer, this);

    // set the new basemap on the map
    m_map->setBasemap(basemap);

    // zoom to the new layer and hide window once loaded
    connect(tiledLayer, &ArcGISTiledLayer::doneLoading, [this, tiledLayer]()
    {
        if (tiledLayer->loadStatus() == LoadStatus::Loaded)
        {
            m_mapView->setViewpointScale(m_mapView->mapScale() * 0.5);
        }
    });
}
<commit_msg>Don't need to load the ExportTIleCacheTask since its being constructed from service info<commit_after>// [WriteFile Name=ExportTiles, Category=Layers]
// [Legal]
// Copyright 2016 Esri.

// 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.
// [Legal]

#include "ExportTiles.h"

#include <QUrl>

#include "Map.h"
#include "MapQuickView.h"
#include "Basemap.h"
#include "ExportTiles.h"
#include "ArcGISTiledLayer.h"
#include "ExportTileCacheTask.h"
#include "Envelope.h"
#include "GeometryEngine.h"
#include "SpatialReference.h"
#include "TileCache.h"

using namespace Esri::ArcGISRuntime;

ExportTiles::ExportTiles(QQuickItem* parent) :
    QQuickItem(parent),
    m_map(nullptr),
    m_mapView(nullptr),
    m_serviceUrl("http://sampleserver6.arcgisonline.com/arcgis/rest/services/World_Street_Map/MapServer"),
    m_exportTileCacheTask(nullptr)
{
}

ExportTiles::~ExportTiles()
{
}

void ExportTiles::componentComplete()
{
    QQuickItem::componentComplete();

    // find QML MapView component
    m_mapView = findChild<MapQuickView*>("mapView");

    // create a tiled basemap
    ArcGISTiledLayer* tiledLayer = new ArcGISTiledLayer(m_serviceUrl);
    Basemap* basemap = new Basemap(tiledLayer, this);

    // create a new map instance
    m_map = new Map(basemap, this);

    // set an initial viewpoint
    Envelope env(12362601, 936021, 10187678, 2567213, SpatialReference(3857));
    Viewpoint viewpoint(env);
    m_map->setInitialViewpoint(viewpoint);

    // set map on the map view
    m_mapView->setMap(m_map);

    // create the task from the tiled layer's map service info once it is loaded
    connect(tiledLayer, &ArcGISTiledLayer::doneLoading, [this, tiledLayer](Error)
    {
      m_exportTileCacheTask = new ExportTileCacheTask(tiledLayer->mapServiceInfo(), this);
    });
}

void ExportTiles::exportTileCacheFromCorners(double xCorner1, double yCorner1, double xCorner2, double yCorner2, QString dataPath)
{
    // create an envelope from the QML rectangle corners
    auto corner1 = m_mapView->screenToLocation(xCorner1, yCorner1);
    auto corner2 = m_mapView->screenToLocation(xCorner2, yCorner2);
    auto extent = Envelope(corner1, corner2);
    auto tileCacheExtent = GeometryEngine::project(extent, SpatialReference::webMercator());

    // generate parameters
    auto params = m_exportTileCacheTask->createDefaultExportTileCacheParameters(tileCacheExtent, m_mapView->mapScale(), m_exportTileCacheTask->mapServiceInfo().maxScale());

    // execute the task and obtain the job
    auto exportJob = m_exportTileCacheTask->exportTileCache(params, dataPath);

    // check if there is a valid job
    if (exportJob)
    {
        // connect to the job's status changed signal
        connect(exportJob, &ExportTileCacheJob::jobStatusChanged, [this, exportJob]()
        {
            // connect to the job's status changed signal to know once it is done
            switch (exportJob->jobStatus()) {
            case JobStatus::Failed:
                emit updateStatus("Export failed");
                emit hideWindow(5000, false);
                break;
            case JobStatus::NotStarted:
                emit updateStatus("Job not started");
                break;
            case JobStatus::Paused:
                emit updateStatus("Job paused");
                break;
            case JobStatus::Started:
                emit updateStatus("In progress...");
                break;
            case JobStatus::Succeeded:
                emit updateStatus("Adding TPK...");
                emit hideWindow(1500, true);
                displayOutputTileCache(exportJob->result());
                break;
            default:
                break;
            }
        });

        // start the export job
        exportJob->start();
    }
    else
    {
        emit updateStatus("Export failed");
        emit hideWindow(5000, false);
    }
}

// display the tile cache once the task is complete
void ExportTiles::displayOutputTileCache(TileCache* tileCache)
{
    // create a new tiled layer from the output tile cache
    auto tiledLayer = new ArcGISTiledLayer(tileCache, this);

    // add the new layer to a basemap
    auto basemap = new Basemap(tiledLayer, this);

    // set the new basemap on the map
    m_map->setBasemap(basemap);

    // zoom to the new layer and hide window once loaded
    connect(tiledLayer, &ArcGISTiledLayer::doneLoading, [this, tiledLayer]()
    {
        if (tiledLayer->loadStatus() == LoadStatus::Loaded)
        {
            m_mapView->setViewpointScale(m_mapView->mapScale() * 0.5);
        }
    });
}
<|endoftext|>


<commit_before>/*=========================================================================

  Library:   CTK

  Copyright (c) Kitware 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.txt

  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.

=========================================================================*/

// ctkDICOMWidget includes
#include "ctkDICOMTableView.h"
#include "ui_ctkDICOMTableView.h"

// Qt includes
#include <QSortFilterProxyModel>
#include <QSqlQueryModel>

//------------------------------------------------------------------------------
class ctkDICOMTableViewPrivate : public Ui_ctkDICOMTableView
{
  Q_DECLARE_PUBLIC (ctkDICOMTableView)

protected:
  ctkDICOMTableView* const q_ptr;

public:
  ctkDICOMTableViewPrivate(ctkDICOMTableView& obj);
  ctkDICOMTableViewPrivate(ctkDICOMTableView& obj, ctkDICOMDatabase* db);
  ~ctkDICOMTableViewPrivate();
  // Initialize UI
  void init();
  // Setup tableview with tablemodel if database is available
  void setUpTableView();
  //Temporay solution to hide UID columns
  void hideUIDColumns();

  QString queryTableName() const;

  ctkDICOMDatabase* dicomDatabase;
  QSqlQueryModel dicomSQLModel;
  QSortFilterProxyModel* dicomSQLFilterModel;
  QString queryForeignKey;

  QStringList currentSelection;
  //Key = QString for columns, Values = QStringList
  QHash<QString, QStringList> sqlWhereConditions;

};

//------------------------------------------------------------------------------
ctkDICOMTableViewPrivate::ctkDICOMTableViewPrivate(ctkDICOMTableView &obj)
  : q_ptr(&obj)
{
  this->dicomSQLFilterModel = new QSortFilterProxyModel(&obj);
  this->dicomDatabase = new ctkDICOMDatabase(&obj);
}

//------------------------------------------------------------------------------
ctkDICOMTableViewPrivate::ctkDICOMTableViewPrivate(ctkDICOMTableView &obj, ctkDICOMDatabase* db)
  : q_ptr(&obj)
  , dicomDatabase(db)
{
  this->dicomSQLFilterModel = new QSortFilterProxyModel(&obj);
}

//------------------------------------------------------------------------------
ctkDICOMTableViewPrivate::~ctkDICOMTableViewPrivate()
{
}

void ctkDICOMTableViewPrivate::init()
{
  Q_Q(ctkDICOMTableView);
  this->setupUi(q);

  this->tblDicomDatabaseView->horizontalHeader()->setResizeMode(QHeaderView::ResizeToContents);

  this->leSearchBox->setAlwaysShowClearIcon(true);
  this->leSearchBox->setShowSearchIcon(true);

  if (this->dicomDatabase != 0)
    {
      this->setUpTableView();
    }
}

//------------------------------------------------------------------------------
void ctkDICOMTableViewPrivate::setUpTableView()
{
  Q_Q(ctkDICOMTableView);
  if (this->dicomDatabase != 0)
    {
      q->setQuery();
      this->dicomSQLFilterModel->setSourceModel(&this->dicomSQLModel);
      this->dicomSQLFilterModel->setFilterKeyColumn(-1);
      this->dicomSQLFilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
      this->tblDicomDatabaseView->setModel(this->dicomSQLFilterModel);
      this->tblDicomDatabaseView->setColumnHidden(0, true);
      this->tblDicomDatabaseView->setSortingEnabled(true);
      this->tblDicomDatabaseView->horizontalHeader()->setResizeMode(QHeaderView::Interactive);
      this->tblDicomDatabaseView->verticalHeader()->setResizeMode(QHeaderView::ResizeToContents);
      this->hideUIDColumns();

      QObject::connect(this->tblDicomDatabaseView->selectionModel(),
                       SIGNAL(selectionChanged(const QItemSelection&,const QItemSelection&)),
                       q, SLOT(onSelectionChanged()));

      QObject::connect(this->tblDicomDatabaseView->selectionModel(),
                       SIGNAL(selectionChanged(const QItemSelection&,const QItemSelection&)),
                       q, SIGNAL(selectionChanged(const QItemSelection&,const QItemSelection&)));

      QObject::connect(this->tblDicomDatabaseView, SIGNAL(doubleClicked(const QModelIndex&)),
                       q, SIGNAL(doubleClicked(const QModelIndex&)));

      QObject::connect(this->leSearchBox, SIGNAL(textChanged(QString)),
                       this->dicomSQLFilterModel, SLOT(setFilterWildcard(QString)));

      QObject::connect(this->leSearchBox, SIGNAL(textChanged(QString)), q, SLOT(onFilterChanged()));

      QObject::connect(this->dicomDatabase, SIGNAL(schemaUpdated()), q, SLOT(onDatabaseChanged()));

      QObject::connect(this->dicomDatabase, SIGNAL(databaseChanged()), q, SLOT(onDatabaseChanged()));
    }
}

//------------------------------------------------------------------------------
//Temporay solution to hide UID columns
void ctkDICOMTableViewPrivate::hideUIDColumns()
{
  const int numberOfColumns = this->tblDicomDatabaseView->model()->columnCount();
  for (int i = 0; i < numberOfColumns; ++i)
    {
    QString columnName = this->tblDicomDatabaseView->model()->headerData(i, Qt::Horizontal).toString();
    if (columnName.contains("UID"))
      {
        this->tblDicomDatabaseView->hideColumn(i);
      }
    }
}

//----------------------------------------------------------------------------
QString ctkDICOMTableViewPrivate::queryTableName() const
{
  return this->lblTableName->text();
}

//----------------------------------------------------------------------------
// ctkDICOMTableView methods

//----------------------------------------------------------------------------
ctkDICOMTableView::ctkDICOMTableView(QWidget *parent)
  : Superclass(parent)
  , d_ptr(new ctkDICOMTableViewPrivate(*this))
{
  Q_D(ctkDICOMTableView);
  d->init();
}

//----------------------------------------------------------------------------
ctkDICOMTableView::ctkDICOMTableView(QString queryTableName, QWidget *parent)
  : Superclass(parent)
  , d_ptr(new ctkDICOMTableViewPrivate(*this))
{
  Q_D(ctkDICOMTableView);
  d->init();
  this->setQueryTableName(queryTableName);
  d->lblTableName->setText(queryTableName);
}

//------------------------------------------------------------------------------
ctkDICOMTableView::ctkDICOMTableView (ctkDICOMDatabase* dicomDataBase, QString queryTableName, QWidget* parent)
  : Superclass(parent)
  , d_ptr(new ctkDICOMTableViewPrivate(*this))
{
  this->setDicomDataBase(dicomDataBase);
  Q_D(ctkDICOMTableView);
  this->setQueryTableName(queryTableName);
  d->init();
}

//------------------------------------------------------------------------------
ctkDICOMTableView::~ctkDICOMTableView()
{
}

//------------------------------------------------------------------------------
void ctkDICOMTableView::setDicomDataBase(ctkDICOMDatabase *dicomDatabase)
{
  Q_D(ctkDICOMTableView);
  d->dicomDatabase = dicomDatabase;
  //Create connections for new database
  QObject::connect(d->dicomDatabase, SIGNAL(schemaUpdated()), this, SLOT(onDatabaseChanged()));
  QObject::connect(d->dicomDatabase, SIGNAL(databaseChanged()), this, SLOT(onDatabaseChanged()));
  this->setQuery();
  d->hideUIDColumns();
}

//------------------------------------------------------------------------------
void ctkDICOMTableView::setQueryTableName(const QString &tableName)
{
  Q_D(ctkDICOMTableView);
  d->lblTableName->setText(tableName);
}

//------------------------------------------------------------------------------
void ctkDICOMTableView::setQueryForeignKey(const QString &foreignKey)
{
  Q_D(ctkDICOMTableView);
  d->queryForeignKey = foreignKey;
}

//------------------------------------------------------------------------------
void ctkDICOMTableView::onSelectionChanged()
{
  emit selectionChanged(currentSelection());
}

//------------------------------------------------------------------------------
void ctkDICOMTableView::onDatabaseChanged()
{
  Q_D(ctkDICOMTableView);
  setQuery();
}

//------------------------------------------------------------------------------
void ctkDICOMTableView::onUpdateQuery(const QStringList& uids)
{
  Q_D(ctkDICOMTableView);

  setQuery(uids);

  const QStringList& newUIDS = this->uidsForAllRows();
  emit queryChanged(newUIDS);
}

//------------------------------------------------------------------------------
void ctkDICOMTableView::onFilterChanged()
{
  Q_D(ctkDICOMTableView);

  const QStringList uids = this->uidsForAllRows();
  d->tblDicomDatabaseView->clearSelection();
  emit queryChanged(uids);
}

//------------------------------------------------------------------------------
void ctkDICOMTableView::setQuery(const QStringList &uids)
{
  Q_D(ctkDICOMTableView);
  QString query = ("select distinct %1.* from Patients, Series, Studies where "
                   "Patients.UID = Studies.PatientsUID and Studies.StudyInstanceUID = Series.StudyInstanceUID");

  if (!uids.empty() && d->queryForeignKey.length() != 0)
    {
      query += " and %1."+d->queryForeignKey+" in ( '";
      query.append(uids.join("','")).append("')");
    }
  if (!d->sqlWhereConditions.empty())
    {
      QHash<QString, QStringList>::const_iterator i = d->sqlWhereConditions.begin();
      while (i != d->sqlWhereConditions.end())
        {
          if (!i.value().empty())
            {
              query += " and "+i.key()+" in ( '";
              query.append(i.value().join("','")).append("')");
            }
          ++i;
        }
    }
  if (d->dicomDatabase != 0)
    d->dicomSQLModel.setQuery(query.arg(d->queryTableName()), d->dicomDatabase->database());
}

void ctkDICOMTableView::addSqlWhereCondition(const std::pair<QString, QStringList> &condition)
{
  Q_D(ctkDICOMTableView);
  d->sqlWhereConditions.insert(condition.first, condition.second);
}

//------------------------------------------------------------------------------
QStringList ctkDICOMTableView::uidsForAllRows() const
{
  Q_D(const ctkDICOMTableView);
  QAbstractItemModel* tableModel = d->tblDicomDatabaseView->model();
  int numberOfRows = tableModel->rowCount();
  QStringList uids;
  if (numberOfRows == 0)
    {
      //Return invalid UID if there are no rows
      uids << QString("#");
    }
  else
    {
      for(int i = 0; i < numberOfRows; ++i)
        {
          uids << QString("%1").arg(tableModel->index(i,0).data().toString());
        }
    }
  return uids;
}

//------------------------------------------------------------------------------
QStringList ctkDICOMTableView::currentSelection() const
{
  Q_D(const ctkDICOMTableView);

  QModelIndexList currentSelection = d->tblDicomDatabaseView->selectionModel()->selectedRows(0);
  QStringList uids;

  foreach(QModelIndex i, currentSelection)
    {
      uids<< i.data().toString();
    }

  return  uids;
}

//------------------------------------------------------------------------------
bool ctkDICOMTableView::filterActive()
{
  Q_D(ctkDICOMTableView);
  return (d->leSearchBox->text().length() != 0);
}
<commit_msg>Changed vertical header resize mode so that all rows have the same height<commit_after>/*=========================================================================

  Library:   CTK

  Copyright (c) Kitware 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.txt

  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.

=========================================================================*/

// ctkDICOMWidget includes
#include "ctkDICOMTableView.h"
#include "ui_ctkDICOMTableView.h"

// Qt includes
#include <QSortFilterProxyModel>
#include <QSqlQueryModel>

//------------------------------------------------------------------------------
class ctkDICOMTableViewPrivate : public Ui_ctkDICOMTableView
{
  Q_DECLARE_PUBLIC (ctkDICOMTableView)

protected:
  ctkDICOMTableView* const q_ptr;

public:
  ctkDICOMTableViewPrivate(ctkDICOMTableView& obj);
  ctkDICOMTableViewPrivate(ctkDICOMTableView& obj, ctkDICOMDatabase* db);
  ~ctkDICOMTableViewPrivate();
  // Initialize UI
  void init();
  // Setup tableview with tablemodel if database is available
  void setUpTableView();
  //Temporay solution to hide UID columns
  void hideUIDColumns();

  QString queryTableName() const;

  ctkDICOMDatabase* dicomDatabase;
  QSqlQueryModel dicomSQLModel;
  QSortFilterProxyModel* dicomSQLFilterModel;
  QString queryForeignKey;

  QStringList currentSelection;
  //Key = QString for columns, Values = QStringList
  QHash<QString, QStringList> sqlWhereConditions;

};

//------------------------------------------------------------------------------
ctkDICOMTableViewPrivate::ctkDICOMTableViewPrivate(ctkDICOMTableView &obj)
  : q_ptr(&obj)
{
  this->dicomSQLFilterModel = new QSortFilterProxyModel(&obj);
  this->dicomDatabase = new ctkDICOMDatabase(&obj);
}

//------------------------------------------------------------------------------
ctkDICOMTableViewPrivate::ctkDICOMTableViewPrivate(ctkDICOMTableView &obj, ctkDICOMDatabase* db)
  : q_ptr(&obj)
  , dicomDatabase(db)
{
  this->dicomSQLFilterModel = new QSortFilterProxyModel(&obj);
}

//------------------------------------------------------------------------------
ctkDICOMTableViewPrivate::~ctkDICOMTableViewPrivate()
{
}

void ctkDICOMTableViewPrivate::init()
{
  Q_Q(ctkDICOMTableView);
  this->setupUi(q);

  this->leSearchBox->setAlwaysShowClearIcon(true);
  this->leSearchBox->setShowSearchIcon(true);

  if (this->dicomDatabase != 0)
    {
      this->setUpTableView();
    }
}

//------------------------------------------------------------------------------
void ctkDICOMTableViewPrivate::setUpTableView()
{
  Q_Q(ctkDICOMTableView);
  if (this->dicomDatabase != 0)
    {
      q->setQuery();
      this->dicomSQLFilterModel->setSourceModel(&this->dicomSQLModel);
      this->dicomSQLFilterModel->setFilterKeyColumn(-1);
      this->dicomSQLFilterModel->setFilterCaseSensitivity(Qt::CaseInsensitive);
      this->tblDicomDatabaseView->setModel(this->dicomSQLFilterModel);
      this->tblDicomDatabaseView->setColumnHidden(0, true);
      this->tblDicomDatabaseView->setSortingEnabled(true);
      this->tblDicomDatabaseView->horizontalHeader()->setResizeMode(QHeaderView::Interactive);
      this->hideUIDColumns();

      QObject::connect(this->tblDicomDatabaseView->selectionModel(),
                       SIGNAL(selectionChanged(const QItemSelection&,const QItemSelection&)),
                       q, SLOT(onSelectionChanged()));

      QObject::connect(this->tblDicomDatabaseView->selectionModel(),
                       SIGNAL(selectionChanged(const QItemSelection&,const QItemSelection&)),
                       q, SIGNAL(selectionChanged(const QItemSelection&,const QItemSelection&)));

      QObject::connect(this->tblDicomDatabaseView, SIGNAL(doubleClicked(const QModelIndex&)),
                       q, SIGNAL(doubleClicked(const QModelIndex&)));

      QObject::connect(this->leSearchBox, SIGNAL(textChanged(QString)),
                       this->dicomSQLFilterModel, SLOT(setFilterWildcard(QString)));

      QObject::connect(this->leSearchBox, SIGNAL(textChanged(QString)), q, SLOT(onFilterChanged()));

      QObject::connect(this->dicomDatabase, SIGNAL(schemaUpdated()), q, SLOT(onDatabaseChanged()));

      QObject::connect(this->dicomDatabase, SIGNAL(databaseChanged()), q, SLOT(onDatabaseChanged()));
    }
}

//------------------------------------------------------------------------------
//Temporay solution to hide UID columns
void ctkDICOMTableViewPrivate::hideUIDColumns()
{
  const int numberOfColumns = this->tblDicomDatabaseView->model()->columnCount();
  for (int i = 0; i < numberOfColumns; ++i)
    {
    QString columnName = this->tblDicomDatabaseView->model()->headerData(i, Qt::Horizontal).toString();
    if (columnName.contains("UID"))
      {
        this->tblDicomDatabaseView->hideColumn(i);
      }
    }
}

//----------------------------------------------------------------------------
QString ctkDICOMTableViewPrivate::queryTableName() const
{
  return this->lblTableName->text();
}

//----------------------------------------------------------------------------
// ctkDICOMTableView methods

//----------------------------------------------------------------------------
ctkDICOMTableView::ctkDICOMTableView(QWidget *parent)
  : Superclass(parent)
  , d_ptr(new ctkDICOMTableViewPrivate(*this))
{
  Q_D(ctkDICOMTableView);
  d->init();
}

//----------------------------------------------------------------------------
ctkDICOMTableView::ctkDICOMTableView(QString queryTableName, QWidget *parent)
  : Superclass(parent)
  , d_ptr(new ctkDICOMTableViewPrivate(*this))
{
  Q_D(ctkDICOMTableView);
  d->init();
  this->setQueryTableName(queryTableName);
  d->lblTableName->setText(queryTableName);
}

//------------------------------------------------------------------------------
ctkDICOMTableView::ctkDICOMTableView (ctkDICOMDatabase* dicomDataBase, QString queryTableName, QWidget* parent)
  : Superclass(parent)
  , d_ptr(new ctkDICOMTableViewPrivate(*this))
{
  this->setDicomDataBase(dicomDataBase);
  Q_D(ctkDICOMTableView);
  this->setQueryTableName(queryTableName);
  d->init();
}

//------------------------------------------------------------------------------
ctkDICOMTableView::~ctkDICOMTableView()
{
}

//------------------------------------------------------------------------------
void ctkDICOMTableView::setDicomDataBase(ctkDICOMDatabase *dicomDatabase)
{
  Q_D(ctkDICOMTableView);
  d->dicomDatabase = dicomDatabase;
  //Create connections for new database
  QObject::connect(d->dicomDatabase, SIGNAL(schemaUpdated()), this, SLOT(onDatabaseChanged()));
  QObject::connect(d->dicomDatabase, SIGNAL(databaseChanged()), this, SLOT(onDatabaseChanged()));
  this->setQuery();
  d->hideUIDColumns();
}

//------------------------------------------------------------------------------
void ctkDICOMTableView::setQueryTableName(const QString &tableName)
{
  Q_D(ctkDICOMTableView);
  d->lblTableName->setText(tableName);
}

//------------------------------------------------------------------------------
void ctkDICOMTableView::setQueryForeignKey(const QString &foreignKey)
{
  Q_D(ctkDICOMTableView);
  d->queryForeignKey = foreignKey;
}

//------------------------------------------------------------------------------
void ctkDICOMTableView::onSelectionChanged()
{
  emit selectionChanged(currentSelection());
}

//------------------------------------------------------------------------------
void ctkDICOMTableView::onDatabaseChanged()
{
  Q_D(ctkDICOMTableView);
  setQuery();
}

//------------------------------------------------------------------------------
void ctkDICOMTableView::onUpdateQuery(const QStringList& uids)
{
  Q_D(ctkDICOMTableView);

  setQuery(uids);

  const QStringList& newUIDS = this->uidsForAllRows();
  emit queryChanged(newUIDS);
}

//------------------------------------------------------------------------------
void ctkDICOMTableView::onFilterChanged()
{
  Q_D(ctkDICOMTableView);

  const QStringList uids = this->uidsForAllRows();
  d->tblDicomDatabaseView->clearSelection();
  emit queryChanged(uids);
}

//------------------------------------------------------------------------------
void ctkDICOMTableView::setQuery(const QStringList &uids)
{
  Q_D(ctkDICOMTableView);
  QString query = ("select distinct %1.* from Patients, Series, Studies where "
                   "Patients.UID = Studies.PatientsUID and Studies.StudyInstanceUID = Series.StudyInstanceUID");

  if (!uids.empty() && d->queryForeignKey.length() != 0)
    {
      query += " and %1."+d->queryForeignKey+" in ( '";
      query.append(uids.join("','")).append("')");
    }
  if (!d->sqlWhereConditions.empty())
    {
      QHash<QString, QStringList>::const_iterator i = d->sqlWhereConditions.begin();
      while (i != d->sqlWhereConditions.end())
        {
          if (!i.value().empty())
            {
              query += " and "+i.key()+" in ( '";
              query.append(i.value().join("','")).append("')");
            }
          ++i;
        }
    }
  if (d->dicomDatabase != 0)
    d->dicomSQLModel.setQuery(query.arg(d->queryTableName()), d->dicomDatabase->database());
}

void ctkDICOMTableView::addSqlWhereCondition(const std::pair<QString, QStringList> &condition)
{
  Q_D(ctkDICOMTableView);
  d->sqlWhereConditions.insert(condition.first, condition.second);
}

//------------------------------------------------------------------------------
QStringList ctkDICOMTableView::uidsForAllRows() const
{
  Q_D(const ctkDICOMTableView);
  QAbstractItemModel* tableModel = d->tblDicomDatabaseView->model();
  int numberOfRows = tableModel->rowCount();
  QStringList uids;
  if (numberOfRows == 0)
    {
      //Return invalid UID if there are no rows
      uids << QString("#");
    }
  else
    {
      for(int i = 0; i < numberOfRows; ++i)
        {
          uids << QString("%1").arg(tableModel->index(i,0).data().toString());
        }
    }
  return uids;
}

//------------------------------------------------------------------------------
QStringList ctkDICOMTableView::currentSelection() const
{
  Q_D(const ctkDICOMTableView);

  QModelIndexList currentSelection = d->tblDicomDatabaseView->selectionModel()->selectedRows(0);
  QStringList uids;

  foreach(QModelIndex i, currentSelection)
    {
      uids<< i.data().toString();
    }

  return  uids;
}

//------------------------------------------------------------------------------
bool ctkDICOMTableView::filterActive()
{
  Q_D(ctkDICOMTableView);
  return (d->leSearchBox->text().length() != 0);
}
<|endoftext|>


<commit_before>/****************************************************************************

This file is part of the QtMediaHub project on http://www.gitorious.org.

Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).*
All rights reserved.

Contact:  Nokia Corporation (qt-info@nokia.com)**

You may use this file under the terms of the BSD license as follows:

"Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."

****************************************************************************/

#include "backend.h"
#include "plugins/qmhplugininterface.h"
#include "plugins/qmhplugin.h"
#include "dataproviders/proxymodel.h"
#include "dataproviders/dirmodel.h"
#include "dataproviders/modelindexiterator.h"
#include "qmh-config.h"
#include "qml-extensions/qmlfilewrapper.h"
#include "qml-extensions/actionmapper.h"
#include "dataproviders/playlist.h"

#include <QDir>
#include <QString>
#include <QPluginLoader>
#include <QCoreApplication>
#include <QVariant>
#include <QFileSystemModel>
#include <QDesktopServices>
#include <QFileSystemWatcher>

#ifdef GL
#include <QGLFormat>
#endif

#include <QDebug>

Backend* Backend::pSelf = 0;

class BackendPrivate : public QObject
{
    Q_OBJECT
public:
    BackendPrivate(Backend *p)
        : QObject(p),
      #ifdef Q_OS_MAC
          platformOffset("/../../.."),
      #endif
          basePath(QCoreApplication::applicationDirPath() + platformOffset),
          skinPath(basePath % "/skins"),
          pluginPath(basePath % "/plugins"),
          resourcePath(basePath % "/resources"),
          // Use "large" instead of appName to fit freedesktop spec
          thumbnailPath(Config::value("thumbnail-path", QDir::homePath() + "/.thumbnails/" + qApp->applicationName() + "/")),
          inputIdleTimer(this),
          qmlEngine(0),
          backendTranslator(0),
          logFile(qApp->applicationName().append(".log")),
          pSelf(p)
    {
        inputIdleTimer.setInterval(Config::value("idle-timeout", 120)*1000);
        inputIdleTimer.setSingleShot(true);
        inputIdleTimer.start();

        connect(&inputIdleTimer, SIGNAL(timeout()), pSelf, SIGNAL(inputIdle()));

        logFile.open(QIODevice::Text|QIODevice::ReadWrite);
        log.setDevice(&logFile);

        connect(&resourcePathMonitor,
                SIGNAL(directoryChanged(const QString &)),
                this,
                SLOT(handleDirChanged(const QString &)));

        resourcePathMonitor.addPath(skinPath);
        resourcePathMonitor.addPath(pluginPath);

        QFileInfo thumbnailFolderInfo(thumbnailPath);
        if (!thumbnailFolderInfo.exists()) {
            QDir dir;
            dir.mkpath(thumbnailFolderInfo.absoluteFilePath());
        }

        discoverSkins();
    }

    ~BackendPrivate()
    {
        delete backendTranslator;
        backendTranslator = 0;
        qDeleteAll(pluginTranslators.begin(), pluginTranslators.end());
    }
public slots:
    void handleDirChanged(const QString &dir);
public:
    void resetLanguage();
    void discoverSkins();
    void discoverEngines();

    QSet<QString> advertizedEngineRoles;

    QList<QObject*> advertizedEngines;

    const QString platformOffset;

    const QString basePath;
    const QString skinPath;
    const QString pluginPath;
    const QString resourcePath;
    const QString thumbnailPath;

    QTimer inputIdleTimer;
    QDeclarativeEngine *qmlEngine;
    QTranslator *backendTranslator;
    QList<QTranslator*> pluginTranslators;
    QFile logFile;
    QTextStream log;
    QFileSystemWatcher resourcePathMonitor;
    QStringList skins;

    Backend *pSelf;
};

void BackendPrivate::handleDirChanged(const QString &dir)
{
    if(dir == pluginPath) {
        qWarning() << "Changes in plugin path, probably about to eat your poodle";
        discoverEngines();
    } else if(dir == skinPath) {
        qWarning() << "Changes in skin path, repopulating skins";
        discoverSkins();
    }
}

void BackendPrivate::resetLanguage()
{
    static QString baseTranslationPath(basePath % "/translations/");
    const QString language = Backend::instance()->language();
    delete backendTranslator;
    backendTranslator = new QTranslator(this);
    backendTranslator->load(baseTranslationPath % language % ".qm");
    qApp->installTranslator(backendTranslator);

    qDeleteAll(pluginTranslators.begin(), pluginTranslators.end());

    foreach(QObject *pluginObject, advertizedEngines) {
        QMHPlugin *plugin = qobject_cast<QMHPlugin*>(pluginObject);
        QTranslator *pluginTranslator = new QTranslator(this);
        pluginTranslator->load(baseTranslationPath % plugin->role() % "_" % language % ".qm");
        pluginTranslators << pluginTranslator;
        qApp->installTranslator(pluginTranslator);
    }
}

void BackendPrivate::discoverSkins()
{
    skins.clear();

    QStringList potentialSkins = QDir(skinPath).entryList(QDir::Dirs | QDir::NoDotAndDotDot);

    foreach(const QString &currentPath, potentialSkins)
        if(QFile(skinPath % "/" % currentPath % "/" % currentPath).exists())
            skins << currentPath;

    qWarning() << "Available skins" << skins;
}

void BackendPrivate::discoverEngines()
{
    foreach(const QString fileName, QDir(pluginPath).entryList(QDir::Files)) {
        QString qualifiedFileName(pluginPath % "/" % fileName);
        QPluginLoader pluginLoader(qualifiedFileName);

        if (!pluginLoader.load()) {
            qWarning() << tr("Cant load plugin: %1 returns %2").arg(qualifiedFileName).arg(pluginLoader.errorString());
            continue;
        }

        QMHPluginInterface* pluginInterface = qobject_cast<QMHPluginInterface*>(pluginLoader.instance());
        if (!pluginInterface)
            qWarning() << tr("Invalid QMH plugin present: %1").arg(qualifiedFileName);
        else if (!pluginInterface->dependenciesSatisfied())
            qWarning() << tr("Can't meet dependencies for %1").arg(qualifiedFileName);
        else if (pluginInterface->role() == "undefined")
            qWarning() << tr("Plugin %1 has an undefined role").arg(qualifiedFileName);
        else {
            QMHPlugin *plugin = new QMHPlugin(qobject_cast<QMHPluginInterface*>(pluginLoader.instance()), this);
            plugin->setParent(this);
            if(qmlEngine)
                plugin->registerPlugin(qmlEngine->rootContext());
            Backend::instance()->advertizeEngine(plugin);
        }
    }
    resetLanguage();
}

Backend::Backend(QObject *parent)
    : QObject(parent),
      d(new BackendPrivate(this))
{
    QFontDatabase::addApplicationFont(d->resourcePath + "/dejavu-fonts-ttf-2.32/ttf/DejaVuSans.ttf");
    QFontDatabase::addApplicationFont(d->resourcePath + "/dejavu-fonts-ttf-2.32/ttf/DejaVuSans-Bold.ttf");
    QFontDatabase::addApplicationFont(d->resourcePath + "/dejavu-fonts-ttf-2.32/ttf/DejaVuSans-Oblique.ttf");
    QFontDatabase::addApplicationFont(d->resourcePath + "/dejavu-fonts-ttf-2.32/ttf/DejaVuSans-BoldOblique.ttf");
    QApplication::setFont(QFont("DejaVu Sans"));
}

Backend::~Backend()
{
    delete d;
    d = 0;
}

void Backend::initialize(QDeclarativeEngine *qmlEngine)
{
    // register dataproviders to QML
    qmlRegisterType<ActionMapper>("ActionMapper", 1, 0, "ActionMapper");
    qmlRegisterType<QMHPlugin>("QMHPlugin", 1, 0, "QMHPlugin");
    qmlRegisterType<ProxyModel>("ProxyModel", 1, 0, "ProxyModel");
    qmlRegisterType<ProxyModelItem>("ProxyModel", 1, 0, "ProxyModelItem");
    qmlRegisterType<DirModel>("DirModel", 1, 0, "DirModel");
    qmlRegisterType<ModelIndexIterator>("ModelIndexIterator", 1, 0, "ModelIndexIterator");
    qmlRegisterType<QMLFileWrapper>("QMLFileWrapper", 1, 0, "QMLFileWrapper");
    qmlRegisterType<Playlist>("Playlist", 1, 0, "Playlist");

    if (qmlEngine) {
        //FIXME: We are clearly failing to keep the backend Declarative free :p
        d->qmlEngine = qmlEngine;
        qmlEngine->rootContext()->setContextProperty("backend", this);
        qmlEngine->rootContext()->setContextProperty("playlist", new Playlist);
    }

    d->discoverEngines();
}

QString Backend::language() const
{
    //FIXME: derive from locale
    //Allow override
    return QString();
    //Bob is a testing translation placeholder
    //return QString("bob");
}

QList<QObject*> Backend::engines() const
{
    return d->advertizedEngines;
}

QStringList Backend::skins() const
{
    return d->skins;
}

Backend* Backend::instance()
{
    if (!pSelf) {
        pSelf = new Backend();
    }
    return pSelf;
}

void Backend::destroy()
{
    delete pSelf;
    pSelf = 0;
}

QString Backend::skinPath() const
{
    return d->skinPath;
}

QString Backend::pluginPath() const
{
    return d->pluginPath;
}

QString Backend::resourcePath() const
{
    return d->resourcePath;
}

QString Backend::thumbnailPath() const
{
    return d->thumbnailPath;
}

bool Backend::transforms() const
{
#ifdef GL
    return (QGLFormat::hasOpenGL() && Config::isEnabled("transforms", true));
#else
    return false;
#endif
}

void Backend::advertizeEngine(QMHPlugin *engine)
{
    QString role = engine->property("role").toString();
    if (role.isEmpty())
        return;
    if (d->advertizedEngineRoles.contains(role)) {
        qWarning() << tr("Duplicate engine found for role %1").arg(role);
        return;
    }
    d->advertizedEngines << engine;
    if (d->qmlEngine)
        d->qmlEngine->rootContext()->setContextProperty(role % "Engine", engine);
    d->advertizedEngineRoles << role;
    emit enginesChanged();
}

void Backend::openUrlExternally(const QUrl & url) const
{
    QDesktopServices::openUrl(url);
}

void Backend::log(const QString &logMsg)
{
    qDebug() << logMsg;
    d->log << logMsg << endl;
}

// again dependent on declarative, needs to be fixed
void Backend::clearComponentCache()
{
    if (d->qmlEngine) {
        d->qmlEngine->clearComponentCache();
    }
}

bool Backend::eventFilter(QObject *obj, QEvent *event) {
    if (event->type() == QEvent::KeyPress
            || event->type() == QEvent::KeyRelease
            || event->type() == QEvent::MouseMove
            || event->type() == QEvent::MouseButtonPress)
    {
        d->inputIdleTimer.start();
    }

    return QObject::eventFilter(obj, event);
}

QObject* Backend::engine(const QString &role)
{
    foreach (QObject *currentEngine, d->advertizedEngines)
        if (qobject_cast<QMHPlugin*>(currentEngine)->role() == role)
            return currentEngine;
    qWarning() << tr("Seeking a non-existant plugin, prepare to die");
    return 0;
}

#include "backend.moc"
<commit_msg>Add tango icons for general icon usage Move DejaVu font to 3rdparty where it belongs<commit_after>/****************************************************************************

This file is part of the QtMediaHub project on http://www.gitorious.org.

Copyright (c) 2009 Nokia Corporation and/or its subsidiary(-ies).*
All rights reserved.

Contact:  Nokia Corporation (qt-info@nokia.com)**

You may use this file under the terms of the BSD license as follows:

"Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
* Neither the name of Nokia Corporation and its Subsidiary(-ies) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."

****************************************************************************/

#include "backend.h"
#include "plugins/qmhplugininterface.h"
#include "plugins/qmhplugin.h"
#include "dataproviders/proxymodel.h"
#include "dataproviders/dirmodel.h"
#include "dataproviders/modelindexiterator.h"
#include "qmh-config.h"
#include "qml-extensions/qmlfilewrapper.h"
#include "qml-extensions/actionmapper.h"
#include "dataproviders/playlist.h"

#include <QDir>
#include <QString>
#include <QPluginLoader>
#include <QCoreApplication>
#include <QVariant>
#include <QFileSystemModel>
#include <QDesktopServices>
#include <QFileSystemWatcher>

#ifdef GL
#include <QGLFormat>
#endif

#include <QDebug>

Backend* Backend::pSelf = 0;

class BackendPrivate : public QObject
{
    Q_OBJECT
public:
    BackendPrivate(Backend *p)
        : QObject(p),
      #ifdef Q_OS_MAC
          platformOffset("/../../.."),
      #endif
          basePath(QCoreApplication::applicationDirPath() + platformOffset),
          skinPath(basePath % "/skins"),
          pluginPath(basePath % "/plugins"),
          resourcePath(basePath % "/resources"),
          // Use "large" instead of appName to fit freedesktop spec
          thumbnailPath(Config::value("thumbnail-path", QDir::homePath() + "/.thumbnails/" + qApp->applicationName() + "/")),
          inputIdleTimer(this),
          qmlEngine(0),
          backendTranslator(0),
          logFile(qApp->applicationName().append(".log")),
          pSelf(p)
    {
        inputIdleTimer.setInterval(Config::value("idle-timeout", 120)*1000);
        inputIdleTimer.setSingleShot(true);
        inputIdleTimer.start();

        connect(&inputIdleTimer, SIGNAL(timeout()), pSelf, SIGNAL(inputIdle()));

        logFile.open(QIODevice::Text|QIODevice::ReadWrite);
        log.setDevice(&logFile);

        connect(&resourcePathMonitor,
                SIGNAL(directoryChanged(const QString &)),
                this,
                SLOT(handleDirChanged(const QString &)));

        resourcePathMonitor.addPath(skinPath);
        resourcePathMonitor.addPath(pluginPath);

        QFileInfo thumbnailFolderInfo(thumbnailPath);
        if (!thumbnailFolderInfo.exists()) {
            QDir dir;
            dir.mkpath(thumbnailFolderInfo.absoluteFilePath());
        }

        discoverSkins();
    }

    ~BackendPrivate()
    {
        delete backendTranslator;
        backendTranslator = 0;
        qDeleteAll(pluginTranslators.begin(), pluginTranslators.end());
    }
public slots:
    void handleDirChanged(const QString &dir);
public:
    void resetLanguage();
    void discoverSkins();
    void discoverEngines();

    QSet<QString> advertizedEngineRoles;

    QList<QObject*> advertizedEngines;

    const QString platformOffset;

    const QString basePath;
    const QString skinPath;
    const QString pluginPath;
    const QString resourcePath;
    const QString thumbnailPath;

    QTimer inputIdleTimer;
    QDeclarativeEngine *qmlEngine;
    QTranslator *backendTranslator;
    QList<QTranslator*> pluginTranslators;
    QFile logFile;
    QTextStream log;
    QFileSystemWatcher resourcePathMonitor;
    QStringList skins;

    Backend *pSelf;
};

void BackendPrivate::handleDirChanged(const QString &dir)
{
    if(dir == pluginPath) {
        qWarning() << "Changes in plugin path, probably about to eat your poodle";
        discoverEngines();
    } else if(dir == skinPath) {
        qWarning() << "Changes in skin path, repopulating skins";
        discoverSkins();
    }
}

void BackendPrivate::resetLanguage()
{
    static QString baseTranslationPath(basePath % "/translations/");
    const QString language = Backend::instance()->language();
    delete backendTranslator;
    backendTranslator = new QTranslator(this);
    backendTranslator->load(baseTranslationPath % language % ".qm");
    qApp->installTranslator(backendTranslator);

    qDeleteAll(pluginTranslators.begin(), pluginTranslators.end());

    foreach(QObject *pluginObject, advertizedEngines) {
        QMHPlugin *plugin = qobject_cast<QMHPlugin*>(pluginObject);
        QTranslator *pluginTranslator = new QTranslator(this);
        pluginTranslator->load(baseTranslationPath % plugin->role() % "_" % language % ".qm");
        pluginTranslators << pluginTranslator;
        qApp->installTranslator(pluginTranslator);
    }
}

void BackendPrivate::discoverSkins()
{
    skins.clear();

    QStringList potentialSkins = QDir(skinPath).entryList(QDir::Dirs | QDir::NoDotAndDotDot);

    foreach(const QString &currentPath, potentialSkins)
        if(QFile(skinPath % "/" % currentPath % "/" % currentPath).exists())
            skins << currentPath;

    qWarning() << "Available skins" << skins;
}

void BackendPrivate::discoverEngines()
{
    foreach(const QString fileName, QDir(pluginPath).entryList(QDir::Files)) {
        QString qualifiedFileName(pluginPath % "/" % fileName);
        QPluginLoader pluginLoader(qualifiedFileName);

        if (!pluginLoader.load()) {
            qWarning() << tr("Cant load plugin: %1 returns %2").arg(qualifiedFileName).arg(pluginLoader.errorString());
            continue;
        }

        QMHPluginInterface* pluginInterface = qobject_cast<QMHPluginInterface*>(pluginLoader.instance());
        if (!pluginInterface)
            qWarning() << tr("Invalid QMH plugin present: %1").arg(qualifiedFileName);
        else if (!pluginInterface->dependenciesSatisfied())
            qWarning() << tr("Can't meet dependencies for %1").arg(qualifiedFileName);
        else if (pluginInterface->role() == "undefined")
            qWarning() << tr("Plugin %1 has an undefined role").arg(qualifiedFileName);
        else {
            QMHPlugin *plugin = new QMHPlugin(qobject_cast<QMHPluginInterface*>(pluginLoader.instance()), this);
            plugin->setParent(this);
            if(qmlEngine)
                plugin->registerPlugin(qmlEngine->rootContext());
            Backend::instance()->advertizeEngine(plugin);
        }
    }
    resetLanguage();
}

Backend::Backend(QObject *parent)
    : QObject(parent),
      d(new BackendPrivate(this))
{
    QFontDatabase::addApplicationFont(d->resourcePath + "/3rdparty/dejavu-fonts-ttf-2.32/ttf/DejaVuSans.ttf");
    QFontDatabase::addApplicationFont(d->resourcePath + "/3rdparty/dejavu-fonts-ttf-2.32/ttf/DejaVuSans-Bold.ttf");
    QFontDatabase::addApplicationFont(d->resourcePath + "/3rdparty/dejavu-fonts-ttf-2.32/ttf/DejaVuSans-Oblique.ttf");
    QFontDatabase::addApplicationFont(d->resourcePath + "/3rdparty/dejavu-fonts-ttf-2.32/ttf/DejaVuSans-BoldOblique.ttf");
    QApplication::setFont(QFont("DejaVu Sans"));
}

Backend::~Backend()
{
    delete d;
    d = 0;
}

void Backend::initialize(QDeclarativeEngine *qmlEngine)
{
    // register dataproviders to QML
    qmlRegisterType<ActionMapper>("ActionMapper", 1, 0, "ActionMapper");
    qmlRegisterType<QMHPlugin>("QMHPlugin", 1, 0, "QMHPlugin");
    qmlRegisterType<ProxyModel>("ProxyModel", 1, 0, "ProxyModel");
    qmlRegisterType<ProxyModelItem>("ProxyModel", 1, 0, "ProxyModelItem");
    qmlRegisterType<DirModel>("DirModel", 1, 0, "DirModel");
    qmlRegisterType<ModelIndexIterator>("ModelIndexIterator", 1, 0, "ModelIndexIterator");
    qmlRegisterType<QMLFileWrapper>("QMLFileWrapper", 1, 0, "QMLFileWrapper");
    qmlRegisterType<Playlist>("Playlist", 1, 0, "Playlist");

    if (qmlEngine) {
        //FIXME: We are clearly failing to keep the backend Declarative free :p
        d->qmlEngine = qmlEngine;
        qmlEngine->rootContext()->setContextProperty("backend", this);
        qmlEngine->rootContext()->setContextProperty("playlist", new Playlist);
    }

    d->discoverEngines();
}

QString Backend::language() const
{
    //FIXME: derive from locale
    //Allow override
    return QString();
    //Bob is a testing translation placeholder
    //return QString("bob");
}

QList<QObject*> Backend::engines() const
{
    return d->advertizedEngines;
}

QStringList Backend::skins() const
{
    return d->skins;
}

Backend* Backend::instance()
{
    if (!pSelf) {
        pSelf = new Backend();
    }
    return pSelf;
}

void Backend::destroy()
{
    delete pSelf;
    pSelf = 0;
}

QString Backend::skinPath() const
{
    return d->skinPath;
}

QString Backend::pluginPath() const
{
    return d->pluginPath;
}

QString Backend::resourcePath() const
{
    return d->resourcePath;
}

QString Backend::thumbnailPath() const
{
    return d->thumbnailPath;
}

bool Backend::transforms() const
{
#ifdef GL
    return (QGLFormat::hasOpenGL() && Config::isEnabled("transforms", true));
#else
    return false;
#endif
}

void Backend::advertizeEngine(QMHPlugin *engine)
{
    QString role = engine->property("role").toString();
    if (role.isEmpty())
        return;
    if (d->advertizedEngineRoles.contains(role)) {
        qWarning() << tr("Duplicate engine found for role %1").arg(role);
        return;
    }
    d->advertizedEngines << engine;
    if (d->qmlEngine)
        d->qmlEngine->rootContext()->setContextProperty(role % "Engine", engine);
    d->advertizedEngineRoles << role;
    emit enginesChanged();
}

void Backend::openUrlExternally(const QUrl & url) const
{
    QDesktopServices::openUrl(url);
}

void Backend::log(const QString &logMsg)
{
    qDebug() << logMsg;
    d->log << logMsg << endl;
}

// again dependent on declarative, needs to be fixed
void Backend::clearComponentCache()
{
    if (d->qmlEngine) {
        d->qmlEngine->clearComponentCache();
    }
}

bool Backend::eventFilter(QObject *obj, QEvent *event) {
    if (event->type() == QEvent::KeyPress
            || event->type() == QEvent::KeyRelease
            || event->type() == QEvent::MouseMove
            || event->type() == QEvent::MouseButtonPress)
    {
        d->inputIdleTimer.start();
    }

    return QObject::eventFilter(obj, event);
}

QObject* Backend::engine(const QString &role)
{
    foreach (QObject *currentEngine, d->advertizedEngines)
        if (qobject_cast<QMHPlugin*>(currentEngine)->role() == role)
            return currentEngine;
    qWarning() << tr("Seeking a non-existant plugin, prepare to die");
    return 0;
}

#include "backend.moc"
<|endoftext|>