1 /***************************************************************************
2 * Copyright (C) 2010 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 ***************************************************************************/
10 #include "boarddatabase.h"
12 #include <QtCore/QDateTime>
13 #include <QtCore/QDir>
14 #include <QtCore/QTextCodec>
15 #include <QtCore/QTextStream>
17 #include <kfilterdev.h>
18 #include <kio/netaccess.h>
19 #include <kio/slaveconfig.h>
23 #include "favoriteboards.h"
24 #include "favoritethreads.h"
26 #include "threadindex.h"
27 #include "threadinfo.h"
31 BoardDataList BoardDatabase::m_boardDataList;
32 QTextCodec* BoardDatabase::m_cp932Codec = 0;
33 QTextCodec* BoardDatabase::m_eucJpCodec = 0;
34 BoardData* BoardDatabase::m_previousBoardData = 0;
35 QString BoardDatabase::m_previousBoardUrl;
37 BoardDatabase::BoardDatabase(const KUrl& url) : m_url(url)
41 void BoardDatabase::setUrl(const KUrl& url)
46 /* (hostname)/(rootPath)/(bbsPath)/ */ /* public */
47 QString BoardDatabase::boardUrl()
49 BoardData * bdata = getBoardData();
50 return (bdata == 0) ? QString() : bdata->basePath();
54 QStringList BoardDatabase::allBoardUrlList()
59 foreach (data, m_boardDataList)
60 urlList += data->basePath();
64 /* (hostname)/(rootPath) */ /* public */
65 QString BoardDatabase::boardRoot()
67 BoardData * bdata = getBoardData();
68 return (bdata == 0) ? QString() : bdata->hostName() + bdata->rootPath();
71 /* (bbspath) */ /* public */
72 QString BoardDatabase::boardPath()
74 BoardData * bdata = getBoardData();
75 return (bdata == 0) ? QString() : bdata->bbsPath();
78 /* (ext) */ /* public */
79 QString BoardDatabase::ext()
81 BoardData * bdata = getBoardData();
82 return (bdata == 0) ? QString() : bdata->ext();
85 /* ID of board for writing */ /* public */
86 QString BoardDatabase::boardId()
88 BoardData * bdata = getBoardData();
89 return (bdata == 0) ? QString() : bdata->bbsPath().mid(1); /* remove "/" */
92 /* (hostname)/(rootPath)/(bbsPath)/subject.txt */ /* public */
93 QString BoardDatabase::subjectUrl()
95 BoardData * bdata = getBoardData();
96 return (bdata == 0) ? QString() : bdata->basePath() + "subject.txt";
100 QString BoardDatabase::boardName()
102 BoardData * bdata = getBoardData();
103 return (bdata == 0) ? QString() : bdata->boardName();
107 int BoardDatabase::type()
109 BoardData * bdata = getBoardData();
110 return (bdata == 0) ? Board_Unknown : bdata->type();
113 /*---------------------------*/
117 /* get list of pointers of Thread classes. */
121 oldLogs: If true, search cache and get list of pointer of old threads.
122 online: online or offline mode.
126 threadList: list of pointers of Thread classes.
127 oldLogList: list of pointers of old threads.
130 void BoardDatabase::getThreadList(
137 QList<Thread*>& threadList,
138 QList<Thread*>& oldLogList)
143 /* get all obtained threads list from cache */
144 if (m_url.prettyUrl() == "http://virtual/obtained/") {
146 QStringList bbslist = allBoardUrlList();
148 /* search all cache dirs */
150 foreach (thread, bbslist) {
151 getCachedThreadList(thread, threadList);
157 /*-------------------------*/
159 BoardData* bdata = getBoardData();
160 if (bdata == 0) return ;
162 /* download subject.txt */
167 QString cacheDir = cache.getDirPath();
168 if (!QDir::root().mkpath(cacheDir)) return;
170 KIO::SlaveConfig::self() ->setConfigData("http",
173 QString("Monazilla/1.00 (Kita/%1)").arg(VERSION));
174 QString subjectPath = cache.getSubjectPath();
175 KIO::NetAccess::download(subjectUrl(), subjectPath, 0);
178 /* open and read subject.txt */
179 readSubjectTxt(bdata, threadList);
184 QList<Thread*> tmpList;
186 getCachedThreadList(m_url, tmpList);
188 for (int i = 0; i < tmpList.count(); i++) {
189 if (threadList.contains(tmpList.at(i)) == 0)
190 oldLogList.append(tmpList.at(i));
195 /* read the cache dir & get the list of all threads. */ /* private */
196 void BoardDatabase::getCachedThreadList(const KUrl& url,
197 QList<Thread*>& threadList)
200 QString cacheDir = cache.getDirPath();
204 /* get all file names */
205 QString ext = getBoardData(url)->ext();
206 QString boardUrl = getBoardData(url)->basePath();
207 QStringList filter('*' + ext);
208 QStringList flist = d.entryList(filter);
210 foreach (file, flist) {
211 if (file.isEmpty()) continue;
213 QString datUrl = boardUrl + "dat/" + file;
216 Thread* thread = Thread::getByUrlNew(datUrl);
219 thread = Thread::getByUrl(datUrl);
222 ThreadIndex threadIndex(datUrl);
223 threadIndex.loadIndex(thread, false);
227 threadList.append(thread);
232 /* open subject.txt and get list of Thread classes */ /* private */
233 bool BoardDatabase::readSubjectTxt(BoardData* bdata, QList<Thread*>& threadList)
235 /* get all names of cached files to read idx. */
236 QStringList cacheList;
237 if (!bdata->readIdx()) {
239 QString cacheDir = cache.getDirPath();
242 QString ext = getBoardData()->ext();
243 QStringList filter('*' + ext);
244 cacheList = d.entryList(filter);
248 /* open subject.txt */
250 QString subjectPath = cache.getSubjectPath();
251 QIODevice * device = KFilterDev::deviceForFile(subjectPath, "application/x-gzip");
252 if (!device->open(QIODevice::ReadOnly))
255 QTextStream stream(device);
257 if (type() == Board_JBBS) {
258 if (!m_eucJpCodec) m_eucJpCodec = QTextCodec::codecForName("eucJP");
259 stream.setCodec(m_eucJpCodec);
261 if (!m_cp932Codec) m_cp932Codec = QTextCodec::codecForName("Shift-JIS");
262 stream.setCodec(m_cp932Codec);
270 regexp.setPattern("(\\d+\\.cgi),(.*)\\((\\d+)\\)");
274 regexp.setPattern("(\\d+\\.dat)<>(.*)\\((\\d+)\\)");
279 while (!(line = stream.readLine()).isEmpty()) {
280 int pos = regexp.indexIn(line);
282 QString fname = regexp.cap(1);
283 QString subject = regexp.cap(2);
284 QString num = regexp.cap(3);
286 /* get pointer of Thread class */
287 QString datUrl = boardUrl() + "dat/" + fname;
288 Thread* thread = Thread::getByUrl(datUrl);
289 ThreadIndex threadIndex(datUrl);
290 if (threadList.indexOf(thread) == -1) {
291 threadList.append(thread);
294 /* set thread name */
295 thread->setThreadName(subject);
297 /* load index file */
298 if (!bdata->readIdx()) {
300 if (cacheList.contains(fname)) {
301 threadIndex.loadIndex(thread, false);
306 int newNum = num.toInt();
307 if (thread->readNum()) { /* cache exists */
308 int oldNum = thread->resNum();
310 if (newNum > oldNum) {
311 threadIndex.setResNum(newNum);
314 thread->setResNum(newNum);
319 bdata->setReadIdx(true); /* never read idx files again */
324 /*---------------------------*/
327 /* reset all BoardData */ /* public */
328 void BoardDatabase::clearBoardData()
331 foreach (data, m_boardDataList)
334 m_boardDataList.clear();
335 m_previousBoardData = 0;
336 m_previousBoardUrl.clear();
342 * @param[in] boardName
348 * @retval Board_recordRecorded if board is already recorded. oldURL is QString().
349 * @retval Board_recordNew if board is new board. oldURL is QString().
350 * @retval Board_recordMoved if board is moved. oldURL is old URL.
352 * @note board is NOT recorded when board is moved.
353 * To record new URL, call BoardDatabase::moveBoard().
355 * "int type" is type of board. It could be "Board_Unknown". See also parseBoardURL().
357 * If "bool test" is true, this function just checks if the board is recorded (never record board).
361 int BoardDatabase::recordBoard(const QString& boardName, QString& oldUrl, int type, bool test)
368 type = parseBoardUrl(type, hostname, rootPath, delimiter, bbsPath, ext);
371 if (type == Board_Unknown) return Board_recordFailed;
373 /* check if the board is recorded or moved. */
375 foreach (data, m_boardDataList) {
377 if (data->boardName() == boardName
378 && data->type() == type
379 && data->bbsPath() == bbsPath) {
381 if (data->hostName() == hostname
382 && data->rootPath() == rootPath) { /* recorded */
383 return Board_recordRecorded;
385 oldUrl = data->basePath();
386 return Board_recordMoved;
393 return Board_recordNew;
395 /* record new board */
396 BoardData* bdata = new BoardData(boardName, hostname, rootPath, delimiter, bbsPath, ext, type);
397 m_boardDataList.append(bdata);
399 return Board_recordNew;
402 /* parse board URL */
403 /* return board type. */ /* private */
404 int BoardDatabase::parseBoardUrl(
407 int type, /* If type = Board_Unknown, type will be decided according to url. */
416 hostname = m_url.protocol() + "://" + m_url.host();
423 if (type == Board_Unknown) {
425 if (m_url.host().contains("machi.to"))
426 type = Board_MachiBBS;
427 else if (m_url.host().contains("jbbs.livedoor.jp"))
436 case Board_MachiBBS: /* MACHI : http:// *.machi.to/(bbsPath)/ */
438 delimiter = "/bbs/read.pl";
439 bbsPath = m_url.fileName();
443 case Board_JBBS: /* JBBS : http://jbbs.livedoor.jp/(bbsPath)/ */
445 delimiter = "/bbs/read.cgi";
446 bbsPath = m_url.prettyUrl().remove(hostname);
451 case Board_FlashCGI: /* test for Flash CGI/Mini Thread */
453 delimiter = "/test/read.cgi";
454 bbsPath = m_url.fileName();
455 rootPath = m_url.prettyUrl().remove(hostname + '/').remove(bbsPath + '/');
456 if (rootPath.length() == 0)
461 default: /* 2ch : http://(hostname)/(rootPath)/(bbsPath)/ */
463 delimiter = "/test/read.cgi";
464 bbsPath = m_url.fileName();
465 rootPath = m_url.prettyUrl().remove(hostname + '/').remove(bbsPath + '/');
466 if (rootPath.length() == 0)
473 /* For example, if bbsPath = "linux/", then m_bbsPath = "/linux" */
474 const QRegExp exp("/$");
475 rootPath.remove(exp);
477 if (!rootPath.isEmpty() && rootPath.at(0) != '/')
478 rootPath = '/' + rootPath;
479 if (!bbsPath.isEmpty() && bbsPath.at(0) != '/')
480 bbsPath = '/' + bbsPath;
486 bool BoardDatabase::isRecorded()
488 return getBoardData() != 0;
492 BoardData* BoardDatabase::getBoardData(const KUrl& url)
496 QString urlstr = url.prettyUrl();
499 if (m_previousBoardData != 0 && m_previousBoardUrl == urlstr)
500 return m_previousBoardData;
503 foreach (data, m_boardDataList) {
505 int count = data->keyBasePathList().count();
506 for (int i = 0; i < count ; ++i) {
507 if (urlstr.contains(data->keyBasePathList()[i])
508 || urlstr.contains(data->keyCgiBasePathList()[i])) {
511 m_previousBoardData = data;
512 m_previousBoardUrl = urlstr;
522 BoardData* BoardDatabase::getBoardData()
524 return getBoardData(m_url);
527 /*--------------------------------*/
531 /* load the bbs history file (BBSHISTORY), and create keys of Data Base. */
532 /* Before calling this, record the board by recordBoard(). */
534 ex) If the host of board moved like :
536 http:://aaa.com -> http://bbb.com -> http://ccc.com -> http://ddd.com
545 bool BoardDatabase::loadBBSHistory()
547 BoardData * bdata = getBoardData();
551 QStringList keyHosts(bdata->hostName());
554 QFile file(cache.getBBSHistoryPath());
555 if (file.open(QIODevice::ReadOnly)) {
557 QTextStream ts(&file);
560 while (!ts.atEnd()) {
562 line = ts.readLine();
566 bdata->createKeys(keyHosts);
576 bool BoardDatabase::moveBoard(const KUrl& fromUrl, const KUrl& toUrl)
578 QString oldhost = fromUrl.protocol() + "://" + fromUrl.host();
579 QString newhost = toUrl.protocol() + "://" + toUrl.host();
581 const QRegExp exp("/$");
582 QString oldUrl = fromUrl.prettyUrl();
583 QString newUrl = toUrl.prettyUrl();
589 if (oldUrl == newUrl) return false;
591 /* Is oldURL recorded? */
592 BoardData* bdata = getBoardData(oldUrl);
595 /* Is newURL recorded? */
596 bdata = getBoardData(newUrl);
597 if (bdata == 0) return false;
601 /*---------------------------*/
602 /* update BoardData */
604 /* get the path of old cache */
605 bdata->setHostName(oldhost);
606 QStringList keyHosts = bdata->keyHostList();
607 keyHosts.removeOne(oldhost);
608 keyHosts.prepend(oldhost);
609 bdata->createKeys(keyHosts);
610 Cache cache(bdata->basePath());
611 QString oldCachePath = cache.getDirPath();
614 bdata->setHostName(newhost);
617 /* The order of keyHosts will be like this:
625 keyHosts = bdata->keyHostList();
626 keyHosts.removeOne(oldhost);
627 keyHosts.prepend(oldhost);
628 keyHosts.removeOne(newhost);
629 keyHosts.prepend(newhost);
630 bdata->createKeys(keyHosts);
632 /* reset BoardData */
633 bdata->setReadIdx(false);
634 bdata->setSettingLoaded(false);
637 /*---------------------------*/
641 if (! qdir.exists(oldCachePath)) return true;
643 /* mkdir new server dir */
644 Cache newCache(bdata->basePath());
645 QString newCachePath = Cache::baseDir() + newCache.serverDir();
646 QDir::root().mkpath(newCachePath);
649 newCachePath += newCache.boardDir();
650 if (qdir.exists (newCachePath)) {
651 QString bkupPath = newCachePath;
652 bkupPath.truncate(bkupPath.length() - 1); /* remove '/' */
654 '.' + QString::number(QDateTime::currentDateTime().toTime_t());
655 qdir.rename(newCachePath, bkupPath);
659 if (qdir.exists(oldCachePath)) {
660 qdir.rename(oldCachePath, newCachePath);
662 QDir::root().mkpath(newCachePath);
665 if (! qdir.exists(oldCachePath)) {
666 QDir::root().mkpath(oldCachePath);
667 /* create BBS_MOVED */
668 QString movedPath = oldCachePath + "/BBS_MOVED";
669 QFile file(movedPath);
670 if (file.open(QIODevice::WriteOnly)) {
671 QTextStream stream(&file);
672 stream << newUrl << endl;
677 /*---------------------------*/
678 /* update BBSHISTRY */
680 Cache historyCache(bdata->basePath());
681 QFile file(historyCache.getBBSHistoryPath());
682 if (file.open(QIODevice::WriteOnly)) {
684 QTextStream ts(&file);
686 keyHosts.removeOne(newhost);
688 foreach (host, keyHosts) {
696 /*---------------------------*/
697 /* update other information */
698 FavoriteThreads::replace(oldUrl, newUrl);
699 Thread::replace(oldUrl, newUrl);
700 ThreadInfo::replace(oldUrl, newUrl);
701 FavoriteBoards::replace(oldUrl, newUrl);