/* Copyright (C) 2008-2009 AbiSource Corporation B.V. * * 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. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA * 02111-1307, USA. */ #include #include "xap_App.h" #include "ev_Menu_Actions.h" #include "ap_Menu_Id.h" #include "ap_Menu_Functions.h" #include "ev_Toolbar_Actions.h" #include "ap_Toolbar_Id.h" #include "ap_Toolbar_Functions.h" #include "ap_LoadBindings.h" #include "ev_EditEventMapper.h" #include "xap_Dlg_MessageBox.h" #include "fv_View.h" #include "xap_Frame.h" #include "ut_debugmsg.h" #include "AsyncWorker.h" #include "ServiceAccountHandler.h" #include #include #include "AbiCollabSaveInterceptor.h" #include "soa_soup.h" #define SAVE_INTERCEPTOR_EM "com.abisource.abiword.abicollab.servicesaveinterceptor" static bool AbiCollabSaveInterceptor_interceptor(AV_View * v, EV_EditMethodCallData * d) { return ServiceAccountHandler::m_saveInterceptor.intercept(v, d); } #define DO_NOT_USE "" static ap_bs_Char CharTable[] = { // {char, /* desc */ { none, _C, _A, _A_C }}, {0x53, /* S */ { "insertData", "fileSaveAs", DO_NOT_USE, "" }}, {0x73, /* s */ { "insertData", SAVE_INTERCEPTOR_EM, DO_NOT_USE, "" }}, }; AbiCollabSaveInterceptor::AbiCollabSaveInterceptor() : m_pOldSaveEM(NULL) { UT_DEBUGMSG(("Installing Save menu interceptor!\n")); EV_EditMethodContainer *pEMC = XAP_App::getApp()->getEditMethodContainer(); // store the old/normale editmethod to fall back when a document is not under service control m_pOldSaveEM = pEMC->findEditMethodByName("fileSave"); UT_return_if_fail(m_pOldSaveEM); // install the edit method we will use to save to the webapp EV_EditMethod* mySaveInterceptor = new EV_EditMethod ( SAVE_INTERCEPTOR_EM, &AbiCollabSaveInterceptor_interceptor, 0, "AbiCollab Service Save Interceptor" ); pEMC->addEditMethod(mySaveInterceptor); // install the new menu action with our custom save edit method XAP_App::getApp()->getMenuActionSet()->setAction( AP_MENU_ID_FILE_SAVE, false, /* holds submenu */ false, /* raises dialog */ false, /* is checkable */ false, /* is radio */ SAVE_INTERCEPTOR_EM, ap_GetState_Changes, NULL, /* state label */ NULL ); // install the new toolbar action with our custom save edit method XAP_App::getApp()->getToolbarActionSet()->setAction( AP_TOOLBAR_ID_FILE_SAVE, EV_TBIT_PushButton, SAVE_INTERCEPTOR_EM, AV_CHG_ALL, ap_ToolbarGetState_Changes ); // install the new CTRL-s hook // TODO: what to the with Save As? const char * szCurrMode = XAP_App::getApp()->getInputMode(); EV_EditBindingMap* pEbMap = XAP_App::getApp()->getBindingMap(szCurrMode); UT_return_if_fail(pEbMap); AP_BindingSet* pBindingSet = static_cast(XAP_App::getApp()->getBindingSet()); UT_return_if_fail(pBindingSet); pBindingSet->_loadChar(pEbMap, CharTable, 2, NULL, 0); } bool AbiCollabSaveInterceptor::intercept(AV_View * v, EV_EditMethodCallData * d) { UT_DEBUGMSG(("AbiCollabSaveInterceptor_intercept\n")); UT_return_val_if_fail(v, false); FV_View* pView = static_cast(v); AbiCollabSessionManager* pManager = AbiCollabSessionManager::getManager(); UT_return_val_if_fail(pManager, false); PD_Document* pDoc = pView->getDocument(); UT_return_val_if_fail(pDoc, false); if (!pDoc->isDirty()) { UT_DEBUGMSG(("Document is not dirty, not saving.\n")); return true; } if (!pManager->isInSession(pDoc)) return m_pOldSaveEM->Fn(v, d); UT_DEBUGMSG(("Document is in a collaboration session!\n")); AbiCollab* pSession = pManager->getSession(pDoc); UT_return_val_if_fail(pSession, m_pOldSaveEM->Fn(v, d)); if (save(pDoc)) { XAP_Frame * pFrame = static_cast (pView->getParentData()); if (pFrame->getViewNumber() > 0) XAP_App::getApp()->updateClones(pFrame); return true; } UT_DEBUGMSG(("This session does not use the abicollab webservice; saving the old fashioned way...\n")); return m_pOldSaveEM->Fn(v, d); } bool AbiCollabSaveInterceptor::save(PD_Document* pDoc) { UT_DEBUGMSG(("AbiCollabSaveInterceptor::save()\n")); UT_return_val_if_fail(pDoc, false); AbiCollabSessionManager* pManager = AbiCollabSessionManager::getManager(); UT_return_val_if_fail(pManager, false); AbiCollab* pSession = pManager->getSession(pDoc); UT_return_val_if_fail(pSession, false); // the session id should be unique on a specific account handler; let's // just look it up amongst all our account handlers (not too efficient or // elegant, but it works) for (UT_uint32 i = 0; i < pManager->getAccounts().size(); i++) { AccountHandler* pHandler = pManager->getAccounts()[i]; UT_continue_if_fail(pHandler); if (pHandler->getStorageType() == SERVICE_ACCOUNT_HANDLER_TYPE) { ServiceAccountHandler* pServiceHandler = static_cast(pHandler); ConnectionPtr connection_ptr = pServiceHandler->getConnection(pDoc); if (!connection_ptr) continue; // apparently we need another abicollab account UT_DEBUGMSG(("Found the abicollab webservice account handler (%s) that controls this session!\n", pServiceHandler->getDescription().utf8_str())); pManager->beginAsyncOperation(pSession); // FIXME: guarantee save order! std::string uri = pServiceHandler->getProperty("uri"); bool verify_webapp_host = (pServiceHandler->getProperty("verify-webapp-host") == "true"); soa::function_call_ptr fc_ptr = pServiceHandler->constructSaveDocumentCall(pDoc, connection_ptr); std::string ssl_ca_file = pServiceHandler->getCA(); boost::shared_ptr result_ptr(new std::string()); boost::shared_ptr > async_save_ptr( new AsyncWorker( boost::bind(&AbiCollabSaveInterceptor::_save, this, uri, verify_webapp_host, ssl_ca_file, fc_ptr, result_ptr), boost::bind(&AbiCollabSaveInterceptor::_save_cb, this, _1, pServiceHandler, pSession, connection_ptr, fc_ptr, result_ptr) ) ); async_save_ptr->start(); // make the document clean (even if it isn't _yet_) pDoc->setClean(); pDoc->signalListeners(PD_SIGNAL_DOCNAME_CHANGED); return true; } } return false; } // NOTE: don't use const std::string& arguments where, as we want a copy for thread safety bool AbiCollabSaveInterceptor::_save(std::string uri, bool verify_webapp_host, std::string ssl_ca_file, soa::function_call_ptr fc_ptr, boost::shared_ptr result_ptr) { UT_DEBUGMSG(("AbiCollabSaveInterceptor::_save()\n")); UT_return_val_if_fail(fc_ptr, false); UT_return_val_if_fail(result_ptr, false); // execute the call bool res = soup_soa::invoke(uri, soa::method_invocation("urn:AbiCollabSOAP", *fc_ptr), verify_webapp_host?ssl_ca_file:"", *result_ptr); UT_return_val_if_fail(res, false); UT_DEBUGMSG(("Document uploaded successfully\n")); return true; } void AbiCollabSaveInterceptor::_save_cb(bool success, ServiceAccountHandler* pAccount, AbiCollab* pSession, ConnectionPtr connection_ptr, soa::function_call_ptr fc_ptr, boost::shared_ptr result_ptr) { UT_DEBUGMSG(("AbiCollabSaveInterceptor::_save_cb()\n")); UT_return_if_fail(pAccount); UT_return_if_fail(pSession); UT_return_if_fail(connection_ptr); UT_return_if_fail(fc_ptr); UT_return_if_fail(result_ptr); AbiCollabSessionManager* pManager = AbiCollabSessionManager::getManager(); UT_return_if_fail(pManager); if (success) { // parse the soap result try { // we ignore the result (the new revision number), we're happy when there were no errors soa::method_invocation mi("urn:AbiCollabSOAP", *fc_ptr); soa::GenericPtr soap_result = soa::parse_response(*result_ptr, mi.function().response()); if (soap_result) { // the save was successful, so we can mark this async action as completed pManager->endAsyncOperation(pSession); return; } } catch (soa::SoapFault& fault) { UT_DEBUGMSG(("Caught a soap fault: %s (error code: %s)!\n", fault.detail() ? fault.detail()->value().c_str() : "(null)", fault.string() ? fault.string()->value().c_str() : "(null)")); acs::SOAP_ERROR err = acs::error(fault); switch (err) { case acs::SOAP_ERROR_INVALID_PASSWORD: { const std::string email = pAccount->getProperty("email"); std::string uri = pAccount->getProperty("uri"); bool verify_webapp_host = (pAccount->getProperty("verify-webapp-host") == "true"); std::string ssl_ca_file = pAccount->getCA(); std::string password; if (ServiceAccountHandler::askPassword(email, password)) { // store the new password pAccount->addProperty("password", password); pManager->storeProfile(); // construct a SOAP call with the new password // FIXME: this is highly ineffictient as it serializes the whole document again fc_ptr = pAccount->constructSaveDocumentCall(pSession->getDocument(), connection_ptr); // re-attempt the save boost::shared_ptr > async_save_ptr( new AsyncWorker( boost::bind(&AbiCollabSaveInterceptor::_save, this, uri, verify_webapp_host, ssl_ca_file, fc_ptr, result_ptr), boost::bind(&AbiCollabSaveInterceptor::_save_cb, this, _1, pAccount, pSession, connection_ptr, fc_ptr, result_ptr) ) ); async_save_ptr->start(); // we re-attempt the save, so don't mark this async action as completed // before exiting this function return; } } case acs::SOAP_ERROR_NO_CHANGES: // the save was successful (unneeded), so we can mark this async action as completed pManager->endAsyncOperation(pSession); UT_DEBUGMSG(("The document was unchanged; ignoring error\n")); return; default: break; } } } // the save failed and we can't recover from it, so we can mark this async action as completed pManager->endAsyncOperation(pSession); // inform the user of the failure to save _saveFailed(pSession); } void AbiCollabSaveInterceptor::_saveFailed(AbiCollab* pSession) { // WARNING: do NOT assume we have a valid view or frame here: it could already // have been deleted if the frame was closed (or abiword shutdown) before this // callback came back. // You can safely use the AbiCollab pointer or PD_Document pointer though, as // the AbiCollabSessionManager makes sure those are still valid. UT_return_if_fail(pSession); PD_Document* pDoc = pSession->getDocument(); UT_return_if_fail(pDoc); // the document was not saved after all, mark it dirty again pDoc->forceDirty(); pDoc->signalListeners(PD_SIGNAL_DOCNAME_CHANGED); // idealy we would use the same frame that was used to save the document, // but we don't know if that one is still valid if (XAP_App::getApp()->getLastFocussedFrame()) { // TODO: add the document name, error type and perhaps the server name // TODO: offer some kind of solution to the user UT_UTF8String msg("An error occured while saving this document to the web-service!"); XAP_App::getApp()->getLastFocussedFrame()->showMessageBox(msg.utf8_str(), XAP_Dialog_MessageBox::b_O, XAP_Dialog_MessageBox::a_OK); } }