/* Copyright (c) 2007 Volker Krause This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "nntpresource.h" #include "nntpcollectionattribute.h" #include "configdialog.h" #include "settings.h" #include "settingsadaptor.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include typedef boost::shared_ptr MessagePtr; using namespace Akonadi; NntpResource::NntpResource(const QString & id) : ResourceBase( id ) { AttributeFactory::registerAttribute(); changeRecorder()->fetchCollection( true ); new SettingsAdaptor( Settings::self() ); QDBusConnection::sessionBus().registerObject( QLatin1String( "/Settings" ), Settings::self(), QDBusConnection::ExportAdaptors ); } NntpResource::~ NntpResource() { } bool NntpResource::retrieveItem(const Akonadi::Item& item, const QSet &parts) { Q_UNUSED( parts ); KIO::Job* job = KIO::storedGet( KUrl( item.remoteId() ), KIO::NoReload, KIO::HideProgressInfo ); setupKioJob( job ); connect( job, SIGNAL(result(KJob*)), SLOT(fetchArticleResult(KJob*)) ); return true; } void NntpResource::retrieveCollections() { remoteCollections.clear(); Collection rootCollection; rootCollection.setParentCollection( Collection::root() ); rootCollection.setRemoteId( baseUrl().url() ); rootCollection.setName( Settings::self()->name() ); CachePolicy policy; policy.setInheritFromParent( false ); policy.setSyncOnDemand( true ); policy.setLocalParts( QStringList( MessagePart::Envelope ) ); rootCollection.setCachePolicy( policy ); QStringList contentTypes; contentTypes << Collection::mimeType(); rootCollection.setContentMimeTypes( contentTypes ); remoteCollections << rootCollection; KUrl url = baseUrl(); QDate lastList = Settings::self()->lastGroupList().date(); if ( lastList.isValid() ) { mIncremental = true; url.addQueryItem( "since", QString( "%1%2%3 000000" ) .arg( lastList.year() % 100, 2, 10, QChar( '0' ) ) .arg( lastList.month(), 2, 10, QChar( '0' ) ) .arg( lastList.day(), 2, 10, QChar( '0' ) ) ); } else { mIncremental = false; } KIO::ListJob* job = KIO::listDir( url, KIO::HideProgressInfo, true ); setupKioJob( job ); connect( job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), SLOT(listGroups(KIO::Job*,KIO::UDSEntryList)) ); connect( job, SIGNAL(result(KJob*)), SLOT(listGroupsResult(KJob*)) ); } void NntpResource::retrieveItems( const Akonadi::Collection & col ) { if ( !(col.contentMimeTypes().count() == 1 && col.contentMimeTypes().first() == "message/news" ) ) { // not a newsgroup, skip it itemsRetrievalDone(); return; } KUrl url = baseUrl(); url.setPath( col.remoteId() ); NntpCollectionAttribute *attr = col.attribute(); if ( attr && attr->lastArticle() > 0 ) url.addQueryItem( "first", QString::number( attr->lastArticle() + 1 ) ); else url.addQueryItem( "max", QString::number( Settings::self()->maxDownload() ) ); KIO::ListJob* job = KIO::listDir( url, KIO::HideProgressInfo, true ); setupKioJob( job ); connect( job, SIGNAL(entries(KIO::Job*,KIO::UDSEntryList)), SLOT(listGroup(KIO::Job*,KIO::UDSEntryList)) ); connect( job, SIGNAL(result(KJob*)), SLOT(listGroupResult(KJob*)) ); } void NntpResource::listGroups(KIO::Job * job, const KIO::UDSEntryList & list) { Q_UNUSED( job ); QString name; QStringList contentTypes; contentTypes << "message/news"; for( KIO::UDSEntryList::ConstIterator it = list.constBegin(); it != list.constEnd(); ++it ) { name = (*it).stringValue( KIO::UDSEntry::UDS_NAME ); if ( name.isEmpty() ) continue; Collection c; c.setRemoteId( name ); c.setContentMimeTypes( contentTypes ); if ( Settings::self()->flatHierarchy() ) { c.setName( name ); c.parentCollection().setRemoteId( baseUrl().url() ); } else { const QStringList path = name.split( '.' ); Q_ASSERT( !path.isEmpty() ); c.setName( path.last() ); c.parentCollection().setRemoteId( findParent( path ) ); } remoteCollections << c; } } void NntpResource::listGroupsResult(KJob * job) { if ( job->error() ) { emit error( job->errorString() ); return; } else { Settings::self()->setLastGroupList( QDateTime::currentDateTime() ); } if ( mIncremental ) collectionsRetrievedIncremental( remoteCollections, Collection::List() ); else collectionsRetrieved( remoteCollections ); } void NntpResource::listGroup(KIO::Job * job, const KIO::UDSEntryList & list) { Q_UNUSED( job ); foreach ( const KIO::UDSEntry &entry, list ) { KUrl url = baseUrl(); url.setPath( currentCollection().remoteId() + '/' + entry.stringValue( KIO::UDSEntry::UDS_NAME ) ); Item item; item.setRemoteId( url.url() ); item.setMimeType( "message/news" ); KMime::NewsArticle *art = new KMime::NewsArticle(); foreach ( uint field, entry.listFields() ) { if ( field >= KIO::UDSEntry::UDS_EXTRA && field <= KIO::UDSEntry::UDS_EXTRA_END ) { const QString value = entry.stringValue( field ); int pos = value.indexOf( ':' ); if ( pos >= value.length() - 1 ) continue; // value is empty const QString hdrName = value.left( pos ); const QString hdrValue = value.right( value.length() - ( hdrName.length() + 2 ) ); if ( hdrName == "Subject" ) { art->subject()->from7BitString( hdrValue.toLatin1() ); if ( art->subject()->isEmpty() ) art->subject()->fromUnicodeString( i18n( "no subject" ), art->defaultCharset() ); } else if ( hdrName == "From" ) { art->from()->from7BitString( hdrValue.toLatin1() ); } else if ( hdrName == "Date" ) { art->date()->from7BitString( hdrValue.toLatin1() ); } else if ( hdrName == "Message-ID" ) { art->messageID()->from7BitString( hdrValue.simplified().toLatin1() ); } else if ( hdrName == "References" ) { if ( !hdrValue.isEmpty() ) art->references()->from7BitString( hdrValue.toLatin1() ); } else if ( hdrName == "Lines" ) { art->lines()->setNumberOfLines( hdrValue.toInt() ); } else { // optional extra headers art->setHeader( new KMime::Headers::Generic( hdrName.toLatin1(), art, hdrValue.toLatin1() ) ); } } } item.setPayload( MessagePtr( art ) ); ItemCreateJob *append = new ItemCreateJob( item, currentCollection() ); // TODO: check error Q_UNUSED( append ) } } void NntpResource::listGroupResult(KJob * job) { if ( job->error() ) { emit error( job->errorString() ); } else { // store last serial number Collection col = currentCollection(); NntpCollectionAttribute *attr = col.attribute( Collection::AddIfMissing ); KIO::Job *j = static_cast( job ); if ( j->metaData().contains( "LastSerialNumber" ) ) attr->setLastArticle( j->metaData().value( "LastSerialNumber" ).toInt() ); CollectionModifyJob *modify = new CollectionModifyJob( col ); // TODO: check result signal Q_UNUSED( modify ) } itemsRetrievalDone(); } KUrl NntpResource::baseUrl() const { KUrl url; if ( Settings::self()->encryption() == Settings::SSL ) url.setProtocol( "nntps" ); else url.setProtocol( "nntp" ); url.setHost( Settings::self()->server() ); url.setPort( Settings::self()->port() ); if ( Settings::self()->requiresAuthentication() ) { url.setUser( Settings::self()->userName() ); url.setPass( Settings::self()->password() ); } return url; } void NntpResource::fetchArticleResult(KJob * job) { if ( job->error() ) { emit error( job->errorString() ); return; } KIO::StoredTransferJob *j = static_cast( job ); KMime::Message *msg = new KMime::Message(); msg->setContent( KMime::CRLFtoLF( j->data() ) ); msg->parse(); Item item = currentItem(); item.setMimeType( "message/news" ); item.setPayload( MessagePtr( msg ) ); itemRetrieved( item ); } void NntpResource::configure( WId windowId ) { ConfigDialog dlg; if ( windowId ) { KWindowSystem::setMainWindow( &dlg, windowId ); } dlg.setWindowIcon( KIcon( "message-news" ) ); if ( dlg.exec() ) { emit configurationDialogAccepted(); } else { emit configurationDialogRejected(); } if ( !Settings::self()->name().isEmpty() ) setName( Settings::self()->name() ); } void NntpResource::setupKioJob(KIO::Job * job) const { Q_ASSERT( job ); if ( Settings::self()->encryption() == Settings::TLS ) job->addMetaData( "tls", "on" ); else job->addMetaData( "tls", "off" ); // TODO connect percent and status message signals to something } QString NntpResource::findParent(const QStringList & _path) { QStringList path = _path; path.removeLast(); if ( path.isEmpty() ) return baseUrl().url(); QString rid = path.join( "." ); foreach ( const Collection &col, remoteCollections ) if ( col.remoteId() == rid ) return col.remoteId(); Collection parent; parent.setRemoteId( rid ); QStringList ct; ct << Collection::mimeType(); parent.setContentMimeTypes( ct ); parent.parentCollection().setRemoteId( findParent( path ) ); parent.setName( path.last() ); remoteCollections << parent; return parent.remoteId(); } void NntpResource::collectionChanged(const Akonadi::Collection & collection) { if ( collection.remoteId() == baseUrl().url() && !collection.name().isEmpty() ) { Settings::self()->setName( collection.name() ); setName( collection.name() ); } changeCommitted( collection ); } AKONADI_RESOURCE_MAIN( NntpResource )