blob: 997f2674e842c2fb11137cd6b278207b5bc4715d [file] [log] [blame]
// Copyright 2010-2015, Google Inc.
// All rights reserved.
//
// 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 Google Inc. 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.
// TODO(horo): write tests.
#include <ppapi/cpp/instance.h>
#include <ppapi/cpp/module.h>
#include <ppapi/cpp/var.h>
#include <ppapi/utility/completion_callback_factory.h>
#include <queue>
#include <string>
#include "base/logging.h"
#include "base/mutex.h"
#include "base/pepper_file_util.h"
#include "base/scheduler.h"
#include "base/thread.h"
#include "base/util.h"
#include "base/version.h"
#include "chrome/nacl/dictionary_downloader.h"
#include "config/config_handler.h"
#include "config/config.pb.h"
#include "data_manager/packed/packed_data_manager.h"
#include "dictionary/user_dictionary_util.h"
#include "dictionary/user_pos.h"
#include "engine/engine_factory.h"
#include "net/http_client.h"
#include "net/http_client_pepper.h"
#include "net/jsoncpp.h"
#include "net/json_util.h"
#include "session/commands.pb.h"
#include "session/session_handler.h"
#include "session/session_usage_observer.h"
#include "usage_stats/usage_stats.h"
#include "usage_stats/usage_stats_uploader.h"
using mozc::net::JsonUtil;
namespace mozc {
namespace {
// TODO(horo): Need to confirm that this 1024 is OK.
const uint32 kFileIoFileSystemExpectedSize = 1024;
// Wrapper class of pthread_cond.
class Condition {
public:
Condition() {
pthread_cond_init(&cond_, NULL);
}
~Condition() {
pthread_cond_destroy(&cond_);
}
int signal() {
return pthread_cond_signal(&cond_);
}
int broadcast() {
return pthread_cond_broadcast(&cond_);
}
void wait(Mutex *mutex) {
pthread_cond_wait(&cond_, mutex->raw_mutex());
}
private:
pthread_cond_t cond_;
DISALLOW_COPY_AND_ASSIGN(Condition);
};
// Simple blocking queue implementation.
template<typename T>
class BlockingQueue {
public:
BlockingQueue()
: blocked_count_(0),
is_stopped_(false) {
}
~BlockingQueue() {
stop();
}
void stop() {
scoped_lock l(&mutex_);
is_stopped_ = true;
condition_.broadcast();
while (blocked_count_) {
condition_.wait(&mutex_);
}
}
void put(T element) {
scoped_lock l(&mutex_);
queue_.push(element);
condition_.signal();
}
T take(bool *stopped) {
if (stopped) {
*stopped = false;
}
scoped_lock l(&mutex_);
++blocked_count_;
while (queue_.empty() && !is_stopped_) {
condition_.wait(&mutex_);
}
--blocked_count_;
if (is_stopped_) {
condition_.broadcast();
if (stopped) {
*stopped = true;
}
}
const T front_element = queue_.front();
queue_.pop();
return front_element;
}
private:
Mutex mutex_;
Condition condition_;
int blocked_count_;
bool is_stopped_;
queue<T> queue_;
DISALLOW_COPY_AND_ASSIGN(BlockingQueue);
};
} // namespace
namespace session {
class MozcSessionHandlerThread : public Thread {
public:
MozcSessionHandlerThread(
pp::Instance *instance,
BlockingQueue<Json::Value*> *queue)
: instance_(instance), message_queue_(queue), factory_(this) {
}
virtual ~MozcSessionHandlerThread() {
}
virtual void Run() {
Util::SetRandomSeed(static_cast<uint32>(Util::GetTime()));
RegisterPepperInstanceForHTTPClient(instance_);
#ifdef GOOGLE_JAPANESE_INPUT_BUILD
const bool filesystem_available =
PepperFileUtil::Initialize(instance_, kFileIoFileSystemExpectedSize);
if (!filesystem_available) {
// Pepper file system is not available, so ignore the big dictionary and
// use the small dictionary.
LoadDictionary();
} else if (!LoadBigDictionary(&big_dictionary_version_)) {
LOG(ERROR) << "LoadBigDictionary error";
StartDownloadDictionary();
LoadDictionary();
} else if (big_dictionary_version_ !=
Version::GetMozcNaclDictionaryVersion()) {
LOG(ERROR) << "LoadBigDictionary version miss match "
<< big_dictionary_version_ << " :"
<< Version::GetMozcNaclDictionaryVersion();
StartDownloadDictionary();
}
#else // GOOGLE_JAPANESE_INPUT_BUILD
PepperFileUtil::Initialize(instance_, kFileIoFileSystemExpectedSize);
LoadDictionary();
#endif // GOOGLE_JAPANESE_INPUT_BUILD
user_pos_.reset(new dictionary::UserPOS(
packed::PackedDataManager::GetUserPosManager()->GetUserPOSData()));
engine_.reset(mozc::EngineFactory::Create());
handler_.reset(new SessionHandler(engine_.get()));
#ifdef GOOGLE_JAPANESE_INPUT_BUILD
usage_observer_.reset(new SessionUsageObserver());
handler_->AddObserver(usage_observer_.get());
// start usage stats timer
// send usage stats within 5 min later
// attempt to send every 5 min -- 2 hours.
Scheduler::AddJob(Scheduler::JobSetting(
"UsageStatsTimer",
usage_stats::UsageStatsUploader::kDefaultScheduleInterval,
usage_stats::UsageStatsUploader::kDefaultScheduleMaxInterval,
usage_stats::UsageStatsUploader::kDefaultSchedulerDelay,
usage_stats::UsageStatsUploader::kDefaultSchedulerRandomDelay,
&MozcSessionHandlerThread::SendUsageStats,
this));
#endif // GOOGLE_JAPANESE_INPUT_BUILD
// Gets the current config.
config::Config config;
config::ConfigHandler::GetStoredConfig(&config);
// Sends "InitializeDone" message to JavaScript side code.
Json::Value message(Json::objectValue);
message["event"] = Json::objectValue;
message["event"]["type"] = "InitializeDone";
JsonUtil::ProtobufMessageToJsonValue(config, &message["event"]["config"]);
message["event"]["version"] = Version::GetMozcVersion();
#ifdef GOOGLE_JAPANESE_INPUT_BUILD
message["event"]["big_dictionary_version"] = big_dictionary_version_;
#endif // GOOGLE_JAPANESE_INPUT_BUILD
pp::Module::Get()->core()->CallOnMainThread(
0,
factory_.NewCallback(
&MozcSessionHandlerThread::PostMessage,
Json::FastWriter().write(message)));
while (true) {
bool stopped = false;
scoped_ptr<Json::Value> message;
message.reset(message_queue_->take(&stopped));
if (stopped) {
LOG(ERROR) << " message_queue_ stopped";
return;
}
if (!message->isMember("id") ||
(!message->isMember("cmd") && !message->isMember("event"))) {
LOG(ERROR) << "request error";
continue;
}
Json::Value response(Json::objectValue);
response["id"] = (*message)["id"];
if (message->isMember("cmd")) {
commands::Command command;
JsonUtil::JsonValueToProtobufMessage((*message)["cmd"], &command);
handler_->EvalCommand(&command);
JsonUtil::ProtobufMessageToJsonValue(command, &response["cmd"]);
}
if (message->isMember("event") && (*message)["event"].isMember("type")) {
response["event"] = Json::objectValue;
const string event_type = (*message)["event"]["type"].asString();
response["event"]["type"] = event_type;
if (event_type == "SyncToFile") {
response["event"]["result"] = PepperFileUtil::SyncMmapToFile();
} else if (event_type == "GetVersionInfo") {
response["event"]["version"] = Version::GetMozcVersion();
#ifdef GOOGLE_JAPANESE_INPUT_BUILD
response["event"]["big_dictionary_version"] = big_dictionary_version_;
response["event"]["big_dictionary_state"] = GetBigDictionaryState();
#endif // GOOGLE_JAPANESE_INPUT_BUILD
} else if (event_type == "GetPosList") {
GetPosList(&response);
} else if (event_type == "IsValidReading") {
IsValidReading((*message)["event"], &response);
} else {
response["event"]["error"] = "Unsupported event";
}
}
pp::Module::Get()->core()->CallOnMainThread(
0,
factory_.NewCallback(
&MozcSessionHandlerThread::PostMessage,
Json::FastWriter().write(response)));
}
}
void PostMessage(int32_t result, const string &message) {
instance_->PostMessage(message);
}
private:
#ifdef GOOGLE_JAPANESE_INPUT_BUILD
// Loads the big dictionary
// Returns true and sets the dictionary version if successful.
bool LoadBigDictionary(string *version) {
string buffer;
// The big dictionary data is in the user's HTML5 file system.
if (!PepperFileUtil::ReadBinaryFile("/zipped_data_google", &buffer)) {
LOG(ERROR) << "PepperFileUtil::ReadBinaryFile error";
return false;
}
scoped_ptr<mozc::packed::PackedDataManager>
data_manager(new mozc::packed::PackedDataManager());
if (!data_manager->InitWithZippedData(buffer)) {
LOG(ERROR) << "InitWithZippedData error";
return false;
}
*version = data_manager->GetDictionaryVersion();
mozc::packed::RegisterPackedDataManager(data_manager.release());
return true;
}
// Starts downloading the big dictionary.
void StartDownloadDictionary() {
downloader_.reset(new chrome::nacl::DictionaryDownloader(
Version::GetMozcNaclDictionaryUrl(),
"/zipped_data_google"));
downloader_->SetOption(10 * 60 * 1000, // 10 minutes start delay
20 * 60 * 1000, // + [0-20] minutes random delay
30 * 60 * 1000, // retry_interval 30 min
4, // retry interval [30, 60, 120, 240, 240, 240...]
10); // 10 retries
downloader_->StartDownload();
}
#endif // GOOGLE_JAPANESE_INPUT_BUILD
// Loads the dictionary.
void LoadDictionary() {
string output;
HTTPClient::Option option;
option.timeout = 200000;
option.max_data_size = 100 * 1024 * 1024; // 100MB
// System dictionary data is in the user's Extensions directory.
#ifdef GOOGLE_JAPANESE_INPUT_BUILD
const string data_file_name = "./zipped_data_chromeos";
#else // GOOGLE_JAPANESE_INPUT_BUILD
const string data_file_name = "./zipped_data_oss";
#endif // GOOGLE_JAPANESE_INPUT_BUILD
CHECK(HTTPClient::Get(data_file_name, option, &output));
scoped_ptr<mozc::packed::PackedDataManager>
data_manager(new mozc::packed::PackedDataManager());
CHECK(data_manager->InitWithZippedData(output));
mozc::packed::RegisterPackedDataManager(data_manager.release());
}
#ifdef GOOGLE_JAPANESE_INPUT_BUILD
// Returns BigDictionaryState
// 0x00: Correct version BigDictionary is found.
// 0x1-: BigDictionary is not found.
// 0x2-: BigDictionary version miss match.
// 0x-*: Status of the downloader.
int GetBigDictionaryState() {
if (big_dictionary_version_ == Version::GetMozcNaclDictionaryVersion()) {
return 0;
}
int status = 0;
if (big_dictionary_version_.empty()) {
status = 0x10;
} else {
status = 0x20;
}
if (downloader_.get()) {
status += downloader_->GetStatus();
}
return status;
}
static bool SendUsageStats(void *data) {
MozcSessionHandlerThread *self =
static_cast<MozcSessionHandlerThread *>(data);
usage_stats::UsageStats::SetInteger("BigDictionaryState",
self->GetBigDictionaryState());
return usage_stats::UsageStatsUploader::Send(NULL);
}
#endif // GOOGLE_JAPANESE_INPUT_BUILD
void GetPosList(Json::Value *response) {
(*response)["event"]["posList"] = Json::Value(Json::arrayValue);
Json::Value *pos_list = &(*response)["event"]["posList"];
vector<string> tmp_pos_vec;
user_pos_->GetPOSList(&tmp_pos_vec);
for (int i = 0; i < tmp_pos_vec.size(); ++i) {
(*pos_list)[i] = Json::Value(Json::objectValue);
const user_dictionary::UserDictionary::PosType pos_type =
UserDictionaryUtil::ToPosType(tmp_pos_vec[i].c_str());
(*pos_list)[i]["type"] =
Json::Value(user_dictionary::UserDictionary::PosType_Name(pos_type));
(*pos_list)[i]["name"] = Json::Value(tmp_pos_vec[i]);
}
}
void IsValidReading(const Json::Value &event, Json::Value *response) {
if (!event.isMember("data")) {
(*response)["event"]["result"] = false;
return;
}
(*response)["event"]["data"] = event["data"].asString();
(*response)["event"]["result"] =
UserDictionaryUtil::IsValidReading(event["data"].asString());
}
pp::Instance *instance_;
BlockingQueue<Json::Value *> *message_queue_;
pp::CompletionCallbackFactory<MozcSessionHandlerThread> factory_;
scoped_ptr<EngineInterface> engine_;
scoped_ptr<SessionHandlerInterface> handler_;
scoped_ptr<const UserPOSInterface> user_pos_;
#ifdef GOOGLE_JAPANESE_INPUT_BUILD
scoped_ptr<SessionUsageObserver> usage_observer_;
scoped_ptr<chrome::nacl::DictionaryDownloader> downloader_;
string big_dictionary_version_;
#endif // GOOGLE_JAPANESE_INPUT_BUILD
DISALLOW_COPY_AND_ASSIGN(MozcSessionHandlerThread);
};
class NaclSessionHandlerInstance : public pp::Instance {
public:
explicit NaclSessionHandlerInstance(PP_Instance instance);
virtual ~NaclSessionHandlerInstance() {}
virtual void HandleMessage(const pp::Var &var_message);
private:
void CheckStatusAndStartMozcSessionHandlerThread();
scoped_ptr<MozcSessionHandlerThread> mozc_thread_;
BlockingQueue<Json::Value *> message_queue_;
DISALLOW_COPY_AND_ASSIGN(NaclSessionHandlerInstance);
};
NaclSessionHandlerInstance::NaclSessionHandlerInstance(PP_Instance instance)
: pp::Instance(instance) {
mozc_thread_.reset(new MozcSessionHandlerThread(this, &message_queue_));
mozc_thread_->Start();
}
void NaclSessionHandlerInstance::HandleMessage(const pp::Var &var_message) {
if (!var_message.is_string()) {
return;
}
scoped_ptr<Json::Value> message(new Json::Value);
if (Json::Reader().parse(var_message.AsString(), *message.get())) {
message_queue_.put(message.release());
}
}
class NaclSessionHandlerModule : public pp::Module {
public:
NaclSessionHandlerModule() : pp::Module() {
}
virtual ~NaclSessionHandlerModule() {}
protected:
virtual pp::Instance *CreateInstance(PP_Instance instance) {
return new NaclSessionHandlerInstance(instance);
}
private:
DISALLOW_COPY_AND_ASSIGN(NaclSessionHandlerModule);
};
} // namespace session
} // namespace mozc
namespace pp {
Module *CreateModule() {
// We use dummy argc and argv to call InitGoogle().
int argc = 1;
char argv0[] = "NaclModule";
char *argv_body[] = {argv0, NULL};
char **argv = argv_body;
InitGoogle(argv[0], &argc, &argv, true);
return new mozc::session::NaclSessionHandlerModule();
}
} // namespace pp