OSDN Git Service

データ構造決定記念バックアップ♪
authorwordring <kouichi_pm@users.osdn.me>
Wed, 19 Aug 2015 15:57:08 +0000 (00:57 +0900)
committerwordring <kouichi_pm@users.osdn.me>
Wed, 19 Aug 2015 15:57:08 +0000 (00:57 +0900)
18 files changed:
html/html.cpp
html/htmlprivate.cpp
proxy/main.cpp
proxy/tm.js
proxy/tmdatabase.cpp
proxy/tmdatabase.h
proxy/tmeditorwidget.cpp
proxy/tmeditorwidget.h
proxy/tmhttp.cpp
proxy/tmservice.cpp
proxy/tmservice.h
proxy/tmsocket.cpp
proxy/tmsocket.h
proxy/tmtext.cpp
proxy/tmtext.h
utility/text.cpp
utility/text.h
utility/userdata.h

index ccc7ec6..17b4b1f 100644 (file)
@@ -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();
 }
 
index 39bcee8..81d58ce 100644 (file)
@@ -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();
 }
 
index 2ea1875..4bbd71e 100644 (file)
@@ -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;
 }
index ef42bbd..6400955 100644 (file)
@@ -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){ 
index 731c5dd..29ca8f5 100644 (file)
@@ -1,13 +1,74 @@
-#include "tmdatabase.h"
-#include "settings.h"
+#include "settings.h"
+#include "tmdatabase.h"
+#include "tmservice.h"
 
 #include <QSqlQuery>
 #include <QSqlError>
 #include <QDir>
 #include <QLocale>
+#include <QDateTime>
 
 #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<sentence_data_type::pointer>();
+}
+
+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<int, QString>::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<int, QString>::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<QPair<int, int>, SentenceDatabase::pointer>::iterator iterator;
+       typedef QMap<QPair<quint32, int>, SentenceDatabase::pointer>::iterator iterator;
+       assert(site_id);
+       assert(code);
 
        // キャッシュが大きすぎる場合、消去する。
        if(m_sentence_cache_limit < m_sentence_databases.size()) m_sentence_databases.clear();
 
        // キャッシュにデータベースが無い場合、作成する。
-       QPair<int,int> key(site_id, code);
+       QPair<quint32, int> 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"; }
+
+
+
+
+
+
+
+
index b60165c..8233730 100644 (file)
@@ -7,8 +7,15 @@
 
 #include <QMap>
 #include <QPair>
+#include <QJsonArray>
+#include <QTime>
+
+#include <QThread>
 
 #include <memory>
+#include <functional>
+
+#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<SiteDatabase> pointer;
-       typedef std::unique_ptr<QSqlQuery> 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<QString, int> m_cache;
        int m_cache_limit;
@@ -53,11 +73,10 @@ private:
 /*!
  * \brief 単語に番号を振るためのクラスです。
  */
-class WordDatabase
+class WordDatabase : public DatabaseBase
 {
 public:
        typedef std::shared_ptr<WordDatabase> pointer;
-       typedef std::unique_ptr<QSqlQuery> 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<QString, int> m_cache;
        int m_cache_limit;
 };
 
-class SentenceDatabase
+class sentence_data_type
+{
+public:
+       typedef std::shared_ptr<sentence_data_type> 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<SentenceDatabase> pointer;
-       typedef std::unique_ptr<QSqlQuery> query_pointer;
+
+
+       typedef QList<sentence_data_type> 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<void(sentence_data_type)> 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<int, QString> m_language_map; /*!< è¨\80èª\9eã\82³ã\83¼ã\83\89ã\81¨è¨\80èª\9eå\90\8dã\81®ã\83\9eã\83\83ã\83\97 */
+       QMap<int, QString> m_language_map; /*!< è¨\80èª\9eã\82³ã\83¼ã\83\89ã\80\81è¨\80èª\9eå\90\8d */
 
        SiteDatabase::pointer m_site_database;
-       QMap<int, WordDatabase::pointer> m_word_databases;
-       QMap<QPair<int, int>, SentenceDatabase::pointer> m_sentence_databases;
-       int m_sentence_cache_limit;
+       QMap<int, WordDatabase::pointer> m_word_databases; /*!< 言語コード, 単語データベース */
 
+       /*! QPair<サイトID、言語コード>、文データベース */
+       QMap<QPair<quint32, int>, 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
index d1fd421..22596c2 100644 (file)
@@ -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<SourcePanel>();
                TargetPanel *tp = ta->append<TargetPanel>();
                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になる。
+       }
+}
 
 
 
index c847740..b09100f 100644 (file)
@@ -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;
 
index 9f61937..720caac 100644 (file)
@@ -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());
index 80418a1..d5b3b9e 100644 (file)
@@ -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 å\8e\9fæ\96\87ã\81¨è¨³æ\96\87ã\81®å¯¾ã\82\92ã\83\87ã\83¼ã\82¿ã\83\99ã\83¼ã\82¹ã\81¸ç\99»é\8c²します。
+ * \brief å\8e\9fæ\96\87ã\81\8bã\82\89訳æ\96\87ã\82\92æ¤\9cç´¢します。
  * \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 å\8e\9fæ\96\87ã\81\8bã\82\89訳æ\96\87ã\82\92æ¤\9cç´¢します。
+ * \brief å\8e\9fæ\96\87ã\81¨è¨³æ\96\87ã\81®å¯¾ã\82\92ã\83\87ã\83¼ã\82¿ã\83\99ã\83¼ã\82¹ã\81¸ç\99»é\8c²します。
  * \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;
 }
 
 
index 32b749c..1330edc 100644 (file)
@@ -2,6 +2,8 @@
 #define TMSERVICE_H
 
 #include "language.h"
+#include "tmtext.h"
+#include "tmdatabase.h"
 
 #include <QObject>
 #include <QString>
@@ -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<int, Language*> m_languages;
+
+       QThread *m_database_thread;
        Database *m_database;
 };
 
index e48c657..178e694 100644 (file)
@@ -4,6 +4,7 @@
 
 #include "settings.h"
 #include "html.h"
+#include "htmltag.h"
 
 #include <QMutex>
 #include <QMutexLocker>
@@ -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<HtmlNode::pointer> 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<HtmlData*>(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<RangeData*>(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<RangeData*>(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<RangeData*>(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<RangeData*>(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<QWebSocket*>(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)
index 16e189e..4060d63 100644 (file)
@@ -3,6 +3,7 @@
 
 #include "html.h"
 #include "text.h"
+#include "tmtext.h"
 
 #include <QObject>
 
@@ -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<TextSentence> 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<HtmlNode::pointer> 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;
 
index 352203c..76d25ed 100644 (file)
@@ -1,5 +1,8 @@
 #include "tmtext.h"
 
+#include <QJsonDocument>
+#include <QJsonDocument>
+
 #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<RangeData const*>(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<RangeData const*>(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));
+}
 
 
 
index 1f68d1b..1e2a9b9 100644 (file)
@@ -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<TextSentence> 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
index 4bbe72c..6c962cc 100644 (file)
@@ -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_));
index 42cb88d..989f25b 100644 (file)
@@ -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_);
 
index 50e0eac..01b7cd6 100644 (file)
@@ -1,6 +1,8 @@
 #ifndef USERDATA_H
 #define USERDATA_H
 
+#include <QString>
+
 #include <memory>
 
 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