1 /***************************************************************************
2 * Copyright (C) 2004 by Kita Developers *
3 * ikemo@users.sourceforge.jp *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 ***************************************************************************/
13 #include <QtCore/QDir>
14 #include <QtCore/QRegExp>
15 #include <QtCore/QStringList>
23 #include "datmanager.h"
24 #include "globalconfig.h"
25 #include "kita_misc.h"
26 #include "kita-utf8.h"
32 static const int RESDAT_DEFAULTSIZE = 10;
33 static const int RESDAT_DELTA = 1000;
36 /*------------------------------------------------------*/
37 /* DatInfo stores & handles all information about *.dat */
39 DatInfo::DatInfo(const KUrl& url) : m_threadIndex(m_datUrl),
40 m_access (0), m_access2(0)
43 m_datUrl = getDatUrl(url, refstr);
44 m_threadIndex = ThreadIndex(m_datUrl);
45 /* get the pointer of Thread class */
46 m_thread = Thread::getByUrlNew(m_datUrl);
50 m_thread = Thread::getByUrl(m_datUrl);
51 if (m_thread == 0) return ;
54 m_threadIndex.loadIndex(m_thread);
57 m_thread = Thread::getByUrl(m_datUrl);
59 /* japanese strings */
60 m_spacestr = QString::fromUtf8(KITAUTF8_ZENSPACE);
61 m_framestr1 = QString::fromUtf8(KITAUTF8_FRAME1); /* | */
62 m_framestr2 = QString::fromUtf8(KITAUTF8_FRAME2); /* |- */
63 m_framestr3 = QString::fromUtf8(KITAUTF8_FRAME3); /* L */
66 Cache cache(m_datUrl);
67 QString cacheDir = cache.getDirPath();
68 if (!QDir::root().mkpath(cacheDir)) return ;
82 /* Usually, don't call this. */ /* public */
85 return initPrivate(true);
88 /* Init. If loadCache = true, load data from cache. */ /* private */
89 void DatInfo::initPrivate(bool loadCache)
91 /* stop & delete dat loader */
101 increaseResDatVec(RESDAT_DEFAULTSIZE);
106 /* create dat loader */
107 m_access = new Access(m_datUrl);
109 connect(m_access, SIGNAL(receiveData(const QStringList&)),
110 SLOT(slotReceiveData(const QStringList&)));
111 connect(m_access, SIGNAL(finishLoad()), SLOT(slotFinishLoad()));
113 if (!loadCache) return ;
115 /* reset ReadNum before loading cache. */
116 /* ReadNum & subject are updated by Access::getcache() */
117 m_thread->setReadNum(0);
119 /* get dat from cahce */
120 /* slotReceiveData() is called from Access::getcache() */
121 m_access->getcache();
123 /* save up-to-date thread information */
124 m_threadIndex.saveIndex(m_thread);
129 void DatInfo::resetResDat(RESDAT& resdat) const
132 resdat.parsed = false;
133 resdat.broken = false;
134 resdat.anclist.clear();
135 resdat.checkAbone = false;
136 resdat.abone = false;
137 resdat.isResponsed = false;
142 void DatInfo::increaseResDatVec(int delta)
144 int size = m_resDatVec.size();
147 m_resDatVec.resize(size + delta);
148 for (int i = size; i < size + delta; i++)
149 m_resDatVec[i] = resdat;
153 /* delete dat loader */ /* private */
154 void DatInfo::deleteAccessJob()
162 m_access2->killJob();
169 const KUrl& DatInfo::url() const
176 /*--------------------------------------*/
177 /* cache handling functions */
181 /* When Access received new data,
182 slotReceiveData is called. */
184 /* When Access fineshed loading,
185 slotFinishLoad is called, and
186 DatInfo emits the finishLoad signal to the parent object */ /* public */
187 bool DatInfo::updateCache(const QObject* parent)
196 connect(this, SIGNAL(receiveData()),
197 parent, SLOT(slotReceiveData()));
199 connect(this, SIGNAL(finishLoad()),
200 parent, SLOT(slotFinishLoad()));
202 m_access->getupdate(m_thread->readNum());
208 /* slot called when Access
209 received new data */ /* private slot */
210 void DatInfo::slotReceiveData(const QStringList& lineList)
212 int rescode = m_access->responseCode();
214 rescode = m_access2->responseCode();
217 if (rescode != 200 && rescode != 206) return ;
219 /* copy lines to buffer */
220 int count = lineList.count();
221 for (int i = 0; i < count ; ++i)
222 copyOneLineToResDat(lineList[ i ]);
228 /* copy one line to resdat.
229 See also DatInfo::slotReceiveData() */ /* private */
230 bool DatInfo::copyOneLineToResDat(const QString& line)
232 if (line.isEmpty()) return false;
235 const int num = m_thread->readNum() + 1;
236 m_thread->setReadNum(num);
238 /* If resdat vector is short, then resize the vector. */
239 while ((int) m_resDatVec.size() <= num)
240 increaseResDatVec(RESDAT_DELTA);
243 RESDAT& resdat = m_resDatVec[ num ];
247 resdat.linestr = line;
250 if (num == 1) parseDat(num);
252 /* search all responses which are responsed by this line. */
253 if (GlobalConfig::checkResponsed()) {
255 if (parseDat(num) && !checkAbonePrivate(num)) { /* parse line here to get AncList */
257 const int maxRange = 10;
259 AncList& anclist = m_resDatVec[ num ].anclist;
260 for (AncList::iterator it = anclist.begin(); it != anclist.end(); ++it) {
262 int fromNum = (*it).from;
263 int toNum = qMin(num - 1, (*it).to);
264 if (toNum - fromNum + 1 > maxRange) continue;
266 for (int i = fromNum; i <= toNum; ++i) {
268 if (!checkAbonePrivate(i)) m_resDatVec[ i ].isResponsed = true;
278 /* slot called when Access
279 finished loading new dat */ /* private slot */
280 void DatInfo::slotFinishLoad()
282 /* save thread information */
283 m_threadIndex.saveIndex(m_thread);
285 /* re-try by offlaw.cgi */
286 DatManager datManager(m_datUrl);
287 if (m_thread->readNum() == 0 && m_access2 == 0
288 && datManager.is2chThread()) {
289 if (Account::isLogged()) {
291 m_access2 = new OfflawAccess(m_datUrl);
292 connect(m_access2, SIGNAL(receiveData(const QStringList&)),
293 SLOT(slotReceiveData(const QStringList&)));
294 connect(m_access2, SIGNAL(finishLoad()), SLOT(slotFinishLoad()));
299 /* finish loading session & emit signal to the parent object */
300 m_nowLoading = false;
303 /* disconnect signals */
304 disconnect(SIGNAL(receiveData()));
305 disconnect(SIGNAL(finishLoad()));
310 int DatInfo::getResponseCode() const
312 return (m_access == 0) ? 0 : m_access->responseCode();
317 int DatInfo::getServerTime() const
319 return (m_access == 0) ? 0 : m_access->serverTime();
324 bool DatInfo::deleteCache()
336 bool DatInfo::isLoadingNow() const
344 void DatInfo::stopLoading() const
347 /* Don't lock the mutex here !!!
348 It will cause deadlock , because
349 Access::stopJob() calls HTMLPart::slotFinishLoad() back,
350 then HTMLPart::slotFinishLoad() calls another functions in DatInfo. */
359 /*------------------------------------------------------*/
360 /* get subject, linedata, id, body, name, HTML, etc. */
362 /* They are public */
364 QString DatInfo::getDat(int num)
366 return (!parseDat(num)) ? QString() : m_resDatVec[ num ].linestr;
369 QString DatInfo::getId(int num)
371 return (!parseDat(num)) ? QString() : m_resDatVec[ num ].id;
374 /* plain strings of name */
375 QString DatInfo::getPlainName(int num)
377 return (!parseDat(num)) ? QString() : m_resDatVec[ num ].name;
381 /* plain strings of title */
382 QString DatInfo::getPlainTitle(int num)
384 if (!parseDat(num)) return QString();
387 createTitleHtml(m_resDatVec[ num ], titleHtml);
390 Parser::datToText(titleHtml, retStr);
396 /* plain strings of body */
397 QString DatInfo::getPlainBody(int num)
399 if (!parseDat(num)) return QString();
402 Parser::datToText(m_resDatVec[ num ].bodyHTML, retStr);
408 /*-----------------------------------------*/
412 /* get HTML strings of title & body.
414 return values are defined in datinfo.h. */ /* public */
416 int DatInfo::getHTML(int num, bool checkAbone, QString& titleHTML,
419 return getHTMLPrivate(num, checkAbone, titleHTML, bodyHTML);
424 * @param[in] checkAbone
426 * @param[out] titleHTML
427 * @param[out] bodyHTML
429 * @retval HTML_NOTPARSED The dat is not parsed.
430 * @retval HTML_ABONE The res dat is marked as abone.
431 * @retval HTML_BROKEN The res dat is marked as broken.
432 * @retval HTML_NORMAL The res dat is normal.
435 int DatInfo::getHTMLPrivate(int num, bool checkAbone, QString& titleHtml,
438 if (!parseDat(num)) return HTML_NOTPARSED;
440 bool abone = checkAbone & checkAbonePrivate(num);
441 RESDAT& resdat = m_resDatVec[ num ];
444 titleHtml = QString::number(num) + ' ' + i18n("Abone");
445 bodyHtml = "<a href=\"#abone" + QString::number(num) + "\">";
446 bodyHtml += i18n("Abone") + "</a>";
449 } else if (resdat.broken) {
450 titleHtml = QString::number(num) + ' ' + i18n("Broken");
451 bodyHtml = i18n("Broken");
455 createTitleHtml(resdat, titleHtml);
456 bodyHtml = resdat.bodyHTML;
462 /* get HTML strings from startnum to endnum.
464 return value is HTML strings */ /* public */
465 QString DatInfo::getHTMLString(int startnum, int endnum, bool checkAbone)
469 for (int num = startnum; num <= endnum; num++) {
472 getHtmlOfOneRes(num, checkAbone, html);
480 /* return HTML strings that have ID = strid. */ /* public */
481 QString DatInfo::getHtmlById(const QString& strid, int &count)
486 for (int i = 1; i <= m_thread->readNum(); i++) {
488 if (!parseDat(i)) continue;
490 if (m_resDatVec[ i ].id == strid) {
494 getHtmlOfOneRes(i, true, html);
504 * convert res dat to html.
507 * @param[in] checkAbone
512 void DatInfo::getHtmlOfOneRes(int num, bool checkAbone, QString& html)
515 QString titleHTML, bodyHTML;
516 if (getHTMLPrivate(num, checkAbone, titleHTML, bodyHTML) == HTML_NOTPARSED)
519 if (m_resDatVec[ num ].isResponsed)
521 "<a href=\"#write", "<a class=\"coloredLink\" href=\"#write");
522 html += "<div class=\"pop_res_title\">" + titleHTML + "</div>";
523 html += "<div class=\"pop_res_body\">" + bodyHTML + "</div>";
528 /*-------------------------------*/
529 /* Get HTML document of res tree.*/
530 /* For example, when rootnum = 1,
536 |-->>20, and return count = 3. */
538 /* Note that this function checks Abone internally. */ /* public */
539 QString DatInfo::getTreeByRes(int rootnum, int& count)
541 return getTreeByResPrivate(rootnum, false, count);
544 /*---------------------------------------*/
545 /* Get HTML document of reverse res tree.*/
546 /* For example, when rootnum = 10,
552 |-->>6, and returns count = 3. */
554 /* Note that this function checks Abone internally. */ /* public */
555 QString DatInfo::getTreeByResReverse(int rootnum, int& count)
557 return getTreeByResPrivate(rootnum, true, count);
562 QString DatInfo::getTreeByResPrivate(int rootnum,
563 bool reverse /* reverse search */, int& count)
566 QString tmp = QString::number(rootnum);
567 QString retstr = "<a href=\"#" + tmp + "\">>>" + tmp + "</a><br>";
569 retstr += getTreeByResCore(rootnum, reverse, count, "");
575 QString DatInfo::getTreeByResCore(int rootnum,
576 bool reverse /* reverse search */, int& count, const QString& prestr)
578 if (!parseDat(rootnum))
580 if (checkAbonePrivate(rootnum))
585 QStringList strlists;
589 /* collect responses that have anchor to rootnum */
590 for (int i = rootnum + 1; i <= m_thread->readNum(); i++) {
591 if (checkAbonePrivate(i)) continue;
592 if (checkRes(i, rootnum)) {
594 strlists += QString::number(i);
598 } else { /* collect responses for which rootnum has anchors */
600 AncList& anclist = m_resDatVec[ rootnum ].anclist;
601 for (AncList::iterator it = anclist.begin(); it != anclist.end(); ++it) {
602 for (int i = (*it).from; i <= qMin(rootnum - 1, (*it).to) ; i++) {
603 if (checkAbonePrivate(i)) continue;
605 strlists += QString::number(i);
610 /* make HTML document */
613 for (QStringList::iterator it = strlists.begin(); it != strlists.end(); ++it) {
615 if ((*it) == strlists.last()) tmpstr = m_framestr3; /* 'L' */
616 else tmpstr = m_framestr2; /* '|-' */
618 retstr += prestr + tmpstr + "<a href=\"#" + (*it) + "\">>>" + (*it) + "</a><br>";
620 /* call myself recursively */
623 if ((*it) == strlists.last()) tmpstr += m_spacestr + m_spacestr + m_spacestr; /* " " */
624 else tmpstr += m_framestr1 + m_spacestr; /* "| " */
625 retstr += getTreeByResCore((*it).toInt(), reverse, tmpnum, tmpstr);
635 /*----------------------------------------------*/
636 /* Check if No.num has anchors to No.target */
637 /* For exsample, if target = 4, and No.num have
638 an anchor >>4, or >>2-6, etc.,
639 then return true. */ /* private */
640 bool DatInfo::checkRes(int num, int target)
642 const int range = 20;
646 AncList& anclist = m_resDatVec[ num ].anclist;
648 for (AncList::iterator it = anclist.begin(); it != anclist.end(); ++it) {
649 if ((*it).to - (*it).from > range)
651 if (target >= (*it).from && target <= (*it).to)
660 /*-----------------------*/
661 /* several information */
663 int DatInfo::getResNum() const
665 return m_thread->resNum();
669 int DatInfo::getReadNum() const
671 return m_thread->readNum();
675 int DatInfo::getViewPos() const
677 return m_thread->viewPos();
681 /* return number of responses that have ID = strid. */
682 /* Note that this function checks Abone internally. */ /* public */
683 int DatInfo::getNumById(const QString& strid)
687 for (int i = 1; i <= m_thread->readNum(); i++) {
691 if (checkAbonePrivate(i))
694 if (m_resDatVec[ i ].id == strid)
703 int DatInfo::getDatSize() const
705 return (m_access == 0) ? 0 : m_access->dataSize();
710 bool DatInfo::isResponsed(int num) const
712 return m_resDatVec[ num ].isResponsed;
717 bool DatInfo::isResValid(int num)
719 return parseDat(num);
723 bool DatInfo::isBroken() const
731 int rescode = m_access->responseCode();
732 bool invalid = m_access->invalidDataReceived();
734 /* see also Access::slotReceiveThreadData() */
735 if (invalid && (rescode == 200 || rescode == 206)) return true;
737 /* maybe "Dat Ochi" */
742 bool DatInfo::isResBroken(int num)
744 return (!parseDat(num)) ? false : m_resDatVec[ num ].broken;
747 /* ID = strid ? */ /* public */
748 bool DatInfo::checkId(const QString& strid, int num)
753 if (m_resDatVec[ num ].id == strid)
760 /* Are keywords included ? */ /* public */
761 bool DatInfo::checkWord(const QStringList& stlist, /* list of keywords */
762 int num, bool checkOr /* AND or OR search */)
767 QString str_text = m_resDatVec[ num ].bodyHTML;
769 for (QStringList::const_iterator it = stlist.begin(); it != stlist.end(); ++it) {
771 QRegExp regexp((*it));
772 regexp.setCaseSensitivity(Qt::CaseInsensitive);
774 if (checkOr) { /* OR */
775 if (str_text.indexOf(regexp, 0) != -1) {
779 if (str_text.indexOf(regexp, 0) == -1) return false;
783 if (checkOr) return false;
790 /*--------------------------------*/
791 /* abone functions */
794 /*-----------------------*/
797 /* call this when config
798 of abone changed. */ /* public */
800 void DatInfo::resetAbone()
802 return resetAbonePrivate();
806 void DatInfo::resetAbonePrivate()
808 for (int i = 1; i < (int) m_resDatVec.size(); i++)
809 m_resDatVec[ i ].checkAbone = false;
811 m_aboneByID = (!AboneConfig::aboneIDList().isEmpty());
812 m_aboneByName = (!AboneConfig::aboneNameList().isEmpty());
813 m_aboneByBody = (!AboneConfig::aboneWordList().isEmpty());
814 m_aboneChain = (m_aboneByID | m_aboneByName | m_aboneByBody)
815 & GlobalConfig::aboneChain() ;
820 /* check abone */ /* public */
822 bool DatInfo::checkAbone(int num)
824 return checkAbonePrivate(num);
829 bool DatInfo::checkAbonePrivate(int num)
834 if (m_resDatVec[num].checkAbone)
835 return m_resDatVec[num].abone;
837 m_resDatVec[num].checkAbone = true;
838 bool checktmp = false;
841 checktmp = checkAboneCore(m_resDatVec[num].id,
842 AboneConfig::aboneIDList());
844 if (!checktmp && m_aboneByName)
845 checktmp = checkAboneCore(m_resDatVec[num].name,
846 AboneConfig::aboneNameList());
848 if (!checktmp && m_aboneByBody)
849 checktmp = checkAboneCore(m_resDatVec[num].bodyHTML,
850 AboneConfig::aboneWordList());
852 if (!checktmp && m_aboneChain) {
853 AncList & anclist = m_resDatVec[num].anclist;
855 for (AncList::iterator it = anclist.begin();
856 it != anclist.end() && !checktmp ; ++it) {
858 int refNum = (*it).from;
859 int refNum2 = (*it).to;
861 /* I don't want to enter loop... */
862 if (refNum >= num) continue;
863 if (refNum2 >= num) refNum2 = num - 1;
865 for (int i = refNum; i <= refNum2; i++) {
866 if (checkAbonePrivate(i)) {
874 m_resDatVec[num].abone = checktmp;
876 return m_resDatVec[num].abone;
880 bool DatInfo::checkAboneCore(const QString& str, const QStringList& strlist)
883 if (strlist.count()) {
886 for (QStringList::const_iterator it = strlist.begin();
887 it != strlist.end(); ++it) {
888 i = str.indexOf((*it));
900 /* parsing function for ResDat */
901 /* This function parses the raw data by parseResDat() */ /* private */
902 bool DatInfo::parseDat(int num)
904 if (num <= 0 || m_thread->readNum() < num)
906 if (m_resDatVec[ num ].parsed)
909 // qDebug("parseDat %d",num);
912 Parser::parseResDat(m_resDatVec[ num ], subject);
913 if (num == 1 && !subject.isEmpty())
914 m_thread->setThreadName(subject);
915 if (m_resDatVec[ num ].broken)
921 bool DatInfo::isOpened() const
926 void DatInfo::setOpened(bool isOpened)
928 m_isOpened = isOpened;
931 /* create HTML of title.
933 struct RESDAT resdat should be parsed by parseResDat before calling this function.
938 void DatInfo::createTitleHtml(RESDAT& resdat, QString& titleHtml)
941 if (!resdat.parsed) return ;
943 bool showMailAddress = GlobalConfig::showMailAddress();
944 bool useTableTag = GlobalConfig::useStyleSheet();
946 if (useTableTag) titleHtml += "<table class=\"res_title\"><tr>";
949 if (useTableTag) titleHtml += "<td class=\"res_title_number\">";
950 titleHtml += "<a href=\"#write" + QString::number(resdat.num) + "\">";
951 titleHtml += QString::number(resdat.num);
952 titleHtml += "</a> ";
954 /* name & mail address */
955 if (useTableTag) titleHtml += "<td class=\"res_title_name\">";
956 titleHtml += "<b>" + QString::fromUtf8(KITAUTF8_NAME);
958 /* show name with mail address */
959 if (showMailAddress) {
961 titleHtml += resdat.nameHTML;
962 if (!resdat.address.isEmpty()) titleHtml += " [" + resdat.address + ']';
964 } else { /* don't show mail address */
966 if (resdat.address.isEmpty()) {
968 titleHtml += "<span class=\"name_noaddr\">";
969 titleHtml += resdat.name;
970 titleHtml += "</span>";
974 titleHtml += "<a href=\"mailto:" + resdat.address + "\"";
975 titleHtml += " title=\"" + resdat.address + "\">";
976 titleHtml += resdat.name;
981 titleHtml += "</b> ";
984 if (useTableTag) titleHtml += "<td class=\"res_title_date\">";
985 titleHtml += QString::fromUtf8(KITAUTF8_COLON) + resdat.date;
986 if (useTableTag) titleHtml += "</td>";
989 if (!resdat.id.isEmpty()) {
991 if (useTableTag) titleHtml += "<td class=\"res_title_id\">";
992 if (resdat.id.count("???") >= 1) titleHtml += " ID:" + resdat.id;
993 else titleHtml += " <a href=\"#idpop" + resdat.id + "\">ID</a>" + ":" + resdat.id;
994 if (useTableTag) titleHtml += "</td>";
998 if (!resdat.be.isEmpty()) {
1000 if (useTableTag) titleHtml += "<td class=\"res_title_be\">";
1001 titleHtml += " <a href=\"#bepop" + resdat.be + "\">?" + resdat.bepointmark + "</a>";
1002 if (useTableTag) titleHtml += "</td>";
1006 if (!resdat.host.isEmpty()) {
1008 if (useTableTag) titleHtml += "<td class=\"res_title_host\">";
1009 titleHtml += " HOST:" + resdat.host;
1010 if (useTableTag) titleHtml += "</td>";
1013 if (useTableTag) titleHtml += "</tr></table>";