winamp/Src/Plugins/Portable/pmp_wifi/device.cpp
2024-09-24 14:54:57 +02:00

797 lines
21 KiB
C++

#include "main.h"
#include "device.h"
#include "XMLString.h"
#include "api.h"
#include "../xml/obj_xml.h"
#include "../xml/ifc_xmlreaderparams.h"
#include <api/service/waServiceFactory.h>
#include "SongListDownloader.h"
#include "SongDownloader.h"
#include "RenameDownloader.h"
#include "resource.h"
#include "PlaylistSync.h"
#include "nu/AutoWide.h"
#include "images.h"
#include <mmsystem.h> // for mmioFOURCC
#include <strsafe.h>
#include <shlwapi.h>
TemplateDevice::TemplateDevice(WifiDevice *device, const char *root_url, DeviceInfo *in_device_info, TrackList *track_list, PlaylistsList *playlists_list)
: url(strdup(root_url))
{
DeviceInfo_Copy(&device_info, in_device_info);
//tracks.own(*track_list);
for (auto track : tracks)
{
delete track;
}
tracks.clear();
tracks.assign(track_list->begin(), track_list->end());
track_list->clear();
//playlists.own(*playlists_list);
for (auto playlist : playlists)
{
delete playlist;
}
playlists.clear();
playlists.assign(playlists_list->begin(), playlists_list->end());
playlists_list->clear();
transcoder=0;
transferQueueLength=0;
transcoder = (Transcoder*)SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(WPARAM)this,PMP_IPC_GET_TRANSCODER);
if(transcoder)
{
transcoder->AddAcceptableFormat(L"m4a");
transcoder->AddAcceptableFormat(L"mp3");
transcoder->AddAcceptableFormat(L"wav");
transcoder->AddAcceptableFormat(L"m4v");
transcoder->AddAcceptableFormat(L"mp4");
transcoder->AddAcceptableFormat(L"avi");
transcoder->AddAcceptableFormat(L"3gp");
transcoder->AddAcceptableFormat(L"mid");
transcoder->AddAcceptableFormat(L"ogg");
}
}
TemplateDevice::~TemplateDevice()
{
free(url);
//tracks.deleteAll();
for (auto track : tracks)
{
delete track;
}
tracks.clear();
//playlists.deleteAll();
for (auto playlist : playlists)
{
delete playlist;
}
playlists.clear();
if (transcoder)
SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(WPARAM)transcoder,PMP_IPC_RELEASE_TRANSCODER);
transcoder=0;
}
__int64 TemplateDevice::getDeviceCapacityAvailable() // in bytes
{
return device_info.total_space - device_info.used_space;
}
__int64 TemplateDevice::getDeviceCapacityTotal()
{
return device_info.total_space;
}
void TemplateDevice::Eject()
{
SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICEDISCONNECTED);
}
void TemplateDevice::Close()
{
SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICEDISCONNECTED);
}
void TemplateDevice::CloseAsync()
{
PostMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICEDISCONNECTED);
}
int PostFile(const char *url, const wchar_t *filename, const itemRecordW *track, obj_xml *parser, int *killswitch,
void (*callback)(void *callbackContext, wchar_t *status), void *context, char *new_item_id, size_t new_item_id_len);
int PostAlbumArt(const char *url, const itemRecordW *track, obj_xml *parser, int *killswitch, void (*callback)(void *callbackContext, wchar_t *status), void *context);
static int64_t FileSize64(const wchar_t * filename)
{
WIN32_FIND_DATA f={0};
HANDLE h = FindFirstFileW(filename,&f);
if(h == INVALID_HANDLE_VALUE) return -1;
FindClose(h);
ULARGE_INTEGER i;
i.HighPart = f.nFileSizeHigh;
i.LowPart = f.nFileSizeLow;
return i.QuadPart;
}
// return 0 for success, -1 for failed or cancelled
int TemplateDevice::transferTrackToDevice(const itemRecordW *track, // the track to transfer
void * callbackContext, //pass this to the callback
void (*callback)(void *callbackContext, wchar_t *status), // call this every so often so the GUI can be updated. Including when finished!
songid_t * songid, // fill in the songid when you are finished
int * killswitch // if this gets set to anything other than zero, the transfer has been cancelled by the user
)
{
wchar_t newfile[MAX_PATH] = {0};
wchar_t *filename = track->filename;
bool delete_file = false;
if(transcoder && transcoder->ShouldTranscode(track->filename))
{
wchar_t ext[10] = {0};
int r = transcoder->CanTranscode(track->filename, ext, track->length);
if(r != 0 && r != -1)
{
transcoder->GetTempFilePath(ext,newfile);
if(transcoder->TranscodeFile(track->filename,newfile,killswitch,callback,callbackContext)) return -1;
filename = newfile;
delete_file=true;
}
}
char new_item_id[512] = {0};
char upload_url[555] = {0};
StringCbPrintfA(upload_url, sizeof(upload_url), "%s/upload", url);
if (PostFile(upload_url, filename, track, 0, killswitch, callback, callbackContext, new_item_id, 512) == 0 && new_item_id[0])
{
StringCbPrintfA(upload_url, sizeof(upload_url), "%s/albumart/%s", url, new_item_id);
PostAlbumArt(upload_url, track, 0, killswitch, callback, callbackContext);
callback(callbackContext, WASABI_API_LNGSTRINGW(IDS_COMPLETED));
WifiTrack *new_track = new WifiTrack(new_item_id, track, filename);
*songid = (songid_t)new_track;
device_info.used_space += FileSize64(filename); // TODO: count album art also. or re-query for device info
if (delete_file)
DeleteFile(filename);
return 0;
}
else
{
callback(callbackContext, L"Failed");
if (delete_file)
DeleteFile(filename);
return -1;
}
}
int TemplateDevice::trackAddedToTransferQueue(const itemRecordW *track)
{
// return 0 to accept, -1 for "not enough space", -2 for "incorrect format"
__int64 l;
if(transcoder && transcoder->ShouldTranscode(track->filename))
{
int k = transcoder->CanTranscode(track->filename, 0, track->length);
if(k == -1) return -2;
if(k == 0) l = (__int64)FileSize64(track->filename);
else l = (__int64)k;
}
else
{
l = FileSize64(track->filename);
}
int64_t avail = getDeviceCapacityAvailable();
int64_t cmp = transferQueueLength;
cmp += l;
cmp += 3000000LL;
if(cmp > avail)
return -1;
else
{
transferQueueLength += l;
return 0;
}
}
void TemplateDevice::trackRemovedFromTransferQueue(const itemRecordW *track)
{
int64_t l = FileSize64(track->filename);
if(transcoder && transcoder->ShouldTranscode(track->filename))
{
int k = transcoder->CanTranscode(track->filename, 0, track->length);
if(k != -1 && k != 0) l = (__int64)k;
}
transferQueueLength -= l;
}
// return the amount of space that will be taken up on the device by the track (once it has been tranferred)
// or 0 for incompatable. This is usually the filesize, unless you are transcoding. An estimate is acceptable.
__int64 TemplateDevice::getTrackSizeOnDevice(const itemRecordW *track)
{
if(transcoder && transcoder->ShouldTranscode(track->filename))
{
int k = transcoder->CanTranscode(track->filename, 0, track->length);
if(k != -1 && k != 0) return k;
}
return track->filesize;
}
int HTTP_Delete(const char *url);
void TemplateDevice::deleteTrack(songid_t songid)
{
// physically remove from device. Be sure to remove it from all the playlists!
WifiTrack *track = (WifiTrack *)songid;
char delete_url[1024] = {0};
StringCbPrintfA(delete_url, sizeof(delete_url), "%s/file/%S", url, track->id);
HTTP_Delete(delete_url);
again1:
for (WifiPlaylist::TrackList::iterator itr2=tracks.begin(); itr2 != tracks.end(); itr2++)
{
WifiTrack *trackitr = *itr2;
if (!wcscmp(trackitr->id, track->id))
{
tracks.erase(itr2);
if (trackitr != track)
delete trackitr;
goto again1; // iterator was invalidated
}
}
for (PlaylistsList::iterator itr=playlists.begin();itr!=playlists.end();itr++)
{
WifiPlaylist *playlist = *itr;
again2:
for (WifiPlaylist::TrackList::iterator itr2=playlist->tracks.begin(); itr2 != playlist->tracks.end(); itr2++)
{
WifiTrack *trackitr = *itr2;
if (!wcscmp(trackitr->id, track->id))
{
playlist->tracks.erase(itr2);
if (trackitr != track)
delete trackitr;
goto again2; // iterator was invalidated
}
}
}
delete track;
}
void TemplateDevice::commitChanges()
{
// optional. Will be called at a good time to save changes
}
int TemplateDevice::getPlaylistCount()
{
// always at least 1. playlistnumber 0 is the Master Playlist containing all tracks.
return 1 + (int)playlists.size();
}
// PlaylistName(0) should return the name of the device.
void TemplateDevice::getPlaylistName(int playlistnumber, wchar_t *buf, int len)
{
if (playlistnumber == 0)
{
StringCchCopy(buf, len, device_info.name);
}
else
{
WifiPlaylist *playlist = playlists[playlistnumber-1];
StringCchCopy(buf, len, playlist->name);
}
}
int TemplateDevice::getPlaylistLength(int playlistnumber)
{
if (playlistnumber == 0)
{
size_t size = tracks.size();
return (int)size;
}
else
{
WifiPlaylist *playlist = playlists[playlistnumber-1];
size_t size = playlist->tracks.size();
return (int)size;
}
}
songid_t TemplateDevice::getPlaylistTrack(int playlistnumber,int songnum)
{
if (playlistnumber == 0)
{
WifiTrack *track = tracks[songnum];
return (songid_t)track;
}
else
{
WifiPlaylist *playlist = playlists[playlistnumber-1];
WifiTrack *track = playlist->tracks[songnum];
return (songid_t)track;
}
}
void TemplateDevice::setPlaylistName(int playlistnumber, const wchar_t *buf)
{
if (playlistnumber == 0) // playlist 0 is the device itself
{
RenameDevice(url, buf);
StringCbCopy(device_info.name, sizeof(device_info.name), buf);
}
else
{
WifiPlaylist *playlist = playlists[playlistnumber-1];
playlist->SetName(buf);
Sync_RenamePlaylist(url, playlist->id, buf);
}
}
void TemplateDevice::playlistSwapItems(int playlistnumber, int posA, int posB)
{
// swap the songs at position posA and posB
// TODO: implement
}
void TemplateDevice::sortPlaylist(int playlistnumber, int sortBy)
{
// TODO: implement
}
void TemplateDevice::addTrackToPlaylist(int playlistnumber, songid_t songid)
{
// adds songid to the end of the playlist
WifiTrack *track = (WifiTrack *)songid;
if (playlistnumber == 0)
{
tracks.push_back(track);
}
else
{
playlists[playlistnumber - 1]->tracks.push_back(new WifiTrack(*track));
Sync_AddToPlaylist(url, playlists[playlistnumber-1]->id, track->id);
}
}
void TemplateDevice::removeTrackFromPlaylist(int playlistnumber, int songnum)
{
//where songnum is the position of the track in the playlist
if (playlistnumber == 0)
{
tracks.erase(tracks.begin() + songnum);
}
else
{
WifiPlaylist *playlist = playlists[playlistnumber-1];
WifiTrack *track = playlist->tracks[songnum];
Sync_RemoveFromPlaylist(url, playlist->id, track->id);
}
}
void TemplateDevice::deletePlaylist(int playlistnumber)
{
if (playlistnumber == 0)
{
}
else
{
WifiPlaylist *playlist = playlists[playlistnumber-1];
Sync_DeletePlaylist(url, playlist->id);
playlists.erase(playlists.begin() + playlistnumber-1);
}
}
int TemplateDevice::newPlaylist(const wchar_t *name)
{
// create empty playlist, returns playlistnumber. -1 for failed.
WifiPlaylist *new_playlist = Sync_NewPlaylist(url, name);
if (new_playlist)
{
playlists.push_back(new_playlist);
return (int)playlists.size();
}
return -1;
}
void TemplateDevice::getTrackArtist(songid_t songid, wchar_t *buf, int len)
{
WifiTrack *track = (WifiTrack *)songid;
StringCchCopy(buf, len, track->artist);
}
void TemplateDevice::getTrackAlbum(songid_t songid, wchar_t *buf, int len)
{
WifiTrack *track = (WifiTrack *)songid;
StringCchCopy(buf, len, track->album);
}
void TemplateDevice::getTrackTitle(songid_t songid, wchar_t *buf, int len)
{
WifiTrack *track = (WifiTrack *)songid;
StringCchCopy(buf, len, track->title);
}
int TemplateDevice::getTrackTrackNum(songid_t songid)
{
WifiTrack *track = (WifiTrack *)songid;
return track->track;
}
int TemplateDevice::getTrackDiscNum(songid_t songid)
{
// TODO: implement
return 0;
}
void TemplateDevice::getTrackGenre(songid_t songid, wchar_t * buf, int len)
{
buf[0]=0;
}
int TemplateDevice::getTrackYear(songid_t songid)
{
WifiTrack *track = (WifiTrack *)songid;
return track->year;
}
__int64 TemplateDevice::getTrackSize(songid_t songid)
{
WifiTrack *track = (WifiTrack *)songid;
return track->size;
}
int TemplateDevice::getTrackLength(songid_t songid)
{
WifiTrack *track = (WifiTrack *)songid;
return track->duration;
}
int TemplateDevice::getTrackBitrate(songid_t songid)
{
return 128;
}
int TemplateDevice::getTrackPlayCount(songid_t songid)
{
return 0;
}
int TemplateDevice::getTrackRating(songid_t songid)
{
return 0;
}
__time64_t TemplateDevice::getTrackLastPlayed(songid_t songid)
{
return 0;
}
__time64_t TemplateDevice::getTrackLastUpdated(songid_t songid)
{
WifiTrack *track = (WifiTrack *)songid;
return track->last_updated;
}
void TemplateDevice::getTrackAlbumArtist(songid_t songid, wchar_t *buf, int len)
{
buf[0]=0;
}
void TemplateDevice::getTrackPublisher(songid_t songid, wchar_t *buf, int len)
{
buf[0]=0;
}
void TemplateDevice::getTrackComposer(songid_t songid, wchar_t *buf, int len)
{
WifiTrack *track = (WifiTrack *)songid;
StringCchCopy(buf, len, track->composer);
}
int TemplateDevice::getTrackType(songid_t songid)
{
return 0;
}
void TemplateDevice::getTrackExtraInfo(songid_t songid, const wchar_t *field, wchar_t *buf, int len)
{
// TODO: implement
//optional
}
// feel free to ignore any you don't support
void TemplateDevice::setTrackArtist(songid_t songid, const wchar_t *value)
{
// TODO: implement
}
void TemplateDevice::setTrackAlbum(songid_t songid, const wchar_t *value)
{
// TODO: implement
}
void TemplateDevice::setTrackTitle(songid_t songid, const wchar_t *value)
{
// TODO: implement
}
void TemplateDevice::setTrackTrackNum(songid_t songid, int value)
{
// TODO: implement
}
void TemplateDevice::setTrackDiscNum(songid_t songid, int value)
{
// TODO: implement
}
void TemplateDevice::setTrackGenre(songid_t songid, const wchar_t *value)
{
// TODO: implement
}
void TemplateDevice::setTrackYear(songid_t songid, int year)
{
// TODO: implement
}
void TemplateDevice::setTrackPlayCount(songid_t songid, int value)
{
// TODO: implement
}
void TemplateDevice::setTrackRating(songid_t songid, int value)
{
// TODO: implement
}
void TemplateDevice::setTrackLastPlayed(songid_t songid, __time64_t value)
{
// TODO: implement
} // in unix time format
void TemplateDevice::setTrackLastUpdated(songid_t songid, __time64_t value)
{
// TODO: implement
} // in unix time format
void TemplateDevice::setTrackAlbumArtist(songid_t songid, const wchar_t *value)
{
// TODO: implement
}
void TemplateDevice::setTrackPublisher(songid_t songid, const wchar_t *value)
{
// TODO: implement
}
void TemplateDevice::setTrackComposer(songid_t songid, const wchar_t *value)
{
// TODO: implement
}
void TemplateDevice::setTrackExtraInfo(songid_t songid, const wchar_t *field, const wchar_t *value)
{
// TODO: implement
} //optional
bool TemplateDevice::playTracks(songid_t * songidList, int listLength, int startPlaybackAt, bool enqueue)
{
if(!enqueue) //clear playlist
{
SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_DELETE);
}
for(int i=0; i<listLength; i++)
{
WifiTrack*curSong = (WifiTrack *)songidList[i];
if (curSong)
{
wchar_t fn[1024] = {0};
if (curSong->mime_type && !_wcsicmp(curSong->mime_type, L"audio/mp4"))
StringCbPrintf(fn, sizeof(fn), L"%S/file/%s?=.m4a", url, curSong->id);
else if (curSong->mime_type && !_wcsicmp(curSong->mime_type, L"audio/x-ms-wma"))
StringCbPrintf(fn, sizeof(fn), L"%S/file/%s?=.wma", url, curSong->id);
else if (curSong->mime_type && (!_wcsicmp(curSong->mime_type, L"application/ogg") || !_wcsicmp(curSong->mime_type, L"audio/ogg")))
StringCbPrintf(fn, sizeof(fn), L"%S/file/%s?=.ogg", url, curSong->id);
else
StringCbPrintf(fn, sizeof(fn), L"%S/file/%s", url, curSong->id);
enqueueFileWithMetaStructW s={0};
s.filename = fn;
s.title = _wcsdup(curSong->title);
s.ext = NULL;
s.length = curSong->duration/1000;
SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW);
}
else
{
//char titleStr[32];
//MessageBoxA(plugin.hwndWinampParent,WASABI_API_LNGSTRING(IDS_CANNOT_OPEN_FILE),
// WASABI_API_LNGSTRING_BUF(IDS_ERROR,titleStr,32),0);
}
}
if(!enqueue)
{
//play item startPlaybackAt
SendMessage(plugin.hwndWinampParent,WM_WA_IPC,startPlaybackAt,IPC_SETPLAYLISTPOS);
SendMessage(plugin.hwndWinampParent,WM_COMMAND,40047,0); //stop
SendMessage(plugin.hwndWinampParent,WM_COMMAND,40045,0); //play
}
return true;
}
static const intptr_t encoder_blacklist[] =
{
mmioFOURCC('W','M','A',' '),
mmioFOURCC('A','A','C','H'),
mmioFOURCC('A','A','C','P'),
mmioFOURCC('A','A','C','r'),
mmioFOURCC('F','L','A','C'),
mmioFOURCC('M','P','2',' '),
mmioFOURCC('A','D','T','S'),
};
intptr_t TemplateDevice::extraActions(intptr_t param1, intptr_t param2, intptr_t param3,intptr_t param4)
{
switch(param1)
{
case DEVICE_SET_ICON: // icons
{
MLTREEIMAGE * i = (MLTREEIMAGE*)param2;
const ModelInfo *modelInfo;
i->hinst = plugin.hDllInstance;
modelInfo = device_info.modelInfo;
if (NULL == modelInfo || NULL == modelInfo->smallIcon)
{
modelInfo = GetDefaultModelInfo();
if (NULL == modelInfo)
break;
}
i->resourceId = (int)(intptr_t)modelInfo->smallIcon;
}
break;
case DEVICE_CAN_RENAME_DEVICE:
return 1;
case DEVICE_GET_ICON:
ModelInfo_GetIconPath(device_info.modelInfo, (int)param2, (int)param3, (wchar_t*)param4, 260, TRUE);
break;
case DEVICE_GET_CONNECTION_TYPE:
{
const char **type = (const char **)param2;
*type = "WiFi";
return 1;
}
case DEVICE_SUPPORTS_PODCASTS:
return 1; // we don't support podcasts
case DEVICE_GET_MODEL:
ModelInfo_CopyDisplayName(device_info.modelInfo, (wchar_t*)param2, param3);
return 1;
case DEVICE_SUPPORTED_METADATA:
{
intptr_t supported = SUPPORTS_ARTIST | SUPPORTS_ALBUM | SUPPORTS_TITLE | SUPPORTS_TRACKNUM /*| SUPPORTS_DISCNUM | SUPPORTS_GENRE */|
SUPPORTS_YEAR | SUPPORTS_SIZE | SUPPORTS_LENGTH /*| SUPPORTS_BITRATE */| SUPPORTS_LASTUPDATED /*| SUPPORTS_ALBUMARTIST */|
SUPPORTS_COMPOSER /*| SUPPORTS_PUBLISHER | SUPPORTS_ALBUMART*/;
return supported;
}
break;
case DEVICE_VETO_ENCODER:
{
for (size_t i=0;i<sizeof(encoder_blacklist)/sizeof(*encoder_blacklist);i++)
{
if (param2 == encoder_blacklist[i])
return 1;
}
}
return 0;
}
// TODO: implement more
return 0;
}
bool TemplateDevice::copyToHardDriveSupported()
{
return true;
}
__int64 TemplateDevice::songSizeOnHardDrive(songid_t song)
{
WifiTrack *track = (WifiTrack *)song;
return track->size;
}
int TemplateDevice::copyToHardDrive(songid_t song, // the song to copy
wchar_t * path, // path to copy to, in the form "c:\directory\song". The directory will already be created, you must append ".mp3" or whatever to this string! (there is space for at least 10 new characters).
void * callbackContext, //pass this to the callback
void (*callback)(void * callbackContext, wchar_t * status), // call this every so often so the GUI can be updated. Including when finished!
int * killswitch // if this gets set to anything other than zero, the transfer has been cancelled by the user
)
{
WifiTrack *track = (WifiTrack *)song;
char download_url[1024] = {0};
StringCbPrintfA(download_url, sizeof(download_url), "%s/file/%S", url, track->id);
HANDLE event = CreateEvent(0, FALSE, FALSE, 0);
if (!_wcsicmp(track->mime_type, L"audio/mpeg"))
wcsncat(path, L".mp3", MAX_PATH);
else if (!_wcsicmp(track->mime_type, L"audio/mp4"))
wcsncat(path, L".m4a", MAX_PATH);
else if (!_wcsicmp(track->mime_type, L"audio/x-ms-wma"))
wcsncat(path, L".wma", MAX_PATH);
else if (!_wcsicmp(track->mime_type, L"application/ogg") || !_wcsicmp(track->mime_type, L"audio/ogg") )
wcsncat(path, L".ogg", MAX_PATH);
// TODO: more
SongDownloader *song_downloader = new SongDownloader(path, event, callback, callbackContext);
song_downloader->AddRef();
WAC_API_DOWNLOADMANAGER->DownloadEx(download_url, song_downloader, api_downloadManager::DOWNLOADEX_CALLBACK);
WaitForSingleObject(event, INFINITE);
song_downloader->Release();
return 0; // TODO: check error code
}
// art functions
void TemplateDevice::setArt(songid_t songid, void *buf, int w, int h)
{
//buf is in format ARGB32*
// TODO: implement
}
pmpart_t TemplateDevice::getArt(songid_t songid)
{
// TODO: implement
return 0;
}
void TemplateDevice::releaseArt(pmpart_t art)
{
// TODO: implement
}
int TemplateDevice::drawArt(pmpart_t art, HDC dc, int x, int y, int w, int h)
{
// TODO: implement
return 0;
}
void TemplateDevice::getArtNaturalSize(pmpart_t art, int *w, int *h)
{
// TODO: implement
}
void TemplateDevice::setArtNaturalSize(pmpart_t art, int w, int h)
{
// TODO: implement
}
void TemplateDevice::getArtData(pmpart_t art, void* data)
{
// data ARGB32* is at natural size
// TODO: implement
}
bool TemplateDevice::artIsEqual(pmpart_t a, pmpart_t b)
{
// TODO: implement
return false;
}