OSDN Git Service

290f1538d167e8820c976f1b8210266282243e25
[wordring-tm/wordring-tm.git] / proxy / tmsocket.cpp
1 #include "tmsocket.h"
2 #include "tmeditorwidget.h"
3 #include "tmservice.h"
4
5 #include "settings.h"
6 #include "html.h"
7 #include "htmltag.h"
8
9 #include <QMutex>
10 #include <QMutexLocker>
11
12 #include <QJsonDocument>
13 #include <QJsonObject>
14
15 #include "debug.h"
16
17
18 // HtmlData -------------------------------------------------------------------
19
20 TM::HtmlData::HtmlData(HtmlNode node_, int begin_, int tail_)
21         : RangeData(begin_, tail_)
22         , m_node(node_)
23 {
24 }
25
26 int TM::HtmlData::type() const { return Type; }
27
28 HtmlNode TM::HtmlData::node() { return m_node; }
29
30 QString TM::HtmlData::debug_dump() const
31 {
32         QString result;
33         result += "[HtmlData:";
34         result += m_node.name() + ",";
35         result += QString::number(begin()) + ",";
36         result += QString::number(tail()) + "]";
37         return result;
38 }
39
40 TM::HtmlData::pointer TM::HtmlData::create(HtmlNode node_, int begin_, int tail_)
41 {
42         return pointer(new HtmlData(node_, begin_, tail_));
43 }
44
45 // TextConverter --------------------------------------------------------------
46
47 /*!
48  * \brief 引数として与えられたHtmlの範囲から構造化テキストを作成します。
49  */
50 Text::pointer TM::TextConverter::to_text(HtmlRange range)
51 {
52         m_state = 0;
53         Text::pointer result = Text::create();
54         int begin = 0;
55
56         for(HtmlNode const &node : range)
57         {
58                 if(node.type() != HtmlNode::Text) continue;
59                 Text::pointer p = stuff_text(result, node.value());
60                 if(!p) continue;
61                 int num = p->string().size();
62                 HtmlData::pointer hd = HtmlData::create(node, begin, begin + num - 1);
63                 p->set_data(hd);
64                 begin += num;
65         }
66         return result;
67 }
68
69 /*!
70  * \brief 引数として与えられたノードから構造化テキストを作成します。
71  *
72  * 連続する空白文字を一つにまとめます。
73  * 無効なノードが与えられた場合、空の構造化テキストを返します。
74  */
75 Text::pointer TM::TextConverter::stuff_text(Text::pointer parent, QString const &string)
76 {
77         QString outstring;
78         for(QChar const &ch : string)
79         {
80                 if(is_ignorable_white_space(ch)) continue;
81                 if(is_white_space(ch)) outstring.append(' ');
82                 else outstring.append(ch);
83         }
84         if(outstring.isEmpty()) return Text::pointer();
85
86         Text::pointer result = Text::create(parent, outstring);
87         parent->append(result);
88         return result;
89 }
90
91 /*!
92  * \brief 連続する空白文字を検出します。
93  *
94  * 最初の空白文字、あるいは空白文字以外は、falseを返します。
95  * 連続する空白文字の二番目以降に対してのみtrueを返します。
96  */
97 bool TM::TextConverter::is_ignorable_white_space(const QChar &ch)
98 {
99         if(!is_white_space(ch))
100         {
101                 m_state = 0;
102                 return false;
103         }
104         if(m_state == 0)
105         {
106                 m_state = 1;
107                 return false;
108         }
109         return true;
110 }
111
112 /*!
113  * \brief 引数として与えられたchが空白文字の場合true、それ以外の場合falseを返します。
114  */
115 bool TM::TextConverter::is_white_space(QChar const &ch)
116 {
117         switch(ch.unicode())
118         {
119         case 0x9:
120         case 0xA:
121         case 0xC:
122         case 0xD:
123         case 0x20:
124         case 0x200B:
125                 return true;
126         }
127         return false;
128 }
129
130 // HtmlConverter --------------------------------------------------------------
131
132
133 void TM::HtmlConverter::append(QString string)
134 {
135         HtmlText::pointer text = HtmlText::create(HtmlText::weak_pointer());
136         text->set_value(string);
137         m_nodes.append(text);
138 }
139
140 void TM::HtmlConverter::append(HtmlNode::pointer node, QString string)
141 {
142         QList<HtmlNode::pointer> left, right;
143         for(HtmlNode hn = node->parent(); hn; hn = hn.parent())
144         {
145                 if(hn.tname() == "body") break;
146                 left.prepend(hn.lself());
147                 right.append(hn.ltail());
148         }
149
150         m_nodes.append(left);
151         append(string);
152         m_nodes.append(right);
153 }
154
155 QString TM::HtmlConverter::to_string()
156 {
157         QString result;
158
159         adjust();
160         for(Html::pointer node : m_nodes) result += node->to_string();
161
162         return result;
163 }
164
165 void TM::HtmlConverter::adjust()
166 {
167         int adjusted = 0;
168         do
169         {
170                 adjusted = 0;
171                 for(int i = 0; i < m_nodes.size() - 1; i++)
172                 {
173                         HtmlNode::pointer p1 = m_nodes.at(i);
174                         if(p1->type() != Html::Element || p1->place() != Html::Close) continue;
175                         HtmlNode::pointer p2 = m_nodes.at(i + 1);
176                         if(p2->type() != Html::Element || p2->place() != Html::Open) continue;
177
178                         if(p1->lbegin() == p2->lbegin())
179                         {
180                                 m_nodes.removeAt(i);
181                                 m_nodes.removeAt(i);
182                                 ++adjusted;
183                         }
184                 }
185         }
186         while(adjusted);
187 }
188
189 // TextSegment ----------------------------------------------------------------
190
191 TM::TextSegment::TextSegment(
192                 Service *service, int scode, int segment_id, QString source)
193         : m_segment_id(segment_id)
194         , m_document(source.toUtf8())
195 {
196         HtmlNode body = m_document.first("html").first("body");
197         TextConverter tc;
198         m_text = tc.to_text(HtmlRange(body, body));
199
200         Text::pointer sentences = service->divide_into_sentences(scode, m_text->to_string());
201         int i = 0;
202         int previous_crc = 0;
203         for(Text::pointer s = sentences->begin(); s; s = s->next()) // s: sentence
204         {
205                 Text::pointer words = service->divide_into_words(scode, s);
206                 int crc = service->crc32(scode, words->to_string());
207                 TextSentence::pointer sentence =
208                                 TextSentence::create(segment_id, i++, crc, words);
209                 sentence->set_previous_crc(previous_crc);
210                 m_sentences.append(sentence);
211                 previous_crc = crc;
212         }
213
214         int next_crc = 0;
215         for(int i = m_sentences.size() - 1; 0 <= i; i--)
216         {
217                 TextSentence::pointer sentence = m_sentences.at(i);
218                 sentence->set_next_crc(next_crc);
219                 next_crc = sentence->crc();
220         }
221 }
222
223 int TM::TextSegment::segment_id() const { return m_segment_id; }
224
225 int TM::TextSegment::size() const { return m_sentences.size(); }
226
227 TM::TextSentence::pointer TM::TextSegment::at(int index)
228 {
229         assert(0 <= index && index < m_sentences.size());
230         return m_sentences.at(index);
231 }
232
233 TM::TextSegment::iterator TM::TextSegment::begin() { return m_sentences.begin(); }
234
235 TM::TextSegment::iterator TM::TextSegment::end() { return m_sentences.end(); }
236
237 HtmlNode::pointer TM::TextSegment::find_html_node_by_offset(int offset)
238 {
239         HtmlNode::pointer result;
240
241         for(Text::pointer p = m_text->begin(); p; p = p->next())
242         {
243                 UserData::pointer ud = p->data();
244                 assert(ud->type() == HtmlData::Type);
245                 HtmlData *hd = static_cast<HtmlData*>(ud.get());
246                 if(hd->begin() <= offset && offset <= hd->tail())
247                 {
248                         result = hd->node().lself();
249                         break;
250                 }
251         }
252         return result;
253 }
254
255 /*!
256  * \brief セグメント全体をHTML文字列に変換します。
257  */
258 QString TM::TextSegment::to_html()
259 {
260         QString result;
261         for(TextSentence::pointer sentence : m_sentences)
262         {
263                 if(!sentence->target_sentence())
264                         result += to_html_from_source(sentence);
265                 else result += to_html_from_target(sentence);
266                 result += "\r\n";
267         }
268         return result;
269 }
270
271 /*!
272  * \brief 原文をHTMLに変換します。
273  *
274  * 訳文のついていない文のために在ります。
275  */
276 QString TM::TextSegment::to_html_from_source(TextSentence::pointer sentence)
277 {
278         HtmlConverter hc;
279
280         UserData *ud = sentence->source_sentence()->data().get();
281         assert(ud->type() == RangeData::Type);
282         RangeData *rd = static_cast<RangeData*>(ud);
283         int sentence_offset = rd->begin();
284
285         Text::pointer source_sentence = sentence->source_sentence();
286         if(!source_sentence) return "";
287
288         for(Text::pointer word = source_sentence->begin(); word; word = word->next())
289         {
290                 QString string = word->to_string();
291                 UserData *ud = word->data().get();
292                 assert(ud->type() == RangeData::Type);
293                 RangeData *rd = static_cast<RangeData*>(ud);
294                 int word_offset = sentence_offset + rd->begin();
295                 HtmlNode::pointer node = find_html_node_by_offset(word_offset);
296                 if(node) hc.append(node, string);
297                 else hc.append(string);
298         }
299         return hc.to_string();
300 }
301
302 /*!
303  * \brief 訳文をHTMLに変換します。
304  */
305 QString TM::TextSegment::to_html_from_target(TextSentence::pointer sentence)
306 {
307         HtmlConverter hc;
308
309         WordLinker* linker = sentence->linker();
310         UserData *ud = sentence->source_sentence()->data().get();
311         assert(ud->type() == RangeData::Type);
312         RangeData *rd = static_cast<RangeData*>(ud);
313         int sentence_offset = rd->begin();
314
315         Text::pointer target_sentence = sentence->target_sentence();
316         if(!target_sentence) return "";
317
318         for(Text::pointer word = target_sentence->begin(); word; word = word->next())
319         {
320                 QString string = word->to_string();
321                 WordLink::pointer link = linker->find(WordLink::Target, word);
322                 if(!link) hc.append(string);
323                 else
324                 {
325                         UserData::pointer ud = link->sources()->at(0)->data();
326                         assert(ud->type() == RangeData::Type);
327                         RangeData *rd = static_cast<RangeData*>(ud.get());
328                         int word_offset = sentence_offset + rd->begin();
329                         HtmlNode::pointer node = find_html_node_by_offset(word_offset);
330                         if(node) hc.append(node, string);
331                         else hc.append(string);
332                 }
333         }
334         return hc.to_string();
335 }
336
337 TM::TextSegment::pointer TM::TextSegment::create(
338                 Service *service, int scode, int segment_id, QString source)
339 {
340         return pointer(new TextSegment(service, scode, segment_id, source));
341 }
342
343 // SocketConnection -----------------------------------------------------------
344
345 TM::SocketConnection::SocketConnection(Settings *settings, Service *service,
346                                                                         EditorWidget *editor_widget, QWebSocket *socket)
347         : QObject(socket)
348         , m_settings(settings)
349         , m_service(service)
350         , m_mutex(QMutex::Recursive)
351         , m_editor_widget(editor_widget)
352         , m_site_id(0)
353         , m_edit_mode(false)
354         , m_source_language_code(0)
355         , m_target_language_code(0)
356 {
357         qRegisterMetaType<pointer>();
358
359         connect(socket, SIGNAL(textMessageReceived(QString const&)),
360                         this, SLOT(onTextMessageReceived(QString const&)));
361         connect(socket, SIGNAL(binaryMessageReceived(QByteArray const&)),
362                         this, SLOT(onBinaryMessageReceived(QByteArray const&)));
363 }
364
365 TM::SocketConnection::~SocketConnection()
366 {
367         m_editor_widget->detach(this);
368 }
369
370 QWebSocket* TM::SocketConnection::socket()
371 {
372         QWebSocket * result = qobject_cast<QWebSocket*>(parent());
373         return result;
374 }
375
376 void TM::SocketConnection::send_message(QString const &message)
377 {
378         QWebSocket *ws = socket();
379         if(ws) ws->sendTextMessage(message);
380 }
381
382 void TM::SocketConnection::send_message(QJsonObject const &json)
383 {
384         QJsonDocument doc;
385         doc.setObject(json);
386         send_message(doc.toJson().data());
387 }
388
389 /*!
390  * \brief データベースへセンテンスの登録とブラウザへの反映を行います。
391  * \param segment_id セグメントのID。
392  * \param index センテンスのインデックス。
393  */
394 void TM::SocketConnection::save_sentence(int segment_id, int index)
395 {
396         assert(m_site_id);
397
398         // セグメントの検索。
399         segment_map_iterator it = m_segments.find(segment_id);
400         assert(it != m_segments.end());
401         TextSegment::pointer segment = it.value();
402
403         // センテンスの検索。
404         TextSentence::pointer sentence = segment->at(index);
405         assert(sentence->source_sentence());
406         if(!sentence->target_sentence()) return;
407
408         // 言語コード。
409         int scode = m_editor_widget->source_language();
410         int tcode = m_editor_widget->target_language();
411
412         // データベースへ登録。
413         m_service->insert_sentence(segment_id, index,
414                                                 m_url.host(), scode, tcode, sentence, pointer(this));
415
416         // ブラウザへ反映。
417         set_segment(segment_id, segment->to_html());
418 }
419
420 /*!
421  * \brief データベースへセンテンスの消去とブラウザへの反映を行います。
422  * \param segment_id セグメントのID。
423  * \param index センテンスのインデックス。
424  *
425  * エディタから呼び出されます。
426  * 呼び出されたとき、訳文はありません。
427  * そこで、clear系がサービスに依頼する時は、原文を引数として渡します。
428  */
429 void TM::SocketConnection::remove_sentence(int segment_id, int index)
430 {
431         assert(m_site_id);
432
433         // セグメントの検索。
434         segment_map_iterator it = m_segments.find(segment_id);
435         assert(it != m_segments.end());
436         TextSegment::pointer segment = it.value();
437
438         // センテンスの検索。
439         TextSentence::pointer sentence = segment->at(index);
440         assert(sentence->source_sentence());
441         // 訳文の消去。
442         sentence->set_target_sentence(Text::pointer());
443
444         // 言語コード。
445         int scode = m_editor_widget->source_language();
446         int tcode = m_editor_widget->target_language();
447
448         // データベースとサーバへ反映。
449         m_service->remove_sentence(m_url.host(), scode, tcode, sentence);
450
451         // ブラウザへ反映。
452         set_segment(segment_id, segment->to_html());
453 }
454
455 /*!
456  * \brief TM::SocketConnection::sentence_found
457  * \param segment_id
458  * \param index
459  * \param result データベースが返す訳文。
460  */
461 void TM::SocketConnection::sentence_found(
462                 int segment_id, int index, sentence_data_type::pointer result)
463 {
464         if(!result) return; // 検索がヒットしなかった場合、何もしない。
465
466         segment_map_iterator it = m_segments.find(segment_id);
467         assert(it != m_segments.end());
468         TextSegment::pointer segment = it.value();
469
470         segment->at(index)->set_source_id(result->source_id);
471         if(100 <= result->quality) // 品質が100以上の場合、セグメントとブラウザを更新する。
472         {
473                 Text::pointer sentences = m_service->divide_into_sentences(
474                                                                                 m_target_language_code, result->sentence);
475                 Text::pointer words = m_service->divide_into_words(
476                                                                                 m_target_language_code, sentences->begin());
477                 segment->at(index)->set_target_sentence(words);
478                 segment->at(index)->set_json(result->json);
479
480                 set_segment(segment_id, segment->to_html()); // ブラウザに送信
481         }
482         else // 品質が100未満の場合、候補として登録する。
483         {
484                 segment->at(index)->append(result);
485         }
486 }
487
488 void TM::SocketConnection::sentence_inserted(int segment_id, int index,
489                                                 quint32 source_id, quint32 target_id)
490 {
491         segment_map_iterator it = m_segments.find(segment_id);
492         assert(it != m_segments.end());
493         TextSegment::pointer segment = it.value();
494
495         assert(source_id);
496         TextSentence::pointer sentence = segment->at(index);
497         sentence->set_source_id(source_id);
498         sentence->set_target_id(target_id);
499 }
500
501 void TM::SocketConnection::changeEditMode(bool edit_mode)
502 {
503         set_edit_mode(edit_mode);
504 }
505
506 /*!
507  * \brief ブラウザに編集モードを反映します。
508  *
509  * ブラウザは、編集モードにある場合、クリックでedit_segmentを発行します。
510  * 編集モードではリンクのクリックは無効です。
511  */
512 void TM::SocketConnection::set_edit_mode(bool edit_mode)
513 {
514         if(edit_mode == m_edit_mode) return;
515
516         m_edit_mode = edit_mode;
517         QJsonObject json;
518         json["cmd"] = "set_edit_mode";
519         json["edit_mode"] = edit_mode;
520         send_message(json);
521 }
522
523 /*!
524  * \brief ウェブブラウザへセグメントの訳文をセットします。
525  * \param segment_id セグメントのID。
526  * \param html セグメントに対応するHTML文字列。
527  */
528 void TM::SocketConnection::set_segment(int segment_id, QString html)
529 {
530         QJsonObject json;
531         json["cmd"] = "set_segment";
532         json["segment_id"] = segment_id;
533         json["html"] = html;
534         send_message(json);
535 }
536
537 /*!
538  * \brief ウェブブラウザ上でクリックされ、edit_segmentコマンドが発行されたときに
539  * 呼び出されます。
540  */
541 void TM::SocketConnection::do_edit_segment(QJsonObject const &json)
542 {
543         assert(json.contains("segment_id"));
544         int segment_id = json["segment_id"].toString().toInt();
545
546         segment_map_iterator it = m_segments.find(segment_id);
547         assert(it != m_segments.end());
548         TextSegment::pointer segment = it.value();
549
550         if(segment != m_current_segment)
551         {
552                 m_current_segment = segment;
553                 m_editor_widget->set_segment(m_current_segment);
554         }
555 }
556
557 /*!
558  * \brief ウェブブラウザ上でドキュメントがフォーカスを取得し、
559  * focusコマンドが発行されたときに呼び出されます。
560  */
561 void TM::SocketConnection::do_focus(QJsonObject const &)
562 {
563         m_editor_widget->attach(this);
564
565         m_editor_widget->set_edit_mode(m_edit_mode);
566         if(m_current_segment) m_editor_widget->set_segment(m_current_segment);
567 }
568
569 void TM::SocketConnection::do_blur(QJsonObject const &)
570 {
571         //m_editor_widget->detach(this);
572 }
573
574 void TM::SocketConnection::do_load(QJsonObject const &json)
575 {
576         m_editor_widget->attach(this);
577
578         set_edit_mode(m_editor_widget->edit_mode());
579
580         m_source_language_code = m_editor_widget->source_language();
581         m_target_language_code = m_editor_widget->target_language();
582         m_url = QUrl(json["url"].toString());
583         m_site_id = m_service->find_site_id(m_url.host());
584 }
585
586 /*!
587  * \brief ブラウザがセグメントを読み込んだ結果呼び出されます。
588  *
589  * この処理の中で、セグメントの設定、完全一致訳文の検索を行います。
590  * セグメント全体の検索が終わった時点でブラウザへsegment_loaded応答を返します。
591  * ブラウザは、segment_loadedを受けて、そのセグメントをクリック可能にします。
592  */
593 void TM::SocketConnection::do_load_segment(QJsonObject const &json)
594 {
595         assert(json.contains("segment_id"));
596         int segment_id = json["segment_id"].toString().toInt();
597         assert(!m_segments.contains(segment_id));
598         assert(json.contains("html"));
599         QString html = json["html"].toString();
600
601         // セグメントの挿入。
602         TextSegment::pointer segment = TextSegment::create(
603                 m_service, m_source_language_code, segment_id, html);
604         m_segments.insert(segment_id, segment);
605
606         // センテンス完全一致訳文の検索。
607         for(TextSentence::pointer sentence : *segment)
608         {
609                 m_service->find_sentence(
610                                 segment_id, sentence->index(),
611                                 m_url.host(), m_source_language_code, m_target_language_code,
612                                 sentence, pointer(this));
613         }
614 }
615
616 void TM::SocketConnection::onTextMessageReceived(QString const &message)
617 {
618         QJsonObject json = QJsonDocument::fromJson(message.toUtf8()).object();
619         QString cmd = json["cmd"].toString();
620         if(cmd == "edit_segment") do_edit_segment(json);
621         else if(cmd == "focus") do_focus(json);
622         else if(cmd == "blur") do_blur(json);
623         else if(cmd == "load") do_load(json);
624         else if(cmd == "load_segment") do_load_segment(json);
625 }
626
627 void TM::SocketConnection::onBinaryMessageReceived(QByteArray const &message)
628 {
629         qDebug() << message;
630 }
631
632 // SocketServer ---------------------------------------------------------------
633
634 TM::SocketServer::SocketServer(Settings *settings, Service* service,
635                                                         EditorWidget *editor_widget, QObject *parent)
636         : QObject(parent)
637         , m_settings(settings)
638         , m_service(service)
639         , m_server(new QWebSocketServer(QStringLiteral("wordring websocket"),
640                                                                         QWebSocketServer::NonSecureMode, this))
641         , m_editor_widget(editor_widget)
642 {
643         connect(m_server, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
644
645         quint16 port = m_settings->value("SocketServer/port").toUInt();
646         bool result = m_server->listen(QHostAddress::LocalHost, port);
647         if(!result) result = m_server->listen(QHostAddress::LocalHost, 0);
648         assert(result);
649         if(!result) qFatal("An error occured in SocketServer().");
650 }
651
652 TM::SocketServer::~SocketServer()
653 {
654 }
655
656 quint16 TM::SocketServer::port() const
657 {
658         return m_server->serverPort();
659 }
660
661 void TM::SocketServer::abort()
662 {
663         for(QWebSocket *ws : m_sockets)
664         {
665                 ws->abort();
666                 ws->deleteLater();
667         }
668         m_sockets.clear();
669 }
670
671 void TM::SocketServer::onNewConnection()
672 {
673         QWebSocket *socket = m_server->nextPendingConnection();
674         connect(socket, SIGNAL(disconnected()), this, SLOT(onDisconnected()));
675         new SocketConnection(m_settings, m_service, m_editor_widget, socket);
676         m_sockets.push_back(socket);
677 }
678
679 void TM::SocketServer::onDisconnected()
680 {
681         QWebSocket *socket = qobject_cast<QWebSocket*>(sender());
682         m_sockets.removeOne(socket);
683         socket->deleteLater();
684 }
685
686
687
688
689
690