OSDN Git Service

Rename boardmanager.cpp and boardmanager.h
[kita/kita.git] / kita / src / libkita / access.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 #include "access.h"
11
12 #include <QtCore/QByteArray>
13 #include <QtCore/QDataStream>
14 #include <QtCore/QDateTime>
15 #include <QtCore/QFile>
16 #include <QtCore/QRegExp>
17
18 #include <kdatetime.h>
19 #include <kurl.h>
20 #include <kio/job.h>
21 #include <kio/jobuidelegate.h>
22 #include <kio/slaveconfig.h>
23
24 #include "account.h"
25 #include "boarddatabase.h"
26 #include "cache.h"
27 #include "config.h"
28 #include "flashcgi.h"
29 #include "jbbs.h"
30 #include "kita_misc.h"
31 #include "k2ch.h"
32 #include "machibbs.h"
33
34 using namespace Kita;
35
36
37 Access::Access(const KUrl& datUrl) : m_datUrl(datUrl), m_currentJob(0)
38 {
39     init();
40 }
41
42 /* public */
43 void Access::init()
44 {
45     m_readNum = 0;
46     m_lastLine.clear();
47     BoardDatabase db(m_datUrl);
48     m_bbstype = db.type();
49     m_header = "HTTP/1.1 200 ";  /* dummy header */
50     m_dataSize = 0;
51     m_threadData.clear();
52 }
53
54 /* read data from cache, then emit data to DatInfo. */ /* public */
55 void Access::getcache()
56 {
57     QByteArray orgData;
58
59     // get cache path.
60     Cache cache(m_datUrl);
61     QString cachePath = cache.getPath();
62     if (cachePath.isEmpty()) {
63         return;
64     }
65
66     // read cache file.
67     QFile file(cachePath);
68     if (file.open(QIODevice::ReadOnly)) {
69         orgData += file.readAll();
70         file.close();
71     }
72
73     // set data size.
74     if (orgData.isEmpty()) return ;
75     m_dataSize = orgData.length();
76
77     switch (m_bbstype) {
78
79     case Board_2ch:
80     case Board_MachiBBS:  /* Machi BBS's data is already parsed as 2ch dat. */
81
82         {
83             QString tmpData = K2ch().datToUnicode(orgData);
84             QStringList tmpList = tmpData.split('\n');
85             emit receiveData(tmpList);
86         }
87         break;
88
89     default:
90         /* convert data stream into 2ch dat.
91         and emit receiveData SIGNAL.        */
92         emitDatLineList(orgData);
93         break;
94     }
95 }
96
97 /* write data to cache */ /* protected */
98 void Access::writeCacheData()
99 {
100     if (m_invalidDataReceived) return ;
101     if (m_threadData.isEmpty()) return ;
102
103     m_dataSize += m_threadData.length();
104
105     Cache cache(m_datUrl);
106     QString cachePath = cache.getPath();
107     if (!cachePath.isEmpty()) {
108         QFile file(QFile::encodeName(cachePath));
109         if (!file.open(QIODevice::WriteOnly))
110             return;
111         QDataStream out(&file);
112         out << m_threadData;
113     }
114     m_threadData.clear(); /* clear baffer */
115
116     return ;
117 }
118
119 /* update cache file */ /* public */
120 bool Access::getupdate(int readNum)
121 {
122     /* init */
123     m_readNum = readNum;
124     m_threadData.clear();
125     m_firstReceive = false;
126     m_invalidDataReceived = false;
127     m_lastLine.clear();
128
129     /* set URL of data */
130     QString getUrl;
131     switch (m_bbstype) {
132
133     case Board_MachiBBS:
134         getUrl = getThreadUrl(m_datUrl);
135         if (m_readNum > 0)
136             getUrl += "&START=" + QString::number(m_readNum + 1);
137         InitParseMachiBBS();
138         break;
139
140     case Board_JBBS:
141         getUrl = getThreadUrl(m_datUrl);
142         getUrl.replace("read.cgi", "rawmode.cgi");  /* adhoc... */
143                 if (m_readNum > 0)
144             getUrl += '/' + QString::number(m_readNum + 1) + '-';
145         break;
146
147     default:
148         getUrl = m_datUrl.prettyUrl();
149     }
150
151     /* set UserAgent */
152     const QString useragent = QString("Monazilla/1.00 (Kita/%1)").arg(VERSION);
153     KIO::SlaveConfig::self() ->setConfigData("http",
154             KUrl(getUrl).host(),
155             "UserAgent", useragent);
156
157     /* create new job */
158     KIO::TransferJob* job
159         = KIO::get(getUrl, KIO::Reload, KIO::HideProgressInfo);
160     m_currentJob = job;
161
162     connect(job, SIGNAL(data(KIO::Job*, const QByteArray&)),
163             SLOT(slotReceiveThreadData(KIO::Job*, const QByteArray&)));
164     connect(job, SIGNAL(result(KJob*)), SLOT(slotThreadResult(KJob*)));
165
166     // use 'HTTP-Headers' metadata.
167     job->addMetaData("PropagateHttpHeader", "true");
168
169     /* resume */
170     if (m_bbstype != Board_MachiBBS
171             && m_bbstype != Board_JBBS
172             && m_dataSize > 0) {
173         m_firstReceive = true; /* remove first char (i.e. \n). see also slotReceiveThreadData() */
174         job->addMetaData("resume", QString::number(m_dataSize - 1));
175         job->addMetaData("AllowCompressedPage", "false");
176     }
177
178     return true;
179 }
180
181 void Access::slotThreadResult(KJob* job)
182 {
183     KIO::TransferJob *tjob = qobject_cast<KIO::TransferJob *>(job);
184     if (!tjob) {
185         return;
186     }
187     m_currentJob = 0;
188     if (tjob->error()) {
189         tjob->ui()->setWindow(0);
190         tjob->ui()->showErrorMessage();
191     } else {
192         m_header = tjob->queryMetaData("HTTP-Headers");
193     }
194
195     writeCacheData();
196     emit finishLoad();
197 }
198
199 void Access::slotReceiveThreadData(KIO::Job*, const QByteArray& data)
200 {
201     QByteArray data_tmp(data);
202
203     // HACK: crash when data contains '\0'.
204     for (int i=0; i< data_tmp.size(); i++) {
205         if (data_tmp[ i ] == '\0') data_tmp[ i ] = ' ';
206     }
207
208     if (m_bbstype == Board_MachiBBS
209             || m_bbstype == Board_JBBS) {
210
211         emitDatLineList(data_tmp);
212         return ;
213     }
214
215     /* check if received data is invalid (or broken). */
216     if ((m_dataSize > 0 && responseCode() != 206)
217             || (m_firstReceive && data_tmp[ 0 ] != '\n')
218             || (m_dataSize == 0 && responseCode() != 200))
219         m_invalidDataReceived = true;
220
221     if (m_invalidDataReceived) return ;
222
223     /* If this is the first call at resumption, remove LF(\n) at head. */
224     if (m_firstReceive) {
225         data_tmp = data_tmp.mid(1);
226     }
227     m_firstReceive = false;
228
229     emitDatLineList(data_tmp);
230 }
231
232
233 /* convert data stream into 2ch dat.
234    and emit receiveData SIGNAL.        */  /* private */
235 void Access::emitDatLineList(const QByteArray& dataStream)
236 {
237     QList<QByteArray> lineList;
238     QStringList datLineList;
239     if (dataStream.isEmpty()) return ;
240
241     bool endIsLF = false;
242     if (dataStream.at(dataStream.length() - 1) == '\n') endIsLF = true;
243
244     /* split the stream */
245     m_lastLine += dataStream;
246     lineList = m_lastLine.split('\n');
247     m_lastLine.clear();
248
249     /* save the last line */
250     if (!endIsLF) {
251
252         QList<QByteArray>::iterator lastit = lineList.end();
253         lastit--;
254         if (lastit != lineList.end()) {
255
256             m_lastLine = (*lastit);
257             lineList.removeLast();
258         }
259     }
260
261     /* filtering */
262
263     /* convert lines into 2ch dat */
264     int count = lineList.count();
265     for (int i = 0; i < count ; ++i) {
266
267         if (! lineList[ i ].isEmpty()) {
268
269             QString line, line2;
270             QByteArray ba;
271             int nextNum = m_readNum + 1;
272
273             /* convert line */
274             switch (m_bbstype) {
275
276             case Board_MachiBBS:
277                 {
278                     MachiBBS machiBbs;
279                     line = machiBbs.datToUnicode(lineList[i]);
280                     line2 = ParseMachiBBSOneLine(line, nextNum);
281                     ba = machiBbs.datFromUnicode(line2);
282                 }
283                 break;
284
285             case Board_JBBS:
286                 {
287                     JBBS jBbs;
288                     line = jBbs.datToUnicode(lineList[i]);
289                     line2 = ParseJBBSOneLine(line, nextNum);
290                     ba = jBbs.datFromUnicode(line2);
291                 }
292                 break;
293
294             case Board_FlashCGI:
295                 {
296                     FlashCGI flashCgi;
297                     line = flashCgi.datToUnicode(lineList[i]);
298                     line2 = ParseFlashCGIOneLine(line);
299                     ba = flashCgi.datFromUnicode(line2);
300                 }
301                 break;
302
303             default:
304                 line = line2 = K2ch().datToUnicode(lineList[i]);
305                 ba = lineList[i];
306             }
307
308             if (line2.isEmpty()) continue;
309
310             /* add abone lines */
311             const char aboneStr[] = "abone<><><>abone<>";
312             while (nextNum > m_readNum + 1) {
313                 datLineList += aboneStr;
314                 m_threadData += aboneStr + '\n';
315                 ++m_readNum;
316             }
317
318             /* save line */
319             if (m_bbstype == Board_MachiBBS) m_threadData += ba + '\n';
320             else m_threadData += lineList[ i ] + '\n';
321             ++m_readNum;
322
323             datLineList += line2;
324         }
325     }
326
327     /* call DatInfo::slotReceiveData() */
328     emit receiveData(datLineList);
329 }
330
331
332
333 void Access::killJob()
334 {
335     if (m_currentJob) m_currentJob->kill();
336 }
337
338 void Access::stopJob()
339 {
340     if (m_currentJob) m_currentJob->kill(); /* emit result signal */
341 }
342
343 int Access::serverTime()
344 {
345     if (m_currentJob) m_header = m_currentJob->queryMetaData("HTTP-Headers");
346     //if (m_header.isEmpty()) return QDateTime::currentDateTime().toTime_t();
347     // parse HTTP headers
348     QStringList headerList = m_header.split('\n');
349     QRegExp regexp("Date: (.*)");
350     QStringList dateStrList = headerList.filter(regexp);
351     if (dateStrList.isEmpty() || regexp.indexIn(dateStrList[0]) == -1) {
352         // 'Date' header is not found. use current time.
353         return QDateTime::currentDateTime().toTime_t();
354     } else {
355         return KDateTime::fromString(regexp.cap(1), KDateTime::RFCDate)
356             .toTime_t();
357     }
358 }
359
360 int Access::responseCode()
361 {
362     if (m_currentJob) m_header = m_currentJob->queryMetaData("HTTP-Headers");
363     // parse HTTP headers
364     QStringList headerList = m_header.split('\n');
365     QRegExp regexp("HTTP/1\\.[01] ([0-9]+) .*");
366     QStringList dateStrList = headerList.filter(regexp);
367     if (dateStrList.isEmpty() || regexp.indexIn(dateStrList[0]) == -1) {
368         // invalid response
369         if (m_bbstype == Board_JBBS) return 200; /* adhoc... */
370         return 0;
371     } else {
372         return regexp.cap(1).toInt();
373     }
374 }
375
376 /* public */
377 int Access::dataSize() const
378 {
379     return m_dataSize;
380 }
381
382 /* public */
383 bool Access::invalidDataReceived() const
384 {
385     return m_invalidDataReceived;
386 }
387
388 //
389 // access offlaw.cgi
390 //
391 QString OfflawAccess::get()
392 {
393     QString getUrl = datToOfflaw(m_datUrl.url());
394     KUrl kgetUrl(getUrl);
395     kgetUrl.addQueryItem("sid", Account::getSessionId());
396
397     m_threadData.clear();
398     m_invalidDataReceived = false;
399
400     KIO::SlaveConfig::self() ->setConfigData("http",
401             KUrl(getUrl).host(),
402             "UserAgent",
403             QString("Monazilla/1.00 (Kita/%1)").arg(VERSION));
404
405     KIO::TransferJob* job
406         = KIO::get(kgetUrl, KIO::Reload, KIO::HideProgressInfo);
407     m_currentJob = job;
408
409     connect(job, SIGNAL(data(KIO::Job*, const QByteArray&)),
410              SLOT(slotReceiveThreadData(KIO::Job*, const QByteArray&)));
411     connect(job, SIGNAL(result(KJob*)), SLOT(slotThreadResult(KJob*)));
412
413     // use 'HTTP-Headers' metadata.
414     job->addMetaData("PropagateHttpHeader", "true");
415
416     return QString(); /* dummy */
417 }
418
419 void OfflawAccess::slotThreadResult(KIO::Job* job)
420 {
421     m_currentJob = 0;
422     if (job->error()) {
423         job->ui()->setWindow(0);
424         job->ui()->showErrorMessage();
425     } else {
426         m_header = job->queryMetaData("HTTP-Headers");
427     }
428
429     if (!m_invalidDataReceived && !m_threadData.isEmpty()) {
430         KUrl url = m_datUrl;
431         writeCacheData();
432     }
433     emit finishLoad();
434 }
435
436 void OfflawAccess::slotReceiveThreadData(KIO::Job*, const QByteArray& data)
437 {
438     QByteArray cstr(data);
439
440     if ((m_dataSize > 0 && responseCode() != 206)
441             || (m_dataSize == 0 && responseCode() != 200)) {
442         m_invalidDataReceived = true;
443     }
444
445     if (m_invalidDataReceived) return ;
446
447     // "+OK ....../1024K\tLocation:temp/\n"
448     if (m_threadData.isEmpty() && cstr[ 0 ] == '+') {
449         // skip first line.
450         int index = cstr.indexOf('\n');
451         cstr = cstr.mid(index + 1);
452     }
453     emitDatLineList(cstr);
454 }