OSDN Git Service

データベースからリンクを再生記念バックアップ♪
[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(m_site_id, scode, tcode, sentence);
414
415         // ブラウザへ反映。
416         set_segment(segment_id, segment->to_html());
417 }
418
419 void TM::SocketConnection::sentence_found(
420                 int segment_id, int index, sentence_data_type::pointer result)
421 {
422         if(!result) return;
423
424         segment_map_iterator it = m_segments.find(segment_id);
425         assert(it != m_segments.end());
426         TextSegment::pointer segment = it.value();
427
428         Text::pointer sentences = m_service->divide_into_sentences(
429                                 m_target_language_code, result->sentence);
430         Text::pointer words = m_service->divide_into_words(
431                                 m_target_language_code, sentences->begin());
432         segment->at(index)->set_target_sentence(words);
433
434         segment->at(index)->set_json(result->json);
435
436         set_segment(segment_id, segment->to_html());
437 }
438
439 void TM::SocketConnection::changeEditMode(bool edit_mode)
440 {
441         set_edit_mode(edit_mode);
442 }
443
444 /*!
445  * \brief ブラウザに編集モードを反映します。
446  *
447  * ブラウザは、編集モードにある場合、クリックでedit_segmentを発行します。
448  * 編集モードではリンクのクリックは無効です。
449  */
450 void TM::SocketConnection::set_edit_mode(bool edit_mode)
451 {
452         if(edit_mode == m_edit_mode) return;
453
454         m_edit_mode = edit_mode;
455         QJsonObject json;
456         json["cmd"] = "set_edit_mode";
457         json["edit_mode"] = edit_mode;
458         send_message(json);
459 }
460
461 /*!
462  * \brief ウェブブラウザへセグメントの訳文をセットします。
463  * \param segment_id セグメントのID。
464  * \param html セグメントに対応するHTML文字列。
465  */
466 void TM::SocketConnection::set_segment(int segment_id, QString html)
467 {
468         QJsonObject json;
469         json["cmd"] = "set_segment";
470         json["segment_id"] = segment_id;
471         json["html"] = html;
472         send_message(json);
473 }
474
475 /*!
476  * \brief ウェブブラウザ上でクリックされ、edit_segmentコマンドが発行されたときに
477  * 呼び出されます。
478  */
479 void TM::SocketConnection::do_edit_segment(QJsonObject const &json)
480 {
481         assert(json.contains("segment_id"));
482         int segment_id = json["segment_id"].toString().toInt();
483
484         segment_map_iterator it = m_segments.find(segment_id);
485         assert(it != m_segments.end());
486         TextSegment::pointer segment = it.value();
487
488         if(segment != m_current_segment)
489         {
490                 m_current_segment = segment;
491                 m_editor_widget->set_segment(m_current_segment);
492         }
493 }
494
495 /*!
496  * \brief ウェブブラウザ上でドキュメントがフォーカスを取得し、
497  * focusコマンドが発行されたときに呼び出されます。
498  */
499 void TM::SocketConnection::do_focus(QJsonObject const &)
500 {
501         m_editor_widget->attach(this);
502
503         m_editor_widget->set_edit_mode(m_edit_mode);
504         if(m_current_segment) m_editor_widget->set_segment(m_current_segment);
505 }
506
507 void TM::SocketConnection::do_blur(QJsonObject const &)
508 {
509         //m_editor_widget->detach(this);
510 }
511
512 void TM::SocketConnection::do_load(QJsonObject const &json)
513 {
514         m_editor_widget->attach(this);
515
516         set_edit_mode(m_editor_widget->edit_mode());
517
518         m_source_language_code = m_editor_widget->source_language();
519         m_target_language_code = m_editor_widget->target_language();
520         m_url = QUrl(json["url"].toString());
521         m_site_id = m_service->find_site_id(m_url.host());
522 }
523
524 /*!
525  * \brief ブラウザがセグメントを読み込んだ結果呼び出されます。
526  *
527  * この処理の中で、セグメントの設定、完全一致訳文の検索を行います。
528  * セグメント全体の検索が終わった時点でブラウザへsegment_loaded応答を返します。
529  * ブラウザは、segment_loadedを受けて、そのセグメントをクリック可能にします。
530  */
531 void TM::SocketConnection::do_load_segment(QJsonObject const &json)
532 {
533         assert(json.contains("segment_id"));
534         int segment_id = json["segment_id"].toString().toInt();
535         assert(!m_segments.contains(segment_id));
536         assert(json.contains("html"));
537         QString html = json["html"].toString();
538
539         // セグメントの挿入。
540         TextSegment::pointer segment = TextSegment::create(
541                 m_service, m_source_language_code, segment_id, html);
542         m_segments.insert(segment_id, segment);
543
544         // センテンス完全一致訳文の検索。
545         for(TextSentence::pointer sentence : *segment)
546         {
547                 m_service->find_sentence(
548                                 m_site_id, m_source_language_code, m_target_language_code,
549                                 segment_id, sentence->index(),
550                                 sentence, pointer(this));
551         }
552         //m_service->find_sentence(m_site_id,)
553
554         // セグメントの挿
555         //QJsonObject json;
556         //json["cmd"] = "set_edit_mode";
557         //json["edit_mode"] = edit_mode;
558         //send_message(json);
559 }
560
561 void TM::SocketConnection::onTextMessageReceived(QString const &message)
562 {
563         QJsonObject json = QJsonDocument::fromJson(message.toUtf8()).object();
564         QString cmd = json["cmd"].toString();
565         if(cmd == "edit_segment") do_edit_segment(json);
566         else if(cmd == "focus") do_focus(json);
567         else if(cmd == "blur") do_blur(json);
568         else if(cmd == "load") do_load(json);
569         else if(cmd == "load_segment") do_load_segment(json);
570 }
571
572 void TM::SocketConnection::onBinaryMessageReceived(QByteArray const &message)
573 {
574         qDebug() << message;
575 }
576
577 // SocketServer ---------------------------------------------------------------
578
579 TM::SocketServer::SocketServer(Settings *settings, Service* service,
580                                                         EditorWidget *editor_widget, QObject *parent)
581         : QObject(parent)
582         , m_settings(settings)
583         , m_service(service)
584         , m_server(new QWebSocketServer(QStringLiteral("wordring websocket"),
585                                                                         QWebSocketServer::NonSecureMode, this))
586         , m_editor_widget(editor_widget)
587 {
588         connect(m_server, SIGNAL(newConnection()), this, SLOT(onNewConnection()));
589
590         quint16 port = m_settings->value("SocketServer/port").toUInt();
591         bool result = m_server->listen(QHostAddress::LocalHost, port);
592         if(!result) result = m_server->listen(QHostAddress::LocalHost, 0);
593         if(!result) qFatal("An error occured in SocketServer().");
594 }
595
596 TM::SocketServer::~SocketServer()
597 {
598 }
599
600 quint16 TM::SocketServer::port() const
601 {
602         return m_server->serverPort();
603 }
604
605 void TM::SocketServer::abort()
606 {
607         for(QWebSocket *ws : m_sockets)
608         {
609                 ws->abort();
610                 ws->deleteLater();
611         }
612         m_sockets.clear();
613 }
614
615 void TM::SocketServer::onNewConnection()
616 {
617         QWebSocket *socket = m_server->nextPendingConnection();
618         connect(socket, SIGNAL(disconnected()), this, SLOT(onDisconnected()));
619         new SocketConnection(m_settings, m_service, m_editor_widget, socket);
620         m_sockets.push_back(socket);
621 }
622
623 void TM::SocketServer::onDisconnected()
624 {
625         QWebSocket *socket = qobject_cast<QWebSocket*>(sender());
626         m_sockets.removeOne(socket);
627         socket->deleteLater();
628 }
629
630
631
632
633
634