platform/shared/common/RhodesApp.cpp in rhodes-2.2.6 vs platform/shared/common/RhodesApp.cpp in rhodes-2.3.0.beta.1

- old
+ new

@@ -12,11 +12,14 @@ #include "net/HttpServer.h" #include "ruby/ext/rho/rhoruby.h" #include "net/AsyncHttp.h" #include "rubyext/WebView.h" #include "rubyext/GeoLocation.h" +#include "common/app_build_configs.h" +#include <algorithm> + #ifdef OS_WINCE #include <winsock.h> #endif using rho::net::HttpHeader; @@ -32,11 +35,197 @@ namespace rho { namespace common{ IMPLEMENT_LOGCLASS(CRhodesApp,"RhodesApp"); +String CRhodesApp::m_strStartParameters; +class CAppCallbacksQueue : public CThreadQueue +{ + DEFINE_LOGCLASS; +public: + enum callback_t + { + app_deactivated, + local_server_restart, + local_server_started, + ui_created, + app_activated + }; + +public: + CAppCallbacksQueue(IRhoClassFactory *factory); + ~CAppCallbacksQueue(); + + //void call(callback_t type); + + friend class Command; + struct Command : public IQueueCommand + { + callback_t type; + Command(callback_t t) :type(t) {} + + boolean equals(IQueueCommand const &) {return false;} + String toString() {return CAppCallbacksQueue::toString(type);} + }; + +private: + + void processCommand(IQueueCommand* pCmd); + + static char const *toString(int type); + +private: + callback_t m_expected; + Vector<int> m_commands; +}; +IMPLEMENT_LOGCLASS(CAppCallbacksQueue,"AppCallbacks"); + +char const *CAppCallbacksQueue::toString(int type) +{ + switch (type) + { + case local_server_started: + return "LOCAL-SERVER-STARTED"; + case ui_created: + return "UI-CREATED"; + case app_activated: + return "APP-ACTIVATED"; + case app_deactivated: + return "APP-DEACTIVATED"; + case local_server_restart: + return "LOCAL-SERVER-RESTART"; + default: + return "UNKNOWN"; + } +} + +CAppCallbacksQueue::CAppCallbacksQueue(IRhoClassFactory *factory) + :CThreadQueue(factory), m_expected(local_server_started) +{ + CThreadQueue::setLogCategory(getLogCategory()); + //setPollInterval(1); + start(epNormal); +} + +CAppCallbacksQueue::~CAppCallbacksQueue() +{ +} +/* +void CAppCallbacksQueue::call(CAppCallbacksQueue::callback_t type) +{ + addQueueCommand(new Command(type)); +}*/ + +void CAppCallbacksQueue::processCommand(IQueueCommand* pCmd) +{ + Command *cmd = (Command *)pCmd; + if (!cmd) + return; +/* + if (cmd->type < m_expected) + { + LOG(ERROR) + "received command " + toString(cmd->type) + " which is less than expected "+toString(m_expected)+" - ignore it"; + return; + } +*/ + if ( m_expected == app_deactivated && cmd->type == app_activated ) + { + LOG(INFO) + "received duplicate activate skip it"; + return; + } + + if ( m_expected == local_server_restart ) + { + RHODESAPP().restartLocalServer(*this); + m_expected = local_server_started; + } + + if (cmd->type > m_expected) + { + boolean bDuplicate = false; + for( int i = 0; i < m_commands.size() ; i++) + { + if ( m_commands.elementAt(i) == cmd->type ) + { + bDuplicate = true; + break; + } + } + + if ( bDuplicate ) + { + LOG(INFO) + "Received duplicate command " + toString(cmd->type) + "skip it"; + }else + { + // Don't do that now + LOG(INFO) + "Received command " + toString(cmd->type) + " which is greater than expected (" + toString(m_expected) + ") - postpone it"; + m_commands.push_back(cmd->type); + std::sort(m_commands.begin(), m_commands.end()); + } + return; + } + + m_commands.insert(m_commands.begin(), cmd->type); + for (Vector<int>::const_iterator it = m_commands.begin(), lim = m_commands.end(); it != lim; ++it) + { + int type = *it; + LOG(INFO) + "process command: " + toString(type); + switch (type) + { + case app_deactivated: +#if !defined( OS_WINCE ) && !defined (OS_WINDOWS) + m_expected = local_server_restart; +#else + m_expected = app_activated; +#endif + break; + + case local_server_started: + // Nothing + break; + case ui_created: + { + common::CAutoPtr<common::IRhoClassFactory> factory = rho_impl_createClassFactory(); + common::CAutoPtr<net::INetRequest> pNetRequest = factory->createNetRequest(); + String strUrl = RHODESAPP().getBaseUrl(); + strUrl += "/system/uicreated"; + NetResponse(resp, pNetRequest->pullData( strUrl, null ) ); + if ( !resp.isOK() ) + LOG(ERROR) + "activate app failed. Code: " + resp.getRespCode() + "; Error body: " + resp.getCharData(); + + m_expected = app_activated; + } + break; + case app_activated: + { +/* static bool navigatedToStartUrl = false; + if (!navigatedToStartUrl) + { + LOG(INFO) + "navigate to first start url"; + RHODESAPP().navigateToUrl(RHODESAPP().getFirstStartUrl()); + navigatedToStartUrl = true; + }*/ + + common::CAutoPtr<common::IRhoClassFactory> factory = rho_impl_createClassFactory(); + common::CAutoPtr<net::INetRequest> pNetRequest = factory->createNetRequest(); + String strUrl = RHODESAPP().getBaseUrl(); + strUrl += "/system/activateapp"; + NetResponse(resp, pNetRequest->pullData( strUrl, null ) ); + if ( !resp.isOK() ) + LOG(ERROR) + "activate app failed. Code: " + resp.getRespCode() + "; Error body: " + resp.getCharData(); + + m_expected = app_deactivated; + } + break; + } + //if (type < app_activated && type != app_deactivated) + // m_expected = (callback_t)(type + 1); + } + m_commands.clear(); +} + /*static*/ CRhodesApp* CRhodesApp::Create(const String& strRootPath) { if ( m_pInstance != null) return (CRhodesApp*)m_pInstance; @@ -50,18 +239,21 @@ delete m_pInstance; m_pInstance = 0; } -CRhodesApp::CRhodesApp(const String& strRootPath) : CRhodesAppBase(strRootPath) +CRhodesApp::CRhodesApp(const String& strRootPath) + :CRhodesAppBase(strRootPath) { m_bExit = false; m_bDeactivationMode = false; - m_activateCounter = 0; + m_bRestartServer = false; + //m_activateCounter = 0; m_ptrFactory = rho_impl_createClassFactory(); m_NetRequest = m_ptrFactory->createNetRequest(); + m_appCallbacksQueue = new CAppCallbacksQueue(rho_impl_createClassFactory()); #if defined( OS_WINCE ) || defined (OS_WINDOWS) //initializing winsock WSADATA WsaData; int result = WSAStartup(MAKEWORD(2,2),&WsaData); @@ -90,23 +282,24 @@ LOG(INFO) + "Starting sync engine..."; sync::CSyncThread::Create(rho_impl_createClassFactory()); LOG(INFO) + "RhoRubyInitApp..."; RhoRubyInitApp(); + rho_ruby_call_config_conflicts(); + RHOCONF().conflictsResolved(); - - //rho_clientregister_create("iphone_client"); -#if defined( OS_WINCE ) || defined( OS_WINDOWS ) - LOG(INFO) + "navigate to first start url"; - RHODESAPP().navigateToUrl(RHODESAPP().getFirstStartUrl()); -#endif - while (!m_bExit) { m_httpServer->run(); if (m_bExit) break; - wait(-1); + + if ( !m_bRestartServer ) + { + LOG(INFO) + "RhodesApp thread wait."; + wait(-1); + } + m_bRestartServer = false; } LOG(INFO) + "RhodesApp thread shutdown"; rubyext::CGeoLocation::Destroy(); @@ -122,12 +315,21 @@ WSACleanup(); #endif } +void CRhodesApp::restartLocalServer(common::CThreadQueue& waitThread) +{ + LOG(INFO) + "restart local server."; + m_bRestartServer = true; + m_httpServer->stop(); +} + void CRhodesApp::stopApp() { + m_appCallbacksQueue->stop(1000); + if (!m_bExit) { m_bExit = true; m_httpServer->stop(); stopWait(); @@ -197,61 +399,74 @@ rho_ruby_deactivateApp(); String strMsg; rho_http_sendresponse(arg, strMsg.c_str()); } +static void callback_uicreated(void *arg, String const &strQuery) +{ + rho_ruby_uiCreated(); + rho_http_sendresponse(arg, ""); +} + +static void callback_uidestroyed(void *arg, String const &strQuery) +{ + rho_ruby_uiDestroyed(); + rho_http_sendresponse(arg, ""); +} + static void callback_loadserversources(void *arg, String const &strQuery) { RhoAppAdapter.loadServerSources(strQuery); String strMsg; rho_http_sendresponse(arg, strMsg.c_str()); } -class CRhoActivateApp +static void callback_loadallsyncsources(void *arg, String const &strQuery) { - String m_strUrl; -public: - CRhoActivateApp(const String& strUrl) :m_strUrl(strUrl) {} - void run(common::CRhoThread &thisThread) - { - while (!rho_is_local_server_started()) - thisThread.wait(1); + RhoAppAdapter.loadAllSyncSources(); + String strMsg; + rho_http_sendresponse(arg, strMsg.c_str()); +} - static bool navigated = false; - if (!navigated) - { - LOG(INFO) + "navigate to first start url"; - RHODESAPP().navigateToUrl(RHODESAPP().getFirstStartUrl()); - navigated = true; - } - // RHODESAPP().getSplashScreen().hide(); +static void callback_resetDBOnSyncUserChanged(void *arg, String const &strQuery) +{ + RhoAppAdapter.resetDBOnSyncUserChanged(); + String strMsg; + rho_http_sendresponse(arg, strMsg.c_str()); +} - common::CAutoPtr<common::IRhoClassFactory> factory = rho_impl_createClassFactory(); - common::CAutoPtr<net::INetRequest> pNetRequest = factory->createNetRequest(); - NetResponse(resp, pNetRequest->pullData( m_strUrl, null ) ); - if ( !resp.isOK() ) - LOG(ERROR) + "activate app failed. Code: " + resp.getRespCode() + "; Error body: " + resp.getCharData(); +void CRhodesApp::callUiCreatedCallback() +{ + m_appCallbacksQueue->addQueueCommand(new CAppCallbacksQueue::Command(CAppCallbacksQueue::ui_created)); +} + +void CRhodesApp::callUiDestroyedCallback() +{ + String strUrl = m_strHomeUrl + "/system/uidestroyed"; + NetResponse(resp,getNet().pullData( strUrl, null )); + if ( !resp.isOK() ) + { + LOG(ERROR) + "UI destroy callback failed. Code: " + resp.getRespCode() + "; Error body: " + resp.getCharData(); } -}; +} void CRhodesApp::callAppActiveCallback(boolean bActive) { LOG(INFO) + "callAppActiveCallback"; if (bActive) { // Restart server each time when we go to foreground - if (m_activateCounter++ > 0) +/* if (m_activateCounter++ > 0) { +#if !defined( OS_WINCE ) && !defined (OS_WINDOWS) m_httpServer->stop(); +#endif this->stopWait(); - } - - String strUrl = m_strHomeUrl + "/system/activateapp"; - // Activation callback need to be runned in separate thread - // Otherwise UI thread will be blocked. This can cause deadlock if user defined - // activate callback contains code which need to hold UI thread for execute - rho_rhodesapp_call_in_thread( new CRhoActivateApp( strUrl ) ); + + } */ + + m_appCallbacksQueue->addQueueCommand(new CAppCallbacksQueue::Command(CAppCallbacksQueue::app_activated)); } else { // Deactivation callback must be called in place (not in separate thread!) // This is because system can kill application at any time after this callback finished @@ -259,10 +474,12 @@ // until application finish executing of user-defined deactivation callback. // However, blocking UI thread can cause problem with API refering to UI (such as WebView.navigate etc) // To fix this problem, new mode 'deactivation' introduced. When this mode active, no UI operations allowed. // All such operation will throw exception in ruby code when calling in 'deactivate' mode. m_bDeactivationMode = true; + m_appCallbacksQueue->addQueueCommand(new CAppCallbacksQueue::Command(CAppCallbacksQueue::app_deactivated)); + String strUrl = m_strHomeUrl + "/system/deactivateapp"; NetResponse(resp,getNet().pullData( strUrl, null )); if ( !resp.isOK() ) { LOG(ERROR) + "deactivate app failed. Code: " + resp.getRespCode() + "; Error body: " + resp.getCharData(); @@ -448,11 +665,15 @@ m_httpServer->register_uri("/system/shared", callback_shared); m_httpServer->register_uri("/AppManager/loader/load", callback_AppManager_load); m_httpServer->register_uri("/system/getrhomessage", callback_getrhomessage); m_httpServer->register_uri("/system/activateapp", callback_activateapp); m_httpServer->register_uri("/system/deactivateapp", callback_deactivateapp); + m_httpServer->register_uri("/system/uicreated", callback_uicreated); + m_httpServer->register_uri("/system/uidestroyed", callback_uidestroyed); m_httpServer->register_uri("/system/loadserversources", callback_loadserversources); + m_httpServer->register_uri("/system/resetDBOnSyncUserChanged", callback_resetDBOnSyncUserChanged); + m_httpServer->register_uri("/system/loadallsyncsources", callback_loadallsyncsources); } const char* CRhodesApp::getFreeListeningPort() { int sockfd = -1; @@ -593,12 +814,11 @@ size_t nFragment = strUrl.find('#'); if ( nFragment != String::npos ) strUrl = strUrl.substr(0, nFragment); - RHOCONF().setString("LastVisitedPage",strUrl); - RHOCONF().saveToFile(); + RHOCONF().setString("LastVisitedPage",strUrl, true); } } void CRhodesApp::setAppBackUrl(const String& url) { @@ -612,59 +832,101 @@ m_strAppBackUrlOrig = ""; m_strAppBackUrl = ""; } } -String CRhodesApp::getAppTitle() +String CRhodesApp::getAppName() { - String strTitle = RHOCONF().getString("title_text"); - if ( strTitle.length() == 0 ) - { + String strAppName; #ifdef OS_WINCE - String path = rho_native_rhopath(); - int last, pre_last; + String path = rho_native_rhopath(); + int last, pre_last; - last = path.find_last_of('\\'); - pre_last = path.substr(0, last).find_last_of('\\'); - strTitle = path.substr(pre_last + 1, last - pre_last - 1); + last = path.find_last_of('\\'); + pre_last = path.substr(0, last).find_last_of('\\'); + strAppName = path.substr(pre_last + 1, last - pre_last - 1); #else - strTitle = "Rhodes"; + strAppName = "Rhodes"; #endif - } + return strAppName; +} + +StringW CRhodesApp::getAppNameW() +{ + return convertToStringW( RHODESAPP().getAppName() ); +} + +String CRhodesApp::getAppTitle() +{ + String strTitle = RHOCONF().getString("title_text"); + if ( strTitle.length() == 0 ) + strTitle = getAppName(); + return strTitle; } const String& CRhodesApp::getStartUrl() { m_strStartUrl = canonicalizeRhoUrl( RHOCONF().getString("start_path") ); return m_strStartUrl; } +boolean CRhodesApp::isOnStartPage() +{ + String strStart = getStartUrl(); + String strCurUrl = getCurrentUrl(0); + + if ( strStart.compare(strCurUrl) == 0 ) + return true; + + //check for index + int nIndexLen = CHttpServer::isIndex(strStart); + if ( nIndexLen > 0 && String_startsWith(strStart, strCurUrl) ) + { + return strncmp(strStart.c_str(), strCurUrl.c_str(), strStart.length() - nIndexLen - 1) == 0; + } + + nIndexLen = CHttpServer::isIndex(strCurUrl); + if ( nIndexLen > 0 && String_startsWith(strCurUrl, strStart) ) + { + return strncmp(strCurUrl.c_str(), strStart.c_str(), strCurUrl.length() - nIndexLen - 1) == 0; + } + + return false; + +} + +const String& CRhodesApp::getBaseUrl() +{ + return m_strHomeUrl; +} + const String& CRhodesApp::getOptionsUrl() { m_strOptionsUrl = canonicalizeRhoUrl( RHOCONF().getString("options_path") ); return m_strOptionsUrl; } const String& CRhodesApp::getCurrentUrl(int /*index*/) { return m_currentUrls[m_currentTabIndex]; } - +/* const String& CRhodesApp::getFirstStartUrl() { m_strFirstStartUrl = getStartUrl(); if ( RHOCONF().getBool("KeepTrackOfLastVisitedPage") ) { rho::String strLastPage = RHOCONF().getString("LastVisitedPage"); if (strLastPage.length() > 0) m_strFirstStartUrl = canonicalizeRhoUrl(strLastPage); } + LOG(INFO) + "Start url: " + m_strFirstStartUrl; return m_strFirstStartUrl; -} +}*/ const String& CRhodesApp::getRhobundleReloadUrl() { m_strRhobundleReloadUrl = RHOCONF().getString("rhobundle_zip_url"); return m_strRhobundleReloadUrl; @@ -830,19 +1092,38 @@ } } void CRhodesApp::loadUrl(String url) { + if ( url.length() == 0 ) + return; + boolean callback = false; if (String_startsWith(url, "callback:") ) { callback = true; url = url.substr(9); }else if ( strcasecmp(url.c_str(), "exit")==0 || strcasecmp(url.c_str(), "close") == 0 ) { rho_sys_app_exit(); return; + }else if ( strcasecmp(url.c_str(), "options")==0 ) + { + rho_webview_navigate(getOptionsUrl().c_str(), 0); + return; + }else if ( strcasecmp(url.c_str(), "home")==0 ) + { + rho_webview_navigate(getStartUrl().c_str(), 0); + return; + }else if ( strcasecmp(url.c_str(), "refresh")==0 ) + { + rho_webview_refresh(0); + return; + }else if ( strcasecmp(url.c_str(), "back")==0 ) + { + navigateBack(); + return; } url = canonicalizeRhoUrl(url); if (callback) { @@ -852,22 +1133,21 @@ } else navigateToUrl(url); } -boolean CRhodesApp::isLocalServerStarted() +void CRhodesApp::notifyLocalServerStarted() { - if (!m_httpServer) - return false; - return m_httpServer->started(); + m_appCallbacksQueue->addQueueCommand(new CAppCallbacksQueue::Command(CAppCallbacksQueue::local_server_started)); } } //namespace common } //namespace rho extern "C" { +using namespace rho; using namespace rho::common; unsigned long rho_rhodesapp_GetCallbackObject(int nIndex) { return RHODESAPP().getCallbackObject(nIndex); } @@ -968,15 +1248,15 @@ void rho_rhodesapp_destroy() { rho::common::CRhodesApp::Destroy(); } - +/* const char* rho_rhodesapp_getfirststarturl() { return RHODESAPP().getFirstStartUrl().c_str(); -} +}*/ const char* rho_rhodesapp_getstarturl() { return RHODESAPP().getStartUrl().c_str(); } @@ -1046,10 +1326,22 @@ { if ( rho::common::CRhodesApp::getInstance() ) RHODESAPP().callAppActiveCallback(nActive!=0); } +void rho_rhodesapp_callUiCreatedCallback() +{ + if ( rho::common::CRhodesApp::getInstance() ) + RHODESAPP().callUiCreatedCallback(); +} + +void rho_rhodesapp_callUiDestroyedCallback() +{ + if ( rho::common::CRhodesApp::getInstance() ) + RHODESAPP().callUiDestroyedCallback(); +} + void rho_rhodesapp_setViewMenu(unsigned long valMenu) { RHODESAPP().getAppMenu().setAppMenu(valMenu); } @@ -1091,81 +1383,10 @@ int rho_conf_send_log() { return RHODESAPP().sendLog(); } -static const char table64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; - -int rho_base64_encode(const char *src, int srclen, char *dst) -{ - if (srclen < 0) - srclen = strlen(src); - if (!dst) - return (srclen/3)*4 + (srclen%3 ? 4 : 0) + 1; - - int out = 0; - for(int in = 0; in < srclen; in += 3, out += 4) { - - unsigned x = 0; - int actual = 0; - for (int i = 0; i < 3; ++i) { - char c; - if (in + i >= srclen) - c = 0; - else { - c = src[in + i]; - actual += 8; - } - x = (x << 8) + (unsigned char)c; - } - - for (int i = 0; i < 4; ++i) { - if (actual <= 0) { - dst[out + i] = '='; - } - else { - dst[out + i] = table64[(x >> 18) & 0x3F]; - x <<= 6; - actual -= 6; - } - } - } - - dst[out++] = '\0'; - return out; -} - -int rho_base64_decode(const char *src, int srclen, char *dst) -{ - if (srclen < 0) - srclen = strlen(src); - // Do not decode in case if srclen can not be divided by 4 - if (srclen%4) - return 0; - if (!dst) - return srclen*3/4 + 1; - - char *found; - int out = 0; - for (int in = 0; in < srclen; in += 4, out += 3) { - unsigned x = 0; - for (int i = 0; i < 4; ++i) { - if ((found = strchr(const_cast<char*>(table64), src[in + i])) != NULL) - x = (x << 6) + (unsigned int)(found - table64); - else if (src[in + i] == '=') - x <<= 6; - } - - for (int i = 0; i < 3; ++i) { - dst[out + i] = (unsigned char)((x >> 16) & 0xFF); - x <<= 8; - } - } - dst[out++] = '\0'; - return out; -} - void rho_net_request(const char *url) { rho::common::CAutoPtr<rho::common::IRhoClassFactory> factory = rho_impl_createClassFactory(); rho::common::CAutoPtr<rho::net::INetRequest> request = factory->createNetRequest(); request->pullData(url, null); @@ -1175,11 +1396,10 @@ rho::common::CAutoPtr<rho::common::IRhoClassFactory> factory = rho_impl_createClassFactory(); rho::common::CAutoPtr<rho::net::INetRequest> request = factory->createNetRequest(); request->pushData(url, str_body, null); } - void rho_rhodesapp_load_url(const char *url) { RHODESAPP().loadUrl(url); } @@ -1191,15 +1411,10 @@ return 0; } return 1; } -int rho_is_local_server_started() -{ - return RHODESAPP().isLocalServerStarted(); -} - #if defined(OS_ANDROID) && defined(RHO_LOG_ENABLED) int rho_log(const char *fmt, ...) { va_list vl; va_start(vl, fmt); @@ -1218,7 +1433,34 @@ void rho_free_callbackdata(void* pData) { //It is used in SyncClient. } - + +int rho_rhodesapp_canstartapp(const char* szCmdLine, const char* szSeparators) +{ + CRhodesApp::setStartParameters(szCmdLine); + + const char* szAppSecToken = get_app_build_config_item("security_token"); + if ( !szAppSecToken || !*szAppSecToken) + return 1; + + String strCmdLineSecToken; + String security_key = "security_token="; + String strCmdLine = szCmdLine ? szCmdLine : ""; + + int skpos = strCmdLine.find(security_key); + if ((String::size_type)skpos != String::npos) + { + String tmp = strCmdLine.substr(skpos+security_key.length(), strCmdLine.length() - security_key.length() - skpos); + + int divider = tmp.find_first_of(szSeparators); + if ((String::size_type)divider != String::npos) + strCmdLineSecToken = tmp.substr(0, divider); + else + strCmdLineSecToken = tmp; + } + + return strCmdLineSecToken.compare(szAppSecToken) != 0 ? 0 : 1; +} + } //extern "C"