From 366328a4c2878c2d5a802c77823541798bccb2bf Mon Sep 17 00:00:00 2001 From: wordring Date: Thu, 20 Aug 2015 00:57:08 +0900 Subject: [PATCH] =?utf8?q?=E3=83=87=E3=83=BC=E3=82=BF=E6=A7=8B=E9=80=A0?= =?utf8?q?=E6=B1=BA=E5=AE=9A=E8=A8=98=E5=BF=B5=E3=83=90=E3=83=83=E3=82=AF?= =?utf8?q?=E3=82=A2=E3=83=83=E3=83=97=E2=99=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- html/html.cpp | 2 +- html/htmlprivate.cpp | 2 +- proxy/main.cpp | 1 + proxy/tm.js | 72 ++++--- proxy/tmdatabase.cpp | 513 +++++++++++++++++++++++++++++++---------------- proxy/tmdatabase.h | 152 +++++++++----- proxy/tmeditorwidget.cpp | 194 +++++++++--------- proxy/tmeditorwidget.h | 44 ++-- proxy/tmhttp.cpp | 2 + proxy/tmservice.cpp | 146 +++++++++++--- proxy/tmservice.h | 30 ++- proxy/tmsocket.cpp | 336 +++++++++++++++++++++++++++---- proxy/tmsocket.h | 54 ++--- proxy/tmtext.cpp | 128 ++++++++++-- proxy/tmtext.h | 50 ++++- utility/text.cpp | 20 +- utility/text.h | 5 +- utility/userdata.h | 3 + 18 files changed, 1264 insertions(+), 490 deletions(-) diff --git a/html/html.cpp b/html/html.cpp index ccc7ec6..17b4b1f 100644 --- a/html/html.cpp +++ b/html/html.cpp @@ -464,7 +464,7 @@ HtmlRange HtmlNode::range() HtmlNode HtmlNode::parent() { pointer self_ = this->lself(); - if(self_) return HtmlNode(self_->lparent()); + if(self_ && self_->lparent()) return HtmlNode(self_->lparent()); return HtmlNode(); } diff --git a/html/htmlprivate.cpp b/html/htmlprivate.cpp index 39bcee8..81d58ce 100644 --- a/html/htmlprivate.cpp +++ b/html/htmlprivate.cpp @@ -112,7 +112,7 @@ Html::pointer HtmlPrivate::lself() */ Html::pointer HtmlPrivate::lparent() { - assert(!m_parent.expired()); + if(m_parent.expired()) return pointer(); return m_parent.lock(); } diff --git a/proxy/main.cpp b/proxy/main.cpp index 2ea1875..4bbd71e 100644 --- a/proxy/main.cpp +++ b/proxy/main.cpp @@ -77,6 +77,7 @@ int main(int argc, char *argv[]) QObject::connect(&w, SIGNAL(closing()), socket, SLOT(abort())); w.show(); int result = a.exec(); + delete service; return result; } diff --git a/proxy/tm.js b/proxy/tm.js index ef42bbd..6400955 100644 --- a/proxy/tm.js +++ b/proxy/tm.js @@ -2,6 +2,7 @@ window.wordring = { socket: null, // TMとの通信用ソケット。 port: 0, // ソケットのポート番号。 + url: '', edit_mode: false, // 編集モード判別フラグ。 slanguage: '', tlanguage: '', @@ -9,15 +10,9 @@ window.wordring = { segments: null, // パラグラフの連想配列。 // ページのセットアップを行う。 - setup: function(){ - wordring.socket = new WebSocket('ws://localhost:' + window.wordring.port + '/'); - wordring.socket.onopen = wordring.onopen; - wordring.socket.onmessage = wordring.onmessage; - wordring.socket.onerror = wordring.onerror; - wordring.socket.onclose = wordring.onclose; - + setup: function() { // 原文のコピーを取る。 - wordring.segments = {}; + wordring.segments = Array(); var elements = document.getElementsByTagName('span'); var element; var i = 0; @@ -27,10 +22,19 @@ window.wordring = { if(!element.hasAttribute('data-wordring-segment')) continue; var id = element.getAttribute('data-wordring-segment'); - wordring.segments[id] = {}; - wordring.segments[id].element = element; - wordring.segments[id].html = element.innerHTML; + var segment = {}; + segment.segment_id = id; + segment.loaded = false; + segment.element = element; + segment.html = element.innerHTML; + wordring.segments[id] = segment; } + + wordring.socket = new WebSocket('ws://localhost:' + window.wordring.port + '/'); + wordring.socket.onopen = wordring.onopen; + wordring.socket.onmessage = wordring.onmessage; + wordring.socket.onerror = wordring.onerror; + wordring.socket.onclose = wordring.onclose; }, onopen: function(ev) { @@ -40,17 +44,20 @@ window.wordring = { wordring.socket.send(JSON.stringify({ 'cmd': 'load', - 'edit_mode': wordring.edit_mode, + 'url': window.wordring.url, })); - - //if(document.hasFocus) wordring.socket.send(JSON.stringify({ - // 'cmd': 'load', - // 'edit_mode': wordring.edit_mode, - //})); - //wordring.socket.send(JSON.stringify({ - // 'cmd': 'sethtml', - // 'html': document.body.innerHTML, - //})); + + // 全てのセグメントの対して、セグメントの情報を送信する。 + var length = wordring.segments.length; + var i = 0; + for(; i < length; i++) { + var segment = wordring.segments[i]; + wordring.socket.send(JSON.stringify({ + 'cmd': 'load_segment', + 'segment_id': segment.segment_id, + 'html': segment.html, + })); + } }, onmessage: function(ev) { @@ -59,6 +66,7 @@ window.wordring = { { case 'set_segment': wordring.set_segment(json); break; case 'set_edit_mode': wordring.set_edit_mode(json); break; + case 'segment_loaded': wordring.segment_loaded(json); break; } }, @@ -73,7 +81,6 @@ window.wordring = { onfocus: function(ev) { wordring.socket.send(JSON.stringify({ 'cmd': 'focus', - 'edit_mode': wordring.edit_mode, })); }, @@ -91,12 +98,12 @@ window.wordring = { for(node = ev.target; node.nodeType == Node.ELEMENT_NODE; node = node.parentNode) { if(node.hasAttribute('data-wordring-segment')) { - var segment_id = node.getAttribute('data-wordring-segment'); + var id = node.getAttribute('data-wordring-segment'); + var segment = wordring.segments[id]; + if(segment.loaded) break; wordring.socket.send(JSON.stringify({ - 'cmd': 'edit', - 'segment_id': segment_id, - 'source': wordring.segments[segment_id].html, - 'target': node.innerHtml, + 'cmd': 'edit_segment', + 'segment_id': segment.segment_id, })); break; } @@ -104,13 +111,20 @@ window.wordring = { }, set_segment: function(json) { - var id = json.id; - wordring.segments[id].element.innerHTML = json.html; + var segment_id = json.segment_id; + wordring.segments[segment_id].element.innerHTML = json.html; }, set_edit_mode: function(json) { wordring.edit_mode = json.edit_mode; }, + + segment_loaded: function(json) { + var segment_id = json.segment_id; + var segment = wordring.segments[segment_id]; + segment.loaded = true; + segment.element.className = ''; + }, }; window.addEventListener('load', function(ev){ diff --git a/proxy/tmdatabase.cpp b/proxy/tmdatabase.cpp index 731c5dd..29ca8f5 100644 --- a/proxy/tmdatabase.cpp +++ b/proxy/tmdatabase.cpp @@ -1,13 +1,74 @@ -#include "tmdatabase.h" -#include "settings.h" +#include "settings.h" +#include "tmdatabase.h" +#include "tmservice.h" #include #include #include #include +#include #include "debug.h" +// DatabaseBase --------------------------------------------------------------- + +bool TM::DatabaseBase::open(char const *message) +{ + assert(!m_database_name.isEmpty()); + m_database = QSqlDatabase::addDatabase("QSQLITE", m_database_name); + m_database.setDatabaseName(m_database_name); + bool ret = m_database.open(); + if(!ret) + { + error(message); + assert(false); + } + return ret; +} + +QSqlQuery TM::DatabaseBase::prepare(char const *sql, char const *message) +{ + QSqlQuery result(m_database); + bool ret = result.prepare(sql); + if(!ret) + { + error(message, &result); + assert(false); + } + return result; +} + +bool TM::DatabaseBase::exec(QSqlQuery &query, char const *message) +{ + bool ret = query.exec(); + if(!ret) + { + error(message, &query); + assert(false); + } + return ret; +} + +QSqlQuery TM::DatabaseBase::exec(char const *sql, char const *message) +{ + QSqlQuery result = m_database.exec(sql); + if(result.lastError().isValid()) + { + error(message, &result); + assert(false); + } + return result; +} + +void TM::DatabaseBase::error(QString message, QSqlQuery *query) +{ + QString s; + if(query) s = query->lastError().text(); + else s = m_database.lastError().text(); + + qCritical() << message << s; +} + // SiteDatabase --------------------------------------------------------------- TM::SiteDatabase::SiteDatabase(Settings *settings) @@ -17,36 +78,24 @@ TM::SiteDatabase::SiteDatabase(Settings *settings) "SiteDatabase/cache-limit", QVariant::fromValue(1024)).toInt(); // データベースを開く。 - m_database_name = settings->value(Database::root_key()).toString() + "/site.db"; - m_database = QSqlDatabase::addDatabase("QSQLITE", m_database_name); - m_database.setDatabaseName(m_database_name); - bool ret = m_database.open(); - if(!ret) qFatal("An error occured while open database in SiteDatabase()."); + m_database_name = settings->value(TMDATABASE_ROOT_PATH_KEY).toString() + "/site.db"; + open(Q_FUNC_INFO); // テーブル作成。 - QSqlQuery q = m_database.exec( - "CREATE TABLE IF NOT EXISTS sites(" + exec("CREATE TABLE IF NOT EXISTS sites(" "site_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," - "domain TEXT UNIQUE);"); - if(q.lastError().isValid()) qFatal("An error occured while create table in SiteDatabase()."); - q = m_database.exec( - "CREATE INDEX IF NOT EXISTS domain_index ON sites(domain);"); - if(q.lastError().isValid()) qFatal("An error occured while create index in SiteDatabase()."); + "host_name TEXT UNIQUE);", Q_FUNC_INFO); - // クエリ作成。 - m_find_site_id.reset(new QSqlQuery(m_database)); - ret = m_find_site_id->prepare("SELECT site_id FROM sites WHERE domain=?;"); - if(!ret) qFatal("An error occured while prepare select statement in SiteDatabase()."); + exec("CREATE INDEX IF NOT EXISTS domain_index ON sites(host_name);", Q_FUNC_INFO); - m_insert_site.reset(new QSqlQuery(m_database)); - ret = m_insert_site->prepare("INSERT INTO sites(domain) VALUES(?);"); - if(!ret) qFatal("An error occured while prepare insert statement in SiteDatabase()."); + // クエリ作成。 + m_find_site_id = prepare("SELECT site_id FROM sites WHERE host_name=?;", Q_FUNC_INFO); + m_insert_site = prepare("INSERT INTO sites(host_name) VALUES(?);", Q_FUNC_INFO); } TM::SiteDatabase::~SiteDatabase() { if(m_database.isOpen()) m_database.close(); - QSqlDatabase::removeDatabase(m_database_name); } /*! @@ -77,28 +126,22 @@ int TM::SiteDatabase::site_id(QString domain) /*! * \brief 引数として与えられたドメイン名に対して一意な番号を返します。 + * + * 登録されていない場合、0を返します。 */ -int TM::SiteDatabase::find_site_id(QString domain) +int TM::SiteDatabase::find_site_id(QString host_name) { - QSqlQuery *q = m_find_site_id.get(); - q->bindValue(0, domain); - bool ret = q->exec(); - if(!ret) qFatal("An error occured in SiteDatabase::find_site_id()."); - if(q->next()) return q->value(0).toInt(); + m_find_site_id.bindValue(0, host_name); + exec(m_find_site_id, Q_FUNC_INFO); + if(m_find_site_id.next()) return m_find_site_id.value(0).toInt(); return 0; } -void TM::SiteDatabase::insert_site(QString domain) +void TM::SiteDatabase::insert_site(QString host_name) { - QSqlQuery *q = m_insert_site.get(); - q->bindValue(0, domain); - bool ret = q->exec(); - if(!ret) - { - qDebug() << q->lastError(); - qFatal("An error occured in SiteDatabase::insert_site()."); - } + m_insert_site.bindValue(0, host_name); + exec(m_insert_site, Q_FUNC_INFO); } TM::SiteDatabase::pointer TM::SiteDatabase::create(Settings *settings) @@ -116,36 +159,26 @@ TM::WordDatabase::WordDatabase(Settings *settings, QString name) // データベースを開く。 m_database_name = settings->value( - Database::root_key()).toString() + "/word-" + name.toLower() + ".db"; - m_database = QSqlDatabase::addDatabase("QSQLITE", m_database_name); - m_database.setDatabaseName(m_database_name); - bool ret = m_database.open(); - if(!ret) qFatal("An error occured while open database in WordDatabase()."); + TMDATABASE_ROOT_PATH_KEY).toString() + "/word-" + name.toLower() + ".db"; + open(Q_FUNC_INFO); // テーブル作成。 - QSqlQuery q = m_database.exec( - "CREATE TABLE IF NOT EXISTS words(" + exec("CREATE TABLE IF NOT EXISTS words(" "word_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," - "word TEXT UNIQUE);"); - if(q.lastError().isValid()) qFatal("An error occured while create table in WordDatabase()."); - q = m_database.exec( - "CREATE INDEX IF NOT EXISTS word_index ON words(word);"); - if(q.lastError().isValid()) qFatal("An error occured while create index in WordDatabase()."); + "word TEXT UNIQUE);", Q_FUNC_INFO); + + exec("CREATE INDEX IF NOT EXISTS word_index ON words(word);", Q_FUNC_INFO); // クエリ作成。 - m_find_word_id.reset(new QSqlQuery(m_database)); - ret = m_find_word_id->prepare("SELECT word_id FROM words WHERE word=?;"); - if(!ret) qFatal("An error occured while prepare select statement in WordDatabase()."); + m_find_word_id = prepare( + "SELECT word_id FROM words WHERE word=?;", Q_FUNC_INFO); - m_insert_word.reset(new QSqlQuery(m_database)); - ret = m_insert_word->prepare("INSERT INTO words(word) VALUES(?);"); - if(!ret) qFatal("An error occured while prepare insert statement in WordDatabase()."); + m_insert_word = prepare("INSERT INTO words(word) VALUES(?);", Q_FUNC_INFO); } TM::WordDatabase::~WordDatabase() { if(m_database.isOpen()) m_database.close(); - QSqlDatabase::removeDatabase(m_database_name); } int TM::WordDatabase::word_id(QString word) @@ -173,21 +206,17 @@ int TM::WordDatabase::word_id(QString word) int TM::WordDatabase::find_word_id(QString word) { - QSqlQuery *q = m_find_word_id.get(); - q->bindValue(0, word); - bool ret = q->exec(); - if(!ret) qFatal("An error occured in WordDatabase::find_word_id()."); - if(q->next()) return q->value(0).toInt(); + m_find_word_id.bindValue(0, word); + exec(m_find_word_id, Q_FUNC_INFO); + if(m_find_word_id.next()) return m_find_word_id.value(0).toInt(); return 0; } void TM::WordDatabase::insert_word(QString word) { - QSqlQuery *q = m_insert_word.get(); - q->bindValue(0, word); - bool ret = q->exec(); - if(!ret) qFatal("An error occured in WordDatabase::insert_word()."); + m_insert_word.bindValue(0, word); + exec(m_insert_word, Q_FUNC_INFO); } TM::WordDatabase::pointer TM::WordDatabase::create(Settings *settings, QString name) @@ -195,98 +224,173 @@ TM::WordDatabase::pointer TM::WordDatabase::create(Settings *settings, QString n return pointer(new WordDatabase(settings, name)); } +// sentence_data_type --------------------------------------------------------- + +TM::sentence_data_type::sentence_data_type() + : sentence_id(0) + , source_id(0) + , crc(0) + , previous_crc(0) + , next_crc(0) + , user_id(0) + , time(0) +{ + +} + +TM::sentence_data_type::pointer TM::sentence_data_type::create() +{ + return pointer(new sentence_data_type()); +} + // SentenceDatabase ----------------------------------------------------------- TM::SentenceDatabase::SentenceDatabase(Settings *settings, int site_id, QString name) { // データベースを開く。 m_database_name = - settings->value(Database::root_key()).toString() + settings->value(TMDATABASE_ROOT_PATH_KEY).toString() + "/sentence-" + QString::number(site_id) + "-" + name.toLower() + ".db"; - m_database = QSqlDatabase::addDatabase("QSQLITE", m_database_name); - m_database.setDatabaseName(m_database_name); - bool ret = m_database.open(); - if(!ret) qFatal("An error occured while open database in SentenceDatabase()."); + open(Q_FUNC_INFO); // テーブル作成。 - QSqlQuery q = m_database.exec( - "CREATE TABLE IF NOT EXISTS sentences(" + exec("CREATE TABLE IF NOT EXISTS sentences(" "sentence_id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," - "crc INTEGER," - "UNIQUE(source_id INTEGER, sentence TEXT)," + "source_id INTEGER," + "sentence TEXT," "json TEXT," + "crc INTEGER," + "previous_crc INTEGER," + "next_crc INTEGER," "user_id INTEGER," - "time TIMESTAMP);"); - if(q.lastError().isValid()) - qFatal("An error occured while create table in SentenceDatabase()."); + "time INTEGER);"); // インデックス作成。 - q = m_database.exec("CREATE INDEX IF NOT EXISTS crc_index ON sentences(crc);"); - if(q.lastError().isValid()) - qFatal("An error occured while create index in SentenceDatabase()."); - - q = m_database.exec("CREATE INDEX IF NOT EXISTS source_id_index ON sentences(source_id);"); - if(q.lastError().isValid()) - qFatal("An error occured while create index in SentenceDatabase()."); - - q = m_database.exec("CREATE INDEX IF NOT EXISTS sentence_index ON sentences(sentence);"); - if(q.lastError().isValid()) - qFatal("An error occured while create index in SentenceDatabase()."); + exec("CREATE INDEX IF NOT EXISTS source_id_index ON sentences(source_id);", Q_FUNC_INFO); + exec("CREATE INDEX IF NOT EXISTS sentence_index ON sentences(sentence);", Q_FUNC_INFO); // クエリ作成。 - m_find_sentence_id.reset(new QSqlQuery(m_database)); - ret = m_find_sentence_id->prepare("SELECT sentence_id FROM sentences WHERE sentence=?;"); - if(!ret) qFatal("An error occured while prepare find_sentence_id statement in SentenceDatabase()."); + m_find_sentence_id = prepare( + "SELECT sentence_id FROM sentences WHERE sentence=?;", Q_FUNC_INFO); + + m_find_sentence_id_with_context = prepare( + "SELECT sentence_id FROM sentences " + "WHERE sentence=? " + "AND previous_crc=? " + "AND next_crc=?;", Q_FUNC_INFO); + + m_find_sentence_by_crc = prepare("SELECT * FROM sentences WHERE crc=?;", Q_FUNC_INFO); + m_find_sentence_by_source_id = prepare("SELECT * FROM sentences WHERE source_id=?;", Q_FUNC_INFO); + m_insert_sentence = prepare( + "INSERT OR REPLACE INTO sentences(" + "source_id, sentence, json, crc, previous_crc, next_crc, user_id, time) " + "VALUES(?, ?, ?, ?, ?, ?, ?, ?);", Q_FUNC_INFO); + m_update_sentence = prepare( + "UPDATE sentences SET " + "source_id=?," + "sentence=?," + "json=?," + "crc=?," + "previous_crc=?," + "next_crc=?," + "user_id=?," + "time=? " + "WHERE sentence_id=?;", Q_FUNC_INFO); +} - m_find_sentence_by_crc.reset(new QSqlQuery(m_database)); - ret = m_find_sentence->prepare("SELECT * FROM sentences WHERE crc=?;"); - if(!ret) qFatal("An error occured while prepare find_sentence_by_crc statement in SentenceDatabase()."); +TM::SentenceDatabase::~SentenceDatabase() +{ + if(m_database.isOpen()) m_database.close(); +} - m_find_sentence_by_source_id.reset(new QSqlQuery(m_database)); - ret = m_find_sentence->prepare("SELECT * FROM sentences WHERE source_id=?;"); - if(!ret) qFatal("An error occured while prepare select_by_crc statement in SentenceDatabase()."); +quint32 TM::SentenceDatabase::find_sentence_id(QString sentence) +{ + m_find_sentence_id.bindValue(0, sentence); + exec(m_find_sentence_id, Q_FUNC_INFO); - m_insert_sentence.reset(new QSqlQuery(m_database)); - ret = m_insert_sentence->prepare( - "INSERT OR REPLACE INTO sentences(crc, source_id, sentence, json, user_id, time) " - "VALUES(?, ?, ?, ?, ?, ?);"); - if(!ret) qFatal("An error occured while prepare insert statement in SentenceDatabase()."); + if(!m_find_sentence_id.next()) return 0; + return m_find_sentence_id.value(0).toUInt(); } -TM::SentenceDatabase::~SentenceDatabase() +quint32 TM::SentenceDatabase::find_sentence_id_with_context( + QString sentence, quint32 previous_crc, quint32 next_crc) { - if(m_database.isOpen()) m_database.close(); - QSqlDatabase::removeDatabase(m_database_name); + m_find_sentence_id_with_context.bindValue(0, sentence); + m_find_sentence_id_with_context.bindValue(1, previous_crc); + m_find_sentence_id_with_context.bindValue(2, next_crc); + exec(m_find_sentence_id_with_context, Q_FUNC_INFO); + + if(!m_find_sentence_id_with_context.next()) return 0; + return m_find_sentence_id_with_context.value(0).toUInt(); } -int TM::SentenceDatabase::sentence_id(QString sentence) +TM::sentence_data_type::pointer +TM::SentenceDatabase::find_sentence_by_source_id(int source_id) { - QSqlQuery *q = m_find_sentence_id.get(); - q->bindValue(0, sentence); - bool ret = q->exec(); - if(!ret) qFatal("An error occured in SentenceDatabase::sentence_id()."); - if(q->next()) return q->value(0).toInt(); + assert(source_id); - return 0; + sentence_data_type::pointer result; + + m_find_sentence_by_source_id.bindValue(0, source_id); + exec(m_find_sentence_by_source_id, Q_FUNC_INFO); + + if(!m_find_sentence_by_source_id.next()) return result; + + return stuff_value(&m_find_sentence_by_source_id); } -/*bool TM::SentenceDatabase::find_sentence(QString sentence, - QString *tsentence, QJsonArray *json) +void TM::SentenceDatabase::insert(sentence_data_type::pointer sentence_data) { + assert(m_database.isValid()); + assert(m_database.isOpen()); + + m_insert_sentence.bindValue(0, sentence_data->source_id); + m_insert_sentence.bindValue(1, sentence_data->sentence); + m_insert_sentence.bindValue(2, sentence_data->json); + m_insert_sentence.bindValue(3, sentence_data->crc); + m_insert_sentence.bindValue(4, sentence_data->previous_crc); + m_insert_sentence.bindValue(5, sentence_data->next_crc); + m_insert_sentence.bindValue(6, sentence_data->user_id); + m_insert_sentence.bindValue(7, sentence_data->time); + + exec(m_insert_sentence, Q_FUNC_INFO); +} +void TM::SentenceDatabase::update(sentence_data_type::pointer sentence_data) +{ + assert(m_database.isValid()); + assert(m_database.isOpen()); + + m_update_sentence.bindValue(0, sentence_data->source_id); + m_update_sentence.bindValue(1, sentence_data->sentence); + m_update_sentence.bindValue(2, sentence_data->json); + m_update_sentence.bindValue(3, sentence_data->crc); + m_update_sentence.bindValue(4, sentence_data->previous_crc); + m_update_sentence.bindValue(5, sentence_data->next_crc); + m_update_sentence.bindValue(6, sentence_data->user_id); + m_update_sentence.bindValue(7, sentence_data->time); + m_update_sentence.bindValue(8, sentence_data->sentence_id); + + exec(m_update_sentence, Q_FUNC_INFO); } -*/ -void TM::SentenceDatabase::insert(int source_id, QString sentence, QString json, int user_id) + +TM::sentence_data_type::pointer +TM::SentenceDatabase::stuff_value(QSqlQuery *query) { - QSqlQuery *q = m_insert_sentence.get(); - q->bindValue(0, 0); // crc - q->bindValue(1, source_id); - q->bindValue(2, sentence); - q->bindValue(3, json); - q->bindValue(4, user_id); - q->bindValue(5, 0); // time - bool ret = q->exec(); - if(!ret) qFatal("An error occured in WordDatabase::insert_word()."); + assert(query->isValid()); + sentence_data_type::pointer result = sentence_data_type::create(); + + result->sentence_id = query->value(0).toUInt(); + result->source_id = query->value(1).toUInt(); + result->sentence = query->value(2).toString(); + result->json = query->value(3).toByteArray(); + result->crc = query->value(4).toUInt(); + result->previous_crc = query->value(5).toUInt(); + result->next_crc = query->value(6).toUInt(); + result->user_id = query->value(7).toUInt(); + result->time = query->value(8).toUInt(); + + return result; } TM::SentenceDatabase::pointer TM::SentenceDatabase::create( @@ -297,36 +401,37 @@ TM::SentenceDatabase::pointer TM::SentenceDatabase::create( // Database ------------------------------------------------------------------- -TM::Database::Database(Settings *settings, QObject *parent) - : QObject(parent) +TM::Database::Database(Settings *settings) + : QObject(0) , m_settings(settings) { + qRegisterMetaType(); +} + +TM::Database::~Database() +{ +} + +/*! + * \brief データベースのセットアップを行います。 + * + * データベース接続を開いたスレッド内でしか使えない制限のため、スレッドの + * イベントループから処理します。 + */ +void TM::Database::setup() +{ // キャッシュの最大値を設定。 - m_sentence_cache_limit = settings->value( + m_sentence_cache_limit = m_settings->value( "Database/sentence-cache-limit", QVariant::fromValue(20)).toInt(); // データベース用のフォルダが無ければ作成する。 - QString key("Database/root"); - if(!settings->contains(key)) qFatal("An error occured while find settings in Database()."); - QString path = settings->value(key).toString(); + if(!m_settings->contains(TMDATABASE_ROOT_PATH_KEY)) qFatal("An error occured while find settings in Database()."); + QString path = m_settings->value(TMDATABASE_ROOT_PATH_KEY).toString(); QDir dir(path); if(!dir.exists()) dir.mkpath(path); // データベースを開く。 - m_site_database = SiteDatabase::create(settings); -} - -QString TM::Database::find_language_name(int code) const -{ - QString result; - QMap::const_iterator it = m_language_map.find(code); - if(it != m_language_map.end()) result = it.value(); - return result; -} - -int TM::Database::find_site_id(QString domain) -{ - return m_site_database->site_id(domain); + m_site_database = SiteDatabase::create(m_settings); } /*! @@ -343,6 +448,81 @@ void TM::Database::open_word_database(int code, QString name) m_word_databases.insert(code, WordDatabase::create(m_settings, name)); } +quint32 TM::Database::find_site_id(QString host_name) +{ + quint32 result = m_site_database->site_id(host_name); + return result; +} + +void TM::Database::find_sentence(quint32 site_id, int scode, int tcode, + QString sstring, sentence_callback callback) +{ + QByteArray json; + + SentenceDatabase::pointer sdb = find_sentence_database(site_id, scode); + assert(sdb); + + SentenceDatabase::pointer tdb = find_sentence_database(site_id, tcode); + assert(tdb); + + int source_id = sdb->find_sentence_id(sstring); + if(!source_id) + { + //tdb-> + } +} + +void TM::Database::insert_sentence(quint32 site_id, + quint32 scode, sentence_data_type::pointer source, + quint32 tcode, sentence_data_type::pointer target) +{ + assert(site_id); + assert(scode); + assert(tcode); + + assert(source->source_id == 0); + assert(source->json.isEmpty()); + assert(target->previous_crc == 0); + assert(target->next_crc == 0); + + SentenceDatabase::pointer sdb = find_sentence_database(site_id, scode); + assert(sdb); + SentenceDatabase::pointer tdb = find_sentence_database(site_id, tcode); + assert(tdb); + + quint32 source_id = source->sentence_id; + if(!source_id) source_id = sdb->find_sentence_id_with_context( + source->sentence, source->previous_crc, source->next_crc); + if(!source_id) + { + source->time = QDateTime::currentDateTime().toMSecsSinceEpoch(); + sdb->insert(source); + source_id = sdb->find_sentence_id_with_context( + source->sentence, source->previous_crc, source->next_crc); + } + assert(source_id); + + target->source_id = source_id; + target->time = QDateTime::currentDateTime().toMSecsSinceEpoch(); + + sentence_data_type::pointer target_sentence = + tdb->find_sentence_by_source_id(source_id); + if(target_sentence) + { + target->sentence_id = target_sentence->sentence_id; + tdb->update(target); + } + else tdb->insert(target); +} + +QString TM::Database::find_language_name(int code) const +{ + QString result; + QMap::const_iterator it = m_language_map.find(code); + if(it != m_language_map.end()) result = it.value(); + return result; +} + /*! * \brief 引数として与えられた単語に対して一意な番号を返します。 * \param code 言語コード。 @@ -365,50 +545,41 @@ int TM::Database::find_word_id(int code, QString word) * センテンス・データベースは、設定によって決められた数まで接続をキャッシュします。 * このメンバは、キャッシュ内にデータベース接続があればそれを返し、無ければ接続します。 */ -TM::SentenceDatabase::pointer TM::Database::find_sentence_database(int site_id, int code) +TM::SentenceDatabase::pointer +TM::Database::find_sentence_database(quint32 site_id, int code) { - typedef QMap, SentenceDatabase::pointer>::iterator iterator; + typedef QMap, SentenceDatabase::pointer>::iterator iterator; + assert(site_id); + assert(code); // キャッシュが大きすぎる場合、消去する。 if(m_sentence_cache_limit < m_sentence_databases.size()) m_sentence_databases.clear(); // キャッシュにデータベースが無い場合、作成する。 - QPair key(site_id, code); + QPair key(site_id, code); iterator it = m_sentence_databases.find(key); if(it == m_sentence_databases.end()) { QString name = find_language_name(code); + assert(!name.isEmpty()); it = m_sentence_databases.insert( key, SentenceDatabase::create(m_settings, site_id, name)); } + assert(it.value()); + return it.value(); } -/*! - * \brief 引数として与えられたセンテンスに対して一意な番号を返します。 - * \param site_id サイトを識別する番号。 - * \param code 言語コード。 - * \param sentence センテンス(文)。 - */ -int TM::Database::find_sentence_id(int site_id, int code, QString sentence) -{ - SentenceDatabase::pointer p = find_sentence_database(site_id, code); - return p->sentence_id(sentence); -} -bool TM::Database::find_sentence(int site_id, int scode, QString ssentence, int tcode, - QString *tsentence, QJsonArray *json) -{ - SentenceDatabase::pointer p = find_sentence_database(site_id, scode); - //p->; - return true; -} -void TM::Database::insert_sentence(int site_id, int code, int source_id, - QString sentence, QString json, int user_id) -{ -} -QString TM::Database::root_key() { return "Database/root"; } + + + + + + + + diff --git a/proxy/tmdatabase.h b/proxy/tmdatabase.h index b60165c..8233730 100644 --- a/proxy/tmdatabase.h +++ b/proxy/tmdatabase.h @@ -7,8 +7,15 @@ #include #include +#include +#include + +#include #include +#include + +#define TMDATABASE_ROOT_PATH_KEY "Database/root" QT_BEGIN_NAMESPACE QT_END_NAMESPACE @@ -18,33 +25,46 @@ class Settings; namespace TM { +class Service; + +class DatabaseBase +{ +protected: + bool open(char const *message); + QSqlQuery prepare(char const *sql, char const *message = nullptr); + bool exec(QSqlQuery &query, char const *message = nullptr); + QSqlQuery exec(char const *sql, char const *message = nullptr); + void error(QString message, QSqlQuery *query = nullptr); + +protected: + QSqlDatabase m_database; + QString m_database_name; +}; + /*! * \brief サイトに番号を振るためのクラスです。 */ -class SiteDatabase +class SiteDatabase : public DatabaseBase { public: typedef std::unique_ptr pointer; - typedef std::unique_ptr query_pointer; private: SiteDatabase(Settings *settings); public: ~SiteDatabase(); - int site_id(QString domain); + private: - int find_site_id(QString domain); - void insert_site(QString domain); + int find_site_id(QString host_name); + void insert_site(QString host_name); + public: static pointer create(Settings *settings); private: - QString m_database_name; - - QSqlDatabase m_database; - query_pointer m_find_site_id; - query_pointer m_insert_site; + QSqlQuery m_find_site_id; + QSqlQuery m_insert_site; QMap m_cache; int m_cache_limit; @@ -53,11 +73,10 @@ private: /*! * \brief 単語に番号を振るためのクラスです。 */ -class WordDatabase +class WordDatabase : public DatabaseBase { public: typedef std::shared_ptr pointer; - typedef std::unique_ptr query_pointer; private: WordDatabase(Settings *settings, QString name); @@ -74,54 +93,72 @@ public: static pointer create(Settings *settings, QString name); private: - QString m_database_name; - - QSqlDatabase m_database; - query_pointer m_find_word_id; - query_pointer m_insert_word; + QSqlQuery m_find_word_id; + QSqlQuery m_insert_word; QMap m_cache; int m_cache_limit; }; -class SentenceDatabase +class sentence_data_type +{ +public: + typedef std::shared_ptr pointer; + + sentence_data_type(); + + static pointer create(); + + quint32 sentence_id; /*!< DBによって自動的に付与される文ID */ + quint32 source_id; /*!< 原文は0固定、訳文は原文のsentence_id */ + QString sentence; /*!< 文本体の文字列 */ + QByteArray json; /*!< 原文は空、訳文はリンク情報のJSON */ + quint32 crc; /*!< 検索に使う文のCRC */ + quint32 previous_crc;/*!< 原文は文脈一致に使う前方文のCRC、訳文は0固定 */ + quint32 next_crc; /*!< 原文は文脈一致に使う後方文のCRC、訳文は0固定 */ + quint32 user_id; /*!< レコードを更新したユーザーのID */ + quint64 time; /*!< 更新時刻 */ +}; + +class SentenceDatabase : public DatabaseBase { public: typedef std::shared_ptr pointer; - typedef std::unique_ptr query_pointer; + + + typedef QList sentences_data_type; private: SentenceDatabase(Settings *settings, int site_id, QString name); public: ~SentenceDatabase(); - int sentence_id(QString sentence); + quint32 find_sentence_id(QString sentence); + quint32 find_sentence_id_with_context( + QString sentence, quint32 previous_crc, quint32 next_crc); -// bool find_sentence_by_source_sentence( -// QString sentence, QString *tsentence, QJsonArray *json = nullptr); bool find_sentence_by_crc( quint32 crc, QString *tsentence, QJsonArray *json = nullptr); - bool find_sentence_by_source_id( - int source_id, QString *tsentence, QJsonArray *json = nullptr); - void insert(int source_id, QString sentence, QString json, int user_id); + sentence_data_type::pointer find_sentence_by_source_id(int source_id); -private: + void insert(sentence_data_type::pointer sentence_data); + void update(sentence_data_type::pointer sentence_data); +private: + sentence_data_type::pointer stuff_value(QSqlQuery *query); public: static pointer create(Settings *settings, int site_id, QString name); private: - QString m_database_name; - - QSqlDatabase m_database; - - query_pointer m_find_sentence_id; - query_pointer m_find_sentence; - query_pointer m_find_sentence_by_crc; - query_pointer m_find_sentence_by_source_id; - query_pointer m_insert_sentence; + QSqlQuery m_find_sentence_id; + QSqlQuery m_find_sentence_id_with_context; + QSqlQuery m_find_sentence; + QSqlQuery m_find_sentence_by_crc; + QSqlQuery m_find_sentence_by_source_id; + QSqlQuery m_insert_sentence; + QSqlQuery m_update_sentence; }; class IndexDatabase @@ -132,42 +169,51 @@ class IndexDatabase class Database : public QObject { Q_OBJECT +public: + typedef std::function sentence_callback; public: - Database(Settings *settings, QObject *parent = 0); + Database(Settings *settings); + ~Database(); - QString find_language_name(int code) const; +signals: - int find_site_id(QString domain); +public slots: + void setup(); void open_word_database(int code, QString name); - int find_word_id(int code, QString word); + + quint32 find_site_id(QString host_name); + + void find_sentence(quint32 site_id, int scode, int tcode, QString sstring, + sentence_callback callback); + void insert_sentence(quint32 site_id, + quint32 scode, sentence_data_type::pointer source, + quint32 tcode, sentence_data_type::pointer target); + private: - SentenceDatabase::pointer find_sentence_database(int site_id, int code); -public: - int find_sentence_id(int site_id, int code, QString sentence); - bool find_sentence(int site_id, int scode, QString ssentence, int tcode, - QString *tsentence, QJsonArray *json); - void insert_sentence(int site_id, int code, int source_id, - QString sentence, QString json, int user_id); + QString find_language_name(int code) const; - static QString root_key(); -signals: + int find_word_id(int code, QString word); -public slots: + SentenceDatabase::pointer find_sentence_database(quint32 site_id, int code); private: Settings *m_settings; - QMap m_language_map; /*!< 言語コードと言語名のマップ */ + QMap m_language_map; /*!< 言語コード、言語名 */ SiteDatabase::pointer m_site_database; - QMap m_word_databases; - QMap, SentenceDatabase::pointer> m_sentence_databases; - int m_sentence_cache_limit; + QMap m_word_databases; /*!< 言語コード, 単語データベース */ + /*! QPair<サイトID、言語コード>、文データベース */ + QMap, SentenceDatabase::pointer> m_sentence_databases; + int m_sentence_cache_limit; /*!< 文データベース接続の最大値 */ }; +Q_DECLARE_METATYPE(sentence_data_type::pointer) +//Q_DECLARE_METATYPE(quint32) + } // namespace TM #endif // TMDATABASE_H diff --git a/proxy/tmeditorwidget.cpp b/proxy/tmeditorwidget.cpp index d1fd421..22596c2 100644 --- a/proxy/tmeditorwidget.cpp +++ b/proxy/tmeditorwidget.cpp @@ -189,11 +189,9 @@ void TM::EditorWidget::set_segment(TextSegment::pointer segment) m_edit->set_segment(segment); } -void TM::EditorWidget::save_sentence(int segment_id, int index, - Text::pointer target_sentence, QJsonArray link) +void TM::EditorWidget::save_sentence(int segment_id, int index) { - qDebug() << "save: " << link; - m_socket->save_sentence(segment_id, index, target_sentence, link); + m_socket->save_sentence(segment_id, index); } /*! @@ -285,34 +283,30 @@ void TM::EditorWidget::onBrowserTriggered(bool) TM::EditorPanel::EditorPanel(QWidget *parent) : TextPanel(parent) - , m_editor(nullptr) + , m_parent_editor(nullptr) { } +TM::Editor* TM::EditorPanel::parent_editor() { return m_parent_editor; } + /*! * \brief 親となるエディタを設定します。 */ -void TM::EditorPanel::set_editor(Editor *editor) +void TM::EditorPanel::set_parent_editor(Editor *editor) { - assert(!m_editor); - m_editor = editor; + assert(!m_parent_editor); + m_parent_editor = editor; } /*! - * \brief 保持している文を返します。 - */ -Text::pointer TM::EditorPanel::sentence() { return m_sentence; } - -/*! - * \brief 文を設定します。 + * \brief 文を再表示します。 */ -void TM::EditorPanel::set_sentence(Text::pointer sentence) +void TM::EditorPanel::ensure_sentence() { - assert(sentence); - m_sentence = sentence; + if(!sentence()) return; clear(); QTextCursor c = textCursor(); - for(Text::pointer p = sentence->begin(); p; p = p->next()) // p=word + for(Text::pointer p = sentence()->begin(); p; p = p->next()) // p=word { QTextCharFormat cf; QVariant v = QVariant::fromValue(Text::weak_pointer(p)); @@ -374,10 +368,7 @@ void TM::EditorPanel::highlight(WordLink::storage_type *link, QColor color) for(Text::pointer p : *link) highlight(p, color); } -void TM::EditorPanel::clear_highlight() -{ - if(m_sentence) set_sentence(m_sentence); -} +void TM::EditorPanel::clear_highlight() { ensure_sentence(); } /*! * \brief 引数として与えられた整数値から色を作成します。 @@ -406,6 +397,36 @@ int TM::SourcePanel::index() const { return m_index; } void TM::SourcePanel::set_index(int index) { m_index = index; } +Text::pointer TM::SourcePanel::sentence() { return source_sentence(); } + +Text::pointer TM::SourcePanel::source_sentence() +{ + return m_text_sentence->source_sentence(); +} + +Text::pointer TM::SourcePanel::target_sentence() +{ + return m_text_sentence->target_sentence(); +} + +void TM::SourcePanel::set_target_sentence(Text::pointer sentence) +{ + m_text_sentence->set_target_sentence(sentence); +} + +TM::TextSentence::pointer TM::SourcePanel::text_sentence() +{ + return m_text_sentence; +} + +void TM::SourcePanel::set_text_sentence(TextSentence::pointer text_sentence) +{ + m_text_sentence = text_sentence; + ensure_sentence(); + assert(target_panel()); + target_panel()->ensure_sentence(); +} + TM::TargetPanel* TM::SourcePanel::target_panel() { return m_target_panel; } void TM::SourcePanel::set_target_panel(TargetPanel *target) @@ -415,14 +436,14 @@ void TM::SourcePanel::set_target_panel(TargetPanel *target) void TM::SourcePanel::commit_link() { - m_linker.commit(); + linker()->commit(); clear_highlight(); m_target_panel->clear_highlight(); } TM::WordLinker* TM::SourcePanel::linker() { - return &m_linker; + return m_text_sentence->linker(); } /*! @@ -433,16 +454,16 @@ void TM::SourcePanel::ensure_highlight() clear_highlight(); m_target_panel->clear_highlight(); - WordLink::pointer wl = m_linker.current(); + WordLink::pointer wl = linker()->current(); if(wl) { highlight(wl->sources(), Qt::cyan); target_panel()->highlight(wl->targets(), Qt::cyan); } - for(WordLink::pointer wl : m_linker) + for(WordLink::pointer wl : *linker()) { - int index = m_linker.index_of(wl); + int index = linker()->index_of(wl); QColor c = color(index); highlight(wl->sources(), c); target_panel()->highlight(wl->targets(), c); @@ -464,12 +485,13 @@ void TM::SourcePanel::inputMethodEvent(QInputMethodEvent *ev) void TM::SourcePanel::keyPressEvent(QKeyEvent *ev) { + Editor *editor = parent_editor(); int key = ev->key(); switch(key) { case Qt::Key_Enter: case Qt::Key_Return: - if(m_editor->link_mode()) m_editor->set_link_mode(false); + if(editor->link_mode()) editor->set_link_mode(false); break; case Qt::Key_C: if(!ev->modifiers().testFlag(Qt::ControlModifier)) break; @@ -484,7 +506,8 @@ void TM::SourcePanel::keyPressEvent(QKeyEvent *ev) void TM::SourcePanel::do_click(QPoint const &pos) { - if(m_editor->link_mode()) do_click_in_link_mode(pos); + Editor *editor = parent_editor(); + if(editor->link_mode()) do_click_in_link_mode(pos); } void TM::SourcePanel::do_click_in_link_mode(QPoint const &pos) @@ -492,20 +515,11 @@ void TM::SourcePanel::do_click_in_link_mode(QPoint const &pos) Text::pointer w = select_word(pos); if(!w) return; - m_linker.toggle(WordLink::Source, w); + linker()->toggle(WordLink::Source, w); ensure_highlight(); setTextCursor(cursorForPosition(pos)); } -void TM::SourcePanel::do_focusin() -{ - //if(m_target_panel) m_target_panel->show(); -} - -void TM::SourcePanel::do_focusout() -{ -} - // TargetPanel ---------------------------------------------------------------- TM::TargetPanel::TargetPanel(QWidget *parent) @@ -524,9 +538,19 @@ void TM::TargetPanel::set_source_panel(SourcePanel *source) m_source_panel = source; } -void TM::TargetPanel::set_sentence(Text::pointer sentence) +Text::pointer TM::TargetPanel::sentence() +{ + return source_panel()->target_sentence(); +} + +void TM::TargetPanel::save_sentence() +{ + +} + +void TM::TargetPanel::ensure_sentence() { - EditorPanel::set_sentence(sentence); + EditorPanel::ensure_sentence(); set_text_dirty(false); set_text_saved(false); } @@ -562,8 +586,9 @@ bool TM::TargetPanel::is_text_dirty() const { return m_text_dirty; } */ void TM::TargetPanel::set_text_dirty(bool dirty) { + EditorWidget *editor_widget = parent_editor()->parent_editor_widget(); m_text_dirty = dirty; - if(dirty) m_editor->parent_editor_widget()->set_link_mode_disabled(false); + if(dirty) editor_widget->set_link_mode_disabled(false); } bool TM::TargetPanel::canInsertFromMimeData(QMimeData const *source) const @@ -581,7 +606,8 @@ void TM::TargetPanel::insertFromMimeData(QMimeData const *source) void TM::TargetPanel::inputMethodEvent(QInputMethodEvent *ev) { - if(m_editor->link_mode()) ev->setCommitString(""); + Editor *editor = parent_editor(); + if(editor->link_mode()) ev->setCommitString(""); if(!ev->commitString().isEmpty()) { set_text_dirty(true); @@ -592,7 +618,8 @@ void TM::TargetPanel::inputMethodEvent(QInputMethodEvent *ev) void TM::TargetPanel::keyPressEvent(QKeyEvent *ev) { - if(m_editor->link_mode()) do_key_press_in_link_mode(ev); + Editor *editor = parent_editor(); + if(editor->link_mode()) do_key_press_in_link_mode(ev); else { TextPanel::keyPressEvent(ev); @@ -606,7 +633,8 @@ void TM::TargetPanel::keyPressEvent(QKeyEvent *ev) void TM::TargetPanel::do_click(QPoint const &pos) { - if(m_editor->link_mode()) do_click_in_link_mode(pos); + Editor *editor = parent_editor(); + if(editor->link_mode()) do_click_in_link_mode(pos); } void TM::TargetPanel::do_click_in_link_mode(QPoint const &pos) @@ -620,24 +648,15 @@ void TM::TargetPanel::do_click_in_link_mode(QPoint const &pos) setTextCursor(cursorForPosition(pos)); } -void TM::TargetPanel::do_focusin() -{ - //show(); -} - -void TM::TargetPanel::do_focusout() -{ - //hide(); -} - void TM::TargetPanel::do_key_press_in_link_mode(QKeyEvent *ev) { + Editor *editor = parent_editor(); int key = ev->key(); switch(key) { case Qt::Key_Enter: case Qt::Key_Return: - m_editor->set_link_mode(false); + editor->set_link_mode(false); break; case Qt::Key_Left: case Qt::Key_Right: @@ -691,19 +710,15 @@ void TM::Editor::set_segment(TextSegment::pointer segment) int i = 0; for(TextSentence::pointer p : *segment) { - Text::pointer s = p->source_sentence(); - Text::pointer t = p->target_sentence(); - SourcePanel *sp = ta->append(); TargetPanel *tp = ta->append(); sp->set_index(i++); sp->set_target_panel(tp); - sp->set_editor(this); + sp->set_parent_editor(this); tp->set_source_panel(sp); - tp->set_editor(this); + tp->set_parent_editor(this); - sp->set_sentence(s); - if(t) tp->set_sentence(t); + sp->set_text_sentence(p); sp->show(); } @@ -800,22 +815,9 @@ void TM::Editor::do_panel_leave(SourcePanel *panel) if(!tp->is_text_saved()) { - int tcode = parent_editor_widget()->target_language(); - - if(tp->is_text_dirty()) - { - panel->linker()->clear(); - QString string = tp->toPlainText(); - Text::pointer sentences = m_service->divide_into_sentences(tcode, string); - if(sentences->size()) - { - Text::pointer words = m_service->divide_into_words(tcode, sentences->begin()); - tp->set_sentence(words); // ココで、text_dirtyがfalseになる。 - } - } - - parent_editor_widget()->save_sentence(m_segment_id, panel->index(), - tp->sentence(), panel->linker()->to_json_array()); + if(tp->is_text_dirty()) divide_target_sentence(panel); + int index = panel->index(); + parent_editor_widget()->save_sentence(m_segment_id, index); tp->set_text_saved(true); } } @@ -828,20 +830,8 @@ void TM::Editor::do_link_mode_enter(SourcePanel *panel) assert(panel); TargetPanel *tp = panel->target_panel(); assert(tp); - int tcode = parent_editor_widget()->target_language(); - - if(tp->is_text_dirty()) - { - panel->linker()->clear(); - QString string = tp->toPlainText(); - Text::pointer sentences = m_service->divide_into_sentences(tcode, string); - if(sentences->size()) - { - Text::pointer words = m_service->divide_into_words(tcode, sentences->begin()); - tp->set_sentence(words); // ココで、text_dirtyがfalseになる。 - } - } + if(tp->is_text_dirty()) divide_target_sentence(panel); panel->ensure_highlight(); } @@ -850,12 +840,30 @@ void TM::Editor::do_link_mode_enter(SourcePanel *panel) */ void TM::Editor::do_link_mode_leave(SourcePanel *panel) { - qDebug() << m_current_source_panel->linker()->to_json_array(); + qDebug() << m_current_source_panel->linker()->to_json(); assert(panel); panel->commit_link(); //panel->clear_highlight(); } +/*! + * \brief 訳文パネルの文を単語に分割し、表示に反映します。 + */ +void TM::Editor::divide_target_sentence(SourcePanel *source_panel) +{ + int tcode = parent_editor_widget()->target_language(); + TargetPanel *tp = source_panel->target_panel(); + QString string = tp->toPlainText(); + + Text::pointer sentences = m_service->divide_into_sentences(tcode, string); + if(sentences->size()) + { + source_panel->linker()->clear(); + Text::pointer words = m_service->divide_into_words(tcode, sentences->begin()); + source_panel->set_target_sentence(words); + tp->ensure_sentence(); // ココで、text_dirtyがfalseになる。 + } +} diff --git a/proxy/tmeditorwidget.h b/proxy/tmeditorwidget.h index c847740..b09100f 100644 --- a/proxy/tmeditorwidget.h +++ b/proxy/tmeditorwidget.h @@ -66,8 +66,7 @@ public: int target_language(); void set_segment(TextSegment::pointer segment); - void save_sentence(int segment_id, int index, - Text::pointer target_sentence, QJsonArray link); + void save_sentence(int segment_id, int index); signals: void editModeChanged(bool mode_); @@ -107,10 +106,12 @@ public: public: explicit EditorPanel(QWidget *parent); - void set_editor(Editor *editor); - Text::pointer sentence(); - virtual void set_sentence(Text::pointer sentence); + Editor* parent_editor(); + void set_parent_editor(Editor *editor); + + virtual Text::pointer sentence() = 0; + virtual void ensure_sentence(); bool is_empty() const; @@ -122,9 +123,8 @@ public: QColor color(int index) const; -protected: - Editor *m_editor; - Text::pointer m_sentence; +private: + Editor *m_parent_editor; }; class SourcePanel : public EditorPanel @@ -136,6 +136,14 @@ public: int index() const; void set_index(int index); + Text::pointer sentence(); + Text::pointer source_sentence(); + Text::pointer target_sentence(); + void set_target_sentence(Text::pointer sentence); + + TextSentence::pointer text_sentence(); + void set_text_sentence(TextSentence::pointer text_sentence); + TargetPanel* target_panel(); void set_target_panel(TargetPanel *target); @@ -151,17 +159,13 @@ protected: void inputMethodEvent(QInputMethodEvent *ev); void keyPressEvent(QKeyEvent *ev); - //void do_enter(); void do_click(QPoint const &pos); void do_click_in_link_mode(QPoint const &pos); - void do_focusin(); - void do_focusout(); - private: - int m_index; + int m_index; /*!< Editor上での位置を示す索引 */ TargetPanel *m_target_panel; - WordLinker m_linker; + TextSentence::pointer m_text_sentence; }; class TargetPanel: public EditorPanel @@ -173,7 +177,9 @@ public: SourcePanel* source_panel(); void set_source_panel(SourcePanel *source); - void set_sentence(Text::pointer sentence); + Text::pointer sentence(); + void save_sentence(); + void ensure_sentence(); bool is_text_saved() const; void set_text_saved(bool saved); @@ -188,13 +194,9 @@ protected: void inputMethodEvent(QInputMethodEvent *ev); void keyPressEvent(QKeyEvent *ev); - //void do_enter(); void do_click(QPoint const &pos); void do_click_in_link_mode(QPoint const &pos); - void do_focusin(); - void do_focusout(); - void do_key_press_in_link_mode(QKeyEvent *ev); private: @@ -220,6 +222,7 @@ public: bool can_link_mode() const; EditorWidget* parent_editor_widget(); +private: TargetPanel* current_target_panel(); TargetPanel const* current_target_panel() const; @@ -235,6 +238,9 @@ protected: void do_link_mode_leave(SourcePanel *panel); private: + void divide_target_sentence(SourcePanel *source_panel); + +private: Settings *m_settings; Service *m_service; diff --git a/proxy/tmhttp.cpp b/proxy/tmhttp.cpp index 9f61937..720caac 100644 --- a/proxy/tmhttp.cpp +++ b/proxy/tmhttp.cpp @@ -187,6 +187,8 @@ int TM::ProxyHandler::response() node = head.insert("script", head.end()).set_attribute("type", "text/javascript"); QString str1 = "\nwindow.wordring.port="; str1 += QString::number(m_context->socket_port()) + ";\n"; + str1 += "window.wordring.url='"; + str1 += m_targetUrl.toString() + "';\n"; node.insert_comment(str1, node.end()); // パラグラフ設定 create_paragraph(body.lbegin(), body.ltail()); diff --git a/proxy/tmservice.cpp b/proxy/tmservice.cpp index 80418a1..d5b3b9e 100644 --- a/proxy/tmservice.cpp +++ b/proxy/tmservice.cpp @@ -17,9 +17,54 @@ TM::Service::Service(Settings *settings, QObject *parent) : QObject(parent) , m_settings(settings) , m_mutex(QMutex::Recursive) - , m_database(nullptr) + , m_database_thread(new QThread(this)) + , m_database(new Database(settings)) { - m_database = new Database(settings, this); + setup_crc_table(); + + m_database->moveToThread(m_database_thread); + connect(m_database_thread, SIGNAL(finished()), m_database, SLOT(deleteLater())); + m_database_thread->start(); + QMetaObject::invokeMethod( + m_database, "setup", + Qt::BlockingQueuedConnection); +} + +TM::Service::~Service() +{ + m_database_thread->quit(); + m_database_thread->wait(); +} + +void TM::Service::setup_crc_table() +{ + for (quint32 i = 0; i < 256; i++) + { + quint32 c = i; + for (int j = 0; j < 8; j++) + c = (c & 1) ? (0xEDB88320 ^ (c >> 1)) : (c >> 1); + m_crc_table[i] = c; + } +} + +quint32 TM::Service::crc32(QString const &string) +{ + quint32 c = 0xFFFFFFFF; + QByteArray const ba = string.toUtf8(); + for (int i = 0; i < ba.size(); i++) + { + c = m_crc_table[(c ^ ba.at(i)) & 0xFF] ^ (c >> 8); + } + return c ^ 0xFFFFFFFF; +} + +quint32 TM::Service::crc32(int code, QString const &string) +{ + assert(m_languages.contains(code)); + Language *language = m_languages[code]; + QString s = language->normalize(string); + assert(!s.isEmpty()); + return crc32(s); } void TM::Service::load_languages(QString const &path) @@ -38,8 +83,12 @@ void TM::Service::load_languages(QString const &path) m_languages[code] = language; language->set_settings(m_settings); - m_database->open_word_database(code, name); emit languageLoaded(code, name, language->icon()); + QMetaObject::invokeMethod( + m_database, "open_word_database", + Qt::BlockingQueuedConnection, + Q_ARG(int, code), + Q_ARG(QString, name)); } } } @@ -69,45 +118,92 @@ Text::pointer TM::Service::divide_into_words(int code, Text::pointer sentence) return m_languages[code]->divide_into_words(sentence); } +quint32 TM::Service::find_site_id(QString host_name) +{ + quint32 result; + QMetaObject::invokeMethod( + m_database, "find_site_id", + Qt::BlockingQueuedConnection, + Q_RETURN_ARG(quint32, result), + Q_ARG(QString, host_name)); + return result; +} + /*! - * \brief 原文と訳文の対をデータベースへ登録します。 + * \brief 原文から訳文を検索します。 * \param site_id 翻訳対象サイトを表すID。 * \param scode 原文の言語コード。 - * \param ssentence 原文の構造化テキスト。 * \param tcode 訳文の言語コード。 - * \param tsentence 訳文の構造化テキスト。 - * \param json 原文と訳文間の単語リンク。 + * \param ssentence 原文の構造化テキスト。 + * \param 結果を返すコールバック関数。 */ -void TM::Service::insert_sentence(int site_id, int scode, Text::pointer ssentence, - int tcode, Text::pointer tsentence, QJsonArray json) +void TM::Service::find_sentence(quint32 site_id, int scode, int tcode, + Text::pointer ssentence, Database::sentence_callback callback) { - QMutexLocker lock(&m_mutex); - assert(m_languages.contains(scode)); - assert(m_languages.contains(tcode)); Language *slanguage = m_languages[scode]; - Language *tlanguage = m_languages[tcode]; - - QString sstring = slanguage->normalize(ssentence->to_string()); - QString tstring = tlanguage->normalize(tsentence->to_string()); - //m_database-> + QString sstring = ssentence->to_string(); + assert(!sstring.isEmpty()); + sstring = slanguage->normalize(sstring); + assert(!sstring.isEmpty()); + + QMetaObject::invokeMethod( + m_database, "find_sentence", + Qt::QueuedConnection, + Q_ARG(quint32, site_id), + Q_ARG(qint32, scode), + Q_ARG(qint32, tcode), + Q_ARG(QString, sstring), + Q_ARG(Database::sentence_callback, callback)); } /*! - * \brief 原文から訳文を検索します。 + * \brief 原文と訳文の対をデータベースへ登録します。 * \param site_id 翻訳対象サイトを表すID。 * \param scode 原文の言語コード。 - * \param ssentence 原文の構造化テキスト。 * \param tcode 訳文の言語コード。 - * \param tsentence 訳文の構造化テキストを受け取るポインタ。 - * \param json 原文と訳文間の単語リンクを受け取るポインタ。 - * \return 訳文が検索できた場合、trueを返します。 + * \param sentence 挿入する原文、訳文の対。 */ -bool TM::Service::find_sentence(int site_id, int scode, Text::pointer ssentence, int tcode, - Text::pointer *tsentence, QJsonArray *json) +void TM::Service::insert_sentence(quint32 site_id, int scode, int tcode, + TextSentence::pointer sentence, quint32 previous_crc, quint32 next_crc) + { - return true; + sentence_data_type::pointer source_sentence_data = + stuff_sentence_data(scode, sentence->source_sentence()); + source_sentence_data->previous_crc = previous_crc; + source_sentence_data->next_crc = next_crc; + + sentence_data_type::pointer target_sentence_data = + stuff_sentence_data(tcode, sentence->target_sentence()); + target_sentence_data->json = sentence->to_json(); + + QMetaObject::invokeMethod( + m_database, "insert_sentence", + Qt::QueuedConnection, + Q_ARG(quint32, site_id), + Q_ARG(quint32, scode), + Q_ARG(sentence_data_type::pointer, source_sentence_data), + Q_ARG(quint32, tcode), + Q_ARG(sentence_data_type::pointer, target_sentence_data)); +} + +TM::sentence_data_type::pointer TM::Service::stuff_sentence_data( + int code, Text::pointer text) +{ + assert(m_languages.contains(code)); + Language *language = m_languages[code]; + + QString string = text->to_string(); + assert(!string.isEmpty()); + string = language->normalize(string); + assert(!string.isEmpty()); + + sentence_data_type::pointer result = sentence_data_type::create(); + + result->sentence = string; + + return result; } diff --git a/proxy/tmservice.h b/proxy/tmservice.h index 32b749c..1330edc 100644 --- a/proxy/tmservice.h +++ b/proxy/tmservice.h @@ -2,6 +2,8 @@ #define TMSERVICE_H #include "language.h" +#include "tmtext.h" +#include "tmdatabase.h" #include #include @@ -22,24 +24,35 @@ class Settings; namespace TM { -class Database; - class Service : public QObject { Q_OBJECT public: Service(Settings *settings, QObject *parent = 0); + ~Service(); + + // CRC32 + void setup_crc_table(); + quint32 crc32(QString const &string); + quint32 crc32(int code, QString const &string); + // 言語プラグイン void load_languages(QString const &path); Text::pointer divide_into_sentences(int code, QString string); Text::pointer divide_into_words(int code, Text::pointer sentence); - void insert_sentence(int site_id, int scode, Text::pointer ssentence, - int tcode, Text::pointer tsentence, QJsonArray json); - bool find_sentence(int site_id, int scode, Text::pointer ssentence, int tcode, - Text::pointer *tsentence, QJsonArray *json); + // データベース + quint32 find_site_id(QString host_name); + void insert_sentence(quint32 site_id, int scode, int tcode, + TextSentence::pointer sentence, quint32 previous_crc, quint32 next_crc); + + void find_sentence(quint32 site_id, int scode, int tcode, + Text::pointer ssentence, Database::sentence_callback callback); +private: + sentence_data_type::pointer stuff_sentence_data(int code, Text::pointer text); +public: signals: /*! * \brief 言語プラグインが読み込まれたときに発火するシグナルです。 @@ -55,7 +68,12 @@ public: QMutex m_mutex; Settings *m_settings; + + quint32 m_crc_table[256]; + QMap m_languages; + + QThread *m_database_thread; Database *m_database; }; diff --git a/proxy/tmsocket.cpp b/proxy/tmsocket.cpp index e48c657..178e694 100644 --- a/proxy/tmsocket.cpp +++ b/proxy/tmsocket.cpp @@ -4,6 +4,7 @@ #include "settings.h" #include "html.h" +#include "htmltag.h" #include #include @@ -24,6 +25,18 @@ TM::HtmlData::HtmlData(HtmlNode node_, int begin_, int tail_) int TM::HtmlData::type() const { return Type; } +HtmlNode TM::HtmlData::node() { return m_node; } + +QString TM::HtmlData::debug_dump() const +{ + QString result; + result += "[HtmlData:"; + result += m_node.name() + ","; + result += QString::number(begin()) + ","; + result += QString::number(tail()) + "]"; + return result; +} + TM::HtmlData::pointer TM::HtmlData::create(HtmlNode node_, int begin_, int tail_) { return pointer(new HtmlData(node_, begin_, tail_)); @@ -114,29 +127,63 @@ bool TM::TextConverter::is_white_space(QChar const &ch) return false; } -// TextSentence --------------------------------------------------------------- +// HtmlConverter -------------------------------------------------------------- + -TM::TextSentence::TextSentence(Text::pointer source_sentence) - : m_source_sentence(source_sentence) +void TM::HtmlConverter::append(QString string) { + HtmlText::pointer text = HtmlText::create(HtmlText::weak_pointer()); + text->set_value(string); + m_nodes.append(text); } -Text::pointer TM::TextSentence::source_sentence() { return m_source_sentence; } +void TM::HtmlConverter::append(HtmlNode::pointer node, QString string) +{ + QList left, right; + for(HtmlNode hn = node->parent(); hn; hn = hn.parent()) + { + if(hn.tname() == "body") break; + left.prepend(hn.lself()); + right.append(hn.ltail()); + } -Text::pointer TM::TextSentence::target_sentence() { return m_target_sentence; } + m_nodes.append(left); + append(string); + m_nodes.append(right); +} -void TM::TextSentence::set_terget_sentence(Text::pointer target_sentence) +QString TM::HtmlConverter::to_string() { - m_target_sentence = target_sentence; -} + QString result; -QJsonArray TM::TextSentence::link_data() { return m_link_data; } + adjust(); + for(Html::pointer node : m_nodes) result += node->to_string(); -void TM::TextSentence::set_link_data(QJsonArray json) { m_link_data = json; } + return result; +} -TM::TextSentence::pointer TM::TextSentence::create(Text::pointer source_sentence) +void TM::HtmlConverter::adjust() { - return pointer(new TextSentence(source_sentence)); + int adjusted = 0; + do + { + adjusted = 0; + for(int i = 0; i < m_nodes.size() - 1; i++) + { + HtmlNode::pointer p1 = m_nodes.at(i); + if(p1->type() != Html::Element || p1->place() != Html::Close) continue; + HtmlNode::pointer p2 = m_nodes.at(i + 1); + if(p2->type() != Html::Element || p2->place() != Html::Open) continue; + + if(p1->lbegin() == p2->lbegin()) + { + m_nodes.removeAt(i); + m_nodes.removeAt(i); + ++adjusted; + } + } + } + while(adjusted); } // TextSegment ---------------------------------------------------------------- @@ -154,14 +201,40 @@ TM::TextSegment::TextSegment( for(Text::pointer s = sentences->begin(); s; s = s->next()) // s: sentence { Text::pointer words = service->divide_into_words(scode, s); - m_sentences.append(TextSentence::create(words)); + m_sentences.append(TextSentence::create(segment_id, words)); } } int TM::TextSegment::segment_id() const { return m_segment_id; } +/*! + * \brief 引数で指定されたインデックスの文について、CRC32を返します。 + * \param service サービスへのポインタ。 + * \param code 言語コード。 + * \param index 文のインデックス。 + * \return CRC32。 + * + * このメンバは、不正なインデックスを指定した場合、0を返します。 + */ +quint32 TM::TextSegment::crc32(Service *service, int code, int index) +{ + if(index < 0 || size() <= index) return 0; + + TextSentence::pointer sentence = at(index); + quint32 result = sentence->crc32(); + if(!result) + { + result = service->crc32(code, sentence->source_sentence()->to_string()); + sentence->set_crc32(result); + } + return result; +} + +int TM::TextSegment::size() const { return m_sentences.size(); } + TM::TextSentence::pointer TM::TextSegment::at(int index) { + assert(0 <= index && index < m_sentences.size()); return m_sentences.at(index); } @@ -169,6 +242,106 @@ TM::TextSegment::iterator TM::TextSegment::begin() { return m_sentences.begin(); TM::TextSegment::iterator TM::TextSegment::end() { return m_sentences.end(); } +HtmlNode::pointer TM::TextSegment::find_html_node_by_offset(int offset) +{ + HtmlNode::pointer result; + + for(Text::pointer p = m_text->begin(); p; p = p->next()) + { + UserData::pointer ud = p->data(); + assert(ud->type() == HtmlData::Type); + HtmlData *hd = static_cast(ud.get()); + if(hd->begin() <= offset && offset <= hd->tail()) + { + result = hd->node().lself(); + break; + } + } + return result; +} + +/*! + * \brief セグメント全体をHTML文字列に変換します。 + */ +QString TM::TextSegment::to_html() +{ + QString result; + for(TextSentence::pointer sentence : m_sentences) + { + if(!sentence->target_sentence()) + result += to_html_from_source(sentence); + else result += to_html_from_target(sentence); + result += "\r\n"; + } + return result; +} + +/*! + * \brief 原文をHTMLに変換します。 + * + * 訳文のついていない文のために在ります。 + */ +QString TM::TextSegment::to_html_from_source(TextSentence::pointer sentence) +{ + HtmlConverter hc; + + UserData *ud = sentence->source_sentence()->data().get(); + assert(ud->type() == RangeData::Type); + RangeData *rd = static_cast(ud); + int sentence_offset = rd->begin(); + + Text::pointer source_sentence = sentence->source_sentence(); + if(!source_sentence) return ""; + + for(Text::pointer word = source_sentence->begin(); word; word = word->next()) + { + QString string = word->to_string(); + UserData *ud = word->data().get(); + assert(ud->type() == RangeData::Type); + RangeData *rd = static_cast(ud); + int word_offset = sentence_offset + rd->begin(); + HtmlNode::pointer node = find_html_node_by_offset(word_offset); + if(node) hc.append(node, string); + else hc.append(string); + } + return hc.to_string(); +} + +/*! + * \brief 訳文をHTMLに変換します。 + */ +QString TM::TextSegment::to_html_from_target(TextSentence::pointer sentence) +{ + HtmlConverter hc; + + WordLinker* linker = sentence->linker(); + UserData *ud = sentence->source_sentence()->data().get(); + assert(ud->type() == RangeData::Type); + RangeData *rd = static_cast(ud); + int sentence_offset = rd->begin(); + + Text::pointer target_sentence = sentence->target_sentence(); + if(!target_sentence) return ""; + + for(Text::pointer word = target_sentence->begin(); word; word = word->next()) + { + QString string = word->to_string(); + WordLink::pointer link = linker->find(WordLink::Target, word); + if(!link) hc.append(string); + else + { + UserData::pointer ud = link->sources()->at(0)->data(); + assert(ud->type() == RangeData::Type); + RangeData *rd = static_cast(ud.get()); + int word_offset = sentence_offset + rd->begin(); + HtmlNode::pointer node = find_html_node_by_offset(word_offset); + if(node) hc.append(node, string); + else hc.append(string); + } + } + return hc.to_string(); +} + TM::TextSegment::pointer TM::TextSegment::create( Service *service, int scode, int segment_id, QString source) { @@ -182,9 +355,12 @@ TM::SocketConnection::SocketConnection(Settings *settings, Service *service, : QObject(socket) , m_settings(settings) , m_service(service) - , m_editor_widget(editor_widget) , m_mutex(QMutex::Recursive) + , m_editor_widget(editor_widget) + , m_site_id(0) , m_edit_mode(false) + , m_source_language_code(0) + , m_target_language_code(0) { connect(socket, SIGNAL(textMessageReceived(QString const&)), this, SLOT(onTextMessageReceived(QString const&))); @@ -200,13 +376,13 @@ TM::SocketConnection::~SocketConnection() QWebSocket* TM::SocketConnection::socket() { QWebSocket * result = qobject_cast(parent()); - assert(result); return result; } void TM::SocketConnection::send_message(QString const &message) { - socket()->sendTextMessage(message); + QWebSocket *ws = socket(); + if(ws) ws->sendTextMessage(message); } void TM::SocketConnection::send_message(QJsonObject const &json) @@ -216,14 +392,38 @@ void TM::SocketConnection::send_message(QJsonObject const &json) send_message(doc.toJson().data()); } -void TM::SocketConnection::save_sentence(int segment_id, int index, - Text::pointer target_sentence, QJsonArray link) +/*! + * \brief データベースへセンテンスの登録とブラウザへの反映を行います。 + * \param segment_id セグメントのID。 + * \param index センテンスのインデックス。 + */ +void TM::SocketConnection::save_sentence(int segment_id, int index) { + assert(m_site_id); + + // セグメントの検索。 segment_map_iterator it = m_segments.find(segment_id); assert(it != m_segments.end()); - TextSentence::pointer sentence = it.value()->at(index); - sentence->set_terget_sentence(target_sentence); - sentence->set_link_data(link); + TextSegment::pointer segment = it.value(); + + // センテンスの検索。 + TextSentence::pointer sentence = segment->at(index); + assert(sentence->source_sentence()); + if(!sentence->target_sentence()) return; + + // 言語コード。 + int scode = m_editor_widget->source_language(); + int tcode = m_editor_widget->target_language(); + + // ソースのCRC + quint32 previous_crc = segment->crc32(m_service, scode, index - 1); + quint32 next_crc = segment->crc32(m_service, scode, index + 1); + + // データベースへ登録。 + m_service->insert_sentence(m_site_id, scode, tcode, sentence, previous_crc, next_crc); + + // ブラウザへ反映。 + set_segment(segment_id, segment->to_html()); } void TM::SocketConnection::changeEditMode(bool edit_mode) @@ -231,6 +431,12 @@ void TM::SocketConnection::changeEditMode(bool edit_mode) set_edit_mode(edit_mode); } +/*! + * \brief ブラウザに編集モードを反映します。 + * + * ブラウザは、編集モードにある場合、クリックでedit_segmentを発行します。 + * 編集モードではリンクのクリックは無効です。 + */ void TM::SocketConnection::set_edit_mode(bool edit_mode) { if(edit_mode == m_edit_mode) return; @@ -240,69 +446,111 @@ void TM::SocketConnection::set_edit_mode(bool edit_mode) json["cmd"] = "set_edit_mode"; json["edit_mode"] = edit_mode; send_message(json); - } /*! - * \brief ウェブブラウザ上でクリックされ、editコマンドが発行されたときに呼び出されます。 + * \brief ウェブブラウザへセグメントの訳文をセットします。 + * \param segment_id セグメントのID。 + * \param html セグメントに対応するHTML文字列。 */ -void TM::SocketConnection::do_edit(QJsonObject const &json) +void TM::SocketConnection::set_segment(int segment_id, QString html) { - int scode = m_editor_widget->source_language(); + QJsonObject json; + json["cmd"] = "set_segment"; + json["segment_id"] = segment_id; + json["html"] = html; + send_message(json); +} +/*! + * \brief ウェブブラウザ上でクリックされ、edit_segmentコマンドが発行されたときに + * 呼び出されます。 + */ +void TM::SocketConnection::do_edit_segment(QJsonObject const &json) +{ assert(json.contains("segment_id")); int segment_id = json["segment_id"].toString().toInt(); segment_map_iterator it = m_segments.find(segment_id); - if(it == m_segments.end()) + assert(it != m_segments.end()); + TextSegment::pointer segment = it.value(); + + if(segment != m_current_segment) { - assert(json.contains("source")); - QString source = json["source"].toString(); - m_current_segment = TextSegment::create(m_service, scode, segment_id, source); - m_segments.insert(segment_id, m_current_segment); + m_current_segment = segment; + m_editor_widget->set_segment(m_current_segment); } - else m_current_segment = it.value(); - - m_editor_widget->set_segment(m_current_segment); } /*! * \brief ウェブブラウザ上でドキュメントがフォーカスを取得し、 * focusコマンドが発行されたときに呼び出されます。 */ -void TM::SocketConnection::do_focus(QJsonObject const &json) +void TM::SocketConnection::do_focus(QJsonObject const &) { m_editor_widget->attach(this); - bool edit_mode = json["edit_mode"].toBool(); - m_edit_mode = edit_mode; - m_editor_widget->set_edit_mode(edit_mode); + m_editor_widget->set_edit_mode(m_edit_mode); if(m_current_segment) m_editor_widget->set_segment(m_current_segment); } void TM::SocketConnection::do_blur(QJsonObject const &) { - //m_widget->detach(this); + //m_editor_widget->detach(this); } -void TM::SocketConnection::do_load(const QJsonObject &json) +void TM::SocketConnection::do_load(QJsonObject const &json) { m_editor_widget->attach(this); - bool edit_mode = json["edit_mode"].toBool(); - m_edit_mode = edit_mode; - m_editor_widget->set_edit_mode(edit_mode); - //set_edit_mode(m_widget->edit_mode()); + set_edit_mode(m_editor_widget->edit_mode()); + + m_source_language_code = m_editor_widget->source_language(); + m_target_language_code = m_editor_widget->target_language(); + m_url = QUrl(json["url"].toString()); + m_site_id = m_service->find_site_id(m_url.host()); +} + +/*! + * \brief ブラウザがセグメントを読み込んだ結果呼び出されます。 + * + * この処理の中で、セグメントの設定、完全一致訳文の検索を行います。 + * セグメント全体の検索が終わった時点でブラウザへsegment_loaded応答を返します。 + * ブラウザは、segment_loadedを受けて、そのセグメントをクリック可能にします。 + */ +void TM::SocketConnection::do_load_segment(QJsonObject const &json) +{ + assert(json.contains("segment_id")); + int segment_id = json["segment_id"].toString().toInt(); + assert(!m_segments.contains(segment_id)); + assert(json.contains("html")); + QString html = json["html"].toString(); + + // セグメントの挿入。 + TextSegment::pointer segment = TextSegment::create( + m_service, m_source_language_code, segment_id, html); + m_segments.insert(segment_id, segment); + + + // センテンス完全一致訳文の検索。 + //m_service->find_sentence(m_site_id,) + + // セグメントの挿 + //QJsonObject json; + //json["cmd"] = "set_edit_mode"; + //json["edit_mode"] = edit_mode; + //send_message(json); } void TM::SocketConnection::onTextMessageReceived(QString const &message) { QJsonObject json = QJsonDocument::fromJson(message.toUtf8()).object(); QString cmd = json["cmd"].toString(); - if(cmd == "edit") do_edit(json); + if(cmd == "edit_segment") do_edit_segment(json); else if(cmd == "focus") do_focus(json); else if(cmd == "blur") do_blur(json); else if(cmd == "load") do_load(json); + else if(cmd == "load_segment") do_load_segment(json); } void TM::SocketConnection::onBinaryMessageReceived(QByteArray const &message) diff --git a/proxy/tmsocket.h b/proxy/tmsocket.h index 16e189e..4060d63 100644 --- a/proxy/tmsocket.h +++ b/proxy/tmsocket.h @@ -3,6 +3,7 @@ #include "html.h" #include "text.h" +#include "tmtext.h" #include @@ -41,6 +42,9 @@ private: public: int type() const; + HtmlNode node(); + + QString debug_dump() const; static pointer create(HtmlNode node_, int begin_, int tail_); @@ -65,29 +69,18 @@ private: int m_state; }; -class TextSentence +class HtmlConverter { public: - typedef std::shared_ptr pointer; + void append(QString string); + void append(HtmlNode::pointer node, QString string); + QString to_string(); private: - TextSentence(Text::pointer source_sentence); -public: - Text::pointer source_sentence(); - Text::pointer target_sentence(); - void set_terget_sentence(Text::pointer target_sentence); - - QJsonArray link_data(); - void set_link_data(QJsonArray json); - - static pointer create(Text::pointer source_sentence); - - QString debug_dump(); + void adjust(); private: - Text::pointer m_source_sentence; - Text::pointer m_target_sentence; - QJsonArray m_link_data; + QList m_nodes; }; class TextSegment @@ -102,11 +95,19 @@ private: public: int segment_id() const; + quint32 crc32(Service *service, int code, int index); + int size() const; TextSentence::pointer at(int index); iterator begin(); iterator end(); + QString to_html(); + QString to_html_from_source(TextSentence::pointer sentence); + QString to_html_from_target(TextSentence::pointer sentence); + + HtmlNode::pointer find_html_node_by_offset(int offset); + static pointer create( Service *service, int scode, int segment_id, QString source); @@ -137,8 +138,8 @@ public: void set_edit_mode(bool edit_mode); - void save_sentence(int segment_id, int index, - Text::pointer target_sentence, QJsonArray link); + void save_sentence(int segment_id, int index); + void do_sentence_loaded(); signals: void editCmd(int id, QString html); @@ -147,10 +148,15 @@ public slots: void changeEditMode(bool edit_mode); private: - void do_edit(QJsonObject const &json); + void set_segment(int segment_id, QString html); + + void do_edit_segment(QJsonObject const &json); void do_focus(QJsonObject const &json); void do_blur(QJsonObject const &json); void do_load(QJsonObject const &json); + void do_load_segment(QJsonObject const &json); + + void do_segment_loaded(int segmnet_id); Text::pointer to_text(HtmlRange range) const; TextSegment::pointer find_segment(int segment_id); @@ -163,12 +169,14 @@ private slots: private: Settings *m_settings; Service *m_service; - QMutex m_mutex; + QUrl m_url; + int m_site_id; + bool m_edit_mode; - int m_source_language; - int m_target_language; + int m_source_language_code; + int m_target_language_code; EditorWidget *m_editor_widget; diff --git a/proxy/tmtext.cpp b/proxy/tmtext.cpp index 352203c..76d25ed 100644 --- a/proxy/tmtext.cpp +++ b/proxy/tmtext.cpp @@ -1,5 +1,8 @@ #include "tmtext.h" +#include +#include + #include "debug.h" // WordLink ------------------------------------------------------------------- @@ -69,33 +72,35 @@ TM::WordLink::storage_type* TM::WordLink::sources() { return &m_sources; } TM::WordLink::storage_type* TM::WordLink::targets() { return &m_targets; } -QJsonObject TM::WordLink::to_json() const +QJsonArray TM::WordLink::to_json() const { QJsonArray sources, targets; for(Text::pointer const &p : m_sources) { RangeData const *rd = static_cast(p->data().get()); - QJsonArray ja; - ja.append(rd->begin()); - ja.append(rd->tail()); - sources.append(ja); + sources.append(rd->begin()); + sources.append(rd->tail()); } for(Text::pointer const &p : m_targets) { RangeData const *rd = static_cast(p->data().get()); QJsonArray ja; - ja.append(rd->begin()); - ja.append(rd->tail()); - targets.append(ja); + targets.append(rd->begin()); + targets.append(rd->tail()); } - QJsonObject result; - result["s"] = sources; - result["t"] = targets; + QJsonArray result; + result.append(sources); + result.append(targets); return result; } +void TM::WordLink::set_json(QJsonArray json) +{ + for(QJsonValue jo : json); +} + QString TM::WordLink::debug_dump() const { QString result; @@ -204,6 +209,8 @@ TM::WordLink::pointer TM::WordLinker::find(Text::pointer word) /*! * \brief placeからvalueを含むWordLinkを検索します。 + * + * 存在しない場合、空のポインタを返します。 */ TM::WordLink::pointer TM::WordLinker::find(int place, Text::pointer word) { @@ -223,21 +230,23 @@ TM::WordLinker::iterator TM::WordLinker::begin() { return m_links.begin(); } TM::WordLinker::iterator TM::WordLinker::end() { return m_links.end(); } -QJsonArray TM::WordLinker::to_json_array() const +QByteArray TM::WordLinker::to_json() const { QJsonArray ja; if(m_current_link && m_current_link->is_valid()) ja.append(m_current_link->to_json()); for(WordLink::pointer const &p : m_links) ja.append(p->to_json()); - return ja; -} -void TM::WordLinker::set_json_array(QJsonArray json) -{ - for(QJsonValue jv : json) + QByteArray result; + if(!ja.isEmpty()) { - QJsonArray ja = jv.toArray(); - //for() + QJsonDocument jdoc(ja); + result = jdoc.toJson(QJsonDocument::Compact); } + return result; +} + +void TM::WordLinker::set_json(QByteArray json) +{ //m_links.append(); } @@ -248,8 +257,89 @@ QString TM::WordLinker::debug_dump() const return result; } +// TextSentence --------------------------------------------------------------- + +TM::TextSentence::TextSentence(int segment_id, Text::pointer source_sentence) + : m_segment_id(segment_id) + , m_source_sentence(source_sentence) + , m_crc32(0) + , m_source_id(0) + , m_target_id(0) + , m_loaded(false) +{ +} + +Text::pointer TM::TextSentence::source_sentence() { return m_source_sentence; } + +Text::pointer TM::TextSentence::target_sentence() { return m_target_sentence; } + +void TM::TextSentence::set_target_sentence(Text::pointer target_sentence) +{ + m_target_sentence = target_sentence; +} + +TM::WordLinker* TM::TextSentence::linker() { return &m_linker; } + +QByteArray TM::TextSentence::to_json() +{ + return m_linker.to_json(); +} + +void TM::TextSentence::set_json(QJsonArray json) +{ + m_linker.clear(); + for(QJsonValue jv : json) + { + QJsonArray ja = jv.toArray(); + //for() + } +} + +quint32 TM::TextSentence::crc32() const { return m_crc32; } + +void TM::TextSentence::set_crc32(quint32 crc32) { m_crc32 = crc32; } + +quint32 TM::TextSentence::source_id() const { return m_source_id; } + +void TM::TextSentence::set_source_id(quint32 source_id) +{ + m_source_id = source_id; +} + +bool TM::TextSentence::is_loaded() const { return m_loaded; } + +void TM::TextSentence::set_loaded(bool loaded) +{ + m_loaded = loaded; +} + +QString TM::TextSentence::debug_dump() const +{ + QString result; + + result += "[TextSentence:"; + if(m_source_sentence) + { + result += "[source_sentence:"; + result += m_source_sentence->debug_dump(); + result += "]"; + } + if(m_target_sentence) + { + result += "[target_sentence:"; + result += m_target_sentence->debug_dump(); + result += "]"; + } + result += "]"; + return result; +} +TM::TextSentence::pointer TM::TextSentence::create( + int segment_id, Text::pointer source_sentence) +{ + return pointer(new TextSentence(segment_id, source_sentence)); +} diff --git a/proxy/tmtext.h b/proxy/tmtext.h index 1f68d1b..1e2a9b9 100644 --- a/proxy/tmtext.h +++ b/proxy/tmtext.h @@ -46,7 +46,8 @@ public: storage_type* sources(); storage_type* targets(); - QJsonObject to_json() const; + QJsonArray to_json() const; + void set_json(QJsonArray json); QString debug_dump() const; @@ -94,8 +95,8 @@ public: iterator begin(); iterator end(); - QJsonArray to_json_array() const; - void set_json_array(QJsonArray json); + QByteArray to_json() const; + void set_json(QByteArray json); QString debug_dump() const; @@ -104,6 +105,49 @@ private: storage_type m_links; }; +class TextSentence +{ +public: + typedef std::shared_ptr pointer; + +private: + TextSentence(int segment_id, Text::pointer source_sentence); +public: + Text::pointer source_sentence(); + Text::pointer target_sentence(); + void set_target_sentence(Text::pointer target_sentence); + + WordLinker* linker(); + QByteArray to_json(); + void set_json(QJsonArray json); + + quint32 crc32() const; + void set_crc32(quint32 crc32); + + quint32 source_id() const; + void set_source_id(quint32 source_id); + + bool is_loaded() const; + void set_loaded(bool loaded); + + QString debug_dump() const; + + static pointer create(int segment_id, Text::pointer source_sentence); + +private: + Text::pointer m_source_sentence; + Text::pointer m_target_sentence; + WordLinker m_linker; + + int m_segment_id; + + quint32 m_crc32; + quint32 m_source_id; + quint32 m_target_id; + + bool m_loaded; /*!< 完全一致訳文の初期検索が終了していればtrue */ +}; + } // namespace TM #endif // TMTEXT_H diff --git a/utility/text.cpp b/utility/text.cpp index 4bbe72c..6c962cc 100644 --- a/utility/text.cpp +++ b/utility/text.cpp @@ -201,9 +201,19 @@ QString Text::to_string() const return result; } -QString Text::debug_dump() +QString Text::debug_dump() const { - return QString(); + QString result; + if(m_data) result += m_data->debug_dump(); + + if(!m_string.isEmpty()) return result + m_string; + + for(Text::const_pointer p = begin(); p; p = p->next()) + { + result.append(p->debug_dump()); + } + + return result; } Text::pointer Text::create() @@ -235,6 +245,12 @@ int RangeData::tail() const { return m_tail; } int RangeData::type() const { return Type; } +QString RangeData::debug_dump() const +{ + return QString("[RangeData:") + QString::number(m_begin) + "," + + QString::number(m_tail) + "]"; +} + UserData::pointer RangeData::create(int begin_, int tail_) { return pointer(new RangeData(begin_, tail_)); diff --git a/utility/text.h b/utility/text.h index 42cb88d..989f25b 100644 --- a/utility/text.h +++ b/utility/text.h @@ -25,6 +25,8 @@ private: Text(Text const &); public: + virtual ~Text() { } + bool is_empty() const; int size(); bool contains(pointer value_); @@ -63,7 +65,7 @@ public: QString to_string() const; - QString debug_dump(); + virtual QString debug_dump() const; static pointer create(); static pointer create(weak_pointer parent_); @@ -94,6 +96,7 @@ public: int tail() const; int type() const; + QString debug_dump() const; static pointer create(int begin_, int tail_); diff --git a/utility/userdata.h b/utility/userdata.h index 50e0eac..01b7cd6 100644 --- a/utility/userdata.h +++ b/utility/userdata.h @@ -1,6 +1,8 @@ #ifndef USERDATA_H #define USERDATA_H +#include + #include struct UserData @@ -16,6 +18,7 @@ struct UserData virtual ~UserData() { } virtual int type() const { return User; } + virtual QString debug_dump() const { return "struct UserData."; } }; #endif // USERDATA_H -- 2.11.0