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 ***************************************************************************/
11 #include "boardmanager.h"
13 #include <QtCore/QDateTime>
14 #include <QtCore/QDir>
15 #include <QtCore/QFile>
16 #include <QtCore/QRegExp>
17 #include <QtCore/QTextCodec>
18 #include <QtCore/QTextStream>
20 #include <kfilterdev.h>
21 #include <kio/netaccess.h>
22 #include <kio/slaveconfig.h>
26 #include "favoriteboards.h"
27 #include "favoritethreads.h"
28 #include "kita_misc.h"
30 #include "threadindex.h"
31 #include "threadinfo.h"
36 /*---------------------------------------------------------*/
40 BoardData::BoardData(const QString& boardName,
41 const QString& hostname,
42 const QString& rootPath,
43 const QString& delimiter,
44 const QString& bbsPath,
47 : m_boardName(boardName), m_readIdx(false), m_rootPath(rootPath),
48 m_delimiter(delimiter), m_bbsPath(bbsPath), m_ext(ext), m_type(boardtype)
50 /* set hostname and create URL of board */
51 setHostName(hostname);
53 /* create default key */
54 QStringList keyHosts(m_hostname);
57 /* reset SETTING.TXT */
58 setSettingLoaded(false);
61 BoardData::~BoardData()
66 void BoardData::setHostName(const QString& hostName)
68 m_hostname = hostName;
70 /* m_basePath = (hostname)/(rootPath)/(bbsPath)/ */
71 m_basePath = m_hostname + m_rootPath + m_bbsPath + '/';
75 case Board_MachiBBS: /* m_cgiBasePath = (hostname)/(rootPath)/(delimiter)?BBS=(bbsPath) */
76 m_cgiBasePath = m_hostname + m_rootPath + m_delimiter + "?BBS=" + m_bbsPath.mid(1);
79 /* m_cgiBasePath = (hostname)/(rootPath)/(delimiter)/(bbsPath)/ */
81 m_cgiBasePath = m_hostname + m_rootPath + m_delimiter + m_bbsPath + '/';
87 /*---------------------------*/
91 bool BoardData::readIdx() const
97 void BoardData::setReadIdx(bool idx)
103 const QString& BoardData::boardName() const
109 const QString& BoardData::hostName() const
115 const QString& BoardData::rootPath() const
121 const QString& BoardData::delimiter() const
127 const QString& BoardData::bbsPath() const
133 const QString& BoardData::ext() const
139 int BoardData::type() const
145 const QString& BoardData::basePath() const
151 const QString& BoardData::cgiBasePath() const
153 return m_cgiBasePath;
157 /*---------------------------*/
161 const QString BoardData::settingUrl() const
163 return m_basePath + "SETTING.TXT";
167 bool BoardData::settingLoaded() const
169 return m_settingLoaded;
173 const QString& BoardData::defaultName() const
175 return m_defaultName;
179 int BoardData::lineNum() const
185 int BoardData::msgCount() const
191 const KUrl& BoardData::titleImgUrl() const
193 return m_titleImgUrl;
197 void BoardData::setSettingLoaded(bool set)
199 m_settingLoaded = set;
201 m_defaultName.clear();
204 m_titleImgUrl.clear();
209 void BoardData::setDefaultName(const QString& newName)
211 m_defaultName = newName;
215 void BoardData::setLineNum(int newLine)
221 void BoardData::setMsgCount(int msgCount)
223 m_msgCount = msgCount;
227 void BoardData::setTitleImgUrl(const KUrl& url)
233 /*---------------------------*/
236 /* create keys of DB */ /* public */
237 void BoardData::createKeys(const QStringList& keyHostList)
240 m_keyBasePathList.clear();
241 m_keyCgiBasePathList.clear();
242 m_keyHostList.clear();
244 m_keyHostList = keyHostList;
246 /* m_basePath = (hostname)/(rootPath)/(bbsPath)/ */
247 for (int i = 0; i < m_keyHostList.count(); ++i) {
248 if (m_keyHostList[ i ].length() > 0)
249 m_keyBasePathList += m_keyHostList[ i ] + m_rootPath + m_bbsPath + '/';
254 case Board_MachiBBS: /* m_cgiBasePath = (hostname)/(rootPath)/(delimiter)?BBS=(bbsPath) */
255 for (int i = 0; i < m_keyHostList.count(); ++i)
256 m_keyCgiBasePathList += m_keyHostList[ i ] + m_rootPath + m_delimiter
257 + "?BBS=" + m_bbsPath.mid(1);
260 /* m_cgiBasePath = (hostname)/(rootPath)/(delimiter)/(bbsPath)/ */
262 for (int i = 0; i < m_keyHostList.count(); ++i)
263 m_keyCgiBasePathList += m_keyHostList[ i ] + m_rootPath + m_delimiter + m_bbsPath + '/';
269 const QStringList& BoardData::keyHostList() const
271 return m_keyHostList;
275 const QStringList& BoardData::keyBasePathList() const
277 return m_keyBasePathList;
281 const QStringList& BoardData::keyCgiBasePathList() const
283 return m_keyCgiBasePathList;
289 /*---------------------------------------------------------------*/
290 /*---------------------------------------------------------------*/
291 /*---------------------------------------------------------------*/
295 QTextCodec* BoardManager::m_cp932Codec = 0;
296 QTextCodec* BoardManager::m_eucJpCodec = 0;
297 BoardDataList BoardManager::m_boardDataList;
298 BoardData* BoardManager::m_previousBoardData = 0;
299 QString BoardManager::m_previousBoardUrl;
302 BoardManager::BoardManager()
308 BoardManager::~BoardManager()
313 /* (hostname)/(rootPath)/(bbsPath)/ */ /* public */ /* static */
314 const QString BoardManager::boardUrl(const KUrl& url)
316 BoardData * bdata = getBoardData(url);
317 if (bdata == 0) return QString();
319 return bdata->basePath();
322 /* public */ /* static */
323 const QStringList BoardManager::allBoardUrlList()
328 for (BoardDataList::Iterator it = m_boardDataList.begin(); it != m_boardDataList.end(); ++it)
329 urlList += (*it) ->basePath();
334 /* (hostname)/(rootPath) */ /* public */ /* static */
335 const QString BoardManager::boardRoot(const KUrl& url)
337 BoardData * bdata = getBoardData(url);
338 if (bdata == 0) return QString();
340 return bdata->hostName() + bdata->rootPath();
343 /* (bbspath) */ /* public */ /* static */
344 const QString BoardManager::boardPath(const KUrl& url)
346 BoardData * bdata = getBoardData(url);
347 if (bdata == 0) return QString();
349 return bdata->bbsPath();
352 /* (ext) */ /* public */ /* static */
353 const QString BoardManager::ext(const KUrl& url)
355 BoardData * bdata = getBoardData(url);
356 if (bdata == 0) return QString();
361 /* ID of board for writing */ /* public */ /* static */
362 const QString BoardManager::boardId(const KUrl& url)
364 BoardData * bdata = getBoardData(url);
365 if (bdata == 0) return QString();
367 return bdata->bbsPath().mid(1); /* remove "/" */
371 /* (hostname)/(rootPath)/(bbsPath)/subject.txt */ /* public */ /* static */
372 const QString BoardManager::subjectUrl(const KUrl& url)
374 BoardData * bdata = getBoardData(url);
375 if (bdata == 0) return QString();
377 return bdata->basePath() + "subject.txt";
381 /* public */ /* static */
382 const QString BoardManager::boardName(const KUrl& url)
384 BoardData * bdata = getBoardData(url);
385 if (bdata == 0) return QString();
387 return bdata->boardName();
391 /* public */ /* static */
392 int BoardManager::type(const KUrl& url)
394 BoardData * bdata = getBoardData(url);
395 if (bdata == 0) return Board_Unknown;
397 return bdata->type();
401 /*---------------------------*/
405 /* get list of pointers of Thread classes. */
410 oldLogs: If true, search cache and get list of pointer of old threads.
411 online: online or offline mode.
415 threadList: list of pointers of Thread classes.
416 oldLogList: list of pointers of old threads.
418 */ /* public */ /* static */
419 void BoardManager::getThreadList(
427 QList<Thread*>& threadList,
428 QList<Thread*>& oldLogList)
433 /* get all obtained threads list from cache */
434 if (url.prettyUrl() == "http://virtual/obtained/") {
436 QStringList bbslist = allBoardUrlList();
438 /* search all cache dirs */
439 for (QStringList::iterator it = bbslist.begin() ; it != bbslist.end(); ++it) {
441 getCachedThreadList((*it), threadList);
447 /*-------------------------*/
449 BoardData* bdata = getBoardData(url);
450 if (bdata == 0) return ;
452 /* download subject.txt */
457 QString cacheDir = cache.getDirPath();
458 if (!QDir::root().mkpath(cacheDir)) return;
460 KIO::SlaveConfig::self() ->setConfigData("http",
463 QString("Monazilla/1.00 (Kita/%1)").arg(VERSION));
464 QString subjectPath = cache.getSubjectPath();
465 KIO::NetAccess::download(subjectUrl(url), subjectPath, 0);
468 /* open and read subject.txt */
469 readSubjectTxt(bdata, url, threadList);
474 QList<Thread*> tmpList;
476 getCachedThreadList(url, tmpList);
478 for (int i = 0; i < tmpList.count(); i++) {
479 if (threadList.contains(tmpList.at(i)) == 0)
480 oldLogList.append(tmpList.at(i));
486 /* read the cache dir & get the list of all threads. */ /* private */ /* static */
487 void BoardManager::getCachedThreadList(const KUrl& url, QList<Thread*>& threadList)
490 QString cacheDir = cache.getDirPath();
494 /* get all file names */
495 QString ext = BoardManager::getBoardData(url) ->ext();
496 QString boardUrl = BoardManager::getBoardData(url) ->basePath();
497 QStringList filter('*' + ext);
498 QStringList flist = d.entryList(filter);
500 for (QStringList::iterator it = flist.begin(); it != flist.end(); ++it) {
501 if ((*it).isEmpty()) continue;
503 QString datUrl = boardUrl + "dat/" + (*it);
506 Thread* thread = Thread::getByUrlNew(datUrl);
509 thread = Thread::getByUrl(datUrl);
512 ThreadIndex threadIndex(datUrl);
513 threadIndex.loadIndex(thread, false);
516 if (thread != 0) threadList.append(thread);
523 /* open subject.txt and get list of Thread classes */ /* private */ /* static */
524 bool BoardManager::readSubjectTxt(BoardData* bdata, const KUrl& url, QList<Thread*>& threadList)
526 /* get all names of cached files to read idx. */
527 QStringList cacheList;
528 if (!bdata->readIdx()) {
530 QString cacheDir = cache.getDirPath();
533 QString ext = BoardManager::getBoardData(url) ->ext();
534 QStringList filter('*' + ext);
535 cacheList = d.entryList(filter);
539 /* open subject.txt */
541 QString subjectPath = cache.getSubjectPath();
542 QIODevice * device = KFilterDev::deviceForFile(subjectPath, "application/x-gzip");
543 if (!device->open(QIODevice::ReadOnly)) return false;
545 QTextStream stream(device);
547 if (BoardManager::type(url) == Board_JBBS) {
548 if (!m_eucJpCodec) m_eucJpCodec = QTextCodec::codecForName("eucJP");
549 stream.setCodec(m_eucJpCodec);
551 if (!m_cp932Codec) m_cp932Codec = QTextCodec::codecForName("Shift-JIS");
552 stream.setCodec(m_cp932Codec);
556 switch (BoardManager::type(url)) {
560 regexp.setPattern("(\\d+\\.cgi),(.*)\\((\\d+)\\)");
564 regexp.setPattern("(\\d+\\.dat)<>(.*)\\((\\d+)\\)");
569 while (!(line = stream.readLine()).isEmpty()) {
570 int pos = regexp.indexIn(line);
572 QString fname = regexp.cap(1);
573 QString subject = regexp.cap(2);
574 QString num = regexp.cap(3);
576 /* get pointer of Thread class */
577 QString datUrl = boardUrl(url) + "dat/" + fname;
578 Thread* thread = Thread::getByUrl(datUrl);
579 ThreadIndex threadIndex(datUrl);
580 if (threadList.indexOf(thread) == -1) {
581 threadList.append(thread);
584 /* set thread name */
585 thread->setThreadName(subject);
587 /* load index file */
588 if (!bdata->readIdx()) {
590 if (cacheList.contains(fname)) {
591 threadIndex.loadIndex(thread, false);
596 int newNum = num.toInt();
597 if (thread->readNum()) { /* cache exists */
598 int oldNum = thread->resNum();
600 if (newNum > oldNum) {
601 threadIndex.setResNum(newNum);
604 thread->setResNum(newNum);
609 bdata->setReadIdx(true); /* never read idx files again */
614 /*---------------------------*/
617 /* reset all BoardData */ /* public */ /* static */
618 void BoardManager::clearBoardData()
620 for (BoardDataList::Iterator it = m_boardDataList.begin(); it != m_boardDataList.end(); ++it)
623 m_boardDataList.clear();
624 m_previousBoardData = 0;
625 m_previousBoardUrl.clear();
631 * @param[in] boardName
637 * @retval Board_enrollEnrolled if board is already enrolled. oldURL is QString().
638 * @retval Board_enrollNew if board is new board. oldURL is QString().
639 * @retval Board_enrollMoved if board is moved. oldURL is old URL.
641 * @note board is NOT enrolled when board is moved.
642 * To enroll new URL, call BoardManager::moveBoard().
644 * "int type" is type of board. It could be "Board_Unknown". See also parseBoardURL().
646 * If "bool test" is true, this function just checks if the board is enrolled (never enroll board).
649 /* public */ /* static */
650 int BoardManager::enrollBoard(const KUrl& url, const QString& boardName, QString& oldUrl, int type, bool test)
657 type = parseBoardUrl(url, type, hostname, rootPath, delimiter, bbsPath, ext);
660 if (type == Board_Unknown) return Board_enrollFailed;
662 /* check if the board is enrolled or moved. */
663 for (BoardDataList::Iterator it = m_boardDataList.begin(); it != m_boardDataList.end(); ++it) {
665 if ((*it) ->boardName() == boardName
666 && (*it) ->type() == type
667 && (*it) ->bbsPath() == bbsPath) {
669 if ((*it) ->hostName() == hostname
670 && (*it) ->rootPath() == rootPath) { /* enrolled */
671 return Board_enrollEnrolled;
673 oldUrl = (*it) ->basePath();
674 return Board_enrollMoved;
680 if (test) return Board_enrollNew;
682 /* enroll new board */
683 BoardData* bdata = new BoardData(boardName, hostname, rootPath, delimiter, bbsPath, ext, type);
684 m_boardDataList.append(bdata);
686 return Board_enrollNew;
690 /* parse board URL */
691 /* return board type. */ /* private */ /* static */
692 int BoardManager::parseBoardUrl(
696 int type, /* If type = Board_Unknown, type will be decided according to url. */
705 hostname = url.protocol() + "://" + url.host();
712 if (type == Board_Unknown) {
714 if (url.host().contains("machi.to")) type = Board_MachiBBS;
715 else if (url.host().contains("jbbs.livedoor.jp")) type = Board_JBBS;
716 else type = Board_2ch;
722 case Board_MachiBBS: /* MACHI : http:// *.machi.to/(bbsPath)/ */
724 delimiter = "/bbs/read.pl";
725 bbsPath = url.fileName();
729 case Board_JBBS: /* JBBS : http://jbbs.livedoor.jp/(bbsPath)/ */
731 delimiter = "/bbs/read.cgi";
732 bbsPath = url.prettyUrl().remove(hostname);
737 case Board_FlashCGI: /* test for Flash CGI/Mini Thread */
739 delimiter = "/test/read.cgi";
740 bbsPath = url.fileName();
741 rootPath = url.prettyUrl().remove(hostname + '/').remove(bbsPath + '/');
742 if (rootPath.length() == 0) rootPath.clear();
746 default: /* 2ch : http://(hostname)/(rootPath)/(bbsPath)/ */
748 delimiter = "/test/read.cgi";
749 bbsPath = url.fileName();
750 rootPath = url.prettyUrl().remove(hostname + '/').remove(bbsPath + '/');
751 if (rootPath.length() == 0) rootPath.clear();
757 /* For example, if bbsPath = "linux/", then m_bbsPath = "/linux" */
758 const QRegExp exp("/$");
759 rootPath.remove(exp);
761 if (!rootPath.isEmpty() && rootPath.at(0) != '/') rootPath = '/' + rootPath;
762 if (!bbsPath.isEmpty() && bbsPath.at(0) != '/') bbsPath = '/' + bbsPath;
768 /* public */ /* static */
769 bool BoardManager::isEnrolled(const KUrl& url)
771 if (getBoardData(url) == 0) return false;
776 /* public */ /* static */
777 BoardData* BoardManager::getBoardData(const KUrl& url)
779 if (url.isEmpty()) return 0;
780 QString urlstr = url.prettyUrl();
783 if (m_previousBoardData != 0 && m_previousBoardUrl == urlstr) return m_previousBoardData;
785 for (BoardDataList::Iterator it = m_boardDataList.begin(); it != m_boardDataList.end(); ++it) {
787 int count = (*it) ->keyBasePathList().count();
788 for (int i = 0; i < count ; ++i) {
789 if (urlstr.contains((*it) ->keyBasePathList() [ i ])
790 || urlstr.contains((*it) ->keyCgiBasePathList() [ i ])) {
793 m_previousBoardData = (*it);
794 m_previousBoardUrl = urlstr;
806 /*--------------------------------*/
810 /* load the bbs history file (BBSHISTORY), and create keys of Data Base. */
811 /* Before calling this, enroll the board by enrollBoard(). */
813 ex) If the host of board moved like :
815 http:://aaa.com -> http://bbb.com -> http://ccc.com -> http://ddd.com
823 */ /* public */ /* static */
824 bool BoardManager::loadBBSHistory(const KUrl& url)
826 BoardData * bdata = getBoardData(url);
827 if (bdata == 0) return false;
829 QStringList keyHosts(bdata->hostName());
832 QFile file(cache.getBBSHistoryPath());
833 if (file.open(QIODevice::ReadOnly)) {
835 QTextStream ts(&file);
838 while (!ts.atEnd()) {
840 line = ts.readLine();
844 bdata->createKeys(keyHosts);
854 /* public */ /* static */
855 bool BoardManager::moveBoard(const KUrl& fromUrl, const KUrl& toUrl)
857 QString oldhost = fromUrl.protocol() + "://" + fromUrl.host();
858 QString newhost = toUrl.protocol() + "://" + toUrl.host();
860 const QRegExp exp("/$");
861 QString oldUrl = fromUrl.prettyUrl();
862 QString newUrl = toUrl.prettyUrl();
868 if (oldUrl == newUrl) return false;
870 /* Is oldURL enrolled? */
871 BoardData* bdata = getBoardData(oldUrl);
874 /* Is newURL enrolled? */
875 bdata = getBoardData(newUrl);
876 if (bdata == 0) return false;
880 /*---------------------------*/
881 /* update BoardData */
883 /* get the path of old cache */
884 bdata->setHostName(oldhost);
885 QStringList keyHosts = bdata->keyHostList();
886 keyHosts.removeOne(oldhost);
887 keyHosts.prepend(oldhost);
888 bdata->createKeys(keyHosts);
889 Cache cache(bdata->basePath());
890 QString oldCachePath = cache.getDirPath();
893 bdata->setHostName(newhost);
896 /* The order of keyHosts will be like this:
904 keyHosts = bdata->keyHostList();
905 keyHosts.removeOne(oldhost);
906 keyHosts.prepend(oldhost);
907 keyHosts.removeOne(newhost);
908 keyHosts.prepend(newhost);
909 bdata->createKeys(keyHosts);
911 /* reset BoardData */
912 bdata->setReadIdx(false);
913 bdata->setSettingLoaded(false);
916 /*---------------------------*/
920 if (! qdir.exists(oldCachePath)) return true;
922 /* mkdir new server dir */
923 Cache newCache(bdata->basePath());
924 QString newCachePath = Cache::baseDir() + newCache.serverDir();
925 QDir::root().mkpath(newCachePath);
928 newCachePath += newCache.boardDir();
929 if (qdir.exists (newCachePath)) {
930 QString bkupPath = newCachePath;
931 bkupPath.truncate(bkupPath.length() - 1); /* remove '/' */
933 '.' + QString::number(QDateTime::currentDateTime().toTime_t());
934 qdir.rename(newCachePath, bkupPath);
938 if (qdir.exists(oldCachePath)) {
939 qdir.rename(oldCachePath, newCachePath);
941 QDir::root().mkpath(newCachePath);
944 if (! qdir.exists(oldCachePath)) {
945 QDir::root().mkpath(oldCachePath);
946 /* create BBS_MOVED */
947 QString movedPath = oldCachePath + "/BBS_MOVED";
948 QFile file(movedPath);
949 if (file.open(QIODevice::WriteOnly)) {
950 QTextStream stream(&file);
951 stream << newUrl << endl;
956 /*---------------------------*/
957 /* update BBSHISTRY */
959 Cache historyCache(bdata->basePath());
960 QFile file(historyCache.getBBSHistoryPath());
961 if (file.open(QIODevice::WriteOnly)) {
963 QTextStream ts(&file);
965 keyHosts.removeOne(newhost);
966 for (QStringList::iterator it = keyHosts.begin() ; it != keyHosts.end(); ++it) {
974 /*---------------------------*/
975 /* update other information */
976 FavoriteThreads::replace(oldUrl, newUrl);
977 Thread::replace(oldUrl, newUrl);
978 ThreadInfo::replace(oldUrl, newUrl);
979 FavoriteBoards::replace(oldUrl, newUrl);