winamp/Src/Plugins/Input/in_mp4/VirtualIO.cpp
2024-09-24 14:54:57 +02:00

717 lines
16 KiB
C++

#include "main.h"
#include "VirtualIO.h"
#include "api__in_mp4.h"
#include "api/service/waservicefactory.h"
#include "../../..\Components\wac_network\wac_network_http_receiver_api.h"
#include "../nu/AutoChar.h"
#include "../nu/ProgressTracker.h"
#include <assert.h>
#include <strsafe.h>
#define HTTP_BUFFER_SIZE 65536
// {C0A565DC-0CFE-405a-A27C-468B0C8A3A5C}
static const GUID internetConfigGroupGUID =
{
0xc0a565dc, 0xcfe, 0x405a, { 0xa2, 0x7c, 0x46, 0x8b, 0xc, 0x8a, 0x3a, 0x5c }
};
static void SetUserAgent(api_httpreceiver *http)
{
char agent[256] = {0};
StringCchPrintfA(agent, 256, "User-Agent: %S/%S", WASABI_API_APP->main_getAppName(), WASABI_API_APP->main_getVersionNumString());
http->addheader(agent);
}
static api_httpreceiver *SetupConnection(const char *url, uint64_t start_position, uint64_t end_position)
{
api_httpreceiver *http = 0;
waServiceFactory *sf = mod.service->service_getServiceByGuid(httpreceiverGUID);
if (sf) http = (api_httpreceiver *)sf->getInterface();
if (!http)
return http;
int use_proxy = 1;
bool proxy80 = AGAVE_API_CONFIG->GetBool(internetConfigGroupGUID, L"proxy80", false);
if (proxy80 && strstr(url, ":") && (!strstr(url, ":80/") && strstr(url, ":80") != (url + strlen(url) - 3)))
use_proxy = 0;
const wchar_t *proxy = use_proxy?AGAVE_API_CONFIG->GetString(internetConfigGroupGUID, L"proxy", 0):0;
http->open(API_DNS_AUTODNS, HTTP_BUFFER_SIZE, (proxy && proxy[0]) ? (const char *)AutoChar(proxy) : NULL);
if (start_position && start_position != (uint64_t)-1)
{
if (end_position == (uint64_t)-1)
{
char temp[128] = {0};
StringCchPrintfA(temp, 128, "Range: bytes=%I64u-", start_position);
http->addheader(temp);
}
else
{
char temp[128] = {0};
StringCchPrintfA(temp, 128, "Range: bytes=%I64u-%I64u", start_position, end_position);
http->addheader(temp);
}
}
SetUserAgent(http);
http->connect(url);
return http;
}
static DWORD CALLBACK ProgressiveThread(LPVOID param);
static __int64 Seek64(HANDLE hf, __int64 distance, DWORD MoveMethod)
{
LARGE_INTEGER li;
li.QuadPart = distance;
li.LowPart = SetFilePointer (hf, li.LowPart, &li.HighPart, MoveMethod);
if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR)
{
li.QuadPart = -1;
}
return li.QuadPart;
}
int bufferCount;
static void Buffering(int bufStatus, const wchar_t *displayString)
{
if (bufStatus < 0 || bufStatus > 100)
return;
char tempdata[75*2] = {0, };
int csa = mod.SAGetMode();
if (csa & 1)
{
for (int x = 0; x < bufStatus*75 / 100; x ++)
tempdata[x] = x * 16 / 75;
}
else if (csa&2)
{
int offs = (csa & 1) ? 75 : 0;
int x = 0;
while (x < bufStatus*75 / 100)
{
tempdata[offs + x++] = -6 + x * 14 / 75;
}
while (x < 75)
{
tempdata[offs + x++] = 0;
}
}
else if (csa == 4)
{
tempdata[0] = tempdata[1] = (bufStatus * 127 / 100);
}
if (csa) mod.SAAdd(tempdata, ++bufferCount, (csa == 3) ? 0x80000003 : csa);
/*
TODO
wchar_t temp[64] = {0};
StringCchPrintf(temp, 64, L"%s: %d%%",displayString, bufStatus);
SetStatus(temp);
*/
//SetVideoStatusText(temp); // TODO: find a way to set the old status back
// videoOutput->notifyBufferState(static_cast<int>(bufStatus*2.55f));
}
class ProgressiveReader
{
public:
ProgressiveReader(const char *url, HANDLE killswitch) : killswitch(killswitch)
{
thread_abort = CreateEvent(NULL, FALSE, FALSE, NULL);
download_thread = 0;
progressive_file_read = 0;
progressive_file_write = 0;
content_length=0;
current_position=0;
stream_disconnected=false;
connected=false;
end_of_file=false;
wchar_t temppath[MAX_PATH-14] = {0}; // MAX_PATH-14 'cause MSDN said so
GetTempPathW(MAX_PATH-14, temppath);
GetTempFileNameW(temppath, L"wdl", 0, filename);
this->url = _strdup(url);
http = SetupConnection(url, 0, (uint64_t)-1);
progressive_file_read = CreateFileW(filename, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, CREATE_ALWAYS, 0, 0);
progressive_file_write = CreateFileW(filename, GENERIC_READ|GENERIC_WRITE, FILE_SHARE_READ|FILE_SHARE_WRITE, 0, CREATE_ALWAYS, 0, 0);
download_thread = CreateThread(0, 0, ProgressiveThread, this, 0, 0);
while (!connected && !stream_disconnected && WaitForSingleObject(killswitch, 55) == WAIT_TIMEOUT)
{
// nop
}
Buffer();
}
~ProgressiveReader()
{
if (download_thread)
{
SetEvent(thread_abort);
WaitForSingleObject(download_thread, INFINITE);
CloseHandle(download_thread);
}
if (thread_abort)
{
CloseHandle(thread_abort);
}
CloseHandle(progressive_file_read);
CloseHandle(progressive_file_write);
DeleteFile(filename);
if (http)
{
waServiceFactory *sf = mod.service->service_getServiceByGuid(httpreceiverGUID);
if (sf) http = (api_httpreceiver *)sf->releaseInterface(http);
http=0;
}
}
void Buffer()
{
bufferCount=0;
for (int i=0;i<101;i++)
{
Buffering(i, L"Buffering: ");
WaitForSingleObject(killswitch, 55);
}
}
void OnFinish()
{
stream_disconnected=true;
}
bool WaitForPosition(uint64_t position, uint64_t size)
{
do
{
bool valid = progress_tracker.Valid(position, position+size);
if (valid)
return true;
else
{
if (position < current_position)
{
Reconnect(position, position+size);
}
else
{
Buffer();
}
}
} while (WaitForSingleObject(killswitch, 0) == WAIT_TIMEOUT);
return false;
}
size_t Read(void *buffer, size_t size)
{
if (WaitForPosition(current_position, (uint64_t)size) == false)
return 0;
DWORD bytes_read=0;
ReadFile(progressive_file_read, buffer, size, &bytes_read, NULL);
current_position += bytes_read;
return bytes_read;
}
uint64_t GetFileLength()
{
return content_length;
}
void Reconnect(uint64_t position, uint64_t end)
{
SetEvent(thread_abort);
WaitForSingleObject(download_thread, INFINITE);
ResetEvent(thread_abort);
uint64_t new_start, new_end;
progress_tracker.Seek(position, end, &new_start, &new_end);
CloseHandle(download_thread);
stream_disconnected=false;
connected=false;
if (http)
{
waServiceFactory *sf = mod.service->service_getServiceByGuid(httpreceiverGUID);
if (sf) http = (api_httpreceiver *)sf->releaseInterface(http);
http=0;
}
http = SetupConnection(url, new_start, new_end);
Seek64(progressive_file_write, new_start, SEEK_SET);
download_thread = CreateThread(0, 0, ProgressiveThread, this, 0, 0);
while (!connected && !stream_disconnected && WaitForSingleObject(killswitch, 55) == WAIT_TIMEOUT)
{
// nop
}
Buffer();
}
int SetPosition(uint64_t position)
{
if (position == content_length)
{
end_of_file=true;
}
else
{
if (!progress_tracker.Valid(position, position))
{
Reconnect(position, (uint64_t)-1);
}
current_position = Seek64(progressive_file_read, position, SEEK_SET);
end_of_file=false;
}
return 0;
}
int GetPosition(uint64_t *position)
{
if (end_of_file)
*position = content_length;
else
*position = current_position;
return 0;
}
int EndOfFile()
{
return !!stream_disconnected;
}
int Close()
{
SetEvent(thread_abort);
while (!stream_disconnected && WaitForSingleObject(killswitch, 55) == WAIT_TIMEOUT)
{
// nop
}
return 0;
}
/* API used by download thread */
void Write(const void *data, size_t data_len)
{
DWORD bytes_written = 0;
WriteFile(progressive_file_write, data, data_len, &bytes_written, 0);
progress_tracker.Write(data_len);
}
int Wait(int milliseconds)
{
HANDLE handles[] = {killswitch, thread_abort};
int ret = WaitForMultipleObjects(2, handles, FALSE, milliseconds);
if (ret == WAIT_OBJECT_0+1)
return 1;
else if (ret == WAIT_TIMEOUT)
return 0;
else
return -1;
}
int DoRead(void *buffer, size_t bufferlen)
{
int ret = http->run();
int bytes_received;
do
{
ret = http->run();
bytes_received= http->get_bytes(buffer, bufferlen);
if (bytes_received)
Write(buffer, bytes_received);
} while (bytes_received);
return ret;
}
int Connect()
{
do
{
int ret = http->run();
if (ret == -1) // connection failed
return ret;
// ---- check our reply code ----
int replycode = http->getreplycode();
switch (replycode)
{
case 0:
case 100:
break;
case 200:
case 206:
{
const char *content_length_header = http->getheader("Content-Length");
if (content_length_header)
{
uint64_t new_content_length = _strtoui64(content_length_header, 0, 10);
//InterlockedExchange64((volatile LONGLONG *)&content_length, new_content_length);
content_length = new_content_length; // TODO interlock on win32
}
connected=true;
return 0;
}
default:
return -1;
}
}
while (Wait(55) == 0);
return 0;
}
private:
uint64_t current_position;
volatile uint64_t content_length;
bool end_of_file;
bool stream_disconnected;
bool connected;
char *url;
wchar_t filename[MAX_PATH];
HANDLE progressive_file_read, progressive_file_write;
ProgressTracker progress_tracker;
HANDLE killswitch;
HANDLE download_thread;
api_httpreceiver *http;
HANDLE thread_abort;
};
static DWORD CALLBACK ProgressiveThread(LPVOID param)
{
ProgressiveReader *reader = (ProgressiveReader *)param;
if (reader->Connect() == 0)
{
int ret = 0;
while (ret == 0)
{
ret=reader->Wait(10);
if (ret >= 0)
{
char buffer[HTTP_BUFFER_SIZE] = {0};
reader->DoRead(buffer, sizeof(buffer));
}
}
}
reader->OnFinish();
return 0;
}
u_int64_t HTTPGetFileLength(void *user)
{
ProgressiveReader *reader = (ProgressiveReader *)user;
return reader->GetFileLength();
}
int HTTPSetPosition(void *user, u_int64_t position)
{
ProgressiveReader *reader = (ProgressiveReader *)user;
return reader->SetPosition(position);
}
int HTTPGetPosition(void *user, u_int64_t *position)
{
ProgressiveReader *reader = (ProgressiveReader *)user;
return reader->GetPosition(position);
}
size_t HTTPRead(void *user, void *buffer, size_t size)
{
ProgressiveReader *reader = (ProgressiveReader *)user;
return reader->Read(buffer, size);
}
size_t HTTPWrite(void *user, void *buffer, size_t size)
{
return 1;
}
int HTTPEndOfFile(void *user)
{
ProgressiveReader *reader = (ProgressiveReader *)user;
return reader->EndOfFile();
}
int HTTPClose(void *user)
{
ProgressiveReader *reader = (ProgressiveReader *)user;
return reader->Close();
}
Virtual_IO HTTPIO =
{
HTTPGetFileLength,
HTTPSetPosition,
HTTPGetPosition,
HTTPRead,
HTTPWrite,
HTTPEndOfFile,
HTTPClose,
};
void *CreateReader(const wchar_t *url, HANDLE killswitch)
{
if ( WAC_API_DOWNLOADMANAGER )
{
return new ProgressiveReader(AutoChar(url), killswitch);
}
else
return 0;
}
void DestroyReader(void *user)
{
ProgressiveReader *reader = (ProgressiveReader *)user;
delete reader;
}
void StopReader(void *user)
{
ProgressiveReader *reader = (ProgressiveReader *)user;
reader->Close();
}
/* ----------------------------------- */
struct Win32_State
{
Win32_State()
{
memset(buffer, 0, sizeof(buffer));
handle=0;
endOfFile=false;
position.QuadPart = 0;
event = CreateEvent(NULL, TRUE, TRUE, NULL);
read_offset=0;
io_active=false;
}
~Win32_State()
{
if (handle && handle != INVALID_HANDLE_VALUE)
CancelIo(handle);
CloseHandle(event);
}
// void *userData;
HANDLE handle;
bool endOfFile;
LARGE_INTEGER position;
HANDLE event;
OVERLAPPED overlapped;
DWORD read_offset;
bool io_active;
char buffer[16384];
};
static __int64 FileSize64(HANDLE file)
{
LARGE_INTEGER position;
position.QuadPart=0;
position.LowPart = GetFileSize(file, (LPDWORD)&position.HighPart);
if (position.LowPart == INVALID_FILE_SIZE && GetLastError() != NO_ERROR)
return INVALID_FILE_SIZE;
else
return position.QuadPart;
}
u_int64_t UnicodeGetFileLength(void *user)
{
Win32_State *state = static_cast<Win32_State *>(user);
assert(state->handle);
return FileSize64(state->handle);
}
int UnicodeSetPosition(void *user, u_int64_t position)
{
Win32_State *state = static_cast<Win32_State *>(user);
assert(state->handle);
__int64 diff = position - state->position.QuadPart;
if ((diff+state->read_offset) >= sizeof(state->buffer)
|| (diff+state->read_offset) < 0)
{
CancelIo(state->handle);
state->io_active = 0;
state->read_offset = 0;
}
else if (diff)
state->read_offset += (DWORD)diff;
state->position.QuadPart = position;
state->endOfFile = false;
return 0;
}
int UnicodeGetPosition(void *user, u_int64_t *position)
{
Win32_State *state = static_cast<Win32_State *>(user);
assert(state->handle);
*position = state->position.QuadPart;
return 0;
}
static void DoRead(Win32_State *state)
{
WaitForSingleObject(state->event, INFINITE);
state->overlapped.hEvent = state->event;
state->overlapped.Offset = state->position.LowPart;
state->overlapped.OffsetHigh = state->position.HighPart;
state->read_offset = 0;
ResetEvent(state->event);
ReadFile(state->handle, state->buffer, sizeof(state->buffer), NULL, &state->overlapped);
//int error = GetLastError();//ERROR_IO_PENDING = 997
state->io_active=true;
}
size_t UnicodeRead(void *user, void *buffer, size_t size)
{
Win32_State *state = static_cast<Win32_State *>(user);
assert(state->handle);
size_t totalRead=0;
HANDLE file = state->handle;
if (!state->io_active)
{
DoRead(state);
}
if (state->read_offset == sizeof(state->buffer))
{
DoRead(state);
}
while (size > (sizeof(state->buffer) - state->read_offset))
{
DWORD bytesRead=0;
BOOL res = GetOverlappedResult(file, &state->overlapped, &bytesRead, TRUE);
if ((res && bytesRead != sizeof(state->buffer))
|| (!res && GetLastError() == ERROR_HANDLE_EOF))
{
state->endOfFile = true;
}
if (bytesRead > state->read_offset)
{
size_t bytesToCopy = bytesRead-state->read_offset;
memcpy(buffer, state->buffer + state->read_offset, bytesToCopy);
buffer=(uint8_t *)buffer + bytesToCopy;
totalRead+=bytesToCopy;
size-=bytesToCopy;
if (state->endOfFile)
return totalRead;
state->position.QuadPart += bytesToCopy;
DoRead(state);
}
else
break;
}
while (1)
{
DWORD bytesRead=0;
BOOL res = GetOverlappedResult(file, &state->overlapped, &bytesRead, FALSE);
if ((res && bytesRead != sizeof(state->buffer))
|| (!res && GetLastError() == ERROR_HANDLE_EOF))
{
state->endOfFile = true;
}
if (bytesRead >= (size + state->read_offset))
{
memcpy(buffer, state->buffer + state->read_offset, size);
state->read_offset += size;
totalRead+=size;
state->position.QuadPart += size;
break;
}
if (state->endOfFile)
break;
WaitForSingleObject(state->event, 10); // wait 10 milliseconds or when buffer is done, whichever is faster
}
return totalRead;
}
size_t UnicodeWrite(void *user, void *buffer, size_t size)
{
Win32_State *state = static_cast<Win32_State *>(user);
DWORD written = 0;
assert(state->handle);
WriteFile(state->handle, buffer, size, &written, NULL);
return 0;
}
int UnicodeEndOfFile(void *user)
{
Win32_State *state = static_cast<Win32_State *>(user);
return state->endOfFile;
}
int UnicodeClose(void *user)
{
Win32_State *state = static_cast<Win32_State *>(user);
if (state->handle)
CloseHandle(state->handle);
state->handle=0;
return 0;
}
Virtual_IO UnicodeIO =
{
UnicodeGetFileLength,
UnicodeSetPosition,
UnicodeGetPosition,
UnicodeRead,
UnicodeWrite,
UnicodeEndOfFile,
UnicodeClose,
};
void *CreateUnicodeReader(const wchar_t *filename)
{
HANDLE fileHandle = CreateFileW(filename, GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, 0);
if (fileHandle == INVALID_HANDLE_VALUE)
return 0;
Win32_State *state = new Win32_State;
if (!state)
{
CloseHandle(fileHandle);
return 0;
}
state->endOfFile = false;
state->handle = fileHandle;
return state;
}
void DestroyUnicodeReader(void *reader)
{
if (reader) // need to check because of the cast
delete (Win32_State *)reader;
}