OSDN Git Service

Use an initialization list in BoardData
[kita/kita.git] / kita / src / libkita / boardmanager.cpp
1 /***************************************************************************
2 *   Copyright (C) 2004 by Kita Developers                                 *
3 *   ikemo@users.sourceforge.jp                                            *
4 *                                                                         *
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
11 #include "boardmanager.h"
12
13 #include <QtCore/QDateTime>
14 #include <QtCore/QDir>
15 #include <QtCore/QFile>
16 #include <QtCore/QRegExp>
17 #include <QtCore/QTextCodec>
18 #include <QtCore/QTextStream>
19
20 #include <kfilterdev.h>
21 #include <kio/netaccess.h>
22 #include <kio/slaveconfig.h>
23
24 #include "cache.h"
25 #include "config.h"
26 #include "favoriteboards.h"
27 #include "favoritethreads.h"
28 #include "kita_misc.h"
29 #include "thread.h"
30 #include "threadindex.h"
31 #include "threadinfo.h"
32
33 using namespace Kita;
34
35
36 /*---------------------------------------------------------*/
37
38 /* BoardData */
39
40 BoardData::BoardData(const QString& boardName,
41                       const QString& hostname,
42                       const QString& rootPath,
43                       const QString& delimiter,
44                       const QString& bbsPath,
45                       const QString& ext,
46                       int boardtype)
47 : m_boardName(boardName), m_readIdx(false), m_rootPath(rootPath),
48     m_delimiter(delimiter), m_bbsPath(bbsPath), m_ext(ext), m_type(boardtype)
49 {
50     /* set hostname and create URL of board */
51     setHostName(hostname);
52
53     /* create default key */
54     QStringList keyHosts(m_hostname);
55     createKeys(keyHosts);
56
57     /* reset SETTING.TXT */
58     setSettingLoaded(false);
59 }
60
61 BoardData::~BoardData()
62 {}
63
64
65 /* public */
66 void BoardData::setHostName(const QString& hostName)
67 {
68     m_hostname = hostName;
69
70     /* m_basePath = (hostname)/(rootPath)/(bbsPath)/ */
71     m_basePath = m_hostname + m_rootPath + m_bbsPath + '/';
72
73     switch (m_type) {
74
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);
77         break;
78
79         /* m_cgiBasePath = (hostname)/(rootPath)/(delimiter)/(bbsPath)/ */
80     default:
81         m_cgiBasePath = m_hostname + m_rootPath + m_delimiter + m_bbsPath + '/';
82         break;
83     }
84 }
85
86
87 /*---------------------------*/
88 /* information */
89
90 /* public */
91 bool BoardData::readIdx() const
92 {
93     return m_readIdx;
94 }
95
96 /* public */
97 void BoardData::setReadIdx(bool idx)
98 {
99     m_readIdx = idx;
100 }
101
102 /* public */
103 const QString& BoardData::boardName() const
104 {
105     return m_boardName;
106 }
107
108 /* public */
109 const QString& BoardData::hostName() const
110 {
111     return m_hostname;
112 }
113
114 /* public */
115 const QString& BoardData::rootPath() const
116 {
117     return m_rootPath;
118 }
119
120 /* public */
121 const QString& BoardData::delimiter() const
122 {
123     return m_delimiter;
124 }
125
126 /* public */
127 const QString& BoardData::bbsPath() const
128 {
129     return m_bbsPath;
130 }
131
132 /* public */
133 const QString& BoardData::ext() const
134 {
135     return m_ext;
136 }
137
138 /* public */
139 int BoardData::type() const
140 {
141     return m_type;
142 }
143
144 /* public */
145 const QString& BoardData::basePath() const
146 {
147     return m_basePath;
148 }
149
150 /* public */
151 const QString& BoardData::cgiBasePath() const
152 {
153     return m_cgiBasePath;
154 }
155
156
157 /*---------------------------*/
158 /* SETTING.TXT */
159
160 /* public */
161 const QString BoardData::settingUrl() const
162 {
163     return m_basePath + "SETTING.TXT";
164 }
165
166 /* public */
167 bool BoardData::settingLoaded() const
168 {
169     return m_settingLoaded;
170 }
171
172 /* public */
173 const QString& BoardData::defaultName() const
174 {
175     return m_defaultName;
176 }
177
178 /* public */
179 int BoardData::lineNum() const
180 {
181     return m_linenum;
182 }
183
184 /* public */
185 int BoardData::msgCount() const
186 {
187     return m_msgCount;
188 }
189
190 /* public */
191 const KUrl& BoardData::titleImgUrl() const
192 {
193     return m_titleImgUrl;
194 }
195
196 /* public */
197 void BoardData::setSettingLoaded(bool set)
198 {
199     m_settingLoaded = set;
200     if (! set) {
201             m_defaultName.clear();
202             m_linenum = 0;
203             m_msgCount = 0;
204             m_titleImgUrl.clear();
205         }
206 }
207
208 /* public */
209 void BoardData::setDefaultName(const QString& newName)
210 {
211     m_defaultName = newName;
212 }
213
214 /* public */
215 void BoardData::setLineNum(int newLine)
216 {
217     m_linenum = newLine;
218 }
219
220 /* public */
221 void BoardData::setMsgCount(int msgCount)
222 {
223     m_msgCount = msgCount;
224 }
225
226 /* public */
227 void BoardData::setTitleImgUrl(const KUrl& url)
228 {
229     m_titleImgUrl = url;
230 }
231
232
233 /*---------------------------*/
234 /* keys */
235
236 /* create keys of DB */ /* public */
237 void BoardData::createKeys(const QStringList& keyHostList)
238 {
239     /* reset keys */
240     m_keyBasePathList.clear();
241     m_keyCgiBasePathList.clear();
242     m_keyHostList.clear();
243
244     m_keyHostList = keyHostList;
245
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 + '/';
250     }
251
252     switch (m_type) {
253
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);
258         break;
259
260         /* m_cgiBasePath = (hostname)/(rootPath)/(delimiter)/(bbsPath)/ */
261     default:
262         for (int i = 0; i < m_keyHostList.count(); ++i)
263             m_keyCgiBasePathList += m_keyHostList[ i ] + m_rootPath + m_delimiter + m_bbsPath + '/';
264         break;
265     }
266 }
267
268 /* public */
269 const QStringList& BoardData::keyHostList() const
270 {
271     return m_keyHostList;
272 }
273
274 /* public */
275 const QStringList& BoardData::keyBasePathList() const
276 {
277     return m_keyBasePathList;
278 }
279
280 /* public */
281 const QStringList& BoardData::keyCgiBasePathList() const
282 {
283     return m_keyCgiBasePathList;
284 }
285
286
287
288
289 /*---------------------------------------------------------------*/
290 /*---------------------------------------------------------------*/
291 /*---------------------------------------------------------------*/
292
293 /* BoardManager */
294
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;
300
301
302 BoardManager::BoardManager()
303 {
304     clearBoardData();
305 }
306
307
308 BoardManager::~BoardManager()
309 {
310     clearBoardData();
311 }
312
313 /* (hostname)/(rootPath)/(bbsPath)/ */ /* public */ /* static */
314 const QString BoardManager::boardUrl(const KUrl& url)
315 {
316     BoardData * bdata = getBoardData(url);
317     if (bdata == 0) return QString();
318
319     return bdata->basePath();
320 }
321
322 /* public */ /* static */
323 const QStringList BoardManager::allBoardUrlList()
324 {
325     QStringList urlList;
326     urlList.clear();
327
328     for (BoardDataList::Iterator it = m_boardDataList.begin(); it != m_boardDataList.end(); ++it)
329         urlList += (*it) ->basePath();
330
331     return urlList;
332 }
333
334 /* (hostname)/(rootPath) */ /* public */ /* static */
335 const QString BoardManager::boardRoot(const KUrl& url)
336 {
337     BoardData * bdata = getBoardData(url);
338     if (bdata == 0) return QString();
339
340     return bdata->hostName() + bdata->rootPath();
341 }
342
343 /* (bbspath) */ /* public */ /* static */
344 const QString BoardManager::boardPath(const KUrl& url)
345 {
346     BoardData * bdata = getBoardData(url);
347     if (bdata == 0) return QString();
348
349     return bdata->bbsPath();
350 }
351
352 /* (ext) */ /* public */ /* static */
353 const QString BoardManager::ext(const KUrl& url)
354 {
355     BoardData * bdata = getBoardData(url);
356     if (bdata == 0) return QString();
357
358     return bdata->ext();
359 }
360
361 /* ID of board for writing */ /* public */ /* static */
362 const QString BoardManager::boardId(const KUrl& url)
363 {
364     BoardData * bdata = getBoardData(url);
365     if (bdata == 0) return QString();
366
367     return bdata->bbsPath().mid(1); /* remove "/" */
368 }
369
370
371 /* (hostname)/(rootPath)/(bbsPath)/subject.txt */ /* public */ /* static */
372 const QString BoardManager::subjectUrl(const KUrl& url)
373 {
374     BoardData * bdata = getBoardData(url);
375     if (bdata == 0) return QString();
376
377     return bdata->basePath() + "subject.txt";
378 }
379
380
381 /* public */ /* static */
382 const QString BoardManager::boardName(const KUrl& url)
383 {
384     BoardData * bdata = getBoardData(url);
385     if (bdata == 0) return QString();
386
387     return bdata->boardName();
388 }
389
390
391 /* public */ /* static */
392 int BoardManager::type(const KUrl& url)
393 {
394     BoardData * bdata = getBoardData(url);
395     if (bdata == 0) return Board_Unknown;
396
397     return bdata->type();
398 }
399
400
401 /*---------------------------*/
402 /* ThreadList */
403
404
405 /*  get list of pointers of Thread classes.     */
406 /*
407   Input:
408  
409   url:  URL of board.
410   oldLogs: If true, search cache and get list of pointer of old threads.
411   online: online or offline mode.
412  
413   Output:
414  
415   threadList: list of pointers of Thread classes.
416   oldLogList: list of pointers of old threads.
417  
418                                                  */ /* public */ /* static */
419 void BoardManager::getThreadList(
420
421     /* input */
422     const KUrl& url,
423     bool oldLogs,
424     bool online,
425
426     /* output */
427     QList<Thread*>& threadList,
428     QList<Thread*>& oldLogList)
429 {
430     threadList.clear();
431     oldLogList.clear();
432
433     /* get all obtained threads list from cache */
434     if (url.prettyUrl() == "http://virtual/obtained/") {
435
436         QStringList bbslist = allBoardUrlList();
437
438         /* search all cache dirs */
439         for (QStringList::iterator it = bbslist.begin() ; it != bbslist.end(); ++it) {
440
441             getCachedThreadList((*it), threadList);
442         }
443
444         return ;
445     }
446
447     /*-------------------------*/
448
449     BoardData* bdata = getBoardData(url);
450     if (bdata == 0) return ;
451
452     /* download subject.txt */
453     if (online) {
454
455         /* make directory */
456         Cache cache(url);
457         QString cacheDir = cache.getDirPath();
458         if (!QDir::root().mkpath(cacheDir)) return;
459
460         KIO::SlaveConfig::self() ->setConfigData("http",
461                 url.host() ,
462                 "UserAgent",
463                 QString("Monazilla/1.00 (Kita/%1)").arg(VERSION));
464         QString subjectPath = cache.getSubjectPath();
465         KIO::NetAccess::download(subjectUrl(url), subjectPath, 0);
466     }
467
468     /* open and read subject.txt */
469     readSubjectTxt(bdata, url, threadList);
470
471     /* get old logs */
472     if (oldLogs) {
473
474         QList<Thread*> tmpList;
475         tmpList.clear();
476         getCachedThreadList(url, tmpList);
477
478         for (int i = 0; i < tmpList.count(); i++) {
479             if (threadList.contains(tmpList.at(i)) == 0)
480                 oldLogList.append(tmpList.at(i));
481         }
482     }
483 }
484
485
486 /* read the cache dir & get the list of all threads. */ /* private */ /* static */
487 void BoardManager::getCachedThreadList(const KUrl& url, QList<Thread*>& threadList)
488 {
489     Cache cache(url);
490     QString cacheDir = cache.getDirPath();
491     QDir d(cacheDir);
492     if (d.exists()) {
493
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);
499
500         for (QStringList::iterator it = flist.begin(); it != flist.end(); ++it) {
501             if ((*it).isEmpty()) continue;
502
503             QString datUrl = boardUrl + "dat/" + (*it);
504
505             /* read idx file */
506             Thread* thread = Thread::getByUrlNew(datUrl);
507             if (thread == 0) {
508
509                 thread = Thread::getByUrl(datUrl);
510                 if (thread == 0)
511                     continue;
512                 ThreadIndex threadIndex(datUrl);
513                 threadIndex.loadIndex(thread, false);
514             }
515
516             if (thread != 0) threadList.append(thread);
517         }
518     }
519 }
520
521
522
523 /* open subject.txt and get list of Thread classes */ /* private */ /* static */
524 bool BoardManager::readSubjectTxt(BoardData* bdata, const KUrl& url, QList<Thread*>& threadList)
525 {
526     /* get all names of cached files to read idx.  */
527     QStringList cacheList;
528     if (!bdata->readIdx()) {
529         Cache cache(url);
530         QString cacheDir = cache.getDirPath();
531         QDir d(cacheDir);
532         if (d.exists()) {
533             QString ext = BoardManager::getBoardData(url) ->ext();
534             QStringList filter('*' + ext);
535             cacheList = d.entryList(filter);
536         }
537     }
538
539     /* open subject.txt */
540     Cache cache(url);
541     QString subjectPath = cache.getSubjectPath();
542     QIODevice * device = KFilterDev::deviceForFile(subjectPath, "application/x-gzip");
543     if (!device->open(QIODevice::ReadOnly)) return false;
544
545     QTextStream stream(device);
546
547     if (BoardManager::type(url) == Board_JBBS) {
548         if (!m_eucJpCodec) m_eucJpCodec = QTextCodec::codecForName("eucJP");
549         stream.setCodec(m_eucJpCodec);
550     } else {
551         if (!m_cp932Codec) m_cp932Codec = QTextCodec::codecForName("Shift-JIS");
552         stream.setCodec(m_cp932Codec);
553     }
554
555     QRegExp regexp;
556     switch (BoardManager::type(url)) {
557
558     case Board_MachiBBS:
559     case Board_JBBS:
560         regexp.setPattern("(\\d+\\.cgi),(.*)\\((\\d+)\\)");
561         break;
562
563     default:
564         regexp.setPattern("(\\d+\\.dat)<>(.*)\\((\\d+)\\)");
565         break;
566     }
567     QString line;
568
569     while (!(line = stream.readLine()).isEmpty()) {
570         int pos = regexp.indexIn(line);
571         if (pos != -1) {
572             QString fname = regexp.cap(1);
573             QString subject = regexp.cap(2);
574             QString num = regexp.cap(3);
575
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);
582             }
583
584             /* set thread name */
585             thread->setThreadName(subject);
586
587             /* load index file */
588             if (!bdata->readIdx()) {
589
590                 if (cacheList.contains(fname)) {
591                     threadIndex.loadIndex(thread, false);
592                 }
593             }
594
595             /* update res num */
596             int newNum = num.toInt();
597             if (thread->readNum()) { /* cache exists */
598                 int oldNum = thread->resNum();
599
600                 if (newNum > oldNum) {
601                     threadIndex.setResNum(newNum);
602                 }
603             }
604             thread->setResNum(newNum);
605         }
606     }
607
608     device->close();
609     bdata->setReadIdx(true); /* never read idx files again */
610
611     return true;
612 }
613
614 /*---------------------------*/
615 /* BoardData */
616
617 /* reset all BoardData */ /* public */ /* static */
618 void BoardManager::clearBoardData()
619 {
620     for (BoardDataList::Iterator it = m_boardDataList.begin(); it != m_boardDataList.end(); ++it)
621         delete(*it);
622
623     m_boardDataList.clear();
624     m_previousBoardData = 0;
625     m_previousBoardUrl.clear();
626 }
627
628 /**
629  *
630  * @param[in] board
631  * @param[in] boardName
632  * @param[in] type
633  * @param[in] test
634  *
635  * @param[out] oldURL
636  *
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.
640  *
641  * @note board is NOT enrolled when board is moved.
642  * To enroll new URL, call BoardManager::moveBoard(). 
643  *
644  * "int type" is type of board. It could be "Board_Unknown". See also parseBoardURL().
645  * 
646  * If "bool test" is true, this function just checks if the board is enrolled (never enroll board).
647  *
648  */ 
649 /* public */ /* static */
650 int BoardManager::enrollBoard(const KUrl& url, const QString& boardName, QString& oldUrl, int type, bool test)
651 {
652     QString hostname;
653     QString rootPath;
654     QString delimiter;
655     QString bbsPath;
656     QString ext;
657     type = parseBoardUrl(url, type, hostname, rootPath, delimiter, bbsPath, ext);
658     oldUrl.clear();
659
660     if (type == Board_Unknown) return Board_enrollFailed;
661
662     /* check if the board is enrolled or moved. */
663     for (BoardDataList::Iterator it = m_boardDataList.begin(); it != m_boardDataList.end(); ++it) {
664
665         if ((*it) ->boardName() == boardName
666                 && (*it) ->type() == type
667                 && (*it) ->bbsPath() == bbsPath) {
668
669             if ((*it) ->hostName() == hostname
670                     && (*it) ->rootPath() == rootPath) { /* enrolled */
671                 return Board_enrollEnrolled;
672             } else { /* moved */
673                 oldUrl = (*it) ->basePath();
674                 return Board_enrollMoved;
675             }
676         }
677     }
678
679     /* test only */
680     if (test) return Board_enrollNew;
681
682     /* enroll new board */
683     BoardData* bdata = new BoardData(boardName, hostname, rootPath, delimiter, bbsPath, ext, type);
684     m_boardDataList.append(bdata);
685
686     return Board_enrollNew;
687 }
688
689
690 /* parse board URL      */
691 /* return board type.   */ /* private */ /* static */
692 int BoardManager::parseBoardUrl(
693
694     /* input */
695     const KUrl& url,
696     int type,   /* If type = Board_Unknown, type will be decided according to url. */
697
698     /* output */
699     QString& hostname,
700     QString& rootPath,
701     QString& delimiter,
702     QString& bbsPath,
703     QString& ext)
704 {
705     hostname = url.protocol() + "://" + url.host();
706     rootPath.clear();
707     delimiter.clear();
708     bbsPath.clear();
709     ext.clear();
710
711     /* decide type */
712     if (type == Board_Unknown) {
713
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;
717     }
718
719     /* parse */
720     switch (type) {
721
722     case Board_MachiBBS:     /* MACHI : http:// *.machi.to/(bbsPath)/ */
723
724         delimiter = "/bbs/read.pl";
725         bbsPath = url.fileName();
726         ext = ".cgi";
727         break;
728
729     case Board_JBBS:   /* JBBS : http://jbbs.livedoor.jp/(bbsPath)/ */
730
731         delimiter = "/bbs/read.cgi";
732         bbsPath = url.prettyUrl().remove(hostname);
733         type = Board_JBBS;
734         ext = ".cgi";
735         break;
736
737     case Board_FlashCGI:  /* test for Flash CGI/Mini Thread  */
738
739         delimiter = "/test/read.cgi";
740         bbsPath = url.fileName();
741         rootPath = url.prettyUrl().remove(hostname + '/').remove(bbsPath + '/');
742         if (rootPath.length() == 0) rootPath.clear();
743         ext = ".dat";
744         break;
745
746     default:   /* 2ch : http://(hostname)/(rootPath)/(bbsPath)/ */
747
748         delimiter = "/test/read.cgi";
749         bbsPath = url.fileName();
750         rootPath = url.prettyUrl().remove(hostname + '/').remove(bbsPath + '/');
751         if (rootPath.length() == 0) rootPath.clear();
752         ext = ".dat";
753         type = Board_2ch;
754         break;
755     }
756
757     /* For example, if bbsPath = "linux/", then m_bbsPath = "/linux" */
758     const QRegExp exp("/$");
759     rootPath.remove(exp);
760     bbsPath.remove(exp);
761     if (!rootPath.isEmpty() && rootPath.at(0) != '/') rootPath = '/' + rootPath;
762     if (!bbsPath.isEmpty() && bbsPath.at(0) != '/') bbsPath = '/' + bbsPath;
763
764     return type;
765 }
766
767
768 /* public */ /* static */
769 bool BoardManager::isEnrolled(const KUrl& url)
770 {
771     if (getBoardData(url) == 0) return false;
772     return true;
773 }
774
775
776 /* public */ /* static */
777 BoardData* BoardManager::getBoardData(const KUrl& url)
778 {
779     if (url.isEmpty()) return 0;
780     QString urlstr = url.prettyUrl();
781
782     /* cache */
783     if (m_previousBoardData != 0 && m_previousBoardUrl == urlstr) return m_previousBoardData;
784
785     for (BoardDataList::Iterator it = m_boardDataList.begin(); it != m_boardDataList.end(); ++it) {
786
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 ])) {
791
792                 /* cache */
793                 m_previousBoardData = (*it);
794                 m_previousBoardUrl = urlstr;
795
796                 return (*it);
797             }
798         }
799     }
800
801     return 0;
802 }
803
804
805
806 /*--------------------------------*/
807 /* BBSHISTORY */
808
809
810 /* load the bbs history file (BBSHISTORY), and create keys of Data Base.  */
811 /* Before calling this, enroll the board by enrollBoard().                  */
812 /*
813     ex) If the host of board moved like :
814  
815     http:://aaa.com -> http://bbb.com -> http://ccc.com -> http://ddd.com
816
817     then, BBSHISTORY is
818  
819     http://ccc.com
820     http://bbb.com
821     http://aaa.com
822  
823 */ /* public */ /* static */
824 bool BoardManager::loadBBSHistory(const KUrl& url)
825 {
826     BoardData * bdata = getBoardData(url);
827     if (bdata == 0) return false;
828
829     QStringList keyHosts(bdata->hostName());
830
831     Cache cache(url);
832     QFile file(cache.getBBSHistoryPath());
833     if (file.open(QIODevice::ReadOnly)) {
834
835         QTextStream ts(&file);
836
837         QString line;
838         while (!ts.atEnd()) {
839
840             line = ts.readLine();
841             keyHosts += line;
842         }
843
844         bdata->createKeys(keyHosts);
845         file.close();
846
847         return true;
848     }
849
850     return false;
851 }
852
853
854 /* public */ /* static */
855 bool BoardManager::moveBoard(const KUrl& fromUrl, const KUrl& toUrl)
856 {
857     QString oldhost = fromUrl.protocol() + "://" + fromUrl.host();
858     QString newhost = toUrl.protocol() + "://" + toUrl.host();
859
860     const QRegExp exp("/$");
861     QString oldUrl = fromUrl.prettyUrl();
862     QString newUrl = toUrl.prettyUrl();
863     oldUrl.remove(exp);
864     newUrl.remove(exp);
865     oldUrl += '/';
866     newUrl += '/';
867
868     if (oldUrl == newUrl) return false;
869
870     /* Is oldURL enrolled? */
871     BoardData* bdata = getBoardData(oldUrl);
872     if (bdata == 0) {
873
874         /* Is newURL enrolled? */
875         bdata = getBoardData(newUrl);
876         if (bdata == 0) return false;
877     }
878
879
880     /*---------------------------*/
881     /* update BoardData */
882
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();
891
892     /* update URL */
893     bdata->setHostName(newhost);
894
895     /* update keys */
896     /* The order of keyHosts will be like this:
897        
898       newhost      
899       oldhost      
900       foohost1
901       foohost2
902       
903     */
904     keyHosts = bdata->keyHostList();
905     keyHosts.removeOne(oldhost);
906     keyHosts.prepend(oldhost);
907     keyHosts.removeOne(newhost);
908     keyHosts.prepend(newhost);
909     bdata->createKeys(keyHosts);
910
911     /* reset BoardData */
912     bdata->setReadIdx(false);
913     bdata->setSettingLoaded(false);
914
915
916     /*---------------------------*/
917     /* move cache dir */
918
919     QDir qdir;
920     if (! qdir.exists(oldCachePath)) return true;
921
922     /* mkdir new server dir */
923     Cache newCache(bdata->basePath());
924     QString newCachePath = Cache::baseDir() + newCache.serverDir();
925     QDir::root().mkpath(newCachePath);
926
927     /* backup old dir */
928     newCachePath += newCache.boardDir();
929     if (qdir.exists (newCachePath)) {
930         QString bkupPath = newCachePath;
931         bkupPath.truncate(bkupPath.length() - 1); /* remove '/' */
932         bkupPath +=
933             '.' + QString::number(QDateTime::currentDateTime().toTime_t());
934         qdir.rename(newCachePath, bkupPath);
935     }
936
937     /* move cache dir */
938     if (qdir.exists(oldCachePath)) {
939         qdir.rename(oldCachePath, newCachePath);
940     } else
941         QDir::root().mkpath(newCachePath);
942
943     /* make old dir */
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;
952         }
953         file.close();
954     }
955
956     /*---------------------------*/
957     /* update BBSHISTRY */
958
959     Cache historyCache(bdata->basePath());
960     QFile file(historyCache.getBBSHistoryPath());
961     if (file.open(QIODevice::WriteOnly)) {
962
963         QTextStream ts(&file);
964
965         keyHosts.removeOne(newhost);
966         for (QStringList::iterator it = keyHosts.begin() ; it != keyHosts.end(); ++it) {
967             ts << (*it) << endl;
968         }
969
970         file.close();
971     }
972
973
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);
980
981     return true;
982 }