/*
webpresenceplugin . cpp
Kopete Web Presence plugin
Copyright ( c ) 2005 by Tommi Rantala < tommi . rantala @ cs . helsinki . fi >
Copyright ( c ) 2002 , 2003 by Will Stephenson < will @ stevello . free - online . co . uk >
Kopete ( c ) 2002 - 2005 by the Kopete developers < kopete - devel @ kde . org >
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
* *
* 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 <tqdom.h>
# include <tqtimer.h>
# include <tqfile.h>
# include <kdebug.h>
# include <kconfig.h>
# include <kgenericfactory.h>
# include <kmessagebox.h>
# include <ktempfile.h>
# include <kstandarddirs.h>
# ifdef HAVE_XSLT
# include <libxml/parser.h>
# include <libxml/tree.h>
# include <libxslt/xsltconfig.h>
# include <libxslt/xsltInternals.h>
# include <libxslt/transform.h>
# include <libxslt/xsltutils.h>
# endif
# include "kopetepluginmanager.h"
# include "kopeteprotocol.h"
# include "kopeteaccountmanager.h"
# include "kopeteaccount.h"
# include "webpresenceplugin.h"
typedef KGenericFactory < WebPresencePlugin > 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 < Kopete : : Account > accounts = Kopete : : AccountManager : : self ( ) - > accounts ( * it ) ;
TQDictIterator < Kopete : : Account > 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. \n Check 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 < Kopete : : Account > list = Kopete : : AccountManager : : self ( ) - > accounts ( ) ;
// If no accounts, stop here
if ( ! list . isEmpty ( ) )
{
for ( TQPtrListIterator < Kopete : : Account > 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 < Kopete : : Protocol * > ( * 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"