//---------------------------------------------------------------------------- // // Microsoft Windows // Copyright (C) Microsoft Corporation, 1997 // // File: update.cpp // // Contents: update subscriptions agent // // Classes: // // Functions: // // History: 01-14-1997 rayen (Raymond Endres) Created // //---------------------------------------------------------------------------- //xnotfmgr - darremi owns this #include "private.h" #include "offline.h" #include "offl_cpp.h" #undef TF_THISMODULE #define TF_THISMODULE TF_UPDATEAGENT const IDMSG_NOTHING = 100 + IDCANCEL; const IDMSG_INITFAILED = 101 + IDCANCEL; const IDMSG_SESSIONEND = 102 + IDCANCEL; const IDMSG_UPDATEBEGIN = 103 + IDCANCEL; const IDMSG_UPDATEPROGRESS = 104 + IDCANCEL; const IDMSG_ADJUSTPROBAR = 105 + IDCANCEL; const TID_UPDATE = 7405; // TimerID const TID_STATISTICS = 1243; // TimerID for statistics update #define SHRESTARTDIALOG_ORDINAL 59 // restart only exported by ordinal typedef BOOL (WINAPI *SHRESTARTDIALOG)( HWND, LPTSTR, DWORD ); CUpdateAgent * g_pUpdate = NULL; BOOL CUpdateDialog::m_bDetail = FALSE; CDialHelper::CDialHelper() : m_cRef(1), m_iDialerStatus(DIALER_OFFLINE) { ASSERT(0 == m_cConnection); ASSERT(NULL == m_pController); } STDMETHODIMP_(ULONG) CDialHelper::AddRef(void) { // DBG("CDialHelper::AddRef"); return ++m_cRef; } STDMETHODIMP_(ULONG) CDialHelper::Release(void) { // DBG("CDialHelper::Release"); if( 0L != --m_cRef ) return m_cRef; DBG("CDialHelper::Release, ref count down to 0"); ASSERT(!m_cConnection); ASSERT(m_iDialerStatus == DIALER_OFFLINE); if (m_iDialerStatus != DIALER_OFFLINE) { DBG("CDialHelper::Release, send disconnect message(abnormal)"); NotifyAutoDialer(NOTIFICATIONTYPE_TASKS_COMPLETED); } SAFERELEASE(m_pNotMgr); PostThreadMessage(m_ThreadID, UM_DECREASE, 0, 0); delete this; return 0L; } STDMETHODIMP CDialHelper::QueryInterface(REFIID riid, void ** ppv) { // DBG("CDialHelper::QueryInterface"); *ppv = NULL; if ((IID_IUnknown == riid) || (IID_INotificationSink == riid)) { *ppv = (INotificationSink *)this; } if( NULL != *ppv ) { // DBG("CDialHelper::QueryInterface/AddRef"); ((LPUNKNOWN)*ppv)->AddRef(); return NOERROR; } return ResultFromScode(E_NOINTERFACE); } STDMETHODIMP CDialHelper::OnNotification( LPNOTIFICATION pNotification, LPNOTIFICATIONREPORT pNotificationReport, DWORD dwReserved) { // DBG("CDialHelper::OnNotification called"); ASSERT(pNotification); // Extract Notification Type HRESULT hr; NOTIFICATIONTYPE notfType; hr = pNotification->GetNotificationInfo(¬fType, NULL, NULL, NULL, 0); ASSERT(SUCCEEDED(hr)); if (NOTIFICATIONTYPE_PROGRESS_REPORT == notfType) { // get hresult from notification HRESULT hrConnect; ReadSCODE(pNotification, NULL, c_szPropStatusCode, &hrConnect); if(SUCCEEDED(hrConnect)) return OnInetOnline(pNotification); else return OnInetOffline(pNotification); } else { DBG("CDialHelper::OnNotification - Unknown notification"); return S_OK; } } STDMETHODIMP CDialHelper::Init(CUpdateController * pController) { ASSERT(pController); m_pController = pController; ASSERT(pController->m_pNotMgr); m_pNotMgr = pController->m_pNotMgr; m_ThreadID = pController->m_ThreadID; m_pNotMgr->AddRef(); return S_OK; } STDMETHODIMP CDialHelper::DialOut(void) { HRESULT hr = S_OK; if (m_iDialerStatus == DIALER_OFFLINE) { DBG("CDialHelper::DialOut - Dialing Out"); hr = NotifyAutoDialer(NOTIFICATIONTYPE_AGENT_START); if (SUCCEEDED(hr)) { m_iDialerStatus = DIALER_CONNECTING; m_cConnection ++; } } return hr; } STDMETHODIMP CDialHelper::HangUp(void) { m_cConnection --; if (!m_cConnection) { if (m_iDialerStatus != DIALER_OFFLINE) { DBG("CDialHelper::HangUp - Hanging up"); m_iDialerStatus = DIALER_OFFLINE; NotifyAutoDialer(NOTIFICATIONTYPE_TASKS_COMPLETED); } } return S_OK; } STDMETHODIMP CDialHelper::CleanUp() { m_pController = NULL; return S_OK; } STDMETHODIMP CDialHelper::OnInetOnline( INotification *pNotification) { HRESULT hr=S_OK; if (m_iDialerStatus == DIALER_CONNECTING) { DBG("Dial Helper: CONNECTION SUCCESSFUL, BEGINNING DOWNLOAD"); m_iDialerStatus = DIALER_ONLINE; if (m_pController) m_pController->StartService(); else HangUp(); } return hr; } STDMETHODIMP CDialHelper::OnInetOffline( INotification *pNotification) { DBG("Dial Helper: received InetOffline, aborting"); SCODE eCode = S_OK; TCHAR szCaption[128]; TCHAR szString[1024]; CONNECT_ERROR error; if (m_iDialerStatus == DIALER_CONNECTING) { error = E_ATTEMPT_FAILED; } else { error = E_CONNECTION_LOST; } if (SUCCEEDED(ReadSCODE(pNotification, NULL, c_szPropStatusCode, & eCode))) { UINT uID; if (eCode == E_INVALIDARG) { uID = IDS_STRING_E_CONFIG; } else if (E_ABORT == eCode) { uID = IDS_STRING_E_SECURITYCHECK; } else { uID = IDS_STRING_E_FAILURE; } MLLoadString(uID, szString , ARRAYSIZE(szString)); MLLoadString(IDS_CAPTION_ERROR_CONNECTING, szCaption, ARRAYSIZE(szCaption)); MessageBox(NULL, szString, szCaption, MB_ICONWARNING | MB_SYSTEMMODAL); } if (m_pController) m_pController->StopService(error); else HangUp(); m_iDialerStatus = DIALER_OFFLINE; return S_OK; } STDMETHODIMP CDialHelper::NotifyAutoDialer(NOTIFICATIONTYPE pType) { HRESULT hr; INotification * pNot = NULL; ASSERT(m_pNotMgr); hr = m_pNotMgr->CreateNotification( pType, (NOTIFICATIONFLAGS) 0, NULL, &pNot, 0); if (pNot) { INotificationSink *pSink = NULL; if (pType == NOTIFICATIONTYPE_AGENT_START) { DBG("CDialHelper::NotifyAutoDialer AGENT_START"); pSink = (INotificationSink *)this; // HACK HACK [darrenmi] Until DZhang yanks out the umbrella code // we need something here - tell conn agent to let this connection // slide WriteAnsiSTR(pNot, NULL, c_szPropURL, TEXT("")); // have not mgr deliver for us hr = m_pNotMgr->DeliverNotification( pNot, CLSID_ConnectionAgent, DM_NEED_COMPLETIONREPORT | DM_DELIVER_DEFAULT_PROCESS, pSink, &m_pConnAgentReport, 0); } else { DBG("CDialHelper::NotifyAutoDialer TASKS_COMPLETED"); if(m_pConnAgentReport) { // deliver using the sink we've already got hr = m_pConnAgentReport->DeliverUpdate(pNot, 0, 0); TraceMsg(TF_THISMODULE, "CDialHelper::NotifyAutoDialer releasing report pointer"); SAFERELEASE(m_pConnAgentReport); } } SAFERELEASE(pNot); } return hr; } DWORD WINAPI DialogThreadProc(LPVOID pData) { ASSERT(pData); CUpdateDialog * pDialog = (CUpdateDialog *)pData; MSG msg; pDialog->m_ThreadID = GetCurrentThreadId(); while (GetMessage(&msg, NULL, 0, 0)) { switch (msg.message) { case UM_READY: { pDialog->Init(NULL, (CUpdateController *)msg.lParam); pDialog->Show(TRUE); } break; default: IsDialogMessage(pDialog->m_hDlg, &msg); break; } } #ifdef DEBUG if(g_fInitTable) LeakDetFunctionTable.pfnDebugMemLeak(DML_TYPE_THREAD | DML_END, TEXT(__FILE__), __LINE__); #endif DBG("DialogThreadProc returning"); return 0; } // application subscription channels can force a reboot void DoReboot() { HRESULT hrReboot = S_OK; HINSTANCE hShell32Lib; DBG("UpdateThreadProc returning - attempting reboot"); SHRESTARTDIALOG pfSHRESTARTDIALOG = NULL; if ((hShell32Lib = LoadLibrary("shell32.dll")) != NULL) { if (!(pfSHRESTARTDIALOG = (SHRESTARTDIALOG) GetProcAddress( hShell32Lib, MAKEINTRESOURCE(SHRESTARTDIALOG_ORDINAL)))) { hrReboot = HRESULT_FROM_WIN32(GetLastError()); } else { // FEATURE: What hwnd to use? pfSHRESTARTDIALOG(NULL, NULL, EWX_REBOOT); } } else { hrReboot = HRESULT_FROM_WIN32(GetLastError()); } if (hShell32Lib) { FreeLibrary(hShell32Lib); } } DWORD WINAPI UpdateThreadProc(LPVOID pData) { ASSERT(pData); CUpdateController * pController = (CUpdateController *) pData; INotification * pNotification = NULL; MSG msg; int l_cObj; BOOL bNeedReboot = FALSE; HRESULT hr = CoInitialize(NULL); if (FAILED(hr)) { DBG("UpdateThreadProc exiting, failed to CoInitialize."); return hr; } while (GetMessage(&msg, NULL, 0, 0)) { switch (msg.message) { case UM_ONREQUEST: if (!pController->m_fInit) break; pNotification = (INotification *)msg.lParam; // WARNING. There is a chance we fail in OnRequest (Failed to // send out Notification to dialer agent). hr = pController->OnRequest(pNotification); if (FAILED(hr)) { ASSERT(0); if ((!pController->m_count) && pController->m_pDialog) { PostMessage(pController->m_pDialog->m_hDlg, WM_COMMAND, IDMSG_SESSIONEND, 0); } } SAFERELEASE(pNotification); break; case UM_BACKGROUND: if (!pController->m_fInit) break; break; case UM_ONABORT: if (!pController->m_fInit) break; #ifdef DEBUG hr = #endif pController->Abort(); #ifdef DEBUG ASSERT(SUCCEEDED(hr)); #endif break; case UM_ONSKIP: if (!pController->m_fInit) break; #ifdef DEBUG hr = #endif pController->Skip(); #ifdef DEBUG ASSERT(SUCCEEDED(hr)); #endif break; case UM_ONADDSINGLE: if (!pController->m_fInit) break; MemFree((HLOCAL)msg.lParam); break; case UM_ONSKIPSINGLE: if (!pController->m_fInit) break; #ifdef DEBUG hr = #endif pController->SkipSingle((CLSID *)msg.lParam); MemFree((HLOCAL)msg.lParam); #ifdef DEBUG ASSERT(SUCCEEDED(hr)); #endif break; case UM_CLEANUP: pController->CleanUp(); pController->Release(); break; case UM_READY: pController->AddRef(); if (FAILED(pController->Init((CUpdateDialog *)msg.lParam))) { DBG("UpdateThreadProc - failed to init controller"); CUpdateDialog * pDlg = (CUpdateDialog *)msg.lParam; PostMessage(pDlg->m_hDlg, WM_COMMAND, IDMSG_INITFAILED, 0); } else { l_cObj = 2; } break; case UM_DECREASE: l_cObj --; if (!l_cObj) goto QUIT; break; case UM_NEEDREBOOT: bNeedReboot = TRUE; break; default: TranslateMessage(&msg); DispatchMessage(&msg); break; } } QUIT: ; #ifdef DEBUG if(g_fInitTable) LeakDetFunctionTable.pfnDebugMemLeak(DML_TYPE_THREAD | DML_END, TEXT(__FILE__), __LINE__); #endif CoUninitialize(); // This may need to be moved to a more appropriate location if (bNeedReboot) DoReboot(); DBG("UpdateThreadProc returning"); return 0; } STDMETHODIMP CUpdateController::ResyncData() { return S_OK; } STDMETHODIMP CUpdateController::StartPending(void) { HRESULT hr; DBG("CUpdateController::StartPending - entered"); ASSERT(GetCurrentThreadId() == m_ThreadID); ASSERT(m_pDialer); ASSERT(m_pDialer->m_iDialerStatus == DIALER_ONLINE); for ( UINT ui = 0; ui < m_cReportCount; ui ++) { if (m_aReport[ui].status == ITEM_STAT_PENDING) { hr = DispatchRequest(&(m_aReport[ui])); ASSERT(SUCCEEDED(hr)); } } return S_OK; } STDMETHODIMP CUpdateController::StartService(void) { HRESULT hr = S_OK; DBG("CUpdateController: Start Service"); ASSERT(GetCurrentThreadId() == m_ThreadID); ASSERT(!m_cFinished); hr = StartPending(); if (!m_count) { m_fSessionEnded = TRUE; m_pDialer->HangUp(); if (m_pDialog) PostMessage(m_pDialog->m_hDlg, WM_COMMAND, IDMSG_SESSIONEND, 0); } else if (m_pDialog) PostMessage(m_pDialog->m_hDlg, WM_COMMAND, IDMSG_UPDATEBEGIN, 0); return S_OK; } STDMETHODIMP CUpdateController::StopService(CONNECT_ERROR err) { DBG("Update Controller: Stop Service, aborting"); ASSERT(GetCurrentThreadId() == m_ThreadID); HRESULT hr; if (!m_count && (err == E_ATTEMPT_FAILED)) { if (m_pDialer) m_pDialer->HangUp(); } for ( UINT ui = 0; ui < m_cReportCount; ui ++) { switch (m_aReport[ui].status) { case ITEM_STAT_UPDATING: case ITEM_STAT_QUEUED: hr = CancelRequest(&(m_aReport[ui])); if (FAILED(hr)) break; else ; // Fall through. case ITEM_STAT_PENDING: m_aReport[ui].status = ITEM_STAT_ABORTED; if (m_pDialog) m_pDialog->RefreshStatus(&(m_aReport[ui].startCookie), NULL, m_aReport[ui].status); break; default: break; } } if (!m_count) { m_fSessionEnded = TRUE; PostMessage(m_pDialog->m_hDlg, WM_COMMAND, IDMSG_SESSIONEND, 0); } return S_OK; } STDMETHODIMP CUpdateController::IncreaseCount() { ASSERT(m_count >= 0); InterlockedIncrement(&m_count); return S_OK; } STDMETHODIMP CUpdateController::DecreaseCount(CLSID * pCookie) { InterlockedDecrement(&m_count); ASSERT(m_count >= 0); m_cFinished ++; // check for growing subscriptions (this could be better) if (m_cFinished > m_cTotal) m_cTotal = m_cFinished; if (m_count == 0) { m_pDialer->HangUp(); } if (m_pDialog) { PostMessage(m_pDialog->m_hDlg, WM_COMMAND, IDMSG_UPDATEPROGRESS, 0); if (!m_count) { m_fSessionEnded = TRUE; PostMessage(m_pDialog->m_hDlg, WM_COMMAND, IDMSG_SESSIONEND, 0); } } return S_OK; } STDMETHODIMP CUpdateController::GetItemList(UINT * pNewItem) { DBG("CUpdateController::GetItemList - entered"); NOTIFICATIONITEM item; item.cbSize = sizeof(NOTIFICATIONITEM); ASSERT(m_pNotMgr); IEnumNotification * pEnumNot = NULL; HRESULT hr; ULONG cItems = 0; UINT count = 0; hr = m_pNotMgr->GetEnumNotification(0, &pEnumNot); RETURN_ON_FAILURE(hr); ASSERT(pEnumNot); hr = pEnumNot->Next(1, &item, &cItems); while (SUCCEEDED(hr) && cItems) { ASSERT(item.pNotification); if ((NOTIFICATIONTYPE_AGENT_START == item.NotificationType) && (item.pNotification) && (TASK_FLAG_HIDDEN & ~item.TaskData.dwTaskFlags)) { SCODE scodeLast; STATUS statusLast; hr = ReadSCODE(item.pNotification, NULL, c_szPropStatusCode, &scodeLast); if (FAILED(scodeLast)) { statusLast = ITEM_STAT_FAILED; } else { statusLast = ITEM_STAT_SUCCEEDED; } hr = AddEntry(&item, statusLast); if (SUCCEEDED(hr)) { count ++; #ifdef DEBUG } else { DBGIID("CUpdateController::GetItemList - Failed to add entry", item.NotificationCookie); #endif } } SAFERELEASE(item.pNotification); item.cbSize = sizeof(NOTIFICATIONITEM); hr = pEnumNot->Next(1, &item, &cItems); } if (pNewItem) *pNewItem = count; SAFERELEASE(pEnumNot); return hr; } STDMETHODIMP CUpdateController::Abort(void) { DBG("CUpdateController::Abort - entered"); ASSERT(GetCurrentThreadId() == m_ThreadID); ASSERT(m_pDialer); HRESULT hr = StopService(ITEM_STAT_ABORTED); return hr; } STDMETHODIMP CUpdateController::SkipSingle(CLSID * pCookie) { ASSERT(pCookie); if (!pCookie) return E_INVALIDARG; HRESULT hr = S_OK; PReportMap pEntry = FindReportEntry(pCookie); if (pEntry) { switch (pEntry->status) { case ITEM_STAT_UPDATING: case ITEM_STAT_QUEUED: hr = CancelRequest(pEntry); if (FAILED(hr)) break; else ; // Fall through. case ITEM_STAT_PENDING: pEntry->status = ITEM_STAT_SKIPPED; if (m_pDialog) { m_pDialog->RefreshStatus(pCookie, NULL, pEntry->status); //select first updating item in list, which should be skippable m_pDialog->SelectFirstUpdatingSubscription(); } break; default: break; } } return hr; } STDMETHODIMP CUpdateController::Skip(void) { DBG("CUpdateController::Skip - entered"); HRESULT hr = S_OK; ASSERT(GetCurrentThreadId() == m_ThreadID); ASSERT(m_pDialog); UINT selCount = 0; hr = m_pDialog->GetSelectionCount(&selCount); if (FAILED(hr)) return hr; if (!selCount) return S_OK; CLSID * pSelCookies = (CLSID *)MemAlloc(LPTR, sizeof(CLSID)*selCount); if (!pSelCookies) return E_OUTOFMEMORY; hr = m_pDialog->GetSelectedCookies(pSelCookies, &selCount); if (FAILED(hr)) { MemFree(pSelCookies); pSelCookies = NULL; return hr; } for (UINT ui = 0; ui < selCount; ui ++) { SkipSingle(pSelCookies + ui); } MemFree(pSelCookies); pSelCookies = NULL; return S_OK; } const GUIDSTR_LEN = GUIDSTR_MAX - 1; STDMETHODIMP CUpdateController::AddSingle(CLSID * pCookie) { ASSERT(pCookie); if (!pCookie) return E_INVALIDARG; HRESULT hr = E_FAIL; PReportMap pEntry = FindReportEntry(pCookie); if (!pEntry) { NOTIFICATIONITEM item; item.cbSize = sizeof(NOTIFICATIONITEM); ASSERT(m_pNotMgr); hr = m_pNotMgr->FindNotification(pCookie, &item, 0); if (FAILED(hr)) return hr; ASSERT(item.pNotification); hr = E_FAIL; if ((NOTIFICATIONTYPE_AGENT_START == item.NotificationType) && (item.pNotification) && (TASK_FLAG_HIDDEN & ~item.TaskData.dwTaskFlags)) { SCODE scodeLast; STATUS statusLast; hr = ReadSCODE(item.pNotification, NULL, c_szPropStatusCode, &scodeLast); if (FAILED(scodeLast)) { statusLast = ITEM_STAT_FAILED; } else { statusLast = ITEM_STAT_SUCCEEDED; } hr = AddEntry(&item, statusLast); #ifdef DEBUG if (FAILED(hr)) { DBGIID("CUpdateController::AddSingle - Failed to add new entry", item.NotificationCookie); } #endif } SAFERELEASE(item.pNotification); item.cbSize = sizeof(NOTIFICATIONITEM); // Why is this line here? if (FAILED(hr)) return hr; pEntry = FindReportEntry(pCookie); ASSERT(pEntry); } if (pEntry) { switch (pEntry->status) { case ITEM_STAT_QUEUED: case ITEM_STAT_UPDATING: hr = S_FALSE; break; #ifdef DEBUG case ITEM_STAT_IDLE: ASSERT(0); break; #endif default: pEntry->status = ITEM_STAT_PENDING; if (m_pDialog) m_pDialog->RefreshStatus(pCookie,pEntry->name,pEntry->status); hr = S_OK; break; } } return hr; } STDMETHODIMP CUpdateController::Restart(UINT count) { if (!count) return S_OK; HRESULT hr = S_OK; m_cTotal = m_cTotal + count; if (m_pDialog) PostMessage(m_pDialog->m_hDlg, WM_COMMAND, IDMSG_ADJUSTPROBAR, 0); ASSERT(m_pDialer); if (m_pDialer->IsOffline()) { hr = m_pDialer->DialOut(); } else if (m_pDialer->IsConnecting()) { ; // Nothing to do; } else { hr = StartPending(); } return hr; } STDMETHODIMP CUpdateController::OnRequest(INotification * pNotification) { BOOL bUpdateAll = TRUE; HRESULT hr; UINT count = 0; DBG("CUpdateController::OnRequest - entered"); ASSERT(GetCurrentThreadId() == m_ThreadID); // There is a chance that we are still receiving request even through // we have already ended the session. if (m_fSessionEnded) return S_FALSE; // We don't accept any more requests. if (pNotification) { VARIANT var; VariantInit(&var); // Right now (02/21/97) urlmon can't handle the SAFEARRAY. // We assembly this array of GUIDs as a BSTR in SendUpdateRequest // and disassembly it here. hr = pNotification->Read(c_szPropGuidsArr, &var); if (var.vt == VT_BSTR) { UINT bstrLen = 0; BSTR bstr = var.bstrVal; int guidCount, i; CLSID cookie; ASSERT(bstr); DBG("CUpdateController::OnRequest - found cookie list"); bstrLen = lstrlenW(bstr); guidCount = bstrLen / GUIDSTR_LEN; SYSTEMTIME stNow; DATE dtNow; NOTIFICATIONITEM item; GetSystemTime(&stNow); SystemTimeToVariantTime(&stNow, &dtNow); item.cbSize = sizeof(NOTIFICATIONITEM); for (i = 0; i < guidCount; i ++) { BSTR bstrCookie = NULL; bstrCookie = SysAllocStringLen(bstr+i*GUIDSTR_LEN, GUIDSTR_LEN); hr = CLSIDFromString(bstrCookie, &cookie); #ifdef DEBUG DBGIID(TEXT("On request to update "), cookie); #endif SysFreeString(bstrCookie); if (FAILED(hr)) continue; if (S_OK == AddSingle(&cookie)) count ++; } hr = Restart(count); VariantClear(&var); return hr; } else { VariantClear(&var); } } DBG("CUpdateController::OnRequest - Update all"); for ( UINT ui = 0; ui < m_cReportCount; ui ++) { switch (m_aReport[ui].status) { #ifdef DEBUG case ITEM_STAT_IDLE: ASSERT(0); break; #endif case ITEM_STAT_UPDATING: case ITEM_STAT_QUEUED: break; default: m_aReport[ui].status = ITEM_STAT_PENDING; if (m_pDialog) m_pDialog->RefreshStatus(&(m_aReport[ui].startCookie), m_aReport[ui].name, m_aReport[ui].status); count ++; break; } } hr = Restart(count); return hr; } CUpdateController::CUpdateController() : m_fInit(FALSE), m_fSessionEnded(FALSE) { m_pNotMgr = NULL; m_pDialog = NULL; m_pDialer = NULL; m_cRef = 1; } CUpdateController::~CUpdateController() { ASSERT(0L == m_cRef); } STDMETHODIMP_(ULONG) CUpdateController::AddRef(void) { // DBG("CUpdateController::AddRef"); return ++m_cRef; } STDMETHODIMP_(ULONG) CUpdateController::Release(void) { // DBG("CUpdateController::Release"); if( 0L != --m_cRef ) return m_cRef; DBG("Destroying Controller object"); m_fInit = FALSE; PostThreadMessage(m_ThreadID, UM_DECREASE, 0, 0); SAFERELEASE(m_pNotMgr); if (m_pDialer) { m_pDialer->CleanUp(); SAFERELEASE(m_pDialer); } m_pDialog = NULL; for (UINT ui = 0; ui < m_cReportCount; ui ++) { SAFELOCALFREE(m_aReport[ui].name); SAFELOCALFREE(m_aReport[ui].url); } SAFELOCALFREE(m_aReport); delete this; return 0L; } STDMETHODIMP CUpdateController::QueryInterface(REFIID riid, void ** ppv) { // DBG("CUpdateController::QueryInterface"); *ppv = NULL; if ((IID_IUnknown == riid) || (IID_INotificationSink == riid)) { *ppv = (INotificationSink *)this; } if( NULL != *ppv ) { ((LPUNKNOWN)*ppv)->AddRef(); return S_OK; } return E_NOINTERFACE; } STDMETHODIMP CUpdateController::Init(CUpdateDialog * pDialog) { DBG("CUpdateController::Init"); ASSERT( !m_pNotMgr ); ASSERT(pDialog); ASSERT(!m_fInit); m_pDialog = pDialog; m_cReportCount = m_cReportCapacity = 0; m_ThreadID = GetCurrentThreadId(); ASSERT (!m_aReport); m_aReport = (PReportMap)MemAlloc(LPTR, sizeof(ReportMapEntry) * CUC_ENTRY_INCRE); if (!m_aReport) { DBG("CUpdateController::Init - Out of mem"); return E_OUTOFMEMORY; } m_cReportCapacity = CUC_ENTRY_INCRE; HRESULT hr = CoCreateInstance(CLSID_StdNotificationMgr, NULL, CLSCTX_INPROC_SERVER, IID_INotificationMgr,(void **)&m_pNotMgr); if (SUCCEEDED(hr)) { m_pDialer = new CDialHelper; if (!m_pDialer) { DBG("CUpdateController::Init - Failed to dialer"); hr = E_OUTOFMEMORY; } else { m_pDialer->AddRef(); hr = m_pDialer->Init(this); } } if (SUCCEEDED(hr)) { m_cFinished = m_cTotal = 0; m_count = 0; m_fInit = TRUE; GetItemList(NULL); if ((!m_cReportCount) && (m_pDialog)) { // Didn't find nothing. PostMessage(m_pDialog->m_hDlg, WM_COMMAND, IDMSG_NOTHING, 0); } } else { SAFERELEASE(m_pNotMgr); SAFERELEASE(m_pDialer); } return hr; } // REARCHITECT: Copied from NotificationMgr code (notifctn.hxx) #define WZ_COOKIE L"Notification_COOKIE" // // INotificationSink member(s) // STDMETHODIMP CUpdateController::OnNotification( LPNOTIFICATION pNotification, LPNOTIFICATIONREPORT pNotificationReport, DWORD dwReserved) { DBG("CUpdateController::OnNotification called"); ASSERT(pNotification); HRESULT hr; NOTIFICATIONTYPE notfType; ASSERT(GetCurrentThreadId() == m_ThreadID); // Extract Notification Type hr = pNotification->GetNotificationInfo(¬fType, NULL, NULL, NULL, 0); ASSERT(SUCCEEDED(hr)); if (FAILED(hr)) { return E_INVALIDARG; } if (notfType == NOTIFICATIONTYPE_END_REPORT) { // // Except those we fail to DeliverNotification in the first place for // each request we will get an End Report when we finished the current // update or aborted/skipped the current update. // DBG("CUpdateController::OnNotification - END REPORT"); CLSID cookie; PReportMap pEntry = NULL; if (SUCCEEDED(ReadGUID(pNotification, NULL, c_szStartCookie, &cookie))) { pEntry = FindReportEntry(&cookie); } if (!pEntry) { DBGIID("CUpdateController::OnNotification(END_REPORT) - invalid cookie", cookie); return E_FAIL; } //update count of total kbytes downloaded with size of this site from end report DWORD dwCrawlKBytes; if (SUCCEEDED (ReadDWORD (pNotification, NULL, c_szPropCrawlActualSize, &dwCrawlKBytes))) { if (m_pDialog) { DWORD dwKBytesPrevious = m_pDialog->SetSiteDownloadSize (&cookie, dwCrawlKBytes); m_pDialog->m_cDlKBytes += dwCrawlKBytes - dwKBytesPrevious; SendMessage (m_pDialog->m_hDlg, WM_TIMER, TID_STATISTICS, 0); //force update } } switch (pEntry->status) { case ITEM_STAT_UPDATING : { SCODE eCode = S_OK; hr = ReadSCODE(pNotification, NULL, c_szPropStatusCode, & eCode); ASSERT(SUCCEEDED(hr)); if (FAILED(eCode)) { pEntry->status = ITEM_STAT_FAILED; } else { pEntry->status = ITEM_STAT_SUCCEEDED; } if (m_pDialog) { m_pDialog->RefreshStatus(&cookie, NULL, pEntry->status); PostMessage(m_pDialog->m_hDlg, WM_COMMAND, IDMSG_UPDATEPROGRESS, 100 - pEntry->progress); } pEntry->progress = 0; break; } case ITEM_STAT_SKIPPED: case ITEM_STAT_ABORTED: ASSERT(!(pEntry->progress)); break; default: ASSERT(0); break; } return S_OK; } else if (notfType == NOTIFICATIONTYPE_TASKS_COMPLETED || notfType == NOTIFICATIONTYPE_TASKS_ERROR) { DBG("CUpdateController::OnNotification - TASKS_ENDED"); CLSID cookie; PReportMap pEntry = NULL; if (SUCCEEDED(ReadGUID(pNotification, NULL, WZ_COOKIE, &cookie))) { pEntry = FindReportEntry(&cookie); } if (!pEntry) { DBGIID("\t(TASKS_ENDED) - invalid cookie ", cookie); return E_FAIL; } else { DBGIID("\t(TASKS_ENDED) - cookie ", cookie); } DecreaseCount(&cookie); switch (pEntry->status) { case ITEM_STAT_UPDATING : { if (notfType == NOTIFICATIONTYPE_TASKS_ERROR) { pEntry->status = ITEM_STAT_FAILED; } else { pEntry->status = ITEM_STAT_SUCCEEDED; } if (m_pDialog) { m_pDialog->RefreshStatus(&cookie, NULL, pEntry->status); PostMessage(m_pDialog->m_hDlg, WM_COMMAND, IDMSG_UPDATEPROGRESS, 100 - pEntry->progress); } pEntry->progress = 0; break; } case ITEM_STAT_SKIPPED: case ITEM_STAT_ABORTED: ASSERT(!(pEntry->progress)); break; case ITEM_STAT_QUEUED: pEntry->status = ITEM_STAT_ABORTED; if (m_pDialog) { m_pDialog->RefreshStatus(&cookie, NULL, pEntry->status); PostMessage(m_pDialog->m_hDlg, WM_COMMAND, IDMSG_UPDATEPROGRESS, 100 - pEntry->progress); } pEntry->progress = 0; break; default: ASSERT(!(pEntry->progress)); break; } return S_OK; } else if (notfType == NOTIFICATIONTYPE_PROGRESS_REPORT) { DBG("CUpdateController::OnNotification - progress report"); CLSID cookie; PReportMap pEntry = NULL; if (SUCCEEDED(ReadGUID(pNotification, NULL, c_szStartCookie, &cookie))) { pEntry = FindReportEntry(&cookie); } if (!pEntry) { DBGIID("CUpdateController::OnNotification(PROGRESS_REPORT) - invalid cookie", cookie); return E_FAIL; } //start a document dl -- update count and status indicators if (m_pDialog) { BSTR bCurrentUrl; TCHAR szCurrentUrl[MAX_URL + 1]; if (SUCCEEDED(ReadBSTR(pNotification, NULL, c_szPropCurrentURL, &bCurrentUrl))) { //does not appear to be a real BSTR (with length byte) -- just an OLESTR MyOleStrToStrN (szCurrentUrl, MAX_URL, bCurrentUrl); SAFEFREEBSTR(bCurrentUrl); m_pDialog->RefreshStatus(&cookie, NULL, pEntry->status, szCurrentUrl); //update size of download DWORD dwKBytesCurrent; if (SUCCEEDED (ReadDWORD (pNotification, NULL, c_szPropCurrentSize, &dwKBytesCurrent)) && (dwKBytesCurrent != -1)) { DWORD dwKBytesPrevious = m_pDialog->SetSiteDownloadSize (&cookie, dwKBytesCurrent); m_pDialog->m_cDlKBytes += dwKBytesCurrent - dwKBytesPrevious; } ++m_pDialog->m_cDlDocs; //increase number of docs downloaded SendMessage (m_pDialog->m_hDlg, WM_TIMER, TID_STATISTICS, 0); //force update } } DWORD dwProgress; DWORD dwProgressMax; if (SUCCEEDED(ReadDWORD(pNotification, NULL, c_szPropProgressMax, &dwProgressMax)) && SUCCEEDED(ReadDWORD(pNotification, NULL, c_szPropProgress, &dwProgress))) { // (INT)dwProgressMax could be -1! if ((((INT)dwProgress) >= 0) && (((INT)dwProgressMax) >= 0)) { ASSERT(dwProgress <= dwProgressMax); // The progress report is sent at the beginning of current // download. We should substrat Progress by 1. UINT cProgress, cProgressMax, newPercentage; cProgress = (dwProgress)?dwProgress - 1:0; cProgressMax = dwProgressMax; newPercentage = MulDiv(cProgress, 100, cProgressMax); if ((newPercentage > pEntry->progress) && m_pDialog) { PostMessage(m_pDialog->m_hDlg, WM_COMMAND, IDMSG_UPDATEPROGRESS, (LPARAM)(newPercentage - pEntry->progress)); } pEntry->progress = newPercentage; } } return S_OK; } else if (notfType == NOTIFICATIONTYPE_BEGIN_REPORT) { DBG("CUpdateController::OnNotification - begin report"); CLSID cookie; PReportMap pEntry = NULL; if (SUCCEEDED(ReadGUID(pNotification, NULL, c_szStartCookie, &cookie))) { pEntry = FindReportEntry(&cookie); } if (!pEntry) { DBGIID("CUpdateController::OnNotification(BEGIN_REPORT) - invalid cookie", cookie); return E_FAIL; } if (pEntry->status == ITEM_STAT_UPDATING) return S_OK; // Note there is a case that we send out the 'Abort' notification to // the agent, and the agent sends 'begin report' at almost the same // time. In that case we can get begin-report when we think we already // cancelled the update. if (pEntry->status != ITEM_STAT_QUEUED) { ASSERT((pEntry->status == ITEM_STAT_SKIPPED) || (pEntry->status == ITEM_STAT_ABORTED)); return S_OK; // We bail out. } pEntry->status = ITEM_STAT_UPDATING; if (m_pDialog) m_pDialog->RefreshStatus(&cookie, NULL, ITEM_STAT_UPDATING); return S_OK; } else if (notfType == NOTIFICATIONTYPE_TASKS_STARTED) { DBG("CUpdateController::OnNotification - TASKS_STARTED"); CLSID cookie; PReportMap pEntry = NULL; if (SUCCEEDED(ReadGUID(pNotification, NULL, WZ_COOKIE, &cookie))) { pEntry = FindReportEntry(&cookie); } if (!pEntry) { DBGIID("\t(TASKS_STARTED) - invalid cookie ", cookie); return E_FAIL; } else { DBGIID("\t(TASKS_STARTED) - cookie ", cookie); } ASSERT(pEntry->status == ITEM_STAT_QUEUED); if (pEntry->status != ITEM_STAT_QUEUED) { ASSERT((pEntry->status == ITEM_STAT_SKIPPED) || (pEntry->status == ITEM_STAT_ABORTED)); return S_OK; // We bail out. } pEntry->status = ITEM_STAT_UPDATING; if (m_pDialog) m_pDialog->RefreshStatus(&cookie, NULL, ITEM_STAT_UPDATING); return S_OK; } else { DBG("CUpdateController::OnNotification - unknown notification"); return S_OK; } } STDMETHODIMP CUpdateController::DispatchRequest(PReportMap pEntry) { DBG("CUpdateController::Dispatch - entered"); ASSERT(pEntry); ASSERT(m_pNotMgr); ASSERT(m_pDialog); ASSERT(ITEM_STAT_PENDING == pEntry->status); HRESULT hr; NOTIFICATIONITEM item; item.cbSize = sizeof(item); hr = m_pNotMgr->FindNotification(&(pEntry->startCookie), &item, 0); ASSERT(SUCCEEDED(hr)); if (SUCCEEDED(hr)) { hr = m_pNotMgr->DeliverNotification(item.pNotification, item.clsidDest, DM_DELIVER_DEFAULT_PROCESS | DM_NEED_COMPLETIONREPORT | DM_NEED_PROGRESSREPORT | DM_THROTTLE_MODE, (INotificationSink *)this, NULL, 0); SAFERELEASE(item.pNotification); if (FAILED(hr)) { DBG("CUpdateController::Dispatch - failed to DeliverNotification"); m_pDialog->RefreshStatus(&(pEntry->startCookie), NULL, ITEM_STAT_FAILED); } else { DBG("Increase Count"); pEntry->status = ITEM_STAT_QUEUED; pEntry->progress = 0; IncreaseCount(); } } return hr; } // In CancelRequest() we only attempt to send out the notification of // abort. The count on agent side will be decreased when we get end report. // So matched number of request and end report is crucial. STDMETHODIMP CUpdateController::CancelRequest(PReportMap pEntry) { ASSERT(pEntry); if ((ITEM_STAT_UPDATING != pEntry->status) && (ITEM_STAT_QUEUED != pEntry->status)) return S_OK; ASSERT(m_pNotMgr); HRESULT hr = S_OK; INotification *pNot = NULL; hr = m_pNotMgr->CreateNotification(NOTIFICATIONTYPE_TASKS_ABORT, (NOTIFICATIONFLAGS)0, NULL, &pNot, 0); ASSERT(SUCCEEDED(hr)); if (SUCCEEDED(hr)) { if (SUCCEEDED(hr)) { hr = m_pNotMgr->DeliverReport(pNot, &(pEntry->startCookie), 0); if (SUCCEEDED(hr)) { if (m_pDialog) { PostMessage(m_pDialog->m_hDlg, WM_COMMAND, IDMSG_UPDATEPROGRESS, 100 - pEntry->progress); } pEntry->progress = 0; // This is the default status afterwards. pEntry->status = ITEM_STAT_ABORTED; } else { TraceMsg(TF_THISMODULE, TEXT("CancelRequest:Error:%x"), hr); } } else { DBG("CUpdateController::Stop failed"); } SAFERELEASE(pNot); } return hr; } PReportMap CUpdateController::FindReportEntry(CLSID * pCookie) { ASSERT(pCookie); for (UINT ui = 0; ui < m_cReportCount; ui ++) { if (m_aReport[ui].startCookie == * pCookie) { return &(m_aReport[ui]); } } return NULL; } STDMETHODIMP CUpdateController::GetLocationOf(CLSID * pCookie, LPTSTR pszText, UINT cchTextMax) { ASSERT(pCookie && pszText); PReportMap pEntry = FindReportEntry(pCookie); if (pEntry) { lstrcpyn(pszText, pEntry->url, cchTextMax); } else { lstrcpyn(pszText, c_szStrEmpty, cchTextMax); } return S_OK; } SUBSCRIPTIONTYPE CUpdateController::GetSubscriptionType(CLSID * pCookie) { ASSERT(pCookie); SUBSCRIPTIONTYPE subType = SUBSTYPE_EXTERNAL; PReportMap pEntry = FindReportEntry(pCookie); if (pEntry) { subType = pEntry->subType; } return subType; } BOOL CUpdateController::IsSkippable(CLSID * pCookie) { ASSERT(pCookie); PReportMap pEntry = FindReportEntry(pCookie); if (pEntry) if (pEntry->status == ITEM_STAT_PENDING || pEntry->status == ITEM_STAT_QUEUED || pEntry->status == ITEM_STAT_UPDATING) { return TRUE; } return FALSE; } STDMETHODIMP CUpdateController::AddEntry(NOTIFICATIONITEM *pItem, STATUS status) { ASSERT(pItem); ASSERT(m_cReportCount <= m_cReportCapacity); if (m_cReportCount == m_cReportCapacity) { UINT newSize = m_cReportCapacity + CUC_ENTRY_INCRE; ASSERT(newSize <= CUC_MAX_ENTRY); HLOCAL newBuf = MemReAlloc(m_aReport, newSize * sizeof(ReportMapEntry), LHND); if (!newBuf) { DBG("CUpdateController::AddEntry - Failed to realloc"); return E_OUTOFMEMORY; } m_aReport = (PReportMap)(newBuf); m_cReportCapacity = newSize; } m_aReport[m_cReportCount].startCookie = pItem->NotificationCookie; m_aReport[m_cReportCount].progress = 0; m_aReport[m_cReportCount].status = status; OOEBuf ooeBuf; DWORD dwSize = 0; ZeroMemory((void *)&ooeBuf, sizeof(ooeBuf)); HRESULT hr = LoadOOEntryInfo(&ooeBuf, pItem, &dwSize); if (S_OK != hr) { if (FAILED(hr)) return hr; else return E_FAIL; } LPTSTR nameStr = NULL, urlStr = NULL; nameStr = (LPTSTR)MemAlloc(LPTR, (lstrlen(ooeBuf.m_Name) + 1) * sizeof(TCHAR)); urlStr = (LPTSTR)MemAlloc(LPTR, (lstrlen(ooeBuf.m_URL) + 1) * sizeof(TCHAR)); if (!(nameStr && urlStr)) { SAFELOCALFREE(nameStr); SAFELOCALFREE(urlStr); return E_OUTOFMEMORY; } lstrcpy(nameStr, ooeBuf.m_Name); lstrcpy(urlStr, ooeBuf.m_URL); m_aReport[m_cReportCount].name = nameStr; m_aReport[m_cReportCount].url = urlStr; m_aReport[m_cReportCount].subType = GetItemCategory(&ooeBuf); m_cReportCount ++; return S_OK; } STDMETHODIMP CUpdateController::CleanUp() { m_pDialog = NULL; return S_OK; } ////////////////////////////////////////////////////////////////////////// // // CUpdateAgent // // The only reason we need this class is that so we can create the // dialog in the different thread. CUpdateAgent::CUpdateAgent() { DBG("Creating CUpdateAgent object"); ASSERT(!(m_pDialog || m_pController)); } CUpdateAgent::~CUpdateAgent() { DBG("Destroying CUpdateAgent object"); if (m_pController) { PostThreadMessage(m_ThreadID, UM_CLEANUP, 0,0); m_pController = NULL; } if (m_pDialog) { // delete m_pDialog; // m_pDialog will be destroyed by m_pController in CleanUp. m_pDialog = NULL; } g_pUpdate = NULL; } STDMETHODIMP CUpdateAgent::Init(void) { DBG("CUpdateAgent::Init"); HRESULT hr = S_OK; ASSERT(!(m_pDialog || m_pController)); if (SUCCEEDED(hr)) { m_pDialog = new CUpdateDialog; if (!m_pDialog) { DBG("CUpdateAgent::Init - Failed to create dialog"); hr = E_OUTOFMEMORY; } } if (SUCCEEDED(hr)) { m_pController = new CUpdateController; if (!m_pController) { DBG("CUpdateAgent::Init - Failed to create download controller"); hr = E_OUTOFMEMORY; } } if (SUCCEEDED(hr)) { HANDLE hThread; hThread = CreateThread(NULL, 0, DialogThreadProc, (LPVOID)m_pDialog, 0, &m_DialogThreadID); if (!hThread) { DBG("CUpdateAgent::Init - Failed to create dialog thread"); hr = E_FAIL; } else { int i = 0; while ( i < 3) { if (PostThreadMessage(m_DialogThreadID, UM_READY, 0, (LPARAM)m_pController)) break; i ++; Sleep(1000); } // Is there a safer way to do this? if (i >= 3) { ASSERT(0); hr = E_FAIL; } CloseHandle(hThread); } } if (SUCCEEDED(hr)) { HANDLE hThread; hThread = CreateThread(NULL, 0, UpdateThreadProc, (LPVOID)m_pController, 0, &m_ThreadID); if (!hThread) { DBG("CUpdateAgent::Init - Failed to create thread"); hr = E_FAIL; } else { int i = 0; while ( i < 3) { if (PostThreadMessage(m_ThreadID, UM_READY, 0, (LPARAM)m_pDialog)) break; i ++; Sleep(1000); } // FEATURE: Is there a safer way to do this? if (i >= 3) { ASSERT(0); hr = E_FAIL; } SetThreadPriority(hThread, THREAD_PRIORITY_IDLE); CloseHandle(hThread); } if (FAILED(hr)) { PostThreadMessage(m_DialogThreadID, WM_QUIT, 0, 0); } } if (FAILED(hr)) { if (m_pController) { delete m_pController; m_pController = NULL; } if (m_pDialog) { delete m_pDialog; m_pDialog = NULL; } } return hr; } BOOL ListView_OnNotify(HWND hDlg, NM_LISTVIEW* plvn, CUpdateController * pController) { ASSERT(plvn && pController); CUpdateDialog * pDialog = pController->m_pDialog; if (!pDialog) return FALSE; // If m_pDialog is NULL, we haven't call Init // for pController on second thread yet. HRESULT hr; switch (plvn->hdr.code) { case LVN_ITEMCHANGED: { if (!(plvn->uChanged & LVIF_STATE)) break; UINT uOldState = plvn->uOldState & LVIS_SELECTED; UINT uNewState = plvn->uNewState & LVIS_SELECTED; UINT count = 0; hr = pDialog->GetSelectionCount(&count); if (FAILED(hr)) break; HWND hButton = GetDlgItem(hDlg, IDCMD_SKIP); BOOL fEnable = FALSE; if (count) { CLSID cookie; int iItem = plvn->iItem; hr = pDialog->IItem2Cookie(iItem, &cookie); if (SUCCEEDED(hr)) { fEnable = pController->IsSkippable(&cookie); } } Button_Enable(hButton, fEnable); return TRUE; } default: break; } return FALSE; } void ResizeDialog(HWND hDlg, BOOL bShowDetail) { ASSERT(hDlg); RECT rcDlg, rcChild; HWND hSplitter, hLV; TCHAR szButton[32]; //calculate margin (upper-left position of IDC_SIZENODETAILS) GetWindowRect(GetDlgItem (hDlg, IDC_SIZENODETAILS), &rcChild); MapWindowPoints(NULL, hDlg, (LPPOINT)&rcChild, 2); int iMargin = rcChild.left; GetWindowRect(hDlg, &rcDlg); if (bShowDetail) { hLV = GetDlgItem(hDlg, IDL_SUBSCRIPTION); ASSERT(hLV); GetWindowRect(hLV, &rcChild); rcDlg.bottom = rcChild.bottom + iMargin + GetSystemMetrics (SM_CXSIZEFRAME); rcDlg.right = rcChild.right + iMargin + GetSystemMetrics (SM_CYSIZEFRAME); LONG dwStyle = GetWindowLong (hDlg, GWL_STYLE); dwStyle = dwStyle | WS_MAXIMIZEBOX | WS_THICKFRAME; SetWindowLong (hDlg, GWL_STYLE, dwStyle); rcDlg.left -= (GetSystemMetrics (SM_CXSIZEFRAME) - GetSystemMetrics (SM_CXFIXEDFRAME)); rcDlg.top -= (GetSystemMetrics (SM_CYSIZEFRAME) - GetSystemMetrics (SM_CYFIXEDFRAME)); MoveWindow(hDlg, rcDlg.left, rcDlg.top, rcDlg.right - rcDlg.left, rcDlg.bottom - rcDlg.top, TRUE); MLLoadString(IDS_NODETAILS, szButton, ARRAYSIZE(szButton)); } else { hSplitter = GetDlgItem(hDlg, IDC_SIZENODETAILS); ASSERT(hSplitter); GetWindowRect(hSplitter, &rcChild); LONG dwStyle = GetWindowLong (hDlg, GWL_STYLE); dwStyle = dwStyle & ~WS_MAXIMIZEBOX & ~WS_THICKFRAME; SetWindowLong (hDlg, GWL_STYLE, dwStyle); MoveWindow(hDlg, rcDlg.left + (GetSystemMetrics (SM_CXSIZEFRAME) - GetSystemMetrics (SM_CXFIXEDFRAME)), rcDlg.top + (GetSystemMetrics (SM_CYSIZEFRAME) - GetSystemMetrics (SM_CYFIXEDFRAME)), rcChild.right - rcDlg.left, rcChild.bottom - rcDlg.top, TRUE); MLLoadString(IDS_DETAILS, szButton, ARRAYSIZE(szButton)); } SetDlgItemText(hDlg, IDCMD_DETAILS, szButton); } void UpdateStatistics (HWND hDlg, int nFiles, int nKBytes, int nSeconds) { TCHAR szStats[128], szFormat[64]; MLLoadString (IDS_STATISTICS, szFormat, ARRAYSIZE(szFormat)); wnsprintf (szStats, ARRAYSIZE(szStats), szFormat, nFiles, nKBytes, nSeconds/60, nSeconds%60); SetDlgItemText (hDlg, IDC_STATISTICS, szStats); } void DrawResizeWidget (HWND hDlg) //copied from athena's CGroupListDlg::OnPaint { PAINTSTRUCT ps; RECT rc; GetClientRect(hDlg, &rc); rc.left = rc.right - GetSystemMetrics(SM_CXSMICON); rc.top = rc.bottom - GetSystemMetrics(SM_CYSMICON); BeginPaint(hDlg, &ps); if (CUpdateDialog::m_bDetail && !IsZoomed(hDlg)) DrawFrameControl(ps.hdc, &rc, DFC_SCROLL, DFCS_SCROLLSIZEGRIP); CUpdateDialog * pDialog = (CUpdateDialog *)GetWindowLong(hDlg, DWL_USER); if (pDialog != NULL) { pDialog->m_cxWidget = rc.left; pDialog->m_cyWidget = rc.top; } EndPaint(hDlg, &ps); } void EraseResizeWidget (HWND hDlg) { CUpdateDialog * pDialog = (CUpdateDialog *)GetWindowLong(hDlg, DWL_USER); RECT rWidget; if (pDialog != NULL) { //invalidate resize widget rWidget.left = pDialog->m_cxWidget; rWidget.top = pDialog->m_cyWidget; rWidget.right = pDialog->m_cxWidget + GetSystemMetrics(SM_CXSMICON); rWidget.bottom = pDialog->m_cyWidget + GetSystemMetrics(SM_CYSMICON); InvalidateRect(hDlg, &rWidget, FALSE); // pDialog->m_cxWidget = rWidget.left; // pDialog->m_cxWidget = rWidget.top; } } extern BOOL GetSubscriptionFolderPath(LPTSTR); //---------------------------------------------------------------------------- // UpdateDlgProc function //---------------------------------------------------------------------------- BOOL CALLBACK UpdateDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) { static DWORD dwStartTicks; CUpdateDialog * pDialog = (CUpdateDialog *)GetWindowLong(hDlg, DWL_USER); CUpdateController * pController = (pDialog)?pDialog->m_pController:NULL; switch (uMsg) { case WM_INITDIALOG : { DBG("DLGBOX: Creating dialog box."); ASSERT(lParam); ASSERT(GetWindowLong(hDlg, DWL_USER) == 0); SetWindowLong(hDlg, DWL_USER, lParam); SetForegroundWindow(hDlg); TCHAR szString[1024]; MLLoadString(IDS_CONNECTING, szString, ARRAYSIZE(szString)); SetDlgItemText(hDlg, IDC_AGENTSTATUS, szString); ResizeDialog(hDlg, CUpdateDialog::m_bDetail); SetTimer (hDlg, TID_STATISTICS, 1000, NULL); dwStartTicks = GetTickCount(); // Check current keyboard layout is IME or not if (((DWORD)GetKeyboardLayout(0) & 0xF0000000L) == 0xE0000000L) { HWND hwndIME = ImmGetDefaultIMEWnd(hDlg); if (hwndIME) SendMessage(hwndIME, WM_IME_NOTIFY, IMN_CLOSESTATUSWINDOW, 0); } return FALSE; // keep the focus on the dialog } case WM_ACTIVATE: if (LOWORD(wParam) != WA_INACTIVE) { // Check current keyboard layout is IME or not if (((DWORD)GetKeyboardLayout(0) & 0xF0000000L) == 0xE0000000L) { HWND hwndIME = ImmGetDefaultIMEWnd(hDlg); if (hwndIME) SendMessage(hwndIME, WM_IME_NOTIFY, IMN_CLOSESTATUSWINDOW, 0); } } break; case WM_NOTIFY : switch (LOWORD (wParam)) { case IDL_SUBSCRIPTION: if (!pController) break; return ListView_OnNotify(hDlg, (NM_LISTVIEW *)lParam, pController); default: return FALSE; } break; case WM_TIMER: switch (wParam) { case TID_UPDATE: KillTimer(hDlg, wParam); pDialog->CleanUp(); SetWindowLong(hDlg, DWL_USER, 0); delete pDialog; return TRUE; case TID_STATISTICS: UpdateStatistics (hDlg, pDialog->m_cDlDocs, pDialog->m_cDlKBytes, (GetTickCount() - dwStartTicks)/1000); return TRUE; } break; case WM_GETMINMAXINFO : if (CUpdateDialog::m_bDetail) { LPMINMAXINFO lpmmi = (LPMINMAXINFO)lParam; DWORD style = GetWindowLong (hDlg, GWL_STYLE); RECT smallest; GetWindowRect (GetDlgItem (hDlg, IDC_MINBORDER), &smallest); AdjustWindowRect (&smallest, style, FALSE); lpmmi->ptMinTrackSize.x = smallest.right - smallest.left; lpmmi->ptMinTrackSize.y = smallest.bottom - smallest.top; return 0; } break; case WM_PAINT: DrawResizeWidget (hDlg); break; case WM_SIZE: if (wParam == SIZE_MAXIMIZED || wParam == SIZE_RESTORED) { const int CHILDREN[] = { IDCMD_HIDE, IDCMD_ABORT, IDCMD_DETAILS, IDCMD_SKIP }; int width = LOWORD(lParam), height = HIWORD(lParam); RECT rChild; int child; //calculate margin (upper-left position of IDC_SIZENODETAILS) GetWindowRect(GetDlgItem (hDlg, IDC_SIZENODETAILS), &rChild); // Use MapWindowPints for mirroring MapWindowPoints(NULL, hDlg, (LPPOINT)&rChild, 2); int iMargin = rChild.left; for (child = 0; child < sizeof(CHILDREN)/sizeof(CHILDREN[0]); child++) { GetWindowRect (GetDlgItem (hDlg, CHILDREN[child]), &rChild); MapWindowPoints(NULL, hDlg, (LPPOINT)&rChild, 2); SetWindowPos (GetDlgItem (hDlg, CHILDREN[child]), 0, width - iMargin - (rChild.right - rChild.left), rChild.top, 0, 0, SWP_NOSIZE | SWP_NOZORDER); } if (CUpdateDialog::m_bDetail) //only apply to bottom half { EraseResizeWidget (hDlg); GetWindowRect (GetDlgItem (hDlg, IDD_SPLITTER), &rChild); MapWindowPoints(NULL, hDlg, (LPPOINT)&rChild, 2); SetWindowPos (GetDlgItem (hDlg, IDD_SPLITTER), 0, 0, 0, width - 2 * rChild.left, rChild.bottom - rChild.top, SWP_NOMOVE | SWP_NOZORDER | SWP_NOCOPYBITS); //for some reason there's a weird redraw bug on this control -- NOCOPYBITS fixes it GetWindowRect (GetDlgItem (hDlg, IDL_SUBSCRIPTION), &rChild); MapWindowPoints(NULL, hDlg, (LPPOINT)&rChild, 2); SetWindowPos (GetDlgItem (hDlg, IDL_SUBSCRIPTION), 0, 0, 0, width - rChild.left - iMargin, height - rChild.top - iMargin, SWP_NOMOVE | SWP_NOZORDER); } return 0; } break; case WM_CLOSE: KillTimer (hDlg, TID_STATISTICS); break; case WM_COMMAND : { if (!pController) { if (IDCANCEL == LOWORD (wParam)) { KillTimer(hDlg, TID_UPDATE); pDialog->CleanUp(); SetWindowLong(hDlg, DWL_USER, 0); delete pDialog; return TRUE; } else { break; } } HWND hProgress = GetDlgItem(hDlg, IDD_PROBAR); HWND hAnimate = GetDlgItem(hDlg, IDD_ANIMATE); TCHAR szString[1024]; switch (LOWORD (wParam)) { case IDMSG_SESSIONEND: // Stop dialog, session concludes. DBG("UpdateDlgProc - all updates are complete"); pController->m_cTotal = pController->m_cFinished = 0; pDialog->m_pController = pController = NULL; delete g_pUpdate; MessageBeep(0xFFFFFFFF); Animate_Close(hAnimate); ShowWindow(hAnimate, SW_HIDE); MLLoadString(IDS_SESSIONEND, szString, ARRAYSIZE(szString)); SetDlgItemText(hDlg, IDC_AGENTSTATUS, szString); ShowWindow (GetDlgItem (hDlg, IDC_AGENTSTATUS), SW_SHOW); Button_Enable(GetDlgItem(hDlg, IDCMD_ABORT), FALSE); Button_Enable(GetDlgItem(hDlg, IDCMD_DETAILS), FALSE); SetTimer(hDlg, TID_UPDATE, 3000, NULL); KillTimer (hDlg, TID_STATISTICS); return TRUE; case IDMSG_UPDATEBEGIN: { DBG("UpdateDlgProc - Start updating"); SetForegroundWindow(hDlg); ShowWindow (hAnimate, SW_SHOW); ShowWindow (GetDlgItem (hDlg, IDC_AGENTSTATUS), SW_HIDE); Animate_Open(hAnimate, IDA_DOWNLOAD); Animate_Play(hAnimate, 0, -1, -1); return TRUE; } case IDMSG_ADJUSTPROBAR: { ASSERT(pController->m_cTotal); ASSERT(pController->m_cFinished <= pController->m_cTotal); SendMessage(hProgress, PBM_SETRANGE32, 0, pController->m_cTotal * 100); SendMessage(hProgress, PBM_SETPOS, pController->m_cFinished * 100, 0); return TRUE; } case IDMSG_NOTHING: // Nothing to show yet. { DBG("UpdateDlgProc - No item found"); MLLoadString(IDS_STRING_NOTHING_TO_UPDATE, szString , ARRAYSIZE(szString)); SetDlgItemText(hDlg, IDC_AGENTSTATUS, szString); pController->m_cTotal = pController->m_cFinished = 0; pDialog->m_pController = pController = NULL; delete g_pUpdate; MessageBeep(0xFFFFFFFF); Button_Enable(GetDlgItem(hDlg, IDCMD_ABORT), FALSE); Button_Enable(GetDlgItem(hDlg, IDCMD_DETAILS), FALSE); SetTimer(hDlg, TID_UPDATE, 3000, NULL); KillTimer (hDlg, TID_STATISTICS); return TRUE; } case IDMSG_UPDATEPROGRESS: SendMessage(hProgress, PBM_DELTAPOS, lParam, 0); return TRUE; case IDMSG_INITFAILED: DBG("UpdateDlgProc - Controller Init Failed."); SetWindowLong(hDlg, DWL_USER, 0); pDialog->m_pController = pController = NULL; delete g_pUpdate; MessageBeep(0xFFFFFFFF); Button_Enable(GetDlgItem(hDlg, IDCMD_ABORT), FALSE); Button_Enable(GetDlgItem(hDlg, IDCMD_DETAILS), FALSE); pDialog->CleanUp(); SetWindowLong(hDlg, DWL_USER, 0); delete pDialog; return TRUE; case IDCANCEL: { int mbRet = SGMessageBox(hDlg, IDS_DOWNLOAD_ABORT_WARNING, MB_YESNO | MB_ICONQUESTION | MB_DEFBUTTON2 | MB_APPLMODAL); if (mbRet == IDNO) { return TRUE; } else { if (!pDialog->m_pController) return TRUE; ; // Fall through. } } // Following messages are from the UI and need to be // forward to update thread. case IDCMD_ABORT: // Abort all updates DBG("UpdateDlgProc - closing, all downloads aborted"); ASSERT(pController->m_ThreadID); PostThreadMessage(pController->m_ThreadID, UM_ONABORT, 0,0); MLLoadString(IDS_ABORTING, szString, ARRAYSIZE(szString)); SetDlgItemText(hDlg, IDC_AGENTSTATUS, szString); Animate_Close(hAnimate); ShowWindow(hAnimate, SW_HIDE); ShowWindow (GetDlgItem (hDlg, IDC_AGENTSTATUS), SW_SHOW); return TRUE; case IDCMD_SKIP: PostThreadMessage(pController->m_ThreadID, UM_ONSKIP, 0,0); return TRUE; case IDCMD_DETAILS: { CUpdateDialog::m_bDetail = !CUpdateDialog::m_bDetail; ResizeDialog(hDlg, CUpdateDialog::m_bDetail); return TRUE; } case IDCMD_HIDE: ShowWindow(hDlg, SW_SHOWMINIMIZED); return TRUE; default: break; } break; } } return FALSE; } HRESULT GetActiveUpdateAgent(CUpdateAgent ** ppUpdate) { ASSERT (ppUpdate); // We are assuming the Update agent is free threaded. *ppUpdate = NULL; if (g_pUpdate == NULL) { DBG("GetActiveUpdateAgent - Creating new agent"); g_pUpdate = new CUpdateAgent(); if (!g_pUpdate) { DBG("GetActiveUpdateAgent - Failed to create new agent"); return E_OUTOFMEMORY; } HRESULT hr = g_pUpdate->Init(); if (FAILED(hr)) { DBG("GetActiveUpdateAgent - Failed to init new agent"); return hr; } } *ppUpdate = g_pUpdate; return NOERROR; } DWORD WINAPI BackgroundUpdate(void) { DBG("BackgroundUpdate entered"); HRESULT hr; CUpdateAgent * pAgent = NULL; hr = GetActiveUpdateAgent(&pAgent); if (SUCCEEDED(hr)) { ASSERT(pAgent); ASSERT(pAgent->m_ThreadID); // APPCOMPAT: Even when we succeed here, there are chances that we won't // get updated because CONTROLLER CAN FAIL TO INITIALIZE AND WE DON'T // KNOW IT! if (!PostThreadMessage(pAgent->m_ThreadID, UM_BACKGROUND,0,0)) { hr = E_FAIL; DBG("Failed to post ONREQUEST message."); } pAgent = NULL; } DBG("BackgroundUpdate ended"); return (DWORD)hr; } DWORD WINAPI UpdateRequest(UINT idCmd, INotification *pNot) { // DBG("UpdateRequest entered"); if (idCmd != UM_ONREQUEST) return E_FAIL; HRESULT hr; CUpdateAgent * pAgent = NULL; hr = GetActiveUpdateAgent(&pAgent); if (SUCCEEDED(hr)) { ASSERT(pAgent); ASSERT(pAgent->m_ThreadID); if (pNot) pNot->AddRef(); // APPCOMPAT: Even when we succeed here, there are chances that we won't // get updated because CONTROLLER CAN FAIL TO INITIALIZE AND WE DON'T // KNOW IT! if (!PostThreadMessage(pAgent->m_ThreadID, UM_ONREQUEST,0,(LPARAM)pNot)) { hr = E_FAIL; SAFERELEASE(pNot); DBG("Failed to post ONREQUEST message."); } pAgent = NULL; } // DBG("UpdateRequest ended"); return (DWORD)hr; } HRESULT UpdateNotifyReboot(void) { DBG("UpdateNotifyReboot entered"); HRESULT hr; CUpdateAgent * pAgent = NULL; hr = GetActiveUpdateAgent(&pAgent); if (SUCCEEDED(hr)) { ASSERT(pAgent); ASSERT(pAgent->m_ThreadID); hr = PostThreadMessage(pAgent->m_ThreadID, UM_NEEDREBOOT, 0, 0); } DBG("UpdateNotifyReboot ended"); return hr; }