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"
12 #include "qcp932codec.h"
13 #include "downloadmanager.h"
15 #include "favoritethreads.h"
17 #include "threadinfo.h"
18 #include "favoriteboards.h"
19 #include "kita_misc.h"
20 #include "threadindex.h"
22 #include <config.h> /* VERSION */
25 #include <kio/netaccess.h>
26 #include <kfilterdev.h>
27 #include <kio/slaveconfig.h>
28 #include <kdeversion.h>
33 #include <qdatetime.h>
34 #include <qeucjpcodec.h>
39 /*---------------------------------------------------------*/
43 BoardData::BoardData( const QString& boardName,
44 const QString& hostname,
45 const QString& rootPath,
46 const QString& delimiter,
47 const QString& bbsPath,
52 m_boardName = boardName;
53 m_rootPath = rootPath;
54 m_delimiter = delimiter;
59 /* set hostname and create URL of board */
60 setHostName( hostname );
62 /* create default key */
63 QStringList keyHosts = m_hostname;
64 createKeys( keyHosts );
66 /* reset SETTING.TXT */
67 setSettingLoaded( FALSE );
70 BoardData::~BoardData()
75 void BoardData::setHostName( const QString& hostName )
77 m_hostname = hostName;
79 /* m_basePath = (hostname)/(rootPath)/(bbsPath)/ */
80 m_basePath = m_hostname + m_rootPath + m_bbsPath + "/";
84 case Board_MachiBBS: /* m_cgiBasePath = (hostname)/(rootPath)/(delimiter)?BBS=(bbsPath) */
85 m_cgiBasePath = m_hostname + m_rootPath + m_delimiter + "?BBS=" + m_bbsPath.mid( 1 );
88 /* m_cgiBasePath = (hostname)/(rootPath)/(delimiter)/(bbsPath)/ */
90 m_cgiBasePath = m_hostname + m_rootPath + m_delimiter + m_bbsPath + "/";
96 /*---------------------------*/
100 const bool BoardData::readIdx() const
106 void BoardData::setReadIdx( bool idx )
112 const QString& BoardData::boardName() const
118 const QString& BoardData::hostName() const
124 const QString& BoardData::rootPath() const
130 const QString& BoardData::delimiter() const
136 const QString& BoardData::bbsPath() const
142 const QString& BoardData::ext() const
148 const int BoardData::type() const
154 const QString& BoardData::basePath() const
160 const QString& BoardData::cgiBasePath() const
162 return m_cgiBasePath;
166 /*---------------------------*/
170 const QString BoardData::settingURL() const
172 return m_basePath + "SETTING.TXT";
176 const bool BoardData::settingLoaded() const
178 return m_settingLoaded;
182 const QString& BoardData::defaultName() const
184 return m_defaultName;
188 const int BoardData::lineNum() const
194 const int BoardData::msgCount() const
200 const KURL& BoardData::titleImgURL() const
202 return m_titleImgURL;
206 void BoardData::setSettingLoaded( bool set )
208 m_settingLoaded = set;
210 m_defaultName = QString::null;
213 m_titleImgURL = QString::null;
218 void BoardData::setDefaultName( const QString& newName )
220 m_defaultName = newName;
224 void BoardData::setLineNum( int newLine )
230 void BoardData::setMsgCount( int msgCount )
232 m_msgCount = msgCount;
236 void BoardData::setTitleImgURL( const KURL& url )
242 /*---------------------------*/
245 /* create keys of DB */ /* public */
246 void BoardData::createKeys( const QStringList& keyHostList )
249 m_keyBasePathList.clear();
250 m_keyCgiBasePathList.clear();
251 m_keyHostList.clear();
253 m_keyHostList = keyHostList;
255 /* m_basePath = (hostname)/(rootPath)/(bbsPath)/ */
256 for ( unsigned int i = 0; i < m_keyHostList.count(); ++i ) {
257 if ( m_keyHostList[ i ].length() > 0 )
258 m_keyBasePathList += m_keyHostList[ i ] + m_rootPath + m_bbsPath + "/";
263 case Board_MachiBBS: /* m_cgiBasePath = (hostname)/(rootPath)/(delimiter)?BBS=(bbsPath) */
264 for ( unsigned int i = 0; i < m_keyHostList.count(); ++i )
265 m_keyCgiBasePathList += m_keyHostList[ i ] + m_rootPath + m_delimiter
266 + "?BBS=" + m_bbsPath.mid( 1 );
269 /* m_cgiBasePath = (hostname)/(rootPath)/(delimiter)/(bbsPath)/ */
271 for ( unsigned int i = 0; i < m_keyHostList.count(); ++i )
272 m_keyCgiBasePathList += m_keyHostList[ i ] + m_rootPath + m_delimiter + m_bbsPath + "/";
278 const QStringList& BoardData::keyHostList() const
280 return m_keyHostList;
284 const QStringList& BoardData::keyBasePathList() const
286 return m_keyBasePathList;
290 const QStringList& BoardData::keyCgiBasePathList() const
292 return m_keyCgiBasePathList;
298 /*---------------------------------------------------------------*/
299 /*---------------------------------------------------------------*/
300 /*---------------------------------------------------------------*/
304 QCp932Codec* Kita::BoardManager::m_cp932Codec = NULL;
305 QEucJpCodec* Kita::BoardManager::m_eucJpCodec = NULL;
306 BoardDataList Kita::BoardManager::m_boardDataList;
307 BoardData* Kita::BoardManager::m_previousBoardData = NULL;
308 QString Kita::BoardManager::m_previousBoardURL;
311 BoardManager::BoardManager()
317 BoardManager::~BoardManager()
322 /* (hostname)/(rootPath)/(bbsPath)/ */ /* public */ /* static */
323 const QString BoardManager::boardURL( const KURL& url )
325 BoardData * bdata = getBoardData( url );
326 if ( bdata == NULL ) return QString::null;
328 return bdata->basePath();
331 /* public */ /* static */
332 const QStringList BoardManager::allBoardURLList()
337 for ( BoardDataList::Iterator it = m_boardDataList.begin(); it != m_boardDataList.end(); ++it )
338 urlList += ( *it ) ->basePath();
343 /* (hostname)/(rootPath) */ /* public */ /* static */
344 const QString BoardManager::boardRoot( const KURL& url )
346 BoardData * bdata = getBoardData( url );
347 if ( bdata == NULL ) return QString::null;
349 return bdata->hostName() + bdata->rootPath();
352 /* (bbspath) */ /* public */ /* static */
353 const QString BoardManager::boardPath( const KURL& url )
355 BoardData * bdata = getBoardData( url );
356 if ( bdata == NULL ) return QString::null;
358 return bdata->bbsPath();
361 /* (ext) */ /* public */ /* static */
362 const QString BoardManager::ext( const KURL& url )
364 BoardData * bdata = getBoardData( url );
365 if ( bdata == NULL ) return QString::null;
370 /* ID of board for writing */ /* public */ /* static */
371 const QString BoardManager::boardID( const KURL& url )
373 BoardData * bdata = getBoardData( url );
374 if ( bdata == NULL ) return QString::null;
376 return bdata->bbsPath().mid( 1 ); /* remove "/" */
380 /* (hostname)/(rootPath)/(bbsPath)/subject.txt */ /* public */ /* static */
381 const QString BoardManager::subjectURL( const KURL& url )
383 BoardData * bdata = getBoardData( url );
384 if ( bdata == NULL ) return QString::null;
386 return bdata->basePath() + "subject.txt";
390 /* public */ /* static */
391 const QString BoardManager::boardName( const KURL& url )
393 BoardData * bdata = getBoardData( url );
394 if ( bdata == NULL ) return QString::null;
396 return bdata->boardName();
400 /* public */ /* static */
401 const int BoardManager::type( const KURL& url )
403 BoardData * bdata = getBoardData( url );
404 if ( bdata == NULL ) return Board_Unknown;
406 return bdata->type();
410 /*---------------------------*/
414 /* get list of pointers of Thread classes. */
419 oldLogs: If TRUE, search cache and get list of pointer of old threads.
420 online: online or offline mode.
424 threadList: list of pointers of Thread classes.
425 oldLogList: list of pointers of old threads.
427 */ /* public */ /* static */
428 void BoardManager::getThreadList(
436 QPtrList< Thread >& threadList,
437 QPtrList< Thread >& oldLogList )
442 /* get all obtained threads list from cache */
443 if ( url.prettyURL() == "http://virtual/obtained/" ) {
445 QStringList bbslist = allBoardURLList();
447 /* search all cache dirs */
448 for ( QStringList::iterator it = bbslist.begin() ; it != bbslist.end(); ++it ) {
450 getCachedThreadList( ( *it ), threadList );
456 /*-------------------------*/
458 BoardData* bdata = getBoardData( url );
459 if ( bdata == NULL ) return ;
461 /* download subject.txt */
465 QString cacheDir = Cache::baseDir() + Cache::serverDir( url ) + Cache::boardDir( url );
466 if ( !Kita::mkdir( cacheDir ) ) return ;
468 KIO::SlaveConfig::self() ->setConfigData( "http",
471 QString( "Monazilla/1.00 (Kita/%1)" ).arg( VERSION ) );
472 QString subjectPath = Cache::getSubjectPath( url );
473 KIO::NetAccess::download( subjectURL( url ), subjectPath, NULL );
476 /* open and read subject.txt */
477 readSubjectTxt( bdata, url, threadList );
482 QPtrList< Thread > tmpList;
484 getCachedThreadList( url, tmpList );
486 for ( unsigned i = 0; i < tmpList.count(); i++ ) {
488 if ( threadList.contains( tmpList.at( i ) ) == 0 ) oldLogList.append( tmpList.at( i ) );
494 /* read the cache dir & get the list of all threads. */ /* private */ /* static */
495 void BoardManager::getCachedThreadList( const KURL& url, QPtrList< Thread >& threadList )
497 QString cacheDir = Cache::baseDir() + Cache::serverDir( url ) + Cache::boardDir( url );
501 /* get all file names */
502 QString ext = BoardManager::getBoardData( url ) ->ext();
503 QString boardURL = BoardManager::getBoardData( url ) ->basePath();
504 QStringList flist = d.entryList( "*" + ext );
506 for ( QStringList::iterator it = flist.begin(); it != flist.end(); ++it ) {
507 if ( ( *it ) == QString::null ) continue;
509 QString datURL = boardURL + "dat/" + ( *it );
512 Kita::Thread* thread = Kita::Thread::getByURLNew( datURL );
513 if ( thread == NULL ) {
515 thread = Kita::Thread::getByURL( datURL );
516 if ( thread == NULL ) continue;
517 ThreadIndex::loadIndex( thread, datURL, FALSE );
520 if ( thread != NULL ) threadList.append( thread );
527 /* open subject.txt and get list of Thread classes */ /* private */ /* static */
528 bool BoardManager::readSubjectTxt( BoardData* bdata, const KURL& url, QPtrList< Thread >& threadList )
530 /* get all names of cached files to read idx. */
531 QStringList cacheList;
532 if ( !bdata->readIdx() ) {
534 QString cacheDir = Cache::baseDir() + Cache::serverDir( url ) + Cache::boardDir( url );
537 QString ext = BoardManager::getBoardData( url ) ->ext();
538 cacheList = d.entryList( "*" + ext );
542 /* open subject.txt */
543 QString subjectPath = Cache::getSubjectPath( url );
544 QIODevice * device = KFilterDev::deviceForFile( subjectPath, "application/x-gzip" );
545 if ( !device->open( IO_ReadOnly ) ) return FALSE;
547 QTextStream stream( device );
549 if ( BoardManager::type( url ) == Board_JBBS ) {
550 if ( !m_eucJpCodec ) m_eucJpCodec = new QEucJpCodec();
551 stream.setCodec( m_eucJpCodec );
553 if ( !m_cp932Codec ) m_cp932Codec = new QCp932Codec();
554 stream.setCodec( m_cp932Codec );
558 switch ( BoardManager::type( url ) ) {
562 regexp.setPattern( "(\\d+\\.cgi),(.*)\\((\\d+)\\)" );
566 regexp.setPattern( "(\\d+\\.dat)<>(.*)\\((\\d+)\\)" );
571 while ( ( line = stream.readLine() ) != QString::null ) {
572 int pos = regexp.search( line );
574 QString fname = regexp.cap( 1 );
575 QString subject = regexp.cap( 2 );
576 QString num = regexp.cap( 3 );
578 /* get pointer of Thread class */
579 QString datURL = boardURL( url ) + "dat/" + fname;
580 Kita::Thread* thread = Kita::Thread::getByURL( datURL );
581 if ( threadList.find( thread ) == -1 ) {
582 threadList.append( thread );
585 /* set thread name */
586 thread->setThreadName( subject );
588 /* load index file */
589 if ( !bdata->readIdx() ) {
591 if ( cacheList.contains( fname ) ) ThreadIndex::loadIndex( thread, datURL, FALSE );
595 int newNum = num.toInt();
596 if ( thread->readNum() ) { /* cache exists */
597 int oldNum = thread->resNum();
599 if ( newNum > oldNum ) {
600 Kita::ThreadIndex::setResNum( datURL, newNum );
603 thread->setResNum( newNum );
608 bdata->setReadIdx( TRUE ); /* never read idx files again */
613 /*---------------------------*/
616 /* reset all BoardData */ /* public */ /* static */
617 void BoardManager::clearBoardData()
619 for ( BoardDataList::Iterator it = m_boardDataList.begin(); it != m_boardDataList.end(); ++it )
622 m_boardDataList.clear();
623 m_previousBoardData = NULL;
624 m_previousBoardURL = QString::null;
630 * @param[in] boardName
636 * @retval Board_enrollEnrolled if board is already enrolled. oldURL is QString::null.
637 * @retval Board_enrollNew if board is new board. oldURL is QString::null.
638 * @retval Board_enrollMoved if board is moved. oldURL is old URL.
640 * @note board is NOT enrolled when board is moved.
641 * To enroll new URL, call BoardManager::moveBoard().
643 * "int type" is type of board. It could be "Kita::Board_Unknown". See also parseBoardURL().
645 * If "bool test" is TRUE, this function just checks if the board is enrolled (never enroll board).
648 /* public */ /* static */
649 int BoardManager::enrollBoard( const KURL& url, const QString& boardName, QString& oldURL, int type, bool test )
656 type = parseBoardURL( url, type, hostname, rootPath, delimiter, bbsPath, ext );
657 oldURL = QString::null;
659 if ( type == Board_Unknown ) return Board_enrollFailed;
661 /* check if the board is enrolled or moved. */
662 for ( BoardDataList::Iterator it = m_boardDataList.begin(); it != m_boardDataList.end(); ++it ) {
664 if ( ( *it ) ->boardName() == boardName
665 && ( *it ) ->type() == type
666 && ( *it ) ->bbsPath() == bbsPath ) {
668 if ( ( *it ) ->hostName() == hostname
669 && ( *it ) ->rootPath() == rootPath ) { /* enrolled */
670 return Board_enrollEnrolled;
672 oldURL = ( *it ) ->basePath();
673 return Board_enrollMoved;
679 if ( test ) return Board_enrollNew;
681 /* enroll new board */
682 BoardData* bdata = new BoardData( boardName, hostname, rootPath, delimiter, bbsPath, ext, type );
683 m_boardDataList.append( bdata );
685 return Board_enrollNew;
689 /* parse board URL */
690 /* return board type. */ /* private */ /* static */
691 int BoardManager::parseBoardURL(
695 int type, /* If type = Board_Unknown, type will be decided according to url. */
704 hostname = url.protocol() + "://" + url.host();
705 rootPath = QString::null;
706 delimiter = QString::null;
707 bbsPath = QString::null;
711 if ( type == Board_Unknown ) {
713 if ( url.host().contains( "machi.to" ) ) type = Board_MachiBBS;
714 else if ( url.host().contains( "jbbs.livedoor.jp" ) ) type = Board_JBBS;
715 else type = Board_2ch;
721 case Board_MachiBBS: /* MACHI : http:// *.machi.to/(bbsPath)/ */
723 delimiter = "/bbs/read.pl";
724 bbsPath = url.filename();
728 case Board_JBBS: /* JBBS : http://jbbs.livedoor.jp/(bbsPath)/ */
730 delimiter = "/bbs/read.cgi";
731 bbsPath = url.prettyURL().remove( hostname );
736 case Board_FlashCGI: /* test for Flash CGI/Mini Thread */
738 delimiter = "/test/read.cgi";
739 bbsPath = url.filename();
740 rootPath = url.prettyURL().remove( hostname + "/" ).remove( bbsPath + "/" );
741 if ( rootPath.length() == 0 ) rootPath = QString::null;
745 default: /* 2ch : http://(hostname)/(rootPath)/(bbsPath)/ */
747 delimiter = "/test/read.cgi";
748 bbsPath = url.filename();
749 rootPath = url.prettyURL().remove( hostname + "/" ).remove( bbsPath + "/" );
750 if ( rootPath.length() == 0 ) rootPath = QString::null;
756 /* For example, if bbsPath = "linux/", then m_bbsPath = "/linux" */
757 const QRegExp exp( "/$" );
758 rootPath.remove( exp );
759 bbsPath.remove( exp );
760 if ( rootPath != QString::null && rootPath.at( 0 ) != '/' ) rootPath = "/" + rootPath;
761 if ( bbsPath != QString::null && bbsPath.at( 0 ) != '/' ) bbsPath = "/" + bbsPath;
767 /* public */ /* static */
768 bool BoardManager::isEnrolled( const KURL& url )
770 if ( getBoardData( url ) == NULL ) return FALSE;
775 /* public */ /* static */
776 BoardData* BoardManager::getBoardData( const KURL& url )
778 if ( url.isEmpty() ) return NULL;
779 QString urlstr = url.prettyURL();
782 if ( m_previousBoardData != NULL && m_previousBoardURL == urlstr ) return m_previousBoardData;
784 for ( BoardDataList::Iterator it = m_boardDataList.begin(); it != m_boardDataList.end(); ++it ) {
786 int count = ( *it ) ->keyBasePathList().count();
787 for ( int i = 0; i < count ; ++i ) {
788 if ( urlstr.contains( ( *it ) ->keyBasePathList() [ i ] )
789 || urlstr.contains( ( *it ) ->keyCgiBasePathList() [ i ] ) ) {
792 m_previousBoardData = ( *it );
793 m_previousBoardURL = urlstr;
805 /*--------------------------------*/
809 /* load the bbs history file ( BBSHISTORY ), and create keys of Data Base. */
810 /* Before calling this, enroll the board by enrollBoard(). */
812 ex) If the host of board moved like :
814 http:://aaa.com -> http://bbb.com -> http://ccc.com -> http://ddd.com
822 */ /* public */ /* static */
823 bool BoardManager::loadBBSHistory( const KURL& url )
825 BoardData * bdata = getBoardData( url );
826 if ( bdata == NULL ) return FALSE;
828 QStringList keyHosts = bdata->hostName();
830 QFile file( Cache::getBBSHistoryPath( url ) );
831 if ( file.open( IO_ReadOnly ) ) {
833 QTextStream ts( &file );
836 while ( !ts.eof() ) {
838 line = ts.readLine();
842 bdata->createKeys( keyHosts );
852 /* public */ /* static */
853 bool BoardManager::moveBoard( const KURL& fromURL, const KURL& toURL )
855 QString oldhost = fromURL.protocol() + "://" + fromURL.host();
856 QString newhost = toURL.protocol() + "://" + toURL.host();
858 const QRegExp exp( "/$" );
859 QString oldURL = fromURL.prettyURL();
860 QString newURL = toURL.prettyURL();
861 oldURL.remove( exp );
862 newURL.remove( exp );
866 if ( oldURL == newURL ) return FALSE;
868 /* Is oldURL enrolled? */
869 BoardData* bdata = getBoardData( oldURL );
870 if ( bdata == NULL ) {
872 /* Is newURL enrolled? */
873 bdata = getBoardData( newURL );
874 if ( bdata == NULL ) return FALSE;
878 /*---------------------------*/
879 /* update BoardData */
881 /* get the path of old cache */
882 bdata->setHostName( oldhost );
883 QStringList keyHosts = bdata->keyHostList();
884 keyHosts.remove( oldhost );
885 keyHosts.prepend( oldhost );
886 bdata->createKeys( keyHosts );
887 QString oldCachePath = Cache::baseDir() + Cache::serverDir( bdata->basePath() )
888 + Cache::boardDir( bdata->basePath() );
891 bdata->setHostName( newhost );
894 /* The order of keyHosts will be like this:
902 keyHosts = bdata->keyHostList();
903 keyHosts.remove( oldhost );
904 keyHosts.prepend( oldhost );
905 keyHosts.remove( newhost );
906 keyHosts.prepend( newhost );
907 bdata->createKeys( keyHosts );
909 /* reset BoardData */
910 bdata->setReadIdx( FALSE );
911 bdata->setSettingLoaded( FALSE );
914 /*---------------------------*/
918 if ( ! qdir.exists( oldCachePath ) ) return TRUE;
920 /* mkdir new server dir */
921 QString newCachePath = Cache::baseDir() + Cache::serverDir( bdata->basePath() );
922 Kita::mkdir( newCachePath );
925 newCachePath += Cache::boardDir( bdata->basePath() );
926 if ( qdir.exists ( newCachePath ) ) {
927 QString bkupPath = newCachePath;
928 bkupPath.truncate( bkupPath.length() - 1 ); /* remove '/' */
929 bkupPath += "." + QString().setNum( QDateTime::currentDateTime().toTime_t() );
930 qdir.rename( newCachePath, bkupPath );
934 if ( qdir.exists( oldCachePath ) ) {
935 qdir.rename( oldCachePath, newCachePath );
936 } else Kita::mkdir( newCachePath );
939 if ( ! qdir.exists( oldCachePath ) ) {
940 Kita::mkdir( oldCachePath );
941 /* create BBS_MOVED */
942 QString movedPath = oldCachePath + "/BBS_MOVED";
943 QFile file( movedPath );
944 if ( file.open( IO_WriteOnly ) ) {
945 QTextStream stream( &file );
946 stream << newURL << endl;
951 /*---------------------------*/
952 /* update BBSHISTRY */
954 QFile file( Cache::getBBSHistoryPath( bdata->basePath() ) );
955 if ( file.open( IO_WriteOnly ) ) {
957 QTextStream ts( &file );
959 keyHosts.remove( newhost );
960 for ( QStringList::iterator it = keyHosts.begin() ; it != keyHosts.end(); ++it ) {
961 ts << ( *it ) << endl;
968 /*---------------------------*/
969 /* update other information */
970 FavoriteThreads::replace( oldURL, newURL );
971 Kita::Thread::replace( oldURL, newURL );
972 KitaThreadInfo::replace( oldURL, newURL );
973 Kita::FavoriteBoards::replace( oldURL, newURL );