/* webpresenceplugin.cpp Kopete Web Presence plugin Copyright (c) 2005 by Tommi Rantala Copyright (c) 2002,2003 by Will Stephenson Kopete (c) 2002-2005 by the Kopete developers ************************************************************************* * * * 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 "config.h" #include #include #include #include #include #include #include #include #include #ifdef HAVE_XSLT #include #include #include #include #include #include #endif #include "kopetepluginmanager.h" #include "kopeteprotocol.h" #include "kopeteaccountmanager.h" #include "kopeteaccount.h" #include "webpresenceplugin.h" typedef KGenericFactory WebPresencePluginFactory; K_EXPORT_COMPONENT_FACTORY( kopete_webpresence, WebPresencePluginFactory( "kopete_webpresence" ) ) WebPresencePlugin::WebPresencePlugin( TQObject *tqparent, const char *name, const TQStringList& /*args*/ ) : Kopete::Plugin( WebPresencePluginFactory::instance(), tqparent, name ), shuttingDown( false ), resultFormatting( WEB_HTML ) { m_writeScheduler = new TQTimer( this ); connect ( m_writeScheduler, TQT_SIGNAL( timeout() ), this, TQT_SLOT( slotWriteFile() ) ); connect( Kopete::AccountManager::self(), TQT_SIGNAL(accountRegistered(Kopete::Account*)), this, TQT_SLOT( listenToAllAccounts() ) ); connect( Kopete::AccountManager::self(), TQT_SIGNAL(accountUnregistered(Kopete::Account*)), this, TQT_SLOT( listenToAllAccounts() ) ); connect(this, TQT_SIGNAL(settingsChanged()), this, TQT_SLOT( loadSettings() ) ); loadSettings(); listenToAllAccounts(); } WebPresencePlugin::~WebPresencePlugin() { } void WebPresencePlugin::loadSettings() { KConfig *kconfig = KGlobal::config(); kconfig->setGroup( "Web Presence Plugin" ); frequency = kconfig->readNumEntry("UploadFrequency", 15); resultURL = kconfig->readPathEntry("uploadURL"); resultFormatting = WEB_UNDEFINED; if ( kconfig->readBoolEntry( "formatHTML", false ) ) { resultFormatting = WEB_HTML; } else if ( kconfig->readBoolEntry( "formatXHTML", false ) ) { resultFormatting = WEB_XHTML; } else if ( kconfig->readBoolEntry( "formatXML", false ) ) { resultFormatting = WEB_XML; } else if ( kconfig->readBoolEntry( "formatStylesheet", false ) ) { resultFormatting = WEB_CUSTOM; userStyleSheet = kconfig->readEntry("formatStylesheetURL"); } // Default to HTML if we dont get anything useful from config file. if ( resultFormatting == WEB_UNDEFINED ) resultFormatting = WEB_HTML; useImagesInHTML = kconfig->readBoolEntry( "useImagesHTML", false ); useImName = kconfig->readBoolEntry("showName", true); userName = kconfig->readEntry("showThisName"); showAddresses = kconfig->readBoolEntry("includeIMAddress", false); // Update file when settings are changed. slotWriteFile(); } void WebPresencePlugin::listenToAllAccounts() { // connect to signals notifying of all accounts' status changes ProtocolList protocols = allProtocols(); for ( ProtocolList::Iterator it = protocols.begin(); it != protocols.end(); ++it ) { TQDict accounts = Kopete::AccountManager::self()->accounts( *it ); TQDictIterator acIt( accounts ); for( ; Kopete::Account *account = acIt.current(); ++acIt ) { listenToAccount( account ); } } slotWaitMoreStatusChanges(); } void WebPresencePlugin::listenToAccount( Kopete::Account* account ) { if(account && account->myself()) { // Connect to the account's status changed signal // because we can't know if the account has already connected TQObject::disconnect( account->myself(), TQT_SIGNAL(onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), this, TQT_SLOT( slotWaitMoreStatusChanges() ) ) ; TQObject::connect( account->myself(), TQT_SIGNAL(onlineStatusChanged( Kopete::Contact *, const Kopete::OnlineStatus &, const Kopete::OnlineStatus & ) ), this, TQT_SLOT( slotWaitMoreStatusChanges() ) ); } } void WebPresencePlugin::slotWaitMoreStatusChanges() { if ( !m_writeScheduler->isActive() ) m_writeScheduler->start( frequency * 1000 ); } void WebPresencePlugin::slotWriteFile() { m_writeScheduler->stop(); // generate the (temporary) XML file representing the current contactlist KURL dest( resultURL ); if ( resultURL.isEmpty() || !dest.isValid() ) { kdDebug(14309) << "url is empty or not valid. NOT UPDATING!" << endl; return; } KTempFile* xml = generateFile(); xml->setAutoDelete( true ); kdDebug(14309) << k_funcinfo << " " << xml->name() << endl; switch( resultFormatting ) { case WEB_XML: m_output = xml; xml = 0L; break; case WEB_HTML: case WEB_XHTML: case WEB_CUSTOM: m_output = new KTempFile(); m_output->setAutoDelete( true ); if ( !transform( xml, m_output ) ) { //TODO: give some error to user, even better if shown only once delete m_output; m_output = 0L; delete xml; return; } delete xml; // might make debugging harder! break; default: return; } // upload it to the specified URL KURL src( m_output->name() ); KIO::FileCopyJob *job = KIO::file_move( src, dest, -1, true, false, false ); connect( job, TQT_SIGNAL( result( KIO::Job * ) ), TQT_SLOT( slotUploadJobResult( KIO::Job * ) ) ); } void WebPresencePlugin::slotUploadJobResult( KIO::Job *job ) { if ( job->error() ) { kdDebug(14309) << "Error uploading presence info." << endl; KMessageBox::queuedDetailedError( 0, i18n("An error occurred when uploading your presence page.\nCheck the path and write permissions of the destination."), 0, displayName() ); delete m_output; m_output = 0L; } } KTempFile* WebPresencePlugin::generateFile() { // generate the (temporary) XML file representing the current contactlist kdDebug( 14309 ) << k_funcinfo << endl; TQString notKnown = i18n( "Not yet known" ); TQDomDocument doc; doc.appendChild( doc.createProcessingInstruction( "xml", "version=\"1.0\" encoding=\"UTF-8\"" ) ); TQDomElement root = doc.createElement( "webpresence" ); doc.appendChild( root ); // insert the current date/time TQDomElement date = doc.createElement( "listdate" ); TQDomText t = doc.createTextNode( KGlobal::locale()->formatDateTime( TQDateTime::tqcurrentDateTime() ) ); date.appendChild( t ); root.appendChild( date ); // insert the user's name TQDomElement name = doc.createElement( "name" ); TQDomText nameText; if ( !useImName && !userName.isEmpty() ) nameText = doc.createTextNode( userName ); else nameText = doc.createTextNode( notKnown ); name.appendChild( nameText ); root.appendChild( name ); // insert the list of the user's accounts TQDomElement accounts = doc.createElement( "accounts" ); root.appendChild( accounts ); TQPtrList list = Kopete::AccountManager::self()->accounts(); // If no accounts, stop here if ( !list.isEmpty() ) { for( TQPtrListIterator it( list ); Kopete::Account *account=it.current(); ++it ) { TQDomElement acc = doc.createElement( "account" ); //output += h.openTag( "account" ); TQDomElement protoName = doc.createElement( "protocol" ); TQDomText protoNameText = doc.createTextNode( account->protocol()->pluginId() ); protoName.appendChild( protoNameText ); acc.appendChild( protoName ); Kopete::Contact* me = account->myself(); TQString displayName = me->property( Kopete::Global::Properties::self()->nickName() ).value().toString(); TQDomElement accName = doc.createElement( "accountname" ); TQDomText accNameText = doc.createTextNode( ( me ) ? displayName : notKnown ); accName.appendChild( accNameText ); acc.appendChild( accName ); TQDomElement acctqStatus = doc.createElement( "accountstatus" ); TQDomText statusText = doc.createTextNode( ( me ) ? statusAsString( me->onlinetqStatus() ) : notKnown ) ; acctqStatus.appendChild( statusText ); // Dont add these if we're shutting down, because the result // would be quite weird. if ( !shuttingDown ) { // Add away message as an attribute, if one exists. if ( me->onlinetqStatus().status() == Kopete::OnlineStatus::Away && !me->property("awayMessage").value().toString().isEmpty() ) { acctqStatus.setAttribute( "awayreason", me->property("awayMessage").value().toString() ); } // Add the online status description as an attribute, if one exits. if ( !me->onlinetqStatus().description().isEmpty() ) { acctqStatus.setAttribute( "statusdescription", me->onlinetqStatus().description() ); } } acc.appendChild( acctqStatus ); if ( showAddresses ) { TQDomElement accAddress = doc.createElement( "accountaddress" ); TQDomText addressText = doc.createTextNode( ( me ) ? me->contactId() : notKnown ); accAddress.appendChild( addressText ); acc.appendChild( accAddress ); } accounts.appendChild( acc ); } } // write the XML to a temporary file KTempFile* file = new KTempFile(); TQTextStream *stream = file->textStream(); stream->setEncoding( TQTextStream::UnicodeUTF8 ); doc.save( *stream, 4 ); file->close(); return file; } bool WebPresencePlugin::transform( KTempFile * src, KTempFile * dest ) { #ifdef HAVE_XSLT bool retval = true; xmlSubstituteEntitiesDefault( 1 ); xmlLoadExtDtdDefaultValue = 1; TQFile sheet; switch ( resultFormatting ) { case WEB_XML: // Oops! We tried to call transform() but XML was requested. return false; case WEB_HTML: if ( useImagesInHTML ) { sheet.setName( locate( "appdata", "webpresence/webpresence_html_images.xsl" ) ); } else { sheet.setName( locate( "appdata", "webpresence/webpresence_html.xsl" ) ); } break; case WEB_XHTML: if ( useImagesInHTML ) { sheet.setName( locate( "appdata", "webpresence/webpresence_xhtml_images.xsl" ) ); } else { sheet.setName( locate( "appdata", "webpresence/webpresence_xhtml.xsl" ) ); } break; case WEB_CUSTOM: sheet.setName( userStyleSheet ); break; default: // Shouldn't ever reach here. return false; } // TODO: auto / smart pointers would be useful here xsltStylesheetPtr cur = 0; xmlDocPtr doc = 0; xmlDocPtr res = 0; if ( !sheet.exists() ) { kdDebug(14309) << k_funcinfo << "ERROR: Style sheet not found" << endl; retval = false; goto end; } // is the cast safe? cur = xsltParseStylesheetFile( (const xmlChar *) sheet.name().latin1() ); if ( !cur ) { kdDebug(14309) << k_funcinfo << "ERROR: Style sheet parsing failed" << endl; retval = false; goto end; } doc = xmlParseFile( TQFile::encodeName( src->name() ) ); if ( !doc ) { kdDebug(14309) << k_funcinfo << "ERROR: XML parsing failed" << endl; retval = false; goto end; } res = xsltApplyStylesheet( cur, doc, 0 ); if ( !res ) { kdDebug(14309) << k_funcinfo << "ERROR: Style sheet apply failed" << endl; retval = false; goto end; } if ( xsltSaveResultToFile(dest->fstream(), res, cur) == -1 ) { kdDebug(14309) << k_funcinfo << "ERROR: Style sheet apply failed" << endl; retval = false; goto end; } // then it all worked! dest->close(); end: xsltCleanupGlobals(); xmlCleanupParser(); if (doc) xmlFreeDoc(doc); if (res) xmlFreeDoc(res); if (cur) xsltFreeStylesheet(cur); return retval; #else Q_UNUSED( src ); Q_UNUSED( dest ); return false; #endif } ProtocolList WebPresencePlugin::allProtocols() { kdDebug( 14309 ) << k_funcinfo << endl; Kopete::PluginList plugins = Kopete::PluginManager::self()->loadedPlugins( "Protocols" ); Kopete::PluginList::ConstIterator it; ProtocolList result; for ( it = plugins.begin(); it != plugins.end(); ++it ) { result.append( static_cast( *it ) ); } return result; } TQString WebPresencePlugin::statusAsString( const Kopete::OnlineStatus &newtqStatus ) { if (shuttingDown) return "OFFLINE"; TQString status; switch ( newtqStatus.status() ) { case Kopete::OnlineStatus::Online: status = "ONLINE"; break; case Kopete::OnlineStatus::Away: status = "AWAY"; break; case Kopete::OnlineStatus::Offline: case Kopete::OnlineStatus::Invisible: status = "OFFLINE"; break; default: status = "UNKNOWN"; } return status; } void WebPresencePlugin::aboutToUnload() { // Stop timer. Dont need it anymore. m_writeScheduler->stop(); // Force statusAsString() report all accounts as OFFLINE. shuttingDown = true; // Do final update of webpresence file. slotWriteFile(); emit readyForUnload(); } // vim: set noet ts=4 sts=4 sw=4: #include "webpresenceplugin.moc"