OSDN Git Service

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