OSDN Git Service

オランダ語プラグイン追加。
[wordring-tm/wordring-tm.git] / proxy / tmsocket.cpp
index 957dc7d..eee02eb 100644 (file)
@@ -1,7 +1,10 @@
 #include "tmsocket.h"
-#include "tmwidget.h"
+#include "tmeditorwidget.h"
+#include "tmservice.h"
 
+#include "settings.h"
 #include "html.h"
+#include "tmtext.h"
 
 #include <QMutex>
 #include <QMutexLocker>
 
 #include "debug.h"
 
-// HtmlStringFragments --------------------------------------------------------
 
-void TM::HtmlStringFragments::set_range(HtmlRange range)
-{
-       m_fragments.clear();
-       m_state = 0;
+// HtmlData -------------------------------------------------------------------
 
-       for(HtmlNode const &node : range)
-       {
-               if(node.type() != HtmlNode::Text) continue;
-               HtmlStringFragment fragment;
-               fragment.node = node;
-               fragment.string = node.value();
-               m_fragments.append(fragment);
-       }
-       for(HtmlStringFragment &fragment : m_fragments)
-       {
-               QString result;
-               QString &string = fragment.string;
-               for(QChar const &ch : string)
-               {
-                       QChar c = trim_whitespace(ch);
-                       if(c != '\0') result.append(c);
-               }
-               string = result;
-       }
-       QString &front = m_fragments.front().string;
-       if(front.startsWith(' ')) front.remove(0, 1);
-       QString &tail = m_fragments.back().string;
-       if(tail.endsWith(' ')) tail.remove(tail.size() - 1, 1);
-}
-
-QChar TM::HtmlStringFragments::trim_whitespace(QChar const &ch)
+TM::HtmlData::HtmlData(HtmlNode node_, int begin_, int tail_)
+       : RangeData(begin_, tail_)
+       , m_node(node_)
 {
-       bool ws = false;
-       switch(ch.toLatin1())
-       {
-       case 0x9:
-       case 0xA:
-       case 0xC:
-       case 0xD:
-       case 0x20:
-       case 0x200B:
-               ws = true;
-       }
-       switch(m_state)
-       {
-       case 0:
-               if(ws)
-               {
-                       m_state = 1;
-                       return ' ';
-               }
-               break;
-       case 1:
-               if(ws) return '\0';
-               else m_state = 0;
-       }
-       return ch;
 }
 
-QString TM::HtmlStringFragments::to_string()
-{
-       QString result;
+int TM::HtmlData::type() const { return Type; }
 
-       for(HtmlStringFragment const &fragment : m_fragments)
-       {
-               result.append(fragment.string);
-       }
+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_));
+}
+
 // SocketConnection -----------------------------------------------------------
 
-TM::SocketConnection::SocketConnection(Widget *widget, QWebSocket *socket)
+TM::SocketConnection::SocketConnection(Settings *settings, Service *service,
+                                                                       EditorWidget *editor_widget, QWebSocket *socket)
        : QObject(socket)
-       , m_widget(widget)
+       , m_settings(settings)
+       , m_service(service)
        , m_mutex(QMutex::Recursive)
+       , m_editor_widget(editor_widget)
+       , m_site_id(0)
        , m_edit_mode(false)
+       , m_scode(0)
+       , m_target_language_code(0)
 {
+       qRegisterMetaType<pointer>();
+
        connect(socket, SIGNAL(textMessageReceived(QString const&)),
                        this, SLOT(onTextMessageReceived(QString const&)));
        connect(socket, SIGNAL(binaryMessageReceived(QByteArray const&)),
@@ -100,19 +66,19 @@ TM::SocketConnection::SocketConnection(Widget *widget, QWebSocket *socket)
 
 TM::SocketConnection::~SocketConnection()
 {
-       m_widget->detach(this);
+       m_editor_widget->detach(this);
 }
 
 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)
@@ -122,11 +88,138 @@ void TM::SocketConnection::send_message(QJsonObject const &json)
        send_message(doc.toJson().data());
 }
 
+/*!
+ * \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());
+       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();
+
+       // データベースへ登録。
+       m_service->insert_sentence(segment_id, index,
+                                               m_url.host(), scode, tcode, sentence, pointer(this));
+
+       // ブラウザへ反映。
+       set_segment(segment_id, segment->to_html());
+}
+
+/*!
+ * \brief データベースへセンテンスの消去とブラウザへの反映を行います。
+ * \param segment_id セグメントのID。
+ * \param index センテンスのインデックス。
+ *
+ * エディタから呼び出されます。
+ * 呼び出されたとき、訳文はありません。
+ * そこで、clear系がサービスに依頼する時は、原文を引数として渡します。
+ */
+void TM::SocketConnection::remove_sentence(int segment_id, int index)
+{
+       assert(m_site_id);
+
+       // セグメントの検索。
+       segment_map_iterator it = m_segments.find(segment_id);
+       assert(it != m_segments.end());
+       TextSegment::pointer segment = it.value();
+
+       // センテンスの検索。
+       TextSentence::pointer sentence = segment->at(index);
+       assert(sentence->source_sentence());
+       // 訳文の消去。
+       sentence->set_target_sentence(Text::pointer());
+
+       // 言語コード。
+       int scode = m_editor_widget->source_language();
+       int tcode = m_editor_widget->target_language();
+
+       // データベースとサーバへ反映。
+       m_service->remove_sentence(m_url.host(), scode, tcode, sentence);
+
+       // ブラウザへ反映。
+       set_segment(segment_id, segment->to_html());
+}
+
+/*!
+ * \brief TM::SocketConnection::sentence_found
+ * \param segment_id
+ * \param index
+ * \param result データベースが返す訳文。
+ */
+void TM::SocketConnection::sentence_found(
+               int segment_id, int index, sentence_data_type::pointer result)
+{
+       if(!result) return; // 検索がヒットしなかった場合、何もしない。
+
+       segment_map_iterator it = m_segments.find(segment_id);
+       assert(it != m_segments.end());
+       TextSegment::pointer segment = it.value();
+
+       segment->at(index)->set_source_id(result->source_id);
+       if(100 <= result->quality) // 品質が100以上の場合、セグメントとブラウザを更新する。
+       {
+               Text::pointer sentences = m_service->divide_into_sentences(
+                                                                               m_target_language_code, result->sentence);
+               Text::pointer words = m_service->divide_into_words(
+                                                                               m_target_language_code, sentences->begin());
+               segment->at(index)->set_target_sentence(words);
+               segment->at(index)->set_json(result->json);
+
+               set_segment(segment_id, segment->to_html()); // ブラウザに送信
+       }
+       else // 品質が100未満の場合、候補として登録する。
+       {
+               segment->at(index)->append(result);
+       }
+}
+
+void TM::SocketConnection::sentence_inserted(int segment_id, int index,
+                                               quint32 source_id, quint32 target_id)
+{
+       segment_map_iterator it = m_segments.find(segment_id);
+       assert(it != m_segments.end());
+       TextSegment::pointer segment = it.value();
+
+       assert(source_id);
+       TextSentence::pointer sentence = segment->at(index);
+       sentence->set_source_id(source_id);
+       sentence->set_target_id(target_id);
+}
+
 void TM::SocketConnection::changeEditMode(bool edit_mode)
 {
        set_edit_mode(edit_mode);
 }
 
+void TM::SocketConnection::changeLanguage()
+{
+       QJsonObject json;
+       json["cmd"] = "reload";
+       send_message(json);
+}
+
+/*!
+ * \brief ブラウザに編集モードを反映します。
+ *
+ * ブラウザは、編集モードにある場合、クリックでedit_segmentを発行します。
+ * 編集モードではリンクのクリックは無効です。
+ */
 void TM::SocketConnection::set_edit_mode(bool edit_mode)
 {
        if(edit_mode == m_edit_mode) return;
@@ -136,58 +229,111 @@ void TM::SocketConnection::set_edit_mode(bool edit_mode)
        json["cmd"] = "set_edit_mode";
        json["edit_mode"] = edit_mode;
        send_message(json);
+}
 
+/*!
+ * \brief ウェブブラウザへセグメントの訳文をセットします。
+ * \param segment_id セグメントのID。
+ * \param html セグメントに対応するHTML文字列。
+ */
+void TM::SocketConnection::set_segment(int segment_id, QString html)
+{
+       QJsonObject json;
+       json["cmd"] = "set_segment";
+       json["segment_id"] = segment_id;
+       json["html"] = html;
+       send_message(json);
 }
 
-void TM::SocketConnection::do_edit(QJsonObject const &json)
+/*!
+ * \brief ウェブブラウザ上でクリックされ、edit_segmentコマンドが発行されたときに
+ * 呼び出されます。
+ */
+void TM::SocketConnection::do_edit_segment(QJsonObject const &json)
 {
-       m_document.set_content(json["source"].toString().toUtf8());
-       HtmlNode body = m_document.first("html").first("body");
-       m_fragments.set_range(HtmlRange(body, body));
-       m_widget->set_string(m_fragments.to_string(), json["target"].toString());
-       //emit editCmd(json["id"].toInt(), m_document.to_string());
-       /*
-       QJsonObject reply;
-       reply["cmd"] = "doedit";
-       reply["id"] = json["id"].toString();
-       reply["html"] = "test2";
-       QJsonDocument doc;
-       doc.setObject(reply);
-       send_message(doc.toJson().data());
-       */
+       assert(json.contains("segment_id"));
+       if(!json.contains("segment_id")) return;
+       int segment_id = json["segment_id"].toString().toInt();
+
+       segment_map_iterator it = m_segments.find(segment_id);
+       assert(it != m_segments.end());
+       TextSegment::pointer segment = it.value();
+
+       if(segment != m_current_segment)
+       {
+               m_current_segment = segment;
+               m_editor_widget->set_segment(m_current_segment);
+       }
 }
 
-void TM::SocketConnection::do_focus(QJsonObject const &json)
+/*!
+ * \brief ウェブブラウザ上でドキュメントがフォーカスを取得し、
+ * focusコマンドが発行されたときに呼び出されます。
+ */
+void TM::SocketConnection::do_focus(QJsonObject const &)
 {
-       m_widget->attach(this);
-       bool edit_mode = json["edit_mode"].toBool();
-       m_edit_mode = edit_mode;
-       m_widget->set_edit_mode(edit_mode);
+       m_editor_widget->attach(this);
+
+       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_widget->attach(this);
-       bool edit_mode = json["edit_mode"].toBool();
-       m_edit_mode = edit_mode;
-       m_widget->set_edit_mode(edit_mode);
+       m_editor_widget->attach(this);
+
+       set_edit_mode(m_editor_widget->edit_mode());
 
-       //set_edit_mode(m_widget->edit_mode());
+       m_scode = 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_scode, segment_id, html);
+       m_segments.insert(segment_id, segment);
+
+       // センテンス完全一致訳文の検索。
+       for(TextSentence::pointer sentence : *segment)
+       {
+               m_service->find_sentence(
+                               segment_id, sentence->index(),
+                               m_url.host(), m_scode, m_target_language_code,
+                               sentence, pointer(this));
+       }
 }
 
 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)
@@ -195,20 +341,28 @@ void TM::SocketConnection::onBinaryMessageReceived(QByteArray const &message)
        qDebug() << message;
 }
 
-
-
 // SocketServer ---------------------------------------------------------------
 
-TM::SocketServer::SocketServer(Widget *widget, quint16 port, QObject *parent)
+TM::SocketServer::SocketServer(Settings *settings, Service* service,
+                                                       EditorWidget *editor_widget, QObject *parent)
        : QObject(parent)
+       , m_settings(settings)
+       , m_service(service)
        , m_server(new QWebSocketServer(QStringLiteral("wordring websocket"),
                                                                        QWebSocketServer::NonSecureMode, this))
-       , m_widget(widget)
+       , m_editor_widget(editor_widget)
 {
+       connect(m_server, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
+
+       quint16 port = m_settings->value("SocketServer/port").toUInt();
        bool result = m_server->listen(QHostAddress::LocalHost, port);
-       if(!result) qFatal("An error occured in SocketServer(quint16, QObject*).");
+       if(!result) result = m_server->listen(QHostAddress::LocalHost, 0);
+       assert(result);
+       if(!result) qFatal("An error occured in SocketServer().");
+}
 
-       connect(m_server, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
+TM::SocketServer::~SocketServer()
+{
 }
 
 quint16 TM::SocketServer::port() const
@@ -216,16 +370,28 @@ quint16 TM::SocketServer::port() const
        return m_server->serverPort();
 }
 
+void TM::SocketServer::abort()
+{
+       for(QWebSocket *ws : m_sockets)
+       {
+               ws->abort();
+               ws->deleteLater();
+       }
+       m_sockets.clear();
+}
+
 void TM::SocketServer::onNewConnection()
 {
        QWebSocket *socket = m_server->nextPendingConnection();
        connect(socket, SIGNAL(disconnected()), this, SLOT(onDisconnected()));
-       new SocketConnection(m_widget, socket);
+       new SocketConnection(m_settings, m_service, m_editor_widget, socket);
+       m_sockets.push_back(socket);
 }
 
 void TM::SocketServer::onDisconnected()
 {
        QWebSocket *socket = qobject_cast<QWebSocket*>(sender());
+       m_sockets.removeOne(socket);
        socket->deleteLater();
 }