#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 #include #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(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(user); assert(state->handle); return FileSize64(state->handle); } int UnicodeSetPosition(void *user, u_int64_t position) { Win32_State *state = static_cast(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(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(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(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(user); return state->endOfFile; } int UnicodeClose(void *user) { Win32_State *state = static_cast(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; }