/*************************************************************************** application.cpp - description ------------------- begin : Thu Mar 20 2003 copyright : (C) 2003 by Mike K. Bennett email : mkb137b@hotmail.com ***************************************************************************/ /*************************************************************************** * * * 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 "application.h" #include "../../currentaccount.h" #include "../../kmessdebug.h" #include "../mimemessage.h" #include <stdlib.h> #include <KLocale> /** * @brief The constructor */ 00033 Application::Application(const QString &contactHandle) : QObject(), chat_(0), closing_(false), contactHandle_(contactHandle), doDelayDeletion_(false), mode_(APP_MODE_NORMAL), type_(ChatMessage::TYPE_APPLICATION), userAnswered_(false), userStartedApp_(true), waitingForUser_(false) { setObjectName("*Application"); } /** * @brief The destructor * * Marks the application as aborting. */ 00055 Application::~Application() { closing_ = true; // for consistency #ifdef KMESSDEBUG_APPLICATION kmDebug() << "DESTROYED. [handle=" << contactHandle_ << "]"; #endif } /** * @brief The contact aborted the session * * This method is called when one of the derived classes detect * the contact aborted the session while data was transferred. * Requests to terminate the application by calling endApplication(). * * @param message Optional message to display, defaults to getContactAbortMessage(). */ 00074 void Application::contactAborted(const QString &message) { #ifdef KMESSDEBUG_APPLICATION kmDebug(); #endif if( closing_ ) { kmWarning() << "Attempted to close application twice (contact=" << contactHandle_ << ")"; } // Remove the accept links modifyOfferMessage(); if( message.isEmpty() ) { showEventMessage( getContactAbortMessage(), ChatMessage::CONTENT_APP_CANCELED, true ); } else { showEventMessage( message, ChatMessage::CONTENT_APP_CANCELED, true ); } endApplication(); } /** * @brief The contact declined the invitation * * This method is called when one of the derived classes detect * the contact declined the invitation for a session. * Requests to terminate the application by calling endApplication(). * * @param message Optional message to display, defaults to getContactRejectMessage(). */ 00110 void Application::contactRejected(const QString &message) { #ifdef KMESSDEBUG_APPLICATION kmDebug(); #endif if( closing_ ) { kmWarning() << "Attempted to close application twice (contact=" << contactHandle_ << ")"; } // Remove the accept links modifyOfferMessage(); if( message.isEmpty() ) { showEventMessage( getContactRejectMessage(), ChatMessage::CONTENT_APP_CANCELED, true ); } else { showEventMessage( message, ChatMessage::CONTENT_APP_CANCELED, true ); } endApplication(); } /** * @brief Step 1 of a contact-started chat: the contact invites the user * * By default the invitation will be declined with a CANCEL_NOT_INSTALLED message * unless a derived class overrides this method. * * The derived method can display an accept/cancel message with offerAcceptCancel(), * or call contactStarted2_UserAccepts() directly to accept an invitation by default. * * @param message The invitation message sent by the contact. */ 00148 void Application::contactStarted1_ContactInvitesUser(const MimeMessage& /*message*/) { #ifdef KMESSDEBUG_APPLICATION kmDebug() << "(rejects invitation)"; #endif // We can assume the switchboard already displayed a better error message #ifdef KMESTEST kmWarning() << "The Application subclass didn't implement contactStarted1_ContactInvitesUser!"; #endif // Cancel the invitation sendCancelMessage( CANCEL_NOT_INSTALLED ); // The application should terminate automatically.. } /** * @brief Step 2 of a contact-started chat: the user accepts * * This method is called by gotCommand() when the user hits * the 'accept' link in the chat window. * This method should be implemented by a derived class. * Most invitations require some confirmation to be sent back. */ 00174 void Application::contactStarted2_UserAccepts() { #ifdef KMESSDEBUG_APPLICATION kmDebug(); #endif } /** * Step 3 of a contact-started chat: the contact confirms the accept messsage. * * This method should be implemented by a derived class. * @param message The confirmation message sent by the contact. */ 00189 void Application::contactStarted3_ContactConfirmsAccept(const MimeMessage& /*message*/) { #ifdef KMESSDEBUG_APPLICATION kmDebug(); #endif } // Request the application to not delete itself while doing external tasks, like displaying a dialog. void Application::delayDeletion( bool doDelay ) { doDelayDeletion_ = doDelay; } /** * @brief Request to delete this application. * * Marks the application as closing (isClosing() will return true), * The deleteMe() signal is fired afterwards. */ 00212 void Application::endApplication() { closing_ = true; // Signal that this instance should be deleted, if there are no ongoing tasks that would break if the class gets deleted. if( doDelayDeletion_ ) { // This happens when a KFileDialog or messagebox is dislayed. A sub event loop is started // which could cause a delete signal to get through. In effect, KMess will crash because once // the KFileDialog returns the code runs in that deleted part. #ifdef KMESSDEBUG_APPLICATION kmDebug() << "delayDeletion is in effect, waiting for the derived class to call endApplication() again."; #endif } else { #ifdef KMESSDEBUG_APPLICATION kmDebug() << "Requesting removal"; #endif emit deleteMe(this); } } /** * @brief Generate a random cookie value. * * This value is used in chat window hyperlinks and used by MimeApplication derived classes. * * @return The generated cookie. */ 00245 QString Application::generateCookie() const { int number, maxNumber; QString cookie; // The maximum size for a cookie is supposedly 2^32 - 1, though I've // never seen one go higher than 100,000. maxNumber = 1048575; // Get a random number in the given range. number = rand()%maxNumber; // Convert the number to a QCString cookie.sprintf("%d", number); // Return the cookie return cookie; } /** * @brief Return the chat the application was originally created for (may be null). * * This pointer is used by ChatMaster to display a MsnObject (display picture, wink or emoticon) * in the correct chat. */ 00269 Chat* Application::getChat() const { return chat_; } /** * @brief Return the handle of the other contact. * @return The peer contact this application exchanges data with. */ 00280 const QString& Application::getContactHandle() const { return contactHandle_; } /** * @brief Return an abort message to display. * * @returns Returns the message "The contact cancelled the session", unless this method is overwritten by a derived class. */ 00292 QString Application::getContactAbortMessage() const { return i18n("The contact cancelled the session."); } /** * @brief Return a reject message to display. * * @returns Returns the message "The contact rejected the invitation", unless this method is overwritten by a derived class. */ 00304 QString Application::getContactRejectMessage() const { return i18n("The contact rejected the invitation."); } /** * @brief Return the application's identifying cookie * * This cookie is used in chat window hyperlinks, and sent by MimeApplication derived classes. * @return The cookie identifying the application. */ 00317 const QString& Application::getCookie() const { return cookie_; } /** * @brief Return the external IP address. * * This is a convenience function, accessing CurrentAccount::getExternalIp(). * It's the IP address the external MSN servers see when the user connected. * * @return The external IP address of the current user. */ 00332 const QString& Application::getExternalIp() const { // A wrapper for now, makes the Application API easier to use. return CurrentAccount::instance()->getExternalIp(); } /** * Return the local IP address * * This is a convenience function, accessing CurrentAccount::getLocalIp(). * It's the IP address of the local socket, which could differ * from the external IP in NAT-routed environments. * * @return THe internal IP address of the current user. */ 00349 const QString& Application::getLocalIp() const { // A wrapper for now, makes the Application API easier to use. return CurrentAccount::instance()->getLocalIp(); } /** * @brief Return the current mode of the application * * This is used by P2PApplication to reject messages properly. * * @return The current mode, one of the values of the ApplicationMode enum. */ 00364 int Application::getMode() const { return mode_; } /** * @brief Return an abort message to display. * * @returns Returns the message "You have cancelled the session", unless this method is overwritten by a derived class. */ 00376 QString Application::getUserAbortMessage() const { return i18n("You have cancelled the session."); } /** * @brief Return a reject message to display. * * @returns Returns the message "You have rejected the invitation", unless this method is overwritten by a derived class. */ 00388 QString Application::getUserRejectMessage() const { return i18n("You have rejected the invitation."); } /** * @brief A command for the application was received (i.e. "Accept" or "Reject") * * This method is invoked when the user hits a hyperlink in the chat window. * The method calls caontactStarted2_UserAccepts(), userAborted() or userRejected(), * depending on the provided command value. * * @param command The command, can be 'accept', 'cancel', or 'reject'. */ 00404 void Application::gotCommand(QString command) { #ifdef KMESSDEBUG_APPLICATION kmDebug() << "Got command " << command; #endif waitingForUser_ = false; if( userAnswered_ ) { #ifdef KMESSDEBUG_APPLICATION kmDebug() << "User has already answered this application before, ignoring."; #endif return; } userAnswered_ = true; if ( command == "accept" ) { contactStarted2_UserAccepts(); } else if ( command == "cancel" ) { userAborted(); } else if ( command == "reject" ) { userRejected(); } else { kmWarning() << "Received unhandled command " << command << "."; } } /** * @brief Indicate whether the application is closing or not. * * When an application is aborting, callers should not attempt to abort it again. * * @return Returns true when the application is aborting (endApplication() or destructor called). */ 00449 bool Application::isClosing() const { return closing_; } /** * @brief Returns whether the application can operate in a multi-chat session, or requires a private chat. * * Most applications require a private chat, only some can operate in a multi-chat session. * Overwrite this method in a derived class when needed. * * @return Default true, unless overwritten by a derived class. */ 00464 bool Application::isPrivateChatRequired() const { // Default answer is yes, only a few applications don't (picture/msnobject transfer). return true; } /** * @brief Return the "user started this app" state * * @return Returns true when user initiated the invitation, false if the contact invited the user. */ 00477 bool Application::isUserStartedApp() const { return userStartedApp_; } /** * @brief Return true if we're waiting for the user to accept. * * @return Returns true when the application is waiting for the user to press the accept link. */ 00489 bool Application::isWaitingForUser() const { return waitingForUser_; } /** * @brief Replace an application's accept/reject/cancel links with another text * * Removes from the chat the confirmation links for the application, and replaces * them with some other informative message. * * @param newMessage The new displayed message */ 00504 void Application::modifyOfferMessage( const QString& newMessage ) { #ifdef KMESSDEBUG_APPLICATION kmDebug() << "Changing offer links in chat"; #endif emit updateApplicationMessage( getCookie(), newMessage ); } /** * @brief Let the user accept or reject the application. * * Displays the accept and cancel link in the chat window. * with a custom HTML-based message above. * The user should choose to accept or reject the invitation from the contact. * * @param appHtml A custom message to display. */ 00524 void Application::offerAcceptOrReject(const QString& appHtml) { #ifdef KMESSDEBUG_APPLICATION kmDebug() << "Displaying accept/reject message in chat"; #endif QString html; const QString cookie( getCookie() ); const QString handle( getContactHandle() ); // Create the message, starting with the application's particulars html += "<span class=\"acceptMessage\">"; html += appHtml; html += "</span>"; html += "<br />"; html += "<span id=\"app" + cookie + "-links-block\" class=\"acceptMessage\">"; html += i18n( "Do you want to <a href=%1>accept</a> or <a href=%2>cancel</a>?", "\"kmess://application/accept/" + handle + "?" + cookie + "\"", "\"kmess://application/reject/" + handle + "?" + cookie + "\"" ); html += "</span>"; // Send the message to the chat window. waitingForUser_ = true; userAnswered_ = false; showEventMessage( html, ChatMessage::CONTENT_APP_INVITE, true ); #ifdef KMESSDEBUG_APPLICATION kmDebug() << "Waiting for user to accept.."; #endif } /** * @brief Let the user cancel the application * * Displays the cancel link in the chat window. * with a custom HTML-based message above. * It allows the user to revoke a sent invitation. * * @param appHtml A status message to display. */ 00566 void Application::offerCancel(const QString& appHtml) { #ifdef KMESSDEBUG_APPLICATION kmDebug() << "Displaying cancel link in chat"; #endif QString html; const QString cookie( getCookie() ); // Create the message, starting with the application's particulars html += "<span class=\"cancelMessage\">"; html += appHtml; html += "</span>"; html += "<br />"; html += "<span id=\"app" + cookie + "-links-block\" class=\"cancelMessage\">"; html += i18n( "Click to <a href=%1>cancel</a>.", "\"kmess://application/cancel/" + getContactHandle() + "?" + cookie + "\"" ); html += "</span>"; // Send the message to the chat window. showEventMessage( html, ChatMessage::CONTENT_APP_INVITE, false ); } /** * @brief Set the type of application we're starting * * This method may be called by derivated Application classes; to make the base class emit * the correct ContentsClass type when outputting messages which may get displayed in a * wrong way by notification balloons. */ 00598 void Application::setApplicationType( ChatMessage::MessageType type ) { switch( type ) { case ChatMessage::TYPE_APPLICATION_FILE: type_ = type; break; default: type_ = ChatMessage::TYPE_APPLICATION; break; } } /** * @brief Set the chat the application was originally created for. * * This pointer is used by ChatMaster to display a MsnObject (display picture, wink or emoticon) * in the correct chat. */ 00619 void Application::setChat( Chat *chat ) { chat_ = chat; } /** * @brief Indicate the application is closing or not. * * This is true when an abort method or destructor is called. * Callers should check for this value to avoid aborting an application twice. * * @returns True when the application is closing. */ 00634 void Application::setClosing(bool closing) { closing_ = closing; } /** * @brief Change the current mode * * This is currently used only to reject unknown messages with P2PApplication. * * @param mode The mode to set. */ 00648 void Application::setMode(ApplicationMode mode) { mode_ = mode; } /** * @brief Show a message to notify the user of an event. * * This message type is used for positive, passive, status messages like: * - connecting to host * - the transfer is accepted. * - the transfer is complete. * * It emits the appMessage() signal * The chat themes display these messages as "notification" message. * * @param message The message to display. * @param contents A brief indication of the contents. This is used for notifications. * @param isIncoming Whether the message is incoming (true), or outgoing (false). */ 00670 void Application::showEventMessage( const QString &message, const ChatMessage::ContentsClass contents, bool isIncoming ) { // Error handling applications shouldn't show anything if( mode_ == APP_MODE_ERROR_HANDLER ) { return; } emit applicationMessage( ChatMessage( type_, contents, isIncoming, message, getContactHandle() ) ); } /** * @brief Show a message to notify about a system error * * This message type is used for serious errors like: * - invitation type not supported. * - the contact is offline. * * @param message The message to display. * @param contents A brief indication of the contents. This is used for notifications. * @param isIncoming Whether the message is incoming (true), or outgoing (false). */ 00694 void Application::showSystemMessage( const QString &message, const ChatMessage::ContentsClass contents, bool isIncoming ) { // Error handling applications shouldn't show anything if( mode_ == APP_MODE_ERROR_HANDLER ) { return; } emit applicationMessage( ChatMessage( ChatMessage::TYPE_SYSTEM, contents, isIncoming, message, getContactHandle() ) ); } /** * @brief Called when the transfer is complete. * * The actual action should be implemented by applications which transfer data. */ 00712 void Application::showTransferComplete() { #ifdef KMESSDEBUG_APPLICATION kmDebug() << "Transfer is complete."; #endif } /** * @brief Show a message to inform about a transfer event. * * This method defaults to showEventMessage(), but can be overwritten * to display the messages in a separate file transfer dialog. * * It's used for status messages like: * - connecting to host. * - awaiting connetion. * - negotiating transfer method. * - reverting to indirect file transfer. * * @param message The message to display. */ 00735 void Application::showTransferMessage(const QString &message) { // Defaults to event messages, can be overwritten. showEventMessage( message, ChatMessage::CONTENT_APP_INFO, true ); } /** * @brief Show the progress made during a transfer. * * This method can be overwritten in a derived class to show the progress somewhere. * * @param bytesTransferred The number of bytes transferred. */ 00750 void Application::showTransferProgress( const ulong bytesTransferred ) { #ifdef KMESSDEBUG_APPLICATION kmDebug() << "Transferred " << bytesTransferred << " bytes."; #else Q_UNUSED( bytesTransferred ); // Avoid compiler warning #endif } /** * @brief Start the application. * * This is the first method to call to send an invitation. * This method starts the application and calls userStarted1_UserInvitesContact(). */ 00767 void Application::start() { userStartedApp_ = true; cookie_ = generateCookie(); // Start the application userStarted1_UserInvitesContact(); } /** * @brief Start the application internally (from an invite message) * * This method is used to initialize the application with a received invitation cookie. */ 00783 void Application::startByInvite(const QString &invitationCookie) { userStartedApp_ = false; cookie_ = invitationCookie; } /** * @brief You have cancelled the session. * * This method is called to abort the application (e.g. the chat window was closed). * It does the following things: * - marks the application as aborting. * - sends a cancel message to the contact. * - displays a status message returned by getUserAbortMessage(). * - requests deletion of the application. */ 00801 void Application::userAborted() { #ifdef KMESSDEBUG_APPLICATION kmDebug(); #endif if( closing_ ) { kmWarning() << "Attempted to close application twice (contact=" << contactHandle_ << ")"; // The application could already have been deleted, just end it before something leads to a crash. endApplication(); } closing_ = true; // Remove the accept links modifyOfferMessage(); showEventMessage( getUserAbortMessage(), ChatMessage::CONTENT_APP_CANCELED, false ); sendCancelMessage( CANCEL_ABORT ); // The application should terminate automatically.. } /** * @brief You have rejected the invitation * * This method is called when the user hits the cancel hyperlink in a chat window. * It does the following things: * - marks the application as aborting. * - sends a cancel message to the contact. * - displays a status message returned by getUserRejectMessage(). * - requests deletion of the application. */ 00835 void Application::userRejected() { #ifdef KMESSDEBUG_APPLICATION kmDebug(); #endif if( closing_ ) { kmWarning() << "Attempted to close application twice (contact=" << contactHandle_ << ")"; // The application could already have been deleted, just end it before something leads to a crash. endApplication(); } closing_ = true; sendCancelMessage( CANCEL_INVITATION ); // The application should terminate automatically.. } /** * @brief Step 1 of a user-started chat: the user invites the contact * * This method should be implemented by a derived class, * sending the invitation message to the contact. */ 00861 void Application::userStarted1_UserInvitesContact() { #ifdef KMESSDEBUG_APPLICATION kmDebug(); #endif } /** * @brief Step 2 of a user-started chat: the contact accepts * * This method should be implemented by a derived class, handling the accept message. * Either this method should call userStarted3_UserPrepares() (typically for MimeApplication classes), * or asks the contact to send a prepare message (for P2PApplication classes). */ 00877 void Application::userStarted2_ContactAccepts(const MimeMessage& /*message*/) { #ifdef KMESSDEBUG_APPLICATION kmDebug(); #endif } /** * @brief Step 3 of a user-started chat: the user prepares for the session * * The application can now start an application, open a connection, or initiate the data transfer. */ 00891 void Application::userStarted3_UserPrepares() { #ifdef KMESSDEBUG_APPLICATION kmDebug(); #endif } #include "application.moc"