diff options
author | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
---|---|---|
committer | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
commit | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/Plugins/Library/ml_pmp/indirectplaybackserver.cpp | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz |
Initial community commit
Diffstat (limited to 'Src/Plugins/Library/ml_pmp/indirectplaybackserver.cpp')
-rw-r--r-- | Src/Plugins/Library/ml_pmp/indirectplaybackserver.cpp | 313 |
1 files changed, 313 insertions, 0 deletions
diff --git a/Src/Plugins/Library/ml_pmp/indirectplaybackserver.cpp b/Src/Plugins/Library/ml_pmp/indirectplaybackserver.cpp new file mode 100644 index 00000000..13536436 --- /dev/null +++ b/Src/Plugins/Library/ml_pmp/indirectplaybackserver.cpp @@ -0,0 +1,313 @@ +#include "main.h" + +#include "api/service/waservicefactory.h" +#include "bfc/dispatch.h" + +#include "../Components/wac_network/wac_network_web_server_api.h" +#include "../Components/wac_network/wac_network_onconncb_api.h" +#include "../Components/wac_network/wac_network_connection_api.h" +#include "../Components/wac_network/wac_network_http_server_api.h" +#include "../Components/wac_network/wac_network_page_generator_api.h" + +#include "api__ml_pmp.h" + +#include "DeviceView.h" + +#include <wchar.h> + +#include "metadata_utils.h" + +#include "nu/AutoWide.h" +#include "nu/AutoChar.h" + +#include <strsafe.h> + +void url_decode(char *in, char *out, int maxlen) +{ + while (in && *in && maxlen>1) + { + if (*in == '+') + { + in++; + *out++=' '; + } + else if (*in == '%' && in[1] != '%' && in[1]) + { + int a=0; + int b=0; + for ( b = 0; b < 2; b ++) + { + int r=in[1+b]; + if (r>='0'&&r<='9') r-='0'; + else if (r>='a'&&r<='z') r-='a'-10; + else if (r>='A'&&r<='Z') r-='A'-10; + else break; + a*=16; + a+=r; + } + if (b < 2) *out++=*in++; + else { *out++=a; in += 3;} + } + else *out++=*in++; + maxlen--; + } + *out=0; +} + +static void callbackThunk(void * callBackContext, wchar_t * status) { +} + +typedef struct { + int status; + wchar_t * device; wchar_t * artist; wchar_t * album; wchar_t * title; + DeviceView * d; songid_t s; +} FindTrackStruct; + +static VOID CALLBACK APC_FindTrack(ULONG_PTR dwParam) { + FindTrackStruct * f = (FindTrackStruct *)dwParam; + for(int i=0; i<devices.GetSize(); i++) { + f->d = (DeviceView*)devices.Get(i); + wchar_t buf[2048] = {0}; int len = 2047; + f->d->dev->getPlaylistName(0,buf,128); + if(wcscmp(buf,f->device)) continue; + int l = f->d->dev->getPlaylistLength(0); + for(int j=0; j<l; j++) { + f->s = f->d->dev->getPlaylistTrack(0,j); + f->d->dev->getTrackArtist(f->s,buf,len); + if(lstrcmpi(buf,f->artist)) continue; + f->d->dev->getTrackAlbum(f->s,buf,len); + if(lstrcmpi(buf,f->album)) continue; + f->d->dev->getTrackTitle(f->s,buf,len); + if(lstrcmpi(buf,f->title)) continue; + f->status = 2; + return; + } + } + f->status = 1; +} + +bool findTrack(Device **dev, songid_t *song,wchar_t * device,wchar_t * artist,wchar_t * album,wchar_t * title) { + FindTrackStruct f = {0,device,artist,album,title,NULL,0}; + SynchronousProcedureCall(APC_FindTrack,(ULONG_PTR)&f); + *dev = f.d->dev; + *song = f.s; + return f.status == 2; +} + +bool copyFileFromDevice(Device * dev, songid_t song,wchar_t * fn) { + int k=0; + if(dev->copyToHardDriveSupported()) { + if(!dev->copyToHardDrive(song,fn,NULL,callbackThunk,&k)) + return true; + } + return false; +} + +class FilePageGenerator : public api_pagegenerator // public IPageGenerator +{ +public: + virtual ~FilePageGenerator(); + FilePageGenerator(wchar_t *fn, api_httpserv *serv); + size_t GetData(char *buf, int size); // return 0 when done + int is_error() { return !m_fp; } + +private: + FILE *m_fp; + int m_file_pos; + int m_file_len; + wchar_t *m_fn; +protected: + RECVS_DISPATCH; +}; + +#define CBCLASS FilePageGenerator +START_DISPATCH; +CB(API_PAGEGENERATOR_GETDATA, GetData); +END_DISPATCH; +#undef CBCLASS + +FilePageGenerator::FilePageGenerator(wchar_t *fn, api_httpserv *serv) +{ + m_fn = _wcsdup(fn); + m_file_pos=m_file_len=0; + m_fp=_wfopen(fn,L"rb"); + if (m_fp) + { + int resume_end=0; + int resume_pos=0; + char *range = serv->getheader("Range"); + if (range) + { + if (!_strnicmp(range,"bytes=",6)) + { + range+=6; + char *t=range; + while (t && *t && (*t < '0' || *t > '9')) t++; + while (t && *t && *t >= '0' && *t <= '9') + { + resume_pos*=10; + resume_pos+=*t-'0'; + t++; + } + if (*t != '-') resume_pos=0; + else if (t[1]) + { + resume_end=atoi(t+1); + } + } + } + + fseek(m_fp,0,SEEK_END); + m_file_len=ftell(m_fp); + char buf[512] = {0}; + StringCchPrintfA(buf, ARRAYSIZE(buf), "Content-Length: %d",m_file_len); + serv->set_reply_header(buf); + + int m_file_len_orig=m_file_len; + + if (resume_end && resume_end < m_file_len) m_file_len=resume_end; + if (resume_pos > 0 && resume_pos < m_file_len) m_file_pos = resume_pos; + fseek(m_fp,m_file_pos,SEEK_SET); + + StringCchPrintfA(buf, ARRAYSIZE(buf), "Content-Range: bytes=%d-%d/%d",resume_pos,resume_end,m_file_len_orig); + serv->set_reply_header(buf); + if (resume_pos != 0) serv->set_reply_string("HTTP/1.1 206 Partial Content"); + } +} + +FilePageGenerator::~FilePageGenerator() +{ + if (m_fp) fclose(m_fp); + if (m_fn) { _wunlink(m_fn); free(m_fn); } +} + +size_t FilePageGenerator::GetData(char *buf, int size) // return 0 when done +{ + if (!m_fp) return 0; + if (m_file_pos+size > m_file_len) size=m_file_len - m_file_pos; + int l=fread(buf,1,size,m_fp); + m_file_pos+=l; + return l; +} + +class onConnCB : public api_onconncb { +public: + api_pagegenerator* onConnection(api_httpserv *serv, int port) + { + api_connection *c = serv->get_con(); + if(c && c->GetRemoteAddress() != 0x0100007f) return 0; // if it's not from localhost, disregard. + serv->set_reply_header("Server:ml_pmp/1.0"); + if (!strcmp(serv->get_request_file(),"/")) + { + // find temporary filename + wchar_t fn[MAX_PATH]={0}; + wchar_t dir[MAX_PATH]={0}; + GetTempPath(MAX_PATH,dir); + GetTempFileName(dir,L"pmp",0,fn); + _wunlink(fn); + {wchar_t * ext = wcsrchr(fn,L'.'); if(ext) *ext=0;} + // decode the parameters needed to find the track from the URL + char device[128]={0}; + char artist[2048]={0}; + char album[2048]={0}; + char title[2048]={0}; + char * p; + p = serv->get_request_parm("d"); + if(p) url_decode(p,device,128); + p = serv->get_request_parm("a"); + if(p) url_decode(p,artist,2048); + p = serv->get_request_parm("l"); + if(p) url_decode(p,album,2048); + p = serv->get_request_parm("t"); + if(p) url_decode(p,title,2048); + char * sc = strrchr(device,';'); + char * ext = "mp3"; + if(sc) { *sc=0; ext = sc+2; } + // find the track based on this info + Device * dev; + songid_t song; + if(findTrack(&dev,&song,AutoWide(device,CP_UTF8),AutoWide(artist,CP_UTF8),AutoWide(album,CP_UTF8),AutoWide(title,CP_UTF8))) { + if(dev->copyToHardDriveSupported()) { + // track found, can be copied back. Lets reply to the user + serv->set_reply_string("HTTP/1.1 200 OK"); + char buf[150]=""; + StringCchPrintfA(buf, ARRAYSIZE(buf), "Content-Type:audio/%s",ext); + serv->set_reply_header(buf); + wchar_t title[128]={0}; + getTitle(dev,song,fn,title,128); + StringCchPrintfA(buf, ARRAYSIZE(buf), "icy-name:%s",(char*)AutoChar(title,CP_UTF8)); + serv->set_reply_header(buf); + serv->send_reply(); + // do the actual copy, and start streaming + if(copyFileFromDevice(dev,song,fn)) return new FilePageGenerator(fn,serv); + } + } + } + serv->set_reply_string("HTTP/1.1 404 NOT FOUND"); + serv->send_reply(); + return 0; // no data + } + void destroyConnection(api_pagegenerator *conn) + { + FilePageGenerator *realObject = static_cast<FilePageGenerator *>(conn); + delete realObject; + } +protected: + RECVS_DISPATCH; +}; + +#define CBCLASS onConnCB +START_DISPATCH; +CB(API_ONCONNCB_ONCONNECTION,onConnection); +VCB(API_ONCONNCB_DESTROYCONNECTION,destroyConnection); +END_DISPATCH; +#undef CBCLASS + +int serverPort = -1; + +static DWORD WINAPI ThreadFunc_Server(LPVOID lpParam) { + extern bool quitting; + if(quitting) return 0; + onConnCB *pOnConnCB; + int * killswitch = (int*)lpParam; + api_webserv *server=0; + waServiceFactory *sf = plugin.service->service_getServiceByGuid(webservGUID); + if (sf) server = (api_webserv *)sf->getInterface(); + if(!server) return NULL; + pOnConnCB = new onConnCB; + server->SetConnectionCallback(pOnConnCB); + serverPort = 54387; + while(server->addListenPort(serverPort,0x0100007f) && serverPort < 54397) serverPort++; + if(serverPort >= 54397) { serverPort=-1; return NULL; } + while (killswitch && *killswitch == 0) + { + server->run(); + server->run(); + Sleep(20); + } + server->removeListenPort(serverPort); + sf->releaseInterface(server); + serverPort=-1; + delete pOnConnCB; + return NULL; +} + +static int killswitch = 0; +static HANDLE serverThread = NULL; + +void startServer() { + if(serverPort == -1) { + killswitch = 0; + DWORD dwThreadId; + serverThread = CreateThread(NULL, 0, ThreadFunc_Server, (LPVOID)&killswitch, 0, &dwThreadId); + } +} + +void stopServer() { + if(serverThread) { + killswitch = 1; + WaitForSingleObject(serverThread,INFINITE); + CloseHandle(serverThread); + serverThread = NULL; + } +}
\ No newline at end of file |