OSDN Git Service

Move the directories
[kita/kita.git] / src / libkita / boarddatabase.cpp
diff --git a/src/libkita/boarddatabase.cpp b/src/libkita/boarddatabase.cpp
new file mode 100644 (file)
index 0000000..f93e7fb
--- /dev/null
@@ -0,0 +1,704 @@
+/***************************************************************************
+*   Copyright (C) 2010 by Kita Developers                                 *
+*   ikemo@users.sourceforge.jp                                            *
+*                                                                         *
+*   This program is free software; you can redistribute it and/or modify  *
+*   it under the terms of the GNU General Public License as published by  *
+*   the Free Software Foundation; either version 2 of the License, or     *
+*   (at your option) any later version.                                   *
+***************************************************************************/
+#include "boarddatabase.h"
+
+#include <QtCore/QDateTime>
+#include <QtCore/QDir>
+#include <QtCore/QTextCodec>
+#include <QtCore/QTextStream>
+
+#include <kfilterdev.h>
+#include <kio/netaccess.h>
+#include <kio/slaveconfig.h>
+
+#include "cache.h"
+#include "config.h"
+#include "favoriteboards.h"
+#include "favoritethreads.h"
+#include "thread.h"
+#include "threadindex.h"
+#include "threadinfo.h"
+
+using namespace Kita;
+
+BoardDataList BoardDatabase::m_boardDataList;
+QTextCodec* BoardDatabase::m_cp932Codec = 0;
+QTextCodec* BoardDatabase::m_eucJpCodec = 0;
+BoardData* BoardDatabase::m_previousBoardData = 0;
+QString BoardDatabase::m_previousBoardUrl;
+
+BoardDatabase::BoardDatabase(const KUrl& url) : m_url(url)
+{
+}
+
+void BoardDatabase::setUrl(const KUrl& url)
+{
+    m_url = url;
+}
+
+/* (hostname)/(rootPath)/(bbsPath)/ */ /* public */
+QString BoardDatabase::boardUrl()
+{
+    BoardData * bdata = getBoardData();
+    return (bdata == 0) ? QString() : bdata->basePath();
+}
+
+/* public */
+QStringList BoardDatabase::allBoardUrlList()
+{
+    QStringList urlList;
+
+    BoardData *data;
+    foreach (data, m_boardDataList)
+        urlList += data->basePath();
+    return urlList;
+}
+
+/* (hostname)/(rootPath) */ /* public */
+QString BoardDatabase::boardRoot()
+{
+    BoardData * bdata = getBoardData();
+    return (bdata == 0) ? QString() : bdata->hostName() + bdata->rootPath();
+}
+
+/* (bbspath) */ /* public */
+QString BoardDatabase::boardPath()
+{
+    BoardData * bdata = getBoardData();
+    return (bdata == 0) ? QString() : bdata->bbsPath();
+}
+
+/* (ext) */ /* public */
+QString BoardDatabase::ext()
+{
+    BoardData * bdata = getBoardData();
+    return (bdata == 0) ? QString() : bdata->ext();
+}
+
+/* ID of board for writing */ /* public */
+QString BoardDatabase::boardId()
+{
+    BoardData * bdata = getBoardData();
+    return (bdata == 0) ? QString() : bdata->bbsPath().mid(1); /* remove "/" */
+}
+
+/* (hostname)/(rootPath)/(bbsPath)/subject.txt */ /* public */
+QString BoardDatabase::subjectUrl()
+{
+    BoardData * bdata = getBoardData();
+    return (bdata == 0) ? QString() : bdata->basePath() + "subject.txt";
+}
+
+/* public */
+QString BoardDatabase::boardName()
+{
+    BoardData * bdata = getBoardData();
+    return (bdata == 0) ? QString() : bdata->boardName();
+}
+
+/* public */
+int BoardDatabase::type()
+{
+    BoardData * bdata = getBoardData();
+    return (bdata == 0) ? Board_Unknown : bdata->type();
+}
+
+/*---------------------------*/
+/* ThreadList */
+
+
+/*  get list of pointers of Thread classes.     */
+/*
+  Input:
+  oldLogs: If true, search cache and get list of pointer of old threads.
+  online: online or offline mode.
+  Output:
+  threadList: list of pointers of Thread classes.
+  oldLogList: list of pointers of old threads.
+                                                 */ /* public */
+void BoardDatabase::getThreadList(
+
+    /* input */
+    bool oldLogs,
+    bool online,
+
+    /* output */
+    QList<Thread*>& threadList,
+    QList<Thread*>& oldLogList)
+{
+    threadList.clear();
+    oldLogList.clear();
+
+    /* get all obtained threads list from cache */
+    if (m_url.prettyUrl() == "http://virtual/obtained/") {
+
+        QStringList bbslist = allBoardUrlList();
+
+        /* search all cache dirs */
+        QString thread;
+        foreach (thread, bbslist) {
+            getCachedThreadList(thread, threadList);
+        }
+
+        return ;
+    }
+
+    /*-------------------------*/
+
+    BoardData* bdata = getBoardData();
+    if (bdata == 0) return ;
+
+    /* download subject.txt */
+    if (online) {
+
+        /* make directory */
+        Cache cache(m_url);
+        QString cacheDir = cache.getDirPath();
+        if (!QDir::root().mkpath(cacheDir)) return;
+
+        KIO::SlaveConfig::self() ->setConfigData("http",
+                m_url.host() ,
+                "UserAgent",
+                QString("Monazilla/1.00 (Kita/%1)").arg(VERSION));
+        QString subjectPath = cache.getSubjectPath();
+        KIO::NetAccess::download(subjectUrl(), subjectPath, 0);
+    }
+
+    /* open and read subject.txt */
+    readSubjectTxt(bdata, threadList);
+
+    /* get old logs */
+    if (oldLogs) {
+
+        QList<Thread*> tmpList;
+        tmpList.clear();
+        getCachedThreadList(m_url, tmpList);
+
+        for (int i = 0; i < tmpList.count(); i++) {
+            if (threadList.contains(tmpList.at(i)) == 0)
+                oldLogList.append(tmpList.at(i));
+        }
+    }
+}
+
+/* read the cache dir & get the list of all threads. */ /* private */
+void BoardDatabase::getCachedThreadList(const KUrl& url,
+    QList<Thread*>& threadList)
+{
+    Cache cache(url);
+    QString cacheDir = cache.getDirPath();
+    QDir d(cacheDir);
+    if (d.exists()) {
+
+        /* get all file names */
+        QString ext = getBoardData(url)->ext();
+        QString boardUrl = getBoardData(url)->basePath();
+        QStringList filter('*' + ext);
+        QStringList flist = d.entryList(filter);
+        QString file;
+        foreach (file, flist) {
+            if (file.isEmpty()) continue;
+
+            QString datUrl = boardUrl + "dat/" + file;
+
+            /* read idx file */
+            Thread* thread = Thread::getByUrlNew(datUrl);
+            if (thread == 0) {
+
+                thread = Thread::getByUrl(datUrl);
+                if (thread == 0)
+                    continue;
+                ThreadIndex threadIndex(datUrl);
+                threadIndex.loadIndex(thread, false);
+            }
+
+            if (thread != 0)
+                threadList.append(thread);
+        }
+    }
+}
+
+/* open subject.txt and get list of Thread classes */ /* private */
+bool BoardDatabase::readSubjectTxt(BoardData* bdata, QList<Thread*>& threadList)
+{
+    /* get all names of cached files to read idx.  */
+    QStringList cacheList;
+    if (!bdata->readIdx()) {
+        Cache cache(m_url);
+        QString cacheDir = cache.getDirPath();
+        QDir d(cacheDir);
+        if (d.exists()) {
+            QString ext = getBoardData()->ext();
+            QStringList filter('*' + ext);
+            cacheList = d.entryList(filter);
+        }
+    }
+
+    /* open subject.txt */
+    Cache cache(m_url);
+    QString subjectPath = cache.getSubjectPath();
+    QIODevice * device = KFilterDev::deviceForFile(subjectPath, "application/x-gzip");
+    if (!device->open(QIODevice::ReadOnly))
+        return false;
+
+    QTextStream stream(device);
+
+    if (type() == Board_JBBS) {
+        if (!m_eucJpCodec) m_eucJpCodec = QTextCodec::codecForName("eucJP");
+        stream.setCodec(m_eucJpCodec);
+    } else {
+        if (!m_cp932Codec) m_cp932Codec = QTextCodec::codecForName("Shift-JIS");
+        stream.setCodec(m_cp932Codec);
+    }
+
+    QRegExp regexp;
+    switch (type()) {
+
+    case Board_MachiBBS:
+    case Board_JBBS:
+        regexp.setPattern("(\\d+\\.cgi),(.*)\\((\\d+)\\)");
+        break;
+
+    default:
+        regexp.setPattern("(\\d+\\.dat)<>(.*)\\((\\d+)\\)");
+        break;
+    }
+    QString line;
+
+    while (!(line = stream.readLine()).isEmpty()) {
+        int pos = regexp.indexIn(line);
+        if (pos != -1) {
+            QString fname = regexp.cap(1);
+            QString subject = regexp.cap(2);
+            QString num = regexp.cap(3);
+
+            /* get pointer of Thread class */
+            QString datUrl = boardUrl() + "dat/" + fname;
+            Thread* thread = Thread::getByUrl(datUrl);
+            ThreadIndex threadIndex(datUrl);
+            if (threadList.indexOf(thread) == -1) {
+                threadList.append(thread);
+            }
+
+            /* set thread name */
+            thread->setThreadName(subject);
+
+            /* load index file */
+            if (!bdata->readIdx()) {
+
+                if (cacheList.contains(fname)) {
+                    threadIndex.loadIndex(thread, false);
+                }
+            }
+
+            /* update res num */
+            int newNum = num.toInt();
+            if (thread->readNum()) { /* cache exists */
+                int oldNum = thread->resNum();
+
+                if (newNum > oldNum) {
+                    threadIndex.setResNum(newNum);
+                }
+            }
+            thread->setResNum(newNum);
+        }
+    }
+
+    device->close();
+    bdata->setReadIdx(true); /* never read idx files again */
+
+    return true;
+}
+
+/*---------------------------*/
+/* BoardData */
+
+/* reset all BoardData */ /* public */
+void BoardDatabase::clearBoardData()
+{
+    BoardData *data;
+    foreach (data, m_boardDataList)
+        delete data;
+
+    m_boardDataList.clear();
+    m_previousBoardData = 0;
+    m_previousBoardUrl.clear();
+}
+
+/**
+ *
+ * @param[in] board
+ * @param[in] boardName
+ * @param[in] type
+ * @param[in] test
+ *
+ * @param[out] oldURL
+ *
+ * @retval Board_enrollEnrolled if board is already enrolled. oldURL is QString().
+ * @retval Board_enrollNew if board is new board. oldURL is QString().
+ * @retval Board_enrollMoved if board is moved. oldURL is old URL.
+ *
+ * @note board is NOT enrolled when board is moved.
+ * To enroll new URL, call BoardDatabase::moveBoard(). 
+ *
+ * "int type" is type of board. It could be "Board_Unknown". See also parseBoardURL().
+ * 
+ * If "bool test" is true, this function just checks if the board is enrolled (never enroll board).
+ *
+ */ 
+/* public */
+int BoardDatabase::enrollBoard(const QString& boardName, QString& oldUrl, int type, bool test)
+{
+    QString hostname;
+    QString rootPath;
+    QString delimiter;
+    QString bbsPath;
+    QString ext;
+    type = parseBoardUrl(type, hostname, rootPath, delimiter, bbsPath, ext);
+    oldUrl.clear();
+
+    if (type == Board_Unknown) return Board_enrollFailed;
+
+    /* check if the board is enrolled or moved. */
+    BoardData *data;
+    foreach (data, m_boardDataList) {
+
+        if (data->boardName() == boardName
+                && data->type() == type
+                && data->bbsPath() == bbsPath) {
+
+            if (data->hostName() == hostname
+                    && data->rootPath() == rootPath) { /* enrolled */
+                return Board_enrollEnrolled;
+            } else { /* moved */
+                oldUrl = data->basePath();
+                return Board_enrollMoved;
+            }
+        }
+    }
+
+    /* test only */
+    if (test)
+        return Board_enrollNew;
+
+    /* enroll new board */
+    BoardData* bdata = new BoardData(boardName, hostname, rootPath, delimiter, bbsPath, ext, type);
+    m_boardDataList.append(bdata);
+
+    return Board_enrollNew;
+}
+
+/* parse board URL      */
+/* return board type.   */ /* private */
+int BoardDatabase::parseBoardUrl(
+
+    /* input */
+    int type,   /* If type = Board_Unknown, type will be decided according to url. */
+
+    /* output */
+    QString& hostname,
+    QString& rootPath,
+    QString& delimiter,
+    QString& bbsPath,
+    QString& ext)
+{
+    hostname = m_url.protocol() + "://" + m_url.host();
+    rootPath.clear();
+    delimiter.clear();
+    bbsPath.clear();
+    ext.clear();
+
+    /* decide type */
+    if (type == Board_Unknown) {
+
+        if (m_url.host().contains("machi.to"))
+            type = Board_MachiBBS;
+        else if (m_url.host().contains("jbbs.livedoor.jp"))
+            type = Board_JBBS;
+        else
+            type = Board_2ch;
+    }
+
+    /* parse */
+    switch (type) {
+
+    case Board_MachiBBS:     /* MACHI : http:// *.machi.to/(bbsPath)/ */
+
+        delimiter = "/bbs/read.pl";
+        bbsPath = m_url.fileName();
+        ext = ".cgi";
+        break;
+
+    case Board_JBBS:   /* JBBS : http://jbbs.livedoor.jp/(bbsPath)/ */
+
+        delimiter = "/bbs/read.cgi";
+        bbsPath = m_url.prettyUrl().remove(hostname);
+        type = Board_JBBS;
+        ext = ".cgi";
+        break;
+
+    case Board_FlashCGI:  /* test for Flash CGI/Mini Thread  */
+
+        delimiter = "/test/read.cgi";
+        bbsPath = m_url.fileName();
+        rootPath = m_url.prettyUrl().remove(hostname + '/').remove(bbsPath + '/');
+        if (rootPath.length() == 0)
+            rootPath.clear();
+        ext = ".dat";
+        break;
+
+    default:   /* 2ch : http://(hostname)/(rootPath)/(bbsPath)/ */
+
+        delimiter = "/test/read.cgi";
+        bbsPath = m_url.fileName();
+        rootPath = m_url.prettyUrl().remove(hostname + '/').remove(bbsPath + '/');
+        if (rootPath.length() == 0)
+            rootPath.clear();
+        ext = ".dat";
+        type = Board_2ch;
+        break;
+    }
+
+    /* For example, if bbsPath = "linux/", then m_bbsPath = "/linux" */
+    const QRegExp exp("/$");
+    rootPath.remove(exp);
+    bbsPath.remove(exp);
+    if (!rootPath.isEmpty() && rootPath.at(0) != '/')
+        rootPath = '/' + rootPath;
+    if (!bbsPath.isEmpty() && bbsPath.at(0) != '/')
+        bbsPath = '/' + bbsPath;
+
+    return type;
+}
+
+/* public */
+bool BoardDatabase::isEnrolled()
+{
+    return getBoardData() != 0;
+}
+
+/* public */
+BoardData* BoardDatabase::getBoardData(const KUrl& url)
+{
+    if (url.isEmpty())
+        return 0;
+    QString urlstr = url.prettyUrl();
+
+    /* cache */
+    if (m_previousBoardData != 0 && m_previousBoardUrl == urlstr)
+        return m_previousBoardData;
+
+    BoardData *data;
+    foreach (data, m_boardDataList) {
+
+        int count = data->keyBasePathList().count();
+        for (int i = 0; i < count ; ++i) {
+            if (urlstr.contains(data->keyBasePathList()[i])
+                    || urlstr.contains(data->keyCgiBasePathList()[i])) {
+
+                /* cache */
+                m_previousBoardData = data;
+                m_previousBoardUrl = urlstr;
+
+                return data;
+            }
+        }
+    }
+    return 0;
+}
+
+/* public */
+BoardData* BoardDatabase::getBoardData()
+{
+    return getBoardData(m_url);
+}
+
+/*--------------------------------*/
+/* BBSHISTORY */
+
+
+/* load the bbs history file (BBSHISTORY), and create keys of Data Base.  */
+/* Before calling this, enroll the board by enrollBoard().                  */
+/*
+    ex) If the host of board moved like :
+    http:://aaa.com -> http://bbb.com -> http://ccc.com -> http://ddd.com
+
+    then, BBSHISTORY is
+    http://ccc.com
+    http://bbb.com
+    http://aaa.com
+*/ /* public */
+bool BoardDatabase::loadBBSHistory()
+{
+    BoardData * bdata = getBoardData();
+    if (bdata == 0)
+        return false;
+
+    QStringList keyHosts(bdata->hostName());
+
+    Cache cache(m_url);
+    QFile file(cache.getBBSHistoryPath());
+    if (file.open(QIODevice::ReadOnly)) {
+
+        QTextStream ts(&file);
+
+        QString line;
+        while (!ts.atEnd()) {
+
+            line = ts.readLine();
+            keyHosts += line;
+        }
+
+        bdata->createKeys(keyHosts);
+        file.close();
+
+        return true;
+    }
+
+    return false;
+}
+
+/* public */
+bool BoardDatabase::moveBoard(const KUrl& fromUrl, const KUrl& toUrl)
+{
+    QString oldhost = fromUrl.protocol() + "://" + fromUrl.host();
+    QString newhost = toUrl.protocol() + "://" + toUrl.host();
+
+    const QRegExp exp("/$");
+    QString oldUrl = fromUrl.prettyUrl();
+    QString newUrl = toUrl.prettyUrl();
+    oldUrl.remove(exp);
+    newUrl.remove(exp);
+    oldUrl += '/';
+    newUrl += '/';
+
+    if (oldUrl == newUrl) return false;
+
+    /* Is oldURL enrolled? */
+    BoardData* bdata = getBoardData(oldUrl);
+    if (bdata == 0) {
+
+        /* Is newURL enrolled? */
+        bdata = getBoardData(newUrl);
+        if (bdata == 0) return false;
+    }
+
+
+    /*---------------------------*/
+    /* update BoardData */
+
+    /* get the path of old cache */
+    bdata->setHostName(oldhost);
+    QStringList keyHosts = bdata->keyHostList();
+    keyHosts.removeOne(oldhost);
+    keyHosts.prepend(oldhost);
+    bdata->createKeys(keyHosts);
+    Cache cache(bdata->basePath());
+    QString oldCachePath = cache.getDirPath();
+
+    /* update URL */
+    bdata->setHostName(newhost);
+
+    /* update keys */
+    /* The order of keyHosts will be like this:
+       
+      newhost      
+      oldhost      
+      foohost1
+      foohost2
+      
+    */
+    keyHosts = bdata->keyHostList();
+    keyHosts.removeOne(oldhost);
+    keyHosts.prepend(oldhost);
+    keyHosts.removeOne(newhost);
+    keyHosts.prepend(newhost);
+    bdata->createKeys(keyHosts);
+
+    /* reset BoardData */
+    bdata->setReadIdx(false);
+    bdata->setSettingLoaded(false);
+
+
+    /*---------------------------*/
+    /* move cache dir */
+
+    QDir qdir;
+    if (! qdir.exists(oldCachePath)) return true;
+
+    /* mkdir new server dir */
+    Cache newCache(bdata->basePath());
+    QString newCachePath = Cache::baseDir() + newCache.serverDir();
+    QDir::root().mkpath(newCachePath);
+
+    /* backup old dir */
+    newCachePath += newCache.boardDir();
+    if (qdir.exists (newCachePath)) {
+        QString bkupPath = newCachePath;
+        bkupPath.truncate(bkupPath.length() - 1); /* remove '/' */
+        bkupPath +=
+            '.' + QString::number(QDateTime::currentDateTime().toTime_t());
+        qdir.rename(newCachePath, bkupPath);
+    }
+
+    /* move cache dir */
+    if (qdir.exists(oldCachePath)) {
+        qdir.rename(oldCachePath, newCachePath);
+    } else
+        QDir::root().mkpath(newCachePath);
+
+    /* make old dir */
+    if (! qdir.exists(oldCachePath)) {
+        QDir::root().mkpath(oldCachePath);
+        /* create BBS_MOVED */
+        QString movedPath = oldCachePath + "/BBS_MOVED";
+        QFile file(movedPath);
+        if (file.open(QIODevice::WriteOnly)) {
+            QTextStream stream(&file);
+            stream << newUrl << endl;
+        }
+        file.close();
+    }
+
+    /*---------------------------*/
+    /* update BBSHISTRY */
+
+    Cache historyCache(bdata->basePath());
+    QFile file(historyCache.getBBSHistoryPath());
+    if (file.open(QIODevice::WriteOnly)) {
+
+        QTextStream ts(&file);
+
+        keyHosts.removeOne(newhost);
+        QString host;
+        foreach (host, keyHosts) {
+            ts << host << endl;
+        }
+
+        file.close();
+    }
+
+
+    /*---------------------------*/
+    /* update other information */
+    FavoriteThreads::replace(oldUrl, newUrl);
+    Thread::replace(oldUrl, newUrl);
+    ThreadInfo::replace(oldUrl, newUrl);
+    FavoriteBoards::replace(oldUrl, newUrl);
+
+    return true;
+}