555 lines
17 KiB
C
555 lines
17 KiB
C
/*
|
|
* Queue Manager (BITS) File
|
|
*
|
|
* Copyright 2007, 2008 Google (Roy Shea, Dan Hipschman)
|
|
*
|
|
* This library is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU Lesser General Public
|
|
* License as published by the Free Software Foundation; either
|
|
* version 2.1 of the License, or (at your option) any later version.
|
|
*
|
|
* This library 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
|
|
* Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public
|
|
* License along with this library; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
|
|
*/
|
|
|
|
#include <stdarg.h>
|
|
|
|
#include "windef.h"
|
|
#include "winbase.h"
|
|
#include "winuser.h"
|
|
#include "winreg.h"
|
|
#include "winhttp.h"
|
|
#define COBJMACROS
|
|
#include "qmgr.h"
|
|
#include "wine/debug.h"
|
|
|
|
WINE_DEFAULT_DEBUG_CHANNEL(qmgr);
|
|
|
|
static inline BackgroundCopyFileImpl *impl_from_IBackgroundCopyFile2(
|
|
IBackgroundCopyFile2 *iface)
|
|
{
|
|
return CONTAINING_RECORD(iface, BackgroundCopyFileImpl, IBackgroundCopyFile2_iface);
|
|
}
|
|
|
|
static HRESULT WINAPI BackgroundCopyFile_QueryInterface(
|
|
IBackgroundCopyFile2 *iface,
|
|
REFIID riid,
|
|
void **obj)
|
|
{
|
|
BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
|
|
|
|
TRACE("(%p)->(%s %p)\n", file, debugstr_guid(riid), obj);
|
|
|
|
if (IsEqualGUID(riid, &IID_IUnknown) ||
|
|
IsEqualGUID(riid, &IID_IBackgroundCopyFile) ||
|
|
IsEqualGUID(riid, &IID_IBackgroundCopyFile2))
|
|
{
|
|
*obj = iface;
|
|
}
|
|
else
|
|
{
|
|
*obj = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
|
|
IBackgroundCopyFile2_AddRef(iface);
|
|
return S_OK;
|
|
}
|
|
|
|
static ULONG WINAPI BackgroundCopyFile_AddRef(
|
|
IBackgroundCopyFile2 *iface)
|
|
{
|
|
BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
|
|
ULONG ref = InterlockedIncrement(&file->ref);
|
|
TRACE("(%p)->(%d)\n", file, ref);
|
|
return ref;
|
|
}
|
|
|
|
static ULONG WINAPI BackgroundCopyFile_Release(
|
|
IBackgroundCopyFile2 *iface)
|
|
{
|
|
BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
|
|
ULONG ref = InterlockedDecrement(&file->ref);
|
|
|
|
TRACE("(%p)->(%d)\n", file, ref);
|
|
|
|
if (ref == 0)
|
|
{
|
|
IBackgroundCopyJob3_Release(&file->owner->IBackgroundCopyJob3_iface);
|
|
HeapFree(GetProcessHeap(), 0, file->info.LocalName);
|
|
HeapFree(GetProcessHeap(), 0, file->info.RemoteName);
|
|
HeapFree(GetProcessHeap(), 0, file);
|
|
}
|
|
|
|
return ref;
|
|
}
|
|
|
|
/* Get the remote name of a background copy file */
|
|
static HRESULT WINAPI BackgroundCopyFile_GetRemoteName(
|
|
IBackgroundCopyFile2 *iface,
|
|
LPWSTR *pVal)
|
|
{
|
|
BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
|
|
|
|
TRACE("(%p)->(%p)\n", file, pVal);
|
|
|
|
return return_strval(file->info.RemoteName, pVal);
|
|
}
|
|
|
|
static HRESULT WINAPI BackgroundCopyFile_GetLocalName(
|
|
IBackgroundCopyFile2 *iface,
|
|
LPWSTR *pVal)
|
|
{
|
|
BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
|
|
|
|
TRACE("(%p)->(%p)\n", file, pVal);
|
|
|
|
return return_strval(file->info.LocalName, pVal);
|
|
}
|
|
|
|
static HRESULT WINAPI BackgroundCopyFile_GetProgress(
|
|
IBackgroundCopyFile2 *iface,
|
|
BG_FILE_PROGRESS *pVal)
|
|
{
|
|
BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
|
|
|
|
TRACE("(%p)->(%p)\n", file, pVal);
|
|
|
|
EnterCriticalSection(&file->owner->cs);
|
|
*pVal = file->fileProgress;
|
|
LeaveCriticalSection(&file->owner->cs);
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT WINAPI BackgroundCopyFile_GetFileRanges(
|
|
IBackgroundCopyFile2 *iface,
|
|
DWORD *RangeCount,
|
|
BG_FILE_RANGE **Ranges)
|
|
{
|
|
BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
|
|
FIXME("(%p)->(%p %p)\n", file, RangeCount, Ranges);
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static HRESULT WINAPI BackgroundCopyFile_SetRemoteName(
|
|
IBackgroundCopyFile2 *iface,
|
|
LPCWSTR Val)
|
|
{
|
|
BackgroundCopyFileImpl *file = impl_from_IBackgroundCopyFile2(iface);
|
|
FIXME("(%p)->(%s)\n", file, debugstr_w(Val));
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
static const IBackgroundCopyFile2Vtbl BackgroundCopyFile2Vtbl =
|
|
{
|
|
BackgroundCopyFile_QueryInterface,
|
|
BackgroundCopyFile_AddRef,
|
|
BackgroundCopyFile_Release,
|
|
BackgroundCopyFile_GetRemoteName,
|
|
BackgroundCopyFile_GetLocalName,
|
|
BackgroundCopyFile_GetProgress,
|
|
BackgroundCopyFile_GetFileRanges,
|
|
BackgroundCopyFile_SetRemoteName
|
|
};
|
|
|
|
HRESULT BackgroundCopyFileConstructor(BackgroundCopyJobImpl *owner,
|
|
LPCWSTR remoteName, LPCWSTR localName,
|
|
BackgroundCopyFileImpl **file)
|
|
{
|
|
BackgroundCopyFileImpl *This;
|
|
|
|
TRACE("(%s, %s, %p)\n", debugstr_w(remoteName), debugstr_w(localName), file);
|
|
|
|
This = HeapAlloc(GetProcessHeap(), 0, sizeof *This);
|
|
if (!This)
|
|
return E_OUTOFMEMORY;
|
|
|
|
This->info.RemoteName = strdupW(remoteName);
|
|
if (!This->info.RemoteName)
|
|
{
|
|
HeapFree(GetProcessHeap(), 0, This);
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
This->info.LocalName = strdupW(localName);
|
|
if (!This->info.LocalName)
|
|
{
|
|
HeapFree(GetProcessHeap(), 0, This->info.RemoteName);
|
|
HeapFree(GetProcessHeap(), 0, This);
|
|
return E_OUTOFMEMORY;
|
|
}
|
|
|
|
This->IBackgroundCopyFile2_iface.lpVtbl = &BackgroundCopyFile2Vtbl;
|
|
This->ref = 1;
|
|
|
|
This->fileProgress.BytesTotal = BG_SIZE_UNKNOWN;
|
|
This->fileProgress.BytesTransferred = 0;
|
|
This->fileProgress.Completed = FALSE;
|
|
This->owner = owner;
|
|
This->read_size = 0;
|
|
This->tempFileName[0] = 0;
|
|
IBackgroundCopyJob3_AddRef(&owner->IBackgroundCopyJob3_iface);
|
|
|
|
*file = This;
|
|
return S_OK;
|
|
}
|
|
|
|
static HRESULT error_from_http_response(DWORD code)
|
|
{
|
|
switch (code)
|
|
{
|
|
case 200: return S_OK;
|
|
case 400: return BG_E_HTTP_ERROR_400;
|
|
case 401: return BG_E_HTTP_ERROR_401;
|
|
case 404: return BG_E_HTTP_ERROR_404;
|
|
case 407: return BG_E_HTTP_ERROR_407;
|
|
case 414: return BG_E_HTTP_ERROR_414;
|
|
case 501: return BG_E_HTTP_ERROR_501;
|
|
case 503: return BG_E_HTTP_ERROR_503;
|
|
case 504: return BG_E_HTTP_ERROR_504;
|
|
case 505: return BG_E_HTTP_ERROR_505;
|
|
default:
|
|
FIXME("unhandled response code %u\n", code);
|
|
return S_OK;
|
|
}
|
|
}
|
|
|
|
static void CALLBACK progress_callback_http(HINTERNET handle, DWORD_PTR context, DWORD status,
|
|
LPVOID buf, DWORD buflen)
|
|
{
|
|
BackgroundCopyFileImpl *file = (BackgroundCopyFileImpl *)context;
|
|
BackgroundCopyJobImpl *job = file->owner;
|
|
|
|
TRACE("%p, %p, %x, %p, %u\n", handle, file, status, buf, buflen);
|
|
|
|
switch (status)
|
|
{
|
|
case WINHTTP_CALLBACK_STATUS_HEADERS_AVAILABLE:
|
|
{
|
|
DWORD code, len, size;
|
|
|
|
size = sizeof(code);
|
|
if (WinHttpQueryHeaders(handle, WINHTTP_QUERY_STATUS_CODE|WINHTTP_QUERY_FLAG_NUMBER,
|
|
NULL, &code, &size, NULL))
|
|
{
|
|
if ((job->error.code = error_from_http_response(code)))
|
|
{
|
|
EnterCriticalSection(&job->cs);
|
|
|
|
job->error.context = BG_ERROR_CONTEXT_REMOTE_FILE;
|
|
if (job->error.file) IBackgroundCopyFile2_Release(job->error.file);
|
|
job->error.file = &file->IBackgroundCopyFile2_iface;
|
|
IBackgroundCopyFile2_AddRef(job->error.file);
|
|
|
|
LeaveCriticalSection(&job->cs);
|
|
transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR);
|
|
}
|
|
else
|
|
{
|
|
EnterCriticalSection(&job->cs);
|
|
|
|
job->error.context = 0;
|
|
if (job->error.file)
|
|
{
|
|
IBackgroundCopyFile2_Release(job->error.file);
|
|
job->error.file = NULL;
|
|
}
|
|
|
|
LeaveCriticalSection(&job->cs);
|
|
}
|
|
}
|
|
size = sizeof(len);
|
|
if (WinHttpQueryHeaders(handle, WINHTTP_QUERY_CONTENT_LENGTH|WINHTTP_QUERY_FLAG_NUMBER,
|
|
NULL, &len, &size, NULL))
|
|
{
|
|
file->fileProgress.BytesTotal = len;
|
|
}
|
|
break;
|
|
}
|
|
case WINHTTP_CALLBACK_STATUS_READ_COMPLETE:
|
|
{
|
|
file->read_size = buflen;
|
|
break;
|
|
}
|
|
case WINHTTP_CALLBACK_STATUS_REQUEST_ERROR:
|
|
{
|
|
WINHTTP_ASYNC_RESULT *result = (WINHTTP_ASYNC_RESULT *)buf;
|
|
job->error.code = result->dwError;
|
|
transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR);
|
|
break;
|
|
}
|
|
default: break;
|
|
}
|
|
|
|
SetEvent(job->wait);
|
|
}
|
|
|
|
static DWORD wait_for_completion(BackgroundCopyJobImpl *job)
|
|
{
|
|
HANDLE handles[2] = {job->wait, job->cancel};
|
|
DWORD error = ERROR_SUCCESS;
|
|
|
|
switch (WaitForMultipleObjects(2, handles, FALSE, INFINITE))
|
|
{
|
|
case WAIT_OBJECT_0:
|
|
break;
|
|
|
|
case WAIT_OBJECT_0 + 1:
|
|
error = ERROR_CANCELLED;
|
|
transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_CANCELLED);
|
|
break;
|
|
|
|
default:
|
|
error = GetLastError();
|
|
transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR);
|
|
break;
|
|
}
|
|
|
|
return error;
|
|
}
|
|
|
|
static UINT target_from_index(UINT index)
|
|
{
|
|
switch (index)
|
|
{
|
|
case 0: return WINHTTP_AUTH_TARGET_SERVER;
|
|
case 1: return WINHTTP_AUTH_TARGET_PROXY;
|
|
default:
|
|
ERR("unhandled index %u\n", index);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static UINT scheme_from_index(UINT index)
|
|
{
|
|
switch (index)
|
|
{
|
|
case 0: return WINHTTP_AUTH_SCHEME_BASIC;
|
|
case 1: return WINHTTP_AUTH_SCHEME_NTLM;
|
|
case 2: return WINHTTP_AUTH_SCHEME_PASSPORT;
|
|
case 3: return WINHTTP_AUTH_SCHEME_DIGEST;
|
|
case 4: return WINHTTP_AUTH_SCHEME_NEGOTIATE;
|
|
default:
|
|
ERR("unhandled index %u\n", index);
|
|
break;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static BOOL set_request_credentials(HINTERNET req, BackgroundCopyJobImpl *job)
|
|
{
|
|
UINT i, j;
|
|
|
|
for (i = 0; i < BG_AUTH_TARGET_PROXY; i++)
|
|
{
|
|
UINT target = target_from_index(i);
|
|
for (j = 0; j < BG_AUTH_SCHEME_PASSPORT; j++)
|
|
{
|
|
UINT scheme = scheme_from_index(j);
|
|
const WCHAR *username = job->http_options.creds[i][j].Credentials.Basic.UserName;
|
|
const WCHAR *password = job->http_options.creds[i][j].Credentials.Basic.Password;
|
|
|
|
if (!username) continue;
|
|
if (!WinHttpSetCredentials(req, target, scheme, username, password, NULL)) return FALSE;
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
static BOOL transfer_file_http(BackgroundCopyFileImpl *file, URL_COMPONENTSW *uc,
|
|
const WCHAR *tmpfile)
|
|
{
|
|
BackgroundCopyJobImpl *job = file->owner;
|
|
HANDLE handle;
|
|
HINTERNET ses, con = NULL, req = NULL;
|
|
DWORD flags = (uc->nScheme == INTERNET_SCHEME_HTTPS) ? WINHTTP_FLAG_SECURE : 0;
|
|
char buf[4096];
|
|
BOOL ret = FALSE;
|
|
|
|
transitionJobState(job, BG_JOB_STATE_QUEUED, BG_JOB_STATE_CONNECTING);
|
|
|
|
if (!(ses = WinHttpOpen(NULL, 0, NULL, NULL, WINHTTP_FLAG_ASYNC))) return FALSE;
|
|
WinHttpSetStatusCallback(ses, progress_callback_http, WINHTTP_CALLBACK_FLAG_ALL_COMPLETIONS, 0);
|
|
if (!WinHttpSetOption(ses, WINHTTP_OPTION_CONTEXT_VALUE, &file, sizeof(file))) goto done;
|
|
|
|
if (!(con = WinHttpConnect(ses, uc->lpszHostName, uc->nPort, 0))) goto done;
|
|
if (!(req = WinHttpOpenRequest(con, NULL, uc->lpszUrlPath, NULL, NULL, NULL, flags))) goto done;
|
|
if (!set_request_credentials(req, job)) goto done;
|
|
|
|
if (!(WinHttpSendRequest(req, job->http_options.headers, ~0u, NULL, 0, 0, (DWORD_PTR)file))) goto done;
|
|
if (wait_for_completion(job) || job->error.code) goto done;
|
|
|
|
if (!(WinHttpReceiveResponse(req, NULL))) goto done;
|
|
if (wait_for_completion(job) || job->error.code) goto done;
|
|
|
|
transitionJobState(job, BG_JOB_STATE_CONNECTING, BG_JOB_STATE_TRANSFERRING);
|
|
|
|
handle = CreateFileW(tmpfile, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
|
|
if (handle == INVALID_HANDLE_VALUE) goto done;
|
|
|
|
for (;;)
|
|
{
|
|
file->read_size = 0;
|
|
if (!(ret = WinHttpReadData(req, buf, sizeof(buf), NULL))) break;
|
|
if (wait_for_completion(job) || job->error.code)
|
|
{
|
|
ret = FALSE;
|
|
break;
|
|
}
|
|
if (!file->read_size) break;
|
|
if (!(ret = WriteFile(handle, buf, file->read_size, NULL, NULL))) break;
|
|
|
|
EnterCriticalSection(&job->cs);
|
|
file->fileProgress.BytesTransferred += file->read_size;
|
|
job->jobProgress.BytesTransferred += file->read_size;
|
|
LeaveCriticalSection(&job->cs);
|
|
}
|
|
|
|
CloseHandle(handle);
|
|
|
|
done:
|
|
WinHttpCloseHandle(req);
|
|
WinHttpCloseHandle(con);
|
|
WinHttpCloseHandle(ses);
|
|
if (!ret) DeleteFileW(tmpfile);
|
|
|
|
SetEvent(job->done);
|
|
return ret;
|
|
}
|
|
|
|
static DWORD CALLBACK progress_callback_local(LARGE_INTEGER totalSize, LARGE_INTEGER totalTransferred,
|
|
LARGE_INTEGER streamSize, LARGE_INTEGER streamTransferred,
|
|
DWORD streamNum, DWORD reason, HANDLE srcFile,
|
|
HANDLE dstFile, LPVOID obj)
|
|
{
|
|
BackgroundCopyFileImpl *file = obj;
|
|
BackgroundCopyJobImpl *job = file->owner;
|
|
ULONG64 diff;
|
|
|
|
EnterCriticalSection(&job->cs);
|
|
diff = (file->fileProgress.BytesTotal == BG_SIZE_UNKNOWN
|
|
? totalTransferred.QuadPart
|
|
: totalTransferred.QuadPart - file->fileProgress.BytesTransferred);
|
|
file->fileProgress.BytesTotal = totalSize.QuadPart;
|
|
file->fileProgress.BytesTransferred = totalTransferred.QuadPart;
|
|
job->jobProgress.BytesTransferred += diff;
|
|
LeaveCriticalSection(&job->cs);
|
|
|
|
return (job->state == BG_JOB_STATE_TRANSFERRING
|
|
? PROGRESS_CONTINUE
|
|
: PROGRESS_CANCEL);
|
|
}
|
|
|
|
static BOOL transfer_file_local(BackgroundCopyFileImpl *file, const WCHAR *tmpname)
|
|
{
|
|
static const WCHAR fileW[] = {'f','i','l','e',':','/','/',0};
|
|
BackgroundCopyJobImpl *job = file->owner;
|
|
const WCHAR *ptr;
|
|
BOOL ret;
|
|
|
|
transitionJobState(job, BG_JOB_STATE_QUEUED, BG_JOB_STATE_TRANSFERRING);
|
|
|
|
if (strlenW(file->info.RemoteName) > 7 && !memicmpW(file->info.RemoteName, fileW, 7))
|
|
ptr = file->info.RemoteName + 7;
|
|
else
|
|
ptr = file->info.RemoteName;
|
|
|
|
if (!(ret = CopyFileExW(ptr, tmpname, progress_callback_local, file, NULL, 0)))
|
|
{
|
|
WARN("Local file copy failed: error %u\n", GetLastError());
|
|
transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_ERROR);
|
|
}
|
|
|
|
SetEvent(job->done);
|
|
return ret;
|
|
}
|
|
|
|
BOOL processFile(BackgroundCopyFileImpl *file, BackgroundCopyJobImpl *job)
|
|
{
|
|
static const WCHAR prefix[] = {'B','I','T', 0};
|
|
WCHAR tmpDir[MAX_PATH], tmpName[MAX_PATH];
|
|
WCHAR host[MAX_PATH], path[MAX_PATH];
|
|
URL_COMPONENTSW uc;
|
|
BOOL ret;
|
|
|
|
if (!GetTempPathW(MAX_PATH, tmpDir))
|
|
{
|
|
ERR("Couldn't create temp file name: %d\n", GetLastError());
|
|
/* Guessing on what state this should give us */
|
|
transitionJobState(job, BG_JOB_STATE_QUEUED, BG_JOB_STATE_TRANSIENT_ERROR);
|
|
return FALSE;
|
|
}
|
|
|
|
if (!GetTempFileNameW(tmpDir, prefix, 0, tmpName))
|
|
{
|
|
ERR("Couldn't create temp file: %d\n", GetLastError());
|
|
/* Guessing on what state this should give us */
|
|
transitionJobState(job, BG_JOB_STATE_QUEUED, BG_JOB_STATE_TRANSIENT_ERROR);
|
|
return FALSE;
|
|
}
|
|
|
|
EnterCriticalSection(&job->cs);
|
|
file->fileProgress.BytesTotal = BG_SIZE_UNKNOWN;
|
|
file->fileProgress.BytesTransferred = 0;
|
|
file->fileProgress.Completed = FALSE;
|
|
LeaveCriticalSection(&job->cs);
|
|
|
|
TRACE("Transferring: %s -> %s -> %s\n",
|
|
debugstr_w(file->info.RemoteName),
|
|
debugstr_w(tmpName),
|
|
debugstr_w(file->info.LocalName));
|
|
|
|
uc.dwStructSize = sizeof(uc);
|
|
uc.nScheme = 0;
|
|
uc.lpszScheme = NULL;
|
|
uc.dwSchemeLength = 0;
|
|
uc.lpszUserName = NULL;
|
|
uc.dwUserNameLength = 0;
|
|
uc.lpszPassword = NULL;
|
|
uc.dwPasswordLength = 0;
|
|
uc.lpszHostName = host;
|
|
uc.dwHostNameLength = sizeof(host)/sizeof(host[0]);
|
|
uc.nPort = 0;
|
|
uc.lpszUrlPath = path;
|
|
uc.dwUrlPathLength = sizeof(path)/sizeof(path[0]);
|
|
uc.lpszExtraInfo = NULL;
|
|
uc.dwExtraInfoLength = 0;
|
|
ret = WinHttpCrackUrl(file->info.RemoteName, 0, 0, &uc);
|
|
if (!ret)
|
|
{
|
|
TRACE("WinHttpCrackUrl failed, trying local file copy\n");
|
|
if (!transfer_file_local(file, tmpName)) return FALSE;
|
|
}
|
|
else if (!transfer_file_http(file, &uc, tmpName))
|
|
{
|
|
WARN("HTTP transfer failed\n");
|
|
return FALSE;
|
|
}
|
|
|
|
if (transitionJobState(job, BG_JOB_STATE_CONNECTING, BG_JOB_STATE_QUEUED) ||
|
|
transitionJobState(job, BG_JOB_STATE_TRANSFERRING, BG_JOB_STATE_QUEUED))
|
|
{
|
|
lstrcpyW(file->tempFileName, tmpName);
|
|
|
|
EnterCriticalSection(&job->cs);
|
|
file->fileProgress.Completed = TRUE;
|
|
job->jobProgress.FilesTransferred++;
|
|
LeaveCriticalSection(&job->cs);
|
|
|
|
return TRUE;
|
|
}
|
|
else
|
|
{
|
|
DeleteFileW(tmpName);
|
|
return FALSE;
|
|
}
|
|
}
|