mirror of
https://github.com/reactos/reactos.git
synced 2025-08-06 03:12:59 +00:00
[RAPPS] snapshot url support (#2925)
* [RAPPS] add snapshot url support * [RAPPS] fix snapshot prev window won't update if size not changed * [RAPPS] fix crash after cancel download * [RAPPS] show broken-img if failed to create file / make network request * [RAPPS] create a snapshots folder if it does not exist * [RAPPS] add a helper function to perform callback * [RAPPS] check bIsCancelled when handling ERROR_INVALID_HANDLE * [RAPPS] remove W suffix for AsyncInetDownload * [RAPPS] add assert for unknown event * [RAPPS] Improve AsyncInetDownload error handling * [RAPPS] move DisplayFailed to private, fix crash when cancelling * [RAPPS] check for ERROR_INTERNET_OPERATION_CANCELLED * [RAPPS] improve error logging * [RAPPS] remove \r in error logging * [RAPPS] rewrite AsyncInet
This commit is contained in:
parent
fbf119fde1
commit
e506581454
6 changed files with 735 additions and 40 deletions
453
base/applications/rapps/asyncinet.cpp
Normal file
453
base/applications/rapps/asyncinet.cpp
Normal file
|
@ -0,0 +1,453 @@
|
|||
/*
|
||||
* PROJECT: ReactOS Applications Manager
|
||||
* LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later)
|
||||
* PURPOSE: Async Internet operation using WinINet
|
||||
* COPYRIGHT: Copyright 2020 He Yang (1160386205@qq.com)
|
||||
*/
|
||||
|
||||
#include "rapps.h"
|
||||
#include <wininet.h>
|
||||
#include <atlbase.h>
|
||||
#include "asyncinet.h"
|
||||
|
||||
|
||||
BOOL AsyncInetIsCanceled(pASYNCINET AsyncInet);
|
||||
BOOL AsyncInetAcquire(pASYNCINET AsyncInet);
|
||||
VOID AsyncInetRelease(pASYNCINET AsyncInet);
|
||||
int AsyncInetPerformCallback(pASYNCINET AsyncInet,
|
||||
ASYNC_EVENT Event,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam
|
||||
);
|
||||
VOID CALLBACK AsyncInetStatusCallback(
|
||||
HINTERNET hInternet,
|
||||
DWORD_PTR dwContext,
|
||||
DWORD dwInternetStatus,
|
||||
LPVOID lpvStatusInformation,
|
||||
DWORD dwStatusInformationLength
|
||||
);
|
||||
VOID AsyncInetReadFileLoop(pASYNCINET AsyncInet);
|
||||
BOOL AsyncInetCleanUp(pASYNCINET AsyncInet);
|
||||
VOID AsyncInetFree(pASYNCINET AsyncInet);
|
||||
|
||||
pASYNCINET AsyncInetDownload(LPCWSTR lpszAgent,
|
||||
DWORD dwAccessType,
|
||||
LPCWSTR lpszProxy,
|
||||
LPCWSTR lpszProxyBypass,
|
||||
LPCWSTR lpszUrl,
|
||||
BOOL bAllowCache,
|
||||
ASYNCINET_CALLBACK Callback,
|
||||
VOID* Extension
|
||||
) // allocate memory for AsyncInet and start a download task
|
||||
{
|
||||
pASYNCINET AsyncInet = NULL;
|
||||
BOOL bSuccess = FALSE;
|
||||
DWORD InetOpenUrlFlag = 0;
|
||||
INTERNET_STATUS_CALLBACK OldCallbackFunc;
|
||||
|
||||
AsyncInet = (pASYNCINET)HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(ASYNCINET));
|
||||
if (!AsyncInet)
|
||||
{
|
||||
OutputDebugStringA("At File: " __FILE__ " HeapAlloc returned 0\n");
|
||||
return 0;
|
||||
}
|
||||
|
||||
AsyncInet->bCancelled = FALSE;
|
||||
|
||||
AsyncInet->hInternet = NULL;
|
||||
AsyncInet->hInetFile = NULL;
|
||||
|
||||
AsyncInet->Callback = Callback;
|
||||
AsyncInet->Extension = Extension;
|
||||
|
||||
AsyncInet->hEventHandleCreated = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
|
||||
InitializeCriticalSection(&(AsyncInet->CriticalSection));
|
||||
AsyncInet->ReferenceCnt = 1; // 1 for callee itself
|
||||
AsyncInet->hEventHandleClose = CreateEvent(NULL, FALSE, FALSE, NULL);
|
||||
|
||||
if (AsyncInet->hEventHandleCreated && AsyncInet->hEventHandleClose)
|
||||
{
|
||||
AsyncInet->hInternet = InternetOpenW(lpszAgent, dwAccessType, lpszProxy, lpszProxyBypass, INTERNET_FLAG_ASYNC);
|
||||
|
||||
if (AsyncInet->hInternet)
|
||||
{
|
||||
OldCallbackFunc = InternetSetStatusCallbackW(AsyncInet->hInternet, AsyncInetStatusCallback);
|
||||
if (OldCallbackFunc != INTERNET_INVALID_STATUS_CALLBACK)
|
||||
{
|
||||
InetOpenUrlFlag |= INTERNET_FLAG_PASSIVE | INTERNET_FLAG_RESYNCHRONIZE;
|
||||
if (!bAllowCache)
|
||||
{
|
||||
InetOpenUrlFlag |= INTERNET_FLAG_DONT_CACHE | INTERNET_FLAG_PRAGMA_NOCACHE | INTERNET_FLAG_RELOAD;
|
||||
}
|
||||
|
||||
AsyncInet->hInetFile = InternetOpenUrlW(AsyncInet->hInternet, lpszUrl, 0, 0, InetOpenUrlFlag, (DWORD_PTR)AsyncInet);
|
||||
|
||||
if (AsyncInet->hInetFile)
|
||||
{
|
||||
// operate complete synchronously
|
||||
bSuccess = TRUE;
|
||||
AsyncInetReadFileLoop(AsyncInet);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (GetLastError() == ERROR_IO_PENDING)
|
||||
{
|
||||
// everything fine. waiting for handle created
|
||||
switch (WaitForSingleObject(AsyncInet->hEventHandleCreated, INFINITE))
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
if (AsyncInet->hInetFile)
|
||||
{
|
||||
bSuccess = TRUE;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!bSuccess)
|
||||
{
|
||||
AsyncInetFree(AsyncInet);
|
||||
AsyncInet = NULL;
|
||||
}
|
||||
|
||||
if (AsyncInet)
|
||||
{
|
||||
// add reference count for caller.
|
||||
// the caller is responsible for call AsyncInetRelease when no longer using it.
|
||||
AsyncInetAcquire(AsyncInet);
|
||||
}
|
||||
return AsyncInet;
|
||||
}
|
||||
|
||||
BOOL AsyncInetCancel(pASYNCINET AsyncInet) // mark as cancelled (this will send a cancel notificaion at last) and do graceful cleanup.
|
||||
{
|
||||
if (AsyncInet)
|
||||
{
|
||||
HINTERNET hInetFile;
|
||||
EnterCriticalSection(&(AsyncInet->CriticalSection));
|
||||
AsyncInet->bCancelled = TRUE;
|
||||
hInetFile = AsyncInet->hInetFile;
|
||||
AsyncInet->hInetFile = NULL;
|
||||
LeaveCriticalSection(&(AsyncInet->CriticalSection));
|
||||
|
||||
if (hInetFile)
|
||||
{
|
||||
InternetCloseHandle(hInetFile);
|
||||
return TRUE;
|
||||
}
|
||||
}
|
||||
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOL AsyncInetIsCanceled(pASYNCINET AsyncInet) // if returned TRUE, no operation should be exectued further
|
||||
{
|
||||
if (AsyncInet)
|
||||
{
|
||||
EnterCriticalSection(&(AsyncInet->CriticalSection));
|
||||
if (AsyncInet->bCancelled)
|
||||
{
|
||||
LeaveCriticalSection(&(AsyncInet->CriticalSection));
|
||||
return TRUE;
|
||||
}
|
||||
LeaveCriticalSection(&(AsyncInet->CriticalSection));
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOL AsyncInetAcquire(pASYNCINET AsyncInet) // try to increase refcnt by 1. if returned FALSE, AsyncInet should not be used anymore
|
||||
{
|
||||
BOOL bResult = FALSE;
|
||||
if (AsyncInet)
|
||||
{
|
||||
EnterCriticalSection(&(AsyncInet->CriticalSection));
|
||||
ATLASSERT(AsyncInet->ReferenceCnt > 0);
|
||||
if (!AsyncInetIsCanceled(AsyncInet))
|
||||
{
|
||||
AsyncInet->ReferenceCnt++;
|
||||
bResult = TRUE;
|
||||
}
|
||||
// otherwise (AsyncInet->bCleanUp == TRUE)
|
||||
// AsyncInetAcquire will return FALSE.
|
||||
// In this case, any thread should no longer use this AsyncInet
|
||||
|
||||
LeaveCriticalSection(&(AsyncInet->CriticalSection));
|
||||
}
|
||||
|
||||
return bResult;
|
||||
}
|
||||
|
||||
VOID AsyncInetRelease(pASYNCINET AsyncInet) // try to decrease refcnt by 1
|
||||
{
|
||||
BOOL bCleanUp = FALSE;
|
||||
if (AsyncInet)
|
||||
{
|
||||
EnterCriticalSection(&(AsyncInet->CriticalSection));
|
||||
|
||||
ATLASSERT(AsyncInet->ReferenceCnt);
|
||||
AsyncInet->ReferenceCnt--;
|
||||
if (AsyncInet->ReferenceCnt == 0)
|
||||
{
|
||||
bCleanUp = TRUE;
|
||||
}
|
||||
|
||||
LeaveCriticalSection(&(AsyncInet->CriticalSection));
|
||||
|
||||
if (bCleanUp)
|
||||
{
|
||||
AsyncInetCleanUp(AsyncInet);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int AsyncInetPerformCallback(pASYNCINET AsyncInet,
|
||||
ASYNC_EVENT Event,
|
||||
WPARAM wParam,
|
||||
LPARAM lParam
|
||||
)
|
||||
{
|
||||
if (AsyncInet && AsyncInet->Callback)
|
||||
{
|
||||
return AsyncInet->Callback(AsyncInet, Event, wParam, lParam, AsyncInet->Extension);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
VOID CALLBACK AsyncInetStatusCallback(
|
||||
HINTERNET hInternet,
|
||||
DWORD_PTR dwContext,
|
||||
DWORD dwInternetStatus,
|
||||
LPVOID lpvStatusInformation,
|
||||
DWORD dwStatusInformationLength
|
||||
)
|
||||
{
|
||||
pASYNCINET AsyncInet = (pASYNCINET)dwContext;
|
||||
switch (dwInternetStatus)
|
||||
{
|
||||
case INTERNET_STATUS_HANDLE_CREATED:
|
||||
{
|
||||
// retrieve handle created by InternetOpenUrlW
|
||||
AsyncInet->hInetFile = *((LPHINTERNET)lpvStatusInformation);
|
||||
SetEvent(AsyncInet->hEventHandleCreated);
|
||||
break;
|
||||
}
|
||||
case INTERNET_STATUS_HANDLE_CLOSING:
|
||||
{
|
||||
if (AsyncInetIsCanceled(AsyncInet))
|
||||
{
|
||||
AsyncInetPerformCallback(AsyncInet, ASYNCINET_CANCELLED, 0, 0);
|
||||
}
|
||||
SetEvent(AsyncInet->hEventHandleClose);
|
||||
break;
|
||||
}
|
||||
case INTERNET_STATUS_REQUEST_COMPLETE:
|
||||
{
|
||||
INTERNET_ASYNC_RESULT* AsyncResult = (INTERNET_ASYNC_RESULT*)lpvStatusInformation;
|
||||
|
||||
if (!AsyncInet->hInetFile)
|
||||
{
|
||||
if (!AsyncInetIsCanceled(AsyncInet))
|
||||
{
|
||||
// some error occurs during InternetOpenUrl
|
||||
// and INTERNET_STATUS_HANDLE_CREATED is skipped
|
||||
SetEvent(AsyncInet->hEventHandleCreated);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
switch (AsyncResult->dwError)
|
||||
{
|
||||
case ERROR_SUCCESS:
|
||||
{
|
||||
// there are two possibilities:
|
||||
// 1: InternetOpenUrlW (async mode) complete. (this should be only for the first time)
|
||||
// 2: InternetReadFile (async mode) complete.
|
||||
// so I use bIsOpenUrlComplete field in ASYNCINET to indicate this.
|
||||
|
||||
if (!(AsyncInet->bIsOpenUrlComplete)) // InternetOpenUrlW completed
|
||||
{
|
||||
AsyncInet->bIsOpenUrlComplete = TRUE;
|
||||
}
|
||||
else // asynchronous InternetReadFile complete
|
||||
{
|
||||
AsyncInetPerformCallback(AsyncInet, ASYNCINET_DATA, (WPARAM)(AsyncInet->ReadBuffer), (LPARAM)(AsyncInet->BytesRead));
|
||||
}
|
||||
|
||||
AsyncInetReadFileLoop(AsyncInet);
|
||||
}
|
||||
break;
|
||||
case ERROR_INVALID_HANDLE:
|
||||
case ERROR_INTERNET_OPERATION_CANCELLED:
|
||||
case ERROR_CANCELLED:
|
||||
if (AsyncInetIsCanceled(AsyncInet))
|
||||
{
|
||||
AsyncInetRelease(AsyncInet);
|
||||
break;
|
||||
}
|
||||
|
||||
// fall down
|
||||
default:
|
||||
// something went wrong
|
||||
if (AsyncInetIsCanceled(AsyncInet))
|
||||
{
|
||||
// sending both ASYNCINET_ERROR and ASYNCINET_CANCELLED may lead to unpredictable behavior
|
||||
// TODO: log the error
|
||||
AsyncInetRelease(AsyncInet);
|
||||
}
|
||||
else
|
||||
{
|
||||
AsyncInetPerformCallback(AsyncInet, ASYNCINET_ERROR, 0, (LPARAM)(AsyncResult->dwError));
|
||||
AsyncInetRelease(AsyncInet);
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
VOID AsyncInetReadFileLoop(pASYNCINET AsyncInet)
|
||||
{
|
||||
if ((!AsyncInet) || (!AsyncInet->hInetFile))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
while (1)
|
||||
{
|
||||
if (AsyncInetIsCanceled(AsyncInet))
|
||||
{
|
||||
// abort now.
|
||||
AsyncInetRelease(AsyncInet);
|
||||
break;
|
||||
}
|
||||
|
||||
BOOL bRet = InternetReadFile(AsyncInet->hInetFile,
|
||||
AsyncInet->ReadBuffer,
|
||||
_countof(AsyncInet->ReadBuffer),
|
||||
&(AsyncInet->BytesRead));
|
||||
|
||||
if (bRet)
|
||||
{
|
||||
if (AsyncInet->BytesRead == 0)
|
||||
{
|
||||
// everything read. now complete.
|
||||
AsyncInetPerformCallback(AsyncInet, ASYNCINET_COMPLETE, 0, 0);
|
||||
AsyncInetRelease(AsyncInet);
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
// read completed immediately.
|
||||
AsyncInetPerformCallback(AsyncInet, ASYNCINET_DATA, (WPARAM)(AsyncInet->ReadBuffer), (LPARAM)(AsyncInet->BytesRead));
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
DWORD dwError;
|
||||
if ((dwError = GetLastError()) == ERROR_IO_PENDING)
|
||||
{
|
||||
// performing asynchronous IO, everything OK.
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (dwError == ERROR_INVALID_HANDLE ||
|
||||
dwError == ERROR_INTERNET_OPERATION_CANCELLED ||
|
||||
dwError == ERROR_CANCELLED)
|
||||
{
|
||||
if (AsyncInetIsCanceled(AsyncInet))
|
||||
{
|
||||
// not an error. just normally cancelling
|
||||
AsyncInetRelease(AsyncInet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!AsyncInetIsCanceled(AsyncInet)) // can not send both ASYNCINET_ERROR and ASYNCINET_CANCELLED
|
||||
{
|
||||
AsyncInetPerformCallback(AsyncInet, ASYNCINET_ERROR, 0, dwError);
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: log the error
|
||||
}
|
||||
AsyncInetRelease(AsyncInet);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
BOOL AsyncInetCleanUp(pASYNCINET AsyncInet) // close all handle and clean up
|
||||
{
|
||||
if (AsyncInet)
|
||||
{
|
||||
ATLASSERT(AsyncInet->ReferenceCnt == 0);
|
||||
// close the handle, waiting for all pending request cancelled.
|
||||
|
||||
if (AsyncInet->bCancelled) // already closed
|
||||
{
|
||||
AsyncInet->hInetFile = NULL;
|
||||
}
|
||||
else if (AsyncInet->hInetFile)
|
||||
{
|
||||
InternetCloseHandle(AsyncInet->hInetFile);
|
||||
AsyncInet->hInetFile = NULL;
|
||||
}
|
||||
|
||||
// only cleanup when handle closed notification received
|
||||
switch (WaitForSingleObject(AsyncInet->hEventHandleClose, INFINITE))
|
||||
{
|
||||
case WAIT_OBJECT_0:
|
||||
{
|
||||
AsyncInetFree(AsyncInet); // now safe to free the structure
|
||||
return TRUE;
|
||||
}
|
||||
default:
|
||||
ATLASSERT(FALSE);
|
||||
AsyncInetFree(AsyncInet);
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
VOID AsyncInetFree(pASYNCINET AsyncInet) // close all handles, free the memory occupied by AsyncInet
|
||||
{
|
||||
if (AsyncInet)
|
||||
{
|
||||
DeleteCriticalSection(&(AsyncInet->CriticalSection));
|
||||
|
||||
if (AsyncInet->hEventHandleCreated)
|
||||
{
|
||||
CloseHandle(AsyncInet->hEventHandleCreated);
|
||||
AsyncInet->hEventHandleCreated = NULL;
|
||||
}
|
||||
if (AsyncInet->hEventHandleClose)
|
||||
{
|
||||
CloseHandle(AsyncInet->hEventHandleClose);
|
||||
AsyncInet->hEventHandleClose = NULL;
|
||||
}
|
||||
if (AsyncInet->hInternet)
|
||||
{
|
||||
InternetCloseHandle(AsyncInet->hInternet);
|
||||
AsyncInet->hInternet = NULL;
|
||||
}
|
||||
if (AsyncInet->hInetFile)
|
||||
{
|
||||
InternetCloseHandle(AsyncInet->hInetFile);
|
||||
AsyncInet->hInetFile = NULL;
|
||||
}
|
||||
HeapFree(GetProcessHeap(), 0, AsyncInet);
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue