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/Portable/pmp_android | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz |
Initial community commit
Diffstat (limited to 'Src/Plugins/Portable/pmp_android')
23 files changed, 4964 insertions, 0 deletions
diff --git a/Src/Plugins/Portable/pmp_android/albumart.cpp b/Src/Plugins/Portable/pmp_android/albumart.cpp new file mode 100644 index 00000000..7d3a26a7 --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/albumart.cpp @@ -0,0 +1,58 @@ +#include "../../Library/ml_pmp/pmp.h" +#include "api.h" +#include "../agave/albumart/svc_albumartprovider.h" +#include <api/service/waservicefactory.h> + +extern PMPDevicePlugin plugin; + +static svc_albumArtProvider *FindProvider(const wchar_t *filename, int providerType, waServiceFactory **factory) +{ + FOURCC albumartprovider = svc_albumArtProvider::getServiceType(); + int n = (int)plugin.service->service_getNumServices(albumartprovider); + for (int i=0; i<n; i++) + { + waServiceFactory *sf = plugin.service->service_enumService(albumartprovider,i); + if (sf) + { + svc_albumArtProvider * provider = (svc_albumArtProvider*)sf->getInterface(); + if (provider) + { + if (provider->ProviderType() == providerType && provider->IsMine(filename)) + { + *factory = sf; + return provider; + } + sf->releaseInterface(provider); + } + } + } + return NULL; +} + +void CopyAlbumArt(const wchar_t *source, const wchar_t *destination) +{ + size_t datalen = 0; + void *data = 0; + wchar_t *mimeType = 0; + waServiceFactory *destinationFactory = 0; + svc_albumArtProvider *destinationProvider = FindProvider(destination, ALBUMARTPROVIDER_TYPE_EMBEDDED, &destinationFactory); + if (destinationFactory) + { + /* First, look to see if there's already embedded album art */ + if (destinationProvider->GetAlbumArtData(destination, L"cover", &data, &datalen, &mimeType) == ALBUMARTPROVIDER_SUCCESS && data && datalen) + { + destinationFactory->releaseInterface(destinationProvider); + WASABI_API_MEMMGR->sysFree(data); + WASABI_API_MEMMGR->sysFree(mimeType); + return; + } + else if (AGAVE_API_ALBUMART->GetAlbumArtData(source, L"cover", &data, &datalen, &mimeType) == ALBUMART_SUCCESS && data && datalen) + { + destinationProvider->SetAlbumArtData(destination, L"cover", data, datalen, mimeType); + WASABI_API_MEMMGR->sysFree(data); + WASABI_API_MEMMGR->sysFree(mimeType); + + destinationFactory->releaseInterface(destinationProvider); + } + } +}
\ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_android/androiddevice.cpp b/Src/Plugins/Portable/pmp_android/androiddevice.cpp new file mode 100644 index 00000000..6ea4e04c --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/androiddevice.cpp @@ -0,0 +1,1869 @@ +#include "androiddevice.h" +#include "resource.h" +#include "androidplaylist.h" +#include "androidplaylistsaver.h" +#include "api.h" +#include "../winamp/wa_ipc.h" +#include <tataki/bitmap/bitmap.h> +#include <tataki/canvas/bltcanvas.h> +#include <shlobj.h> +#include <strsafe.h> +#include <shlwapi.h> + +// from main.cpp +extern PMPDevicePlugin plugin; +extern std::vector<AndroidDevice*> devices; +extern bool loading_devices[26]; + +// from utils.cpp +extern BOOL RecursiveCreateDirectory(wchar_t* buf); +extern bool supportedFormat(wchar_t * file, wchar_t * supportedFormats); +extern DeviceType detectDeviceType(wchar_t drive); +extern __int64 fileSize(wchar_t * filename); +extern void removebadchars(wchar_t *s); +extern wchar_t * fixReplacementVars(wchar_t *str, int str_size, Device * dev, songid_t song); +static INT_PTR CALLBACK prefs_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam); +int CopyFile(const wchar_t *infile, const wchar_t *outfile, void * callbackContext, void (*callback)(void * callbackContext, wchar_t * status), int * killswitch); +extern BOOL EjectVolume(TCHAR cDriveLetter); + +static int connected_drives[26] = {0, }; +// from albumart.cpp +void CopyAlbumArt(const wchar_t *source, const wchar_t *destination); + +// called from ml_pmp +__int64 AndroidDevice::getDeviceCapacityAvailable() // in bytes +{ + ULARGE_INTEGER tfree={0,}, total={0,}, freeb={0,}; + wchar_t path[4]=L"x:\\"; + path[0]=drive; + GetDiskFreeSpaceEx(path, &tfree, &total, &freeb); + return freeb.QuadPart; +} + +// called from ml_pmp +__int64 AndroidDevice::getDeviceCapacityTotal() +{ + // in bytes + ULARGE_INTEGER tfree={0,}, total={0,}, freeb={0,}; + wchar_t path[4]=L"x:\\"; + path[0]=drive; + GetDiskFreeSpaceEx(path, &tfree, &total, &freeb); + return total.QuadPart; +} + +// called from ml_pmp +void AndroidDevice::Eject() +{ + // if you ejected successfully, you MUST call PMP_IPC_DEVICEDISCONNECTED and delete this + for(size_t i=0; i < devices.size(); i++) + { + AndroidDevice *device = devices.at(i); + if (device == this) + { + if (EjectVolume(drive)) + { + devices.erase(devices.begin() + i); + SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICEDISCONNECTED); + delete this; + } + else + { + wchar_t titleStr[128] = {0}; + MessageBox(plugin.hwndLibraryParent,WASABI_API_LNGSTRINGW(IDS_FAILED_TO_EJECT_DRIVE), WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,titleStr,128),0); + } + + break; + } + } +} + +// called from ml_pmp +void AndroidDevice::Close() +{ + // save any changes, and call PMP_IPC_DEVICEDISCONNECTED AND delete this + for (size_t i=0; i < devices.size(); i++) + { + if (((AndroidDevice*)devices.at(i)) == this) + { + devices.erase(devices.begin() + i); + SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICEDISCONNECTED); + delete this; + break; + } + } +} + +// called from ml_pmp +// return 0 for success, -1 for failed or cancelled +int AndroidDevice::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 fn[MAX_PATH] = L"X:\\"; + lstrcpyn(fn, songFormat, MAX_PATH); + fn[0] = drive; + wchar_t * src = track->filename; + wchar_t ext[10] = {0}; + wchar_t *e = wcsrchr(src,L'.'); + if (e) lstrcpyn(ext, e, 10); + + bool transcodefile = false; + if (transcoder && transcoder->ShouldTranscode(src)) + { + int r = transcoder->CanTranscode(src, ext); + if (r != 0 && r != -1) transcodefile = true; + } + + AndroidSong *s = new AndroidSong(); + lstrcpyn(s->filename, src, MAX_PATH); //this will get written over, but for now we have this so that the user can keep the old filename + + fillMetaData(s); // TODO: benski> used cached info inside track (itemRecordW) if available + fixReplacementVars(fn, MAX_PATH, this, (songid_t)s); + + StringCchCat(fn, MAX_PATH, ext); //place extension + StringCchCopy(s->filename, MAX_PATH, fn); + + wchar_t * dir = wcsrchr(fn,L'\\'); + wchar_t * dir2 = wcsrchr(fn,L'/'); + wchar_t slash; + if (dir2 > dir) + { + dir = dir2; + slash=L'/'; + } + else slash = L'\\'; + if (dir) *dir = 0; + RecursiveCreateDirectory(fn); + if (dir) *dir = slash; + int r; + if (transcodefile) + { + r = transcoder->TranscodeFile(src, fn, killswitch, callback, callbackContext); + } + else + { + r = CopyFile(src, fn, callbackContext, callback, killswitch); + } + + if (r == 0) + { + // TODO: benski> do we need to update any fields from the transcoded filed? + CopyAlbumArt(src, fn); + writeRecordToDB(s); + callback(callbackContext, WASABI_API_LNGSTRINGW(IDS_DONE)); + *songid = (songid_t)s; + } + else + { + callback(callbackContext, WASABI_API_LNGSTRINGW(IDS_TRANSFER_FAILED)); + delete s; + } + return r; +} + +// called from ml_pmp +int AndroidDevice::trackAddedToTransferQueue(const itemRecordW *track) +{ + // return 0 to accept, -1 for "not enough space", -2 for "incorrect format" + __int64 k = getTrackSizeOnDevice(track); + if (!k) return -2; + __int64 l = (__int64)k; + __int64 avail = getDeviceCapacityAvailable(); + __int64 cmp = transferQueueLength; + cmp += l; + if (cmp > avail) return -1; + else + { + transferQueueLength += l; + return 0; + } +} + +// called from ml_pmp +void AndroidDevice::trackRemovedFromTransferQueue(const itemRecordW *track) +{ + transferQueueLength -= (__int64)getTrackSizeOnDevice(track); +} + +// called from ml_pmp +// 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 AndroidDevice::getTrackSizeOnDevice(const itemRecordW *track) +{ + if (transcoder) + { + if (transcoder->ShouldTranscode(track->filename)) + { + int k = transcoder->CanTranscode(track->filename); + if (k != -1 && k != 0) return k; + return 0; + } + else return fileSize(track->filename); + } + else + { + if (!supportedFormat(track->filename,supportedFormats)) return 0; + return fileSize(track->filename); + } +} + +// called from ml_pmp +void AndroidDevice::deleteTrack( songid_t songid ) +{ + // physically remove from device. Be sure to remove it from all the playlists! + AndroidSong *s = (AndroidSong *)songid; + + //errno == 2 is ENOENT + if ( !_wunlink( s->filename ) || errno == 2 ) //will continue delete if file was deleted successfully or file path does not exist in the first place (errno==2) + { + for ( size_t i = 0; i < androidPlaylists.size(); i++ ) + { + AndroidPlaylist *pl = androidPlaylists.at( i ); + pl->RemoveSong( s ); + } + + if ( purgeFolders[ 0 ] == '1' ) + { + RemoveDirectory( s->filename ); + } + + delete s; + } + else + { + char titleStr[ 32 ] = { 0 }; + MessageBoxA( plugin.hwndLibraryParent, WASABI_API_LNGSTRING( IDS_TRACK_IN_USE ), WASABI_API_LNGSTRING_BUF( IDS_ERROR, titleStr, 32 ), 0 ); + } +} + +// called from ml_pmp +// optional. Will be called at a good time to save changes +void AndroidDevice::commitChanges() +{ + Nullsoft::Utility::AutoLock lock(dbcs); + if (deviceTable && androidPlaylists.size() && androidPlaylists[0]->dirty) + { + NDE_Table_Sync(deviceTable); + androidPlaylists[0]->dirty=false; + } + + for (size_t i=1;i<androidPlaylists.size();i++) + { + AndroidPlaylist *pl = androidPlaylists[i]; + if (pl->dirty) + { + // Lets delete the current playlist file + _wunlink(pl->filename); + + AndroidPlaylistSaver playlistSaver(pl->filename, L"autosaved", pl); + playlistSaver.Save(); + pl->dirty = false; + } + } + +} + +// called from ml_pmp +int AndroidDevice::getPlaylistCount() +{ + // always at least 1. playlistnumber 0 is the Master Playlist containing all tracks. + return (int)androidPlaylists.size(); +} + +// called from ml_pmp +// PlaylistName(0) should return the name of the device. +void AndroidDevice::getPlaylistName(int playlistnumber, wchar_t *buf, int len) +{ + wchar_t * pathName = androidPlaylists.at(playlistnumber)->filename; + if (playlistnumber != 0) + { + if (pathName[0]) + { + wchar_t * playlistName = PathFindFileNameW(pathName); + lstrcpyn(buf,playlistName,len); + PathRemoveExtension(buf); + } + } + else //playlist number = 0 -> this is the device + { + if (pathName[0]) + { + //if we have a custom device name + lstrcpyn(buf,pathName,len); + } + else + { + WASABI_API_LNGSTRINGW_BUF(IDS_ANDROID_DRIVE_X,buf,len); + wchar_t * x = wcsrchr(buf,L'X'); + if (x) *x = drive; + } + } +} + +// called from ml_pmp +int AndroidDevice::getPlaylistLength(int playlistnumber) +{ + return (int)androidPlaylists.at(playlistnumber)->size(); +} + +// called from ml_pmp +songid_t AndroidDevice::getPlaylistTrack(int playlistnumber,int songnum) +{ + // returns a songid + return (songid_t) androidPlaylists.at(playlistnumber)->at(songnum); +} + +// called from ml_pmp +void AndroidDevice::setPlaylistName(int playlistnumber, const wchar_t *buf) +{ + // with playlistnumber==0, set the name of the device. + AndroidPlaylist * pl = androidPlaylists.at(playlistnumber); + if (playlistnumber==0) + { + WritePrivateProfileString(L"pmp_android",L"customName",buf,iniFile); + lstrcpyn(pl->filename,buf,sizeof(pl->filename)/sizeof(wchar_t)); + } + else + { + wchar_t currentFilename[MAX_PATH] = {0}; + lstrcpynW(currentFilename, pl->filename, MAX_PATH); + + wchar_t * newFilename = const_cast<wchar_t *>(buf); + if (wcslen(buf) >= MAX_PATH-1) newFilename[MAX_PATH-1]=0; + while (newFilename && *newFilename && *newFilename == L'.') newFilename++; + removebadchars(newFilename); + StringCchPrintf(pl->filename,MAX_PATH,L"%s\\%s.m3u",pldir,newFilename); + pl->filename[0]=drive; + MoveFile(currentFilename, pl->filename); + + pl->dirty=true; + } +} + +// called from ml_pmp +void AndroidDevice::playlistSwapItems(int playlistnumber, int posA, int posB) +{ + // swap the songs at position posA and posB + AndroidPlaylist * pl = (AndroidPlaylist*)androidPlaylists.at(playlistnumber); + pl->swap(posA, posB); +} + +// called from ml_pmp +void AndroidDevice::sortPlaylist(int playlistnumber, int sortBy) +{ + // TODO: implement +} + +// called from ml_pmp +void AndroidDevice::addTrackToPlaylist(int playlistnumber, songid_t songid) +{ + // adds songid to the end of the playlist + AndroidSong* song = (AndroidSong *) songid; + AndroidPlaylist * pl = (AndroidPlaylist*)androidPlaylists.at(playlistnumber); + pl->push_back(song); +} + +// called from ml_pmp +void AndroidDevice::removeTrackFromPlaylist(int playlistnumber, int songnum) +{ + //where songnum is the position of the track in the playlist + AndroidPlaylist * pl = (AndroidPlaylist*)androidPlaylists.at(playlistnumber); + pl->eraseAt(songnum); + +} + +// called from ml_pmp +void AndroidDevice::deletePlaylist(int playlistnumber) +{ + AndroidPlaylist * pl = (AndroidPlaylist*)androidPlaylists.at(playlistnumber); + _wunlink(pl->filename); + androidPlaylists.erase(androidPlaylists.begin() + playlistnumber); + delete pl; +} + +// called from ml_pmp +int AndroidDevice::newPlaylist(const wchar_t *name) +{ + wchar_t plname[MAX_PATH] = {0}; + StringCchCopy(plname, MAX_PATH, name); + removebadchars(plname); + + + // create empty playlist, returns playlistnumber. -1 for failed. + for (std::vector<AndroidPlaylist*>::iterator itr=androidPlaylists.begin();itr!=androidPlaylists.end();itr++) + { + AndroidPlaylist *p = *itr; + if (!_wcsicmp(p->playlistName, plname)) + return -1; + } + + wchar_t buff[MAX_PATH] = {0}; + StringCchPrintf(buff, MAX_PATH, L"%s\\%s.m3u",pldir,plname); + + AndroidPlaylist * pl = new AndroidPlaylist(*this, buff, false); + pl->filename[0]=drive; + + //Lets save the playlist right away + AndroidPlaylistSaver playlistSaver(pl->filename, L"autosaved", pl); + playlistSaver.Save(); + + androidPlaylists.push_back(pl); + return (int)androidPlaylists.size()-1; +} + +// called from ml_pmp +void AndroidDevice::getTrackArtist(songid_t songid, wchar_t *buf, int len) +{ + AndroidSong* song = (AndroidSong*)songid; + if (!song) return; + + buf[0] = L'\0'; + StringCchCopy(buf, len, song->artist); +} + +// called from ml_pmp +void AndroidDevice::getTrackAlbum(songid_t songid, wchar_t *buf, int len) +{ + AndroidSong* song = (AndroidSong*)songid; + if (!song) return; + + buf[0] = L'\0'; + StringCchCopy(buf, len, song->album); +} + +// called from ml_pmp +void AndroidDevice::getTrackTitle(songid_t songid, wchar_t *buf, int len) +{ + AndroidSong* song = (AndroidSong*)songid; + if (!song) return; + + StringCchCopy(buf, len, song->title); +} + +// called from ml_pmp +int AndroidDevice::getTrackTrackNum(songid_t songid) +{ + AndroidSong* song = (AndroidSong*)songid; + if (!song) return 0; + + return song->track; +} + +// called from ml_pmp +int AndroidDevice::getTrackDiscNum(songid_t songid) +{ + AndroidSong* song = (AndroidSong*)songid; + if (!song) return 0; + + return song->discnum; +} + +// called from ml_pmp +void AndroidDevice::getTrackGenre(songid_t songid, wchar_t * buf, int len) +{ + AndroidSong* song = (AndroidSong*)songid; + if (!song) return; + + StringCchCopy(buf, len, song->genre); +} + +// called from ml_pmp +int AndroidDevice::getTrackYear(songid_t songid) +{ + AndroidSong* song = (AndroidSong*)songid; + if (!song) return 0; + + return song->year; +} + +// called from ml_pmp +__int64 AndroidDevice::getTrackSize(songid_t songid) +{ + // in bytes + AndroidSong* song = (AndroidSong*)songid; + if (!song) return 0; + + return song->size; +} + +// called from ml_pmp +int AndroidDevice::getTrackLength(songid_t songid) +{ + // in millisecs + AndroidSong* song = (AndroidSong*)songid; + if (!song) return 0; + + return song->length; +} + +// called from ml_pmp +int AndroidDevice::getTrackBitrate(songid_t songid) +{ + // in kbps + AndroidSong* song = (AndroidSong*)songid; + if (!song) return 0; + + return song->bitrate; +} + +// called from ml_pmp +int AndroidDevice::getTrackPlayCount(songid_t songid) +{ + AndroidSong* song = (AndroidSong*)songid; + if (!song) return 0; + + return song->playcount; +} + +// called from ml_pmp +int AndroidDevice::getTrackRating(songid_t songid) +{ + //0-5 + // TODO: implement + return 0; +} + +// called from ml_pmp +__time64_t AndroidDevice::getTrackLastPlayed(songid_t songid) +{ + // in unix time format + // TODO: implement + return 0; +} + +// called from ml_pmp +__time64_t AndroidDevice::getTrackLastUpdated(songid_t songid) +{ + // in unix time format + // TODO: implement + return 0; +} + +// called from ml_pmp +void AndroidDevice::getTrackAlbumArtist(songid_t songid, wchar_t *buf, int len) +{ + AndroidSong* song = (AndroidSong*)songid; + if (!song) return; + + StringCchCopy(buf, len, song->albumartist); +} + +// called from ml_pmp +void AndroidDevice::getTrackPublisher(songid_t songid, wchar_t *buf, int len) +{ + AndroidSong* song = (AndroidSong*)songid; + if (!song) return; + + StringCchCopy(buf, len, song->publisher); +} + +// called from ml_pmp +void AndroidDevice::getTrackComposer(songid_t songid, wchar_t *buf, int len) +{ + AndroidSong* song = (AndroidSong*)songid; + if (!song) return; + + StringCchCopy(buf, len, song->composer); +} + +// called from ml_pmp +int AndroidDevice::getTrackType(songid_t songid) +{ + // TODO: implement + return 0; +} + +// called from ml_pmp +void AndroidDevice::getTrackExtraInfo(songid_t songid, const wchar_t *field, wchar_t *buf, int len) +{ + // TODO: implement + //optional +} + +// called from ml_pmp +// feel free to ignore any you don't support +void AndroidDevice::setTrackArtist(songid_t songid, const wchar_t *value) +{ + AndroidSong *song = (AndroidSong *) songid; + if (song) + { + updateTrackField(song, DEVICEVIEW_COL_ARTIST, value, FIELD_STRING); + StringCchCopy(song->artist, FIELD_LENGTH, value); + AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"artist", value); + } +} + +// called from ml_pmp +void AndroidDevice::setTrackAlbum(songid_t songid, const wchar_t *value) +{ + AndroidSong *song = (AndroidSong *) songid; + if (song) + { + updateTrackField(song, DEVICEVIEW_COL_ALBUM, value, FIELD_STRING); + StringCchCopy(song->album, FIELD_LENGTH, value); + AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"album", value); + } +} + +// called from ml_pmp +void AndroidDevice::setTrackTitle(songid_t songid, const wchar_t *value) +{ + AndroidSong *song = (AndroidSong *) songid; + if (song) + { + updateTrackField(song, DEVICEVIEW_COL_TITLE, value, FIELD_STRING); + StringCchCopy(song->title, FIELD_LENGTH, value); + AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"title", value); + } +} + +// called from ml_pmp +void AndroidDevice::setTrackTrackNum(songid_t songid, int value) +{ + AndroidSong *song = (AndroidSong *) songid; + + if (song) + { + wchar_t track[FIELD_LENGTH] = {0}; + updateTrackField(song, DEVICEVIEW_COL_TRACK, &value, FIELD_INTEGER); + song->track = value; + StringCchPrintf(track, FIELD_LENGTH, L"%d", value); + AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"track", track); + } +} + +// called from ml_pmp +void AndroidDevice::setTrackDiscNum(songid_t songid, int value) +{ + AndroidSong *song = (AndroidSong *) songid; + + if (song) + { + wchar_t discNum[FIELD_LENGTH] = {0}; + updateTrackField(song, DEVICEVIEW_COL_DISC_NUMBER, &value, FIELD_INTEGER); + song->discnum = value; + StringCchPrintf(discNum, FIELD_LENGTH, L"%d", value); + AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"disc", discNum); + } +} + +// called from ml_pmp +void AndroidDevice::setTrackGenre(songid_t songid, const wchar_t *value) +{ + AndroidSong *song = (AndroidSong *) songid; + if (song) + { + updateTrackField(song, DEVICEVIEW_COL_GENRE, value, FIELD_STRING); + StringCchCopy(song->genre, FIELD_LENGTH, value); + AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"genre", value); + } +} + +// called from ml_pmp +void AndroidDevice::setTrackYear(songid_t songid, int year) +{ + AndroidSong *song = (AndroidSong *) songid; + + if (song) + { + wchar_t yearStr[FIELD_LENGTH] = {0}; + updateTrackField(song, DEVICEVIEW_COL_YEAR, &year, FIELD_INTEGER); + song->year = year; + StringCchPrintf(yearStr, FIELD_LENGTH, L"%d", year); + AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"year", yearStr); + } +} + +// called from ml_pmp +void AndroidDevice::setTrackPlayCount(songid_t songid, int value) +{ + AndroidSong *song = (AndroidSong *) songid; + + if (song) + { + wchar_t playCount[FIELD_LENGTH] = {0}; + updateTrackField(song, DEVICEVIEW_COL_PLAY_COUNT, &value, FIELD_INTEGER); + song->playcount = value; + StringCchPrintf(playCount, FIELD_LENGTH, L"%d", value); + AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"playcount", playCount); + } +} + +// called from ml_pmp +void AndroidDevice::setTrackRating(songid_t songid, int value) +{ + AndroidSong *song = (AndroidSong *) songid; + + if (song) + { + wchar_t rating[FIELD_LENGTH] = {0}; + updateTrackField(song, DEVICEVIEW_COL_PLAY_COUNT, &value, FIELD_INTEGER); + song->playcount = value; + StringCchPrintf(rating, FIELD_LENGTH, L"%d", value); + AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"rating", rating); + } +} + +// called from ml_pmp +void AndroidDevice::setTrackLastPlayed(songid_t songid, __time64_t value) +{ + // TODO: implement + +} // in unix time format + +// called from ml_pmp +void AndroidDevice::setTrackLastUpdated(songid_t songid, __time64_t value) +{ + // TODO: implement + +} // in unix time format + +// called from ml_pmp +void AndroidDevice::setTrackAlbumArtist(songid_t songid, const wchar_t *value) +{ + AndroidSong *song = (AndroidSong *) songid; + if (song) + { + updateTrackField(song, DEVICEVIEW_COL_ALBUM_ARTIST, value, FIELD_STRING); + StringCchCopy(song->albumartist, FIELD_LENGTH, value); + AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"albumartist", value); + } +} + +// called from ml_pmp +void AndroidDevice::setTrackPublisher(songid_t songid, const wchar_t *value) +{ + AndroidSong *song = (AndroidSong *) songid; + if (song) + { + updateTrackField(song, DEVICEVIEW_COL_PUBLISHER, value, FIELD_STRING); + StringCchCopy(song->publisher, FIELD_LENGTH, value); + AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"publisher", value); + } +} + +// called from ml_pmp +void AndroidDevice::setTrackComposer(songid_t songid, const wchar_t *value) +{ + AndroidSong *song = (AndroidSong *) songid; + if (song) + { + updateTrackField(song, DEVICEVIEW_COL_COMPOSER, value, FIELD_STRING); + StringCchCopy(song->composer, FIELD_LENGTH, value); + AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"composer", value); + } +} + +// called from ml_pmp +void AndroidDevice::setTrackExtraInfo(songid_t songid, const wchar_t *field, const wchar_t *value) +{ + // TODO: implement + +} //optional + +// called from ml_pmp +bool AndroidDevice::playTracks(songid_t * songidList, int listLength, int startPlaybackAt, bool enqueue) +{ + // return false if unsupported + if (!enqueue) //clear playlist + { + SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_DELETE); + } + + for (int i=0; i<listLength; i++) + { + AndroidSong *curSong = (AndroidSong*)songidList[i]; + + if (curSong) + { + enqueueFileWithMetaStructW s={0}; + s.filename = _wcsdup(curSong->filename); + s.title = _wcsdup( curSong->title ); + s.ext = NULL; + s.length = curSong->length/1000; + + SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW); + } + else + { + char titleStr[32] = {0}; + 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'), +}; + +// called from ml_pmp +intptr_t AndroidDevice::extraActions(intptr_t param1, intptr_t param2, intptr_t param3,intptr_t param4) +{ + switch (param1) + { + case DEVICE_SET_ICON: + { + MLTREEIMAGE * i = (MLTREEIMAGE*)param2; + i->hinst = plugin.hDllInstance; + i->resourceId = IDR_ANDROID_ICON; + } + break; + + 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_CAN_RENAME_DEVICE: + return 1; + + case DEVICE_GET_INI_FILE: + StringCchCopy((wchar_t*)param2, MAX_PATH, iniFile); + break; + case DEVICE_GET_PREFS_DIALOG: + if (param3 == 0) + { + pref_tab * p = (pref_tab *)param2; + p->hinst = WASABI_API_LNG_HINST; + p->dlg_proc = prefs_dialogProc; + p->res_id = IDD_CONFIG; + WASABI_API_LNGSTRINGW_BUF(IDS_ADVANCED,p->title,100); + } + break; + case DEVICE_DONE_SETTING: + { + AndroidSong * song = (AndroidSong *) param2; + AGAVE_API_METADATA->WriteExtendedFileInfo(song->filename); + return true; + } + break; + case DEVICE_VETO_ENCODER: + { + for (size_t i=0;i<sizeof(encoder_blacklist)/sizeof(*encoder_blacklist);i++) + { + // TODO: check device info XML for aacPlus support + if (param2 == encoder_blacklist[i]) + return 1; + } + } + return 0; + case DEVICE_GET_ICON: + { + if (param2 <= 16 && param3 <= 16) + { + // TODO: get the name of the DLL at load time + StringCchPrintfW((wchar_t *)param4, 260, L"res://%s/PNG/#%u", L"pmp_android.dll", IDR_ANDROID_ICON); + } + else + { + // TODO: get the name of the DLL at load time + StringCchPrintfW((wchar_t *)param4, 260, L"res://%s/PNG/#%u", L"pmp_android.dll", IDB_ANDROID_160); + } + } + break; + } + return false; +} + +// called from ml_pmp +bool AndroidDevice::copyToHardDriveSupported() +{ + return true; +} + +// called from ml_pmp +__int64 AndroidDevice::songSizeOnHardDrive(songid_t song) +{ + // how big a song will be when copied back. Return -1 for not supported. + // TODO: implement + return 0; + +} + +// called from ml_pmp +int AndroidDevice::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 + ) +{ + // -1 for failed/not supported. 0 for success. + AndroidSong* track = (AndroidSong*)song; + wchar_t * ext = PathFindExtensionW(track->filename); + if (ext && (lstrlen(ext)<10)) StringCchCat(path,MAX_PATH, ext); // append correct extention + return CopyFile(track->filename,path,callbackContext, callback, killswitch); +} + +// called from ml_pmp +// art functions +void AndroidDevice::setArt(songid_t songid, void *buf, int w, int h) +{ + //buf is in format ARGB32* + // TODO: implement +} + +// called from ml_pmp +pmpart_t AndroidDevice::getArt(songid_t songid) +{ + AndroidSong *song = (AndroidSong *)songid; + ARGB32 *bits; + int w, h; + if (AGAVE_API_ALBUMART && AGAVE_API_ALBUMART->GetAlbumArt(song->filename, L"cover", &w, &h, &bits) == ALBUMART_SUCCESS && bits) + { + return (pmpart_t) new AndroidArt(bits, w, h); + } + return 0; +} + +// called from ml_pmp +void AndroidDevice::releaseArt(pmpart_t art) +{ + AndroidArt *image = (AndroidArt *)art; + delete image; +} + +// called from ml_pmp +int AndroidDevice::drawArt(pmpart_t art, HDC dc, int x, int y, int w, int h) +{ + AndroidArt *image = (AndroidArt *)art; + if (image) + { + HQSkinBitmap temp(image->bits, image->w, image->h); // wrap into a SkinBitmap (no copying involved) + DCCanvas canvas(dc); + temp.stretch(&canvas,x,y,w,h); + return 1; + } + return 0; +} + +// called from ml_pmp +void AndroidDevice::getArtNaturalSize(pmpart_t art, int *w, int *h) +{ + AndroidArt *image = (AndroidArt *)art; + if (image) + { + *w = image->w; + *h = image->h; + } +} + +// called from ml_pmp +void AndroidDevice::setArtNaturalSize(pmpart_t art, int w, int h) +{ + // TODO: implement + //DebugBreak(); +} + +// called from ml_pmp +void AndroidDevice::getArtData(pmpart_t art, void* data) +{ + AndroidArt *image = (AndroidArt *)art; + if (image) + memcpy(data, image->bits, image->w*image->h*sizeof(ARGB32)); + // data ARGB32* is at natural size +} + +// called from ml_pmp +bool AndroidDevice::artIsEqual(pmpart_t a, pmpart_t b) +{ + if (a == b) + return true; + // TODO: implement + return false; +} + +////////////////////////////////////////////////////////////////////////////////////////// +// Initialize class statics +nde_database_t AndroidDevice::discDB = 0; + + +// The getter that returns the master playlist +// the playlist vector always carries a master playlist +AndroidPlaylist* AndroidDevice::getMasterPlaylist() +{ + for (std::vector<AndroidPlaylist*>::const_iterator e = androidPlaylists.begin(); e != androidPlaylists.end(); e++) + { + AndroidPlaylist* playlist = (*e); + if (playlist->isMaster()) return playlist; + } + return NULL; +} + +// constructor +AndroidDevice::AndroidDevice(wchar_t drive, pmpDeviceLoading * load): transcoder(NULL) +{ + deviceTable = 0; + + StringCchPrintf(ndeDataFile, 100, L"%c:\\Winamp\\winamp_metadata.dat", drive); + StringCchPrintf(ndeIndexFile, 100, L"%c:\\Winamp\\winamp_metadata.idx", drive); + + load->dev = this; + load->UpdateCaption = NULL; + + //pass load to ml_pmp, ml updates load->UpdateCaption and context + SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)load,PMP_IPC_DEVICELOADING); + + if (load->UpdateCaption) + { + wchar_t buf[100] = L""; + WASABI_API_LNGSTRINGW_BUF(IDS_LOADING_DRIVE_X,buf,100); + wchar_t * x = wcsrchr(buf,L'X'); + if (x) *x = drive; + load->UpdateCaption(buf,load->context); + } + + // load settings + StringCchCopy(iniFile, MAX_PATH, L"x:\\Winamp\\"); + iniFile[0]=drive; + + CreateDirectory(iniFile, NULL); + StringCchCat(iniFile,MAX_PATH,L"pmp_android.ini"); + + wchar_t customName[FIELD_LENGTH] = {0}; + GetPrivateProfileString(L"pmp_android",L"pldir",L"X:\\Music",pldir,sizeof(pldir)/sizeof(wchar_t),iniFile); + GetPrivateProfileString(L"pmp_android",L"songFormat",L"X:\\Music\\<Artist>\\<Album>\\## - <Title>",songFormat,sizeof(songFormat)/sizeof(wchar_t),iniFile); + GetPrivateProfileString(L"pmp_android",L"supportedFormats",L"mp3;wav;m4a;mp4;m4v;avi;3gp;mid;ogg",supportedFormats,sizeof(supportedFormats)/sizeof(wchar_t),iniFile); + GetPrivateProfileString(L"pmp_android",L"purgeFolders",L"1",purgeFolders,sizeof(purgeFolders)/sizeof(wchar_t),iniFile); + GetPrivateProfileString(L"pmp_android",L"customName",L"",customName,sizeof(customName)/sizeof(wchar_t),iniFile); + pl_write_mode = GetPrivateProfileInt(L"pmp_android",L"pl_write_mode",0,iniFile); + + pldir[0] = drive; + songFormat[0] = drive; + + transferQueueLength = 0; + this->drive = drive; + AndroidPlaylist * mpl = new AndroidPlaylist(*this, customName, true); + androidPlaylists.push_back(mpl); + wchar_t * pl = _wcsdup(pldir); + pl[0] = drive; + RecursiveCreateDirectory(pl); + wchar_t root[3] = L"X:"; + root[0] = drive; + + openDeviceTable(); + + fileProbe(root); + + // sort out and read playlists.... + if (WASABI_API_PLAYLISTMNGR != NULL && WASABI_API_PLAYLISTMNGR != (api_playlistmanager *)1) + { + for (std::vector<AndroidPlaylist*>::const_iterator e = androidPlaylists.begin(); e != androidPlaylists.end(); e++) + { + AndroidPlaylist* playlist = (*e); + if (playlist->isMaster() == false) + { + WASABI_API_PLAYLISTMNGR->Load(playlist->getFilename(), playlist); + } + } + } + + tag(); + devices.push_back(this); + extern HWND config; + if (config) PostMessage(config,WM_USER,0,0); + + SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICECONNECTED); + setupTranscoder(); +} + +AndroidDevice::AndroidDevice() +{ +} + +AndroidDevice::~AndroidDevice() +{ + closeDeviceTable(); + if (transcoder) + SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(WPARAM)transcoder,PMP_IPC_RELEASE_TRANSCODER); +} + +//read files from device's folder 'indir' +void AndroidDevice::fileProbe(wchar_t * indir) +{ + wchar_t dir[MAX_PATH] = {0}; + WIN32_FIND_DATA FindFileData = {0}; + + StringCchPrintf(dir,MAX_PATH,L"%s\\*",indir); + HANDLE hFind = FindFirstFile(dir, &FindFileData); + if (hFind == INVALID_HANDLE_VALUE) return; + + do + { + if (wcscmp(FindFileData.cFileName,L".") && wcscmp(FindFileData.cFileName,L"..")) + { + wchar_t fullfile[MAX_PATH] = {0}; + StringCchPrintf(fullfile,MAX_PATH,L"%s\\%s",indir,FindFileData.cFileName); + + if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //file is directory + { + fileProbe(fullfile); //call until we have found a file + } + else // found a file! + { + wchar_t * ext = wcsrchr(FindFileData.cFileName,'.'); + if (!ext) continue; //no files with extensions in the directory + ext++; + + int isM3UPlaylistFormat = CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, ext, -1, L"m3u", -1)-2; + if (isM3UPlaylistFormat == 0) // its a playlist + { + AndroidPlaylist *playlist = new AndroidPlaylist(*this, fullfile, false); + androidPlaylists.push_back(playlist); + continue; + } //its a file + + if (supportedFormat(fullfile, supportedFormats)) //check extension + { + AndroidSong *s = new AndroidSong(); + lstrcpynW(s->filename, fullfile, MAX_PATH); + this->getMasterPlaylist()->push_back(s); //add track to alltrack list (playlist 0) + } + } + } + } + while (FindNextFile(hFind, &FindFileData) != 0); + + FindClose(hFind); +} + +int AndroidDevice::getFileInfoW(const wchar_t *filename, const wchar_t *metadata, wchar_t *dest, size_t len) +{ + dest[0]=0; + return AGAVE_API_METADATA->GetExtendedFileInfo(filename, metadata, dest, len); +} + +// read all metadata from the metadata wasabi service +void AndroidDevice::fillMetaData(AndroidSong *t) +{ + if (!t->filled) + { + wchar_t tmp[1024] = {0}; + if (getFileInfoW(t->filename,L"artist",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0]) + { + StringCchCopyW(t->artist, FIELD_LENGTH, tmp); + t->filled = true; + } + + if (getFileInfoW(t->filename,L"title",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0]) + { + StringCchCopyW(t->title, FIELD_LENGTH, tmp); + t->filled = true; + } + + if (getFileInfoW(t->filename,L"album",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0]) + { + StringCchCopyW(t->album, FIELD_LENGTH, tmp); + t->filled = true; + } + + if (getFileInfoW(t->filename,L"composer",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0]) + { + StringCchCopyW(t->composer, FIELD_LENGTH, tmp); + t->filled = true; + } + + if (getFileInfoW(t->filename,L"publisher",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0]) + { + StringCchCopyW(t->publisher, FIELD_LENGTH, tmp); + t->filled = true; + } + + if (getFileInfoW(t->filename,L"albumartist",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0]) + { + StringCchCopyW(t->albumartist, FIELD_LENGTH, tmp); + t->filled = true; + } + + if (getFileInfoW(t->filename, L"length",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0]) + { + t->length = _wtoi(tmp); + t->filled = true; + } + + if (getFileInfoW(t->filename, L"track",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0]) + { + t->track = _wtoi(tmp); + t->filled = true; + } + + if (getFileInfoW(t->filename, L"disc",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0]) + { + t->discnum = _wtoi(tmp); + t->filled = true; + } + + if (getFileInfoW(t->filename, L"genre",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0]) + { + StringCchCopyW(t->genre, FIELD_LENGTH, tmp); + t->filled = true; + } + + if (getFileInfoW(t->filename, L"year",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0]) + { + if (!wcsstr(tmp,L"__") && !wcsstr(tmp,L"/") && !wcsstr(tmp,L"\\") && !wcsstr(tmp,L".")) + { + wchar_t *p = tmp; + while (p && *p) + { + if (*p == L'_') *p=L'0'; + p++; + } + t->year = _wtoi(tmp); + t->filled = true; + } + } + + if (getFileInfoW(t->filename, L"bitrate",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0]) + { + t->bitrate = _wtoi(tmp); + t->filled = true; + } + + if (getFileInfoW(t->filename, L"size",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0]) + { + t->size = _wtoi(tmp); + t->filled = true; + } + else + { + t->size = fileSize(t->filename); + t->filled = true; + } + + if (getFileInfoW(t->filename, L"playcount",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0]) + { + t->playcount = _wtoi(tmp); + t->filled = true; + } + } +} + +int AndroidDevice::openDeviceDatabase() +{ + Nullsoft::Utility::AutoLock lock(dbcs); + if (!discDB) + { + discDB = NDE_CreateDatabase(plugin.hDllInstance); + } + return NDE_ANDROID_SUCCESS; +} + +void AndroidDevice::createDeviceFields() +{ + // create defaults + NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_FILENAME, L"filename", FIELD_FILENAME); + NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_ARTIST, L"artist", FIELD_STRING); + NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_ALBUM, L"album", FIELD_STRING); + NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_TITLE, L"title", FIELD_STRING); + NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_GENRE, L"genre", FIELD_STRING); + NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_ALBUM_ARTIST, L"albumartist", FIELD_STRING); + NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_PUBLISHER, L"publisher", FIELD_STRING); + NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_COMPOSER, L"composer", FIELD_STRING); + NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_YEAR, L"year", FIELD_INTEGER); + NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_TRACK, L"track", FIELD_INTEGER); + NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_BITRATE, L"bitrate", FIELD_INTEGER); + NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_DISC_NUMBER, L"discnumber", FIELD_INTEGER); + NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_LENGTH, L"length", FIELD_INTEGER); + NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_SIZE, L"size", FIELD_INTEGER); + NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_PLAY_COUNT, L"playcount", FIELD_INTEGER); + NDE_Table_PostColumns(deviceTable); + NDE_Table_AddIndexByIDW(deviceTable, 0, L"filename"); +} + +int AndroidDevice::openDeviceTable() +{ + Nullsoft::Utility::AutoLock lock(dbcs); + int ret = openDeviceDatabase(); + if (ret != NDE_ANDROID_SUCCESS) + return ret; + + if (!deviceTable) + { + deviceTable = NDE_Database_OpenTable(discDB, ndeDataFile, ndeIndexFile,NDE_OPEN_ALWAYS,NDE_CACHE); + if (deviceTable) + { + createDeviceFields(); + } + } + return deviceTable?NDE_ANDROID_SUCCESS:NDE_ANDROID_FAILURE; +} + +/* static */ +void AndroidDevice::CloseDatabase() +{ + if (discDB) + { + NDE_DestroyDatabase(discDB); + discDB=0; + } +} + +void AndroidDevice::closeDeviceTable() +{ + if (deviceTable) + { + NDE_Table_Sync(deviceTable); + NDE_Database_CloseTable(discDB, deviceTable); + deviceTable=0; + } +} + +static void db_setFieldInt(nde_scanner_t s, unsigned char id, int data) +{ + nde_field_t f = NDE_Scanner_GetFieldByID(s, id); + if (!f) f = NDE_Scanner_NewFieldByID(s, id); + NDE_IntegerField_SetValue(f, data); +} + +static void db_setFieldString(nde_scanner_t s, unsigned char id, const wchar_t *data) +{ + nde_field_t f = NDE_Scanner_GetFieldByID(s, id); + if (!f) f = NDE_Scanner_NewFieldByID(s, id); + NDE_StringField_SetString(f, data); +} + +static void db_removeField(nde_scanner_t s, unsigned char id) +{ + nde_field_t f = NDE_Scanner_GetFieldByID(s, id); + if (f) + { + NDE_Scanner_DeleteField(s, f); + } +} + +static int db_getFieldInt(nde_scanner_t s, unsigned char id, int defaultVal) +{ + nde_field_t f = NDE_Scanner_GetFieldByID(s, id); + if (f) + { + return NDE_IntegerField_GetValue(f); + } + else + { + return defaultVal; + } +} + +static wchar_t* db_getFieldString(nde_scanner_t s, unsigned char id) +{ + nde_field_t f = NDE_Scanner_GetFieldByID(s, id); + if (f) + { + return NDE_StringField_GetString(f); + } + else + { + return 0; + } +} + + +void AndroidDevice::refreshNDECache(void) +{ + tag(); +} + + +AndroidSong *AndroidDevice::findSongInMasterPlaylist(const wchar_t *songfn) +{ + AndroidPlaylist* mpl = this->getMasterPlaylist(); + return mpl->FindSong(songfn); +} + +void AndroidDevice::tag(void) +{ + /** + loop thru the newly probed disk + check for updates on each of the songs + if there is an update or if metadata does not exist for the file, re-read the metadata + if there is no update and the song is found in the master playlist, just read from the db + */ + AndroidPlaylist *mpl = this->getMasterPlaylist(); + int top = (int)mpl->size(); + + //first load in all songs data from ID3 - this is what we were trying to avoid + for (int i = 0; i < top; i++) + { + AndroidSong *t = (AndroidSong *)mpl->at(i); + + // now check if this song has changed + // check if the nde cache exists in the first place + if (songChanged(t) || !readRecordFromDB(t)) + { + this->fillMetaData(t); + // now since we've refreshed the metadata write to NDE + this->writeRecordToDB(t); + } + } +} + +// check change in filetimes for the song +bool AndroidDevice::songChanged(AndroidSong* song) +{ + if (!song) return true; + if (!PathFileExists(ndeDataFile)) return true; + + //For fLastAccess/LastWrite information, use GetFileAttributesEx + WIN32_FILE_ATTRIBUTE_DATA cacheFileInfo, tempInfo; + GetFileAttributesExW(ndeDataFile, GetFileExInfoStandard, (LPVOID)&cacheFileInfo); + + if (song->filename) + { + GetFileAttributesExW(song->filename, GetFileExInfoStandard, (LPVOID)&tempInfo); + } + else + { + return true; + } + + //cachetime - song time + if (CompareFileTime(&cacheFileInfo.ftLastWriteTime, &tempInfo.ftLastWriteTime) < 0) + { + return true; + } + return false; +} + +// read metadata for a specific song from the NDE cache +bool AndroidDevice::readRecordFromDB(AndroidSong* song) +{ + if (!song) return false; + + Nullsoft::Utility::AutoLock lock(dbcs); + openDeviceTable(); + nde_scanner_t scanner = NDE_Table_CreateScanner(deviceTable); + + if (NDE_Scanner_LocateFilename(scanner, DEVICEVIEW_COL_FILENAME, FIRST_RECORD, song->filename)) + { + nde_field_t artist = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_ARTIST); + wchar_t* artistString = NDE_StringField_GetString(artist); + lstrcpyn(song->artist, artistString, FIELD_LENGTH); + + nde_field_t album = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_ALBUM); + wchar_t* albumString = NDE_StringField_GetString(album); + lstrcpyn(song->album, albumString, FIELD_LENGTH); + + nde_field_t albumArtist = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_ALBUM_ARTIST); + wchar_t* albumArtistString = NDE_StringField_GetString(albumArtist); + lstrcpyn(song->albumartist, albumArtistString, FIELD_LENGTH); + + nde_field_t publisher = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_PUBLISHER); + wchar_t* publisherString = NDE_StringField_GetString(publisher); + lstrcpyn(song->publisher, publisherString, FIELD_LENGTH); + + nde_field_t composer = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_COMPOSER); + wchar_t* composerString = NDE_StringField_GetString(composer); + lstrcpyn(song->composer, composerString, FIELD_LENGTH); + + nde_field_t title = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_TITLE); + wchar_t* titleString = NDE_StringField_GetString(title); + lstrcpyn(song->title, titleString, FIELD_LENGTH); + + nde_field_t genre = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_GENRE); + wchar_t* genreString = NDE_StringField_GetString(genre); + lstrcpyn(song->genre, genreString, FIELD_LENGTH); + + nde_field_t track = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_TRACK); + song->track = NDE_IntegerField_GetValue(track); + + nde_field_t year = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_YEAR); + song->year = NDE_IntegerField_GetValue(year); + + nde_field_t discNumber = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_DISC_NUMBER); + song->discnum = NDE_IntegerField_GetValue(discNumber); + + nde_field_t length = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_LENGTH); + song->length = NDE_IntegerField_GetValue(length); + + nde_field_t bitrate = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_BITRATE); + int bitrateInt= NDE_IntegerField_GetValue(bitrate); + song->bitrate = bitrateInt; + + nde_field_t size = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_SIZE); + int sizeInt= NDE_IntegerField_GetValue(size); + song->size = sizeInt; + + nde_field_t playcount = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_PLAY_COUNT); + song->playcount = NDE_IntegerField_GetValue(playcount); + } + else + { + return false; + } + + NDE_Table_DestroyScanner(deviceTable, scanner); + //closeDeviceTable(); + return true; +} + +// write a single record to the nde database +void AndroidDevice::writeRecordToDB(AndroidSong* songToPrint) +{ + Nullsoft::Utility::AutoLock lock(dbcs); + openDeviceTable(); + nde_scanner_t s = NDE_Table_CreateScanner(deviceTable); + + if (! NDE_Scanner_LocateFilename(s, DEVICEVIEW_COL_FILENAME, FIRST_RECORD, songToPrint->filename)) + { + NDE_Scanner_New(s); + } + + if (songToPrint->filename) + { + db_setFieldString(s, DEVICEVIEW_COL_FILENAME, songToPrint->filename); + } + + if (songToPrint->artist) + { + db_setFieldString(s, DEVICEVIEW_COL_ARTIST, songToPrint->artist); + } + + if (songToPrint->albumartist) + { + db_setFieldString(s, DEVICEVIEW_COL_ALBUM_ARTIST, songToPrint->albumartist); + } + + if (songToPrint->publisher) + { + db_setFieldString(s, DEVICEVIEW_COL_PUBLISHER, songToPrint->publisher); + } + + if (songToPrint->composer) + { + db_setFieldString(s, DEVICEVIEW_COL_COMPOSER, songToPrint->composer); + } + + if (songToPrint->album) + { + db_setFieldString(s, DEVICEVIEW_COL_ALBUM, songToPrint->album); + } + + if (songToPrint->title) + { + db_setFieldString(s, DEVICEVIEW_COL_TITLE, songToPrint->title); + } + + if (songToPrint->genre) + { + db_setFieldString(s, DEVICEVIEW_COL_GENRE, songToPrint->genre); + } + + if (songToPrint->year) + { + db_setFieldInt(s, DEVICEVIEW_COL_YEAR, songToPrint->year); + } + + if (songToPrint->track) + { + db_setFieldInt(s, DEVICEVIEW_COL_TRACK, songToPrint->track); + } + + if (songToPrint->bitrate) + { + db_setFieldInt(s, DEVICEVIEW_COL_BITRATE, songToPrint->bitrate); + } + + if (songToPrint->discnum) + { + db_setFieldInt(s, DEVICEVIEW_COL_DISC_NUMBER, songToPrint->discnum); + } + + if (songToPrint->length) + { + db_setFieldInt(s, DEVICEVIEW_COL_LENGTH, songToPrint->length); + } + + if (songToPrint->size) + { + db_setFieldInt(s, DEVICEVIEW_COL_SIZE, (int)songToPrint->size); + } + + if (songToPrint->playcount) + { + db_setFieldInt(s, DEVICEVIEW_COL_PLAY_COUNT, songToPrint->playcount); + } + NDE_Scanner_Post(s); + NDE_Table_DestroyScanner(deviceTable, s); + // NDE_Table_Sync(deviceTable); + //closeDeviceTable(); +} + +void AndroidDevice::setupTranscoder() +{ + if (transcoder) SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(WPARAM)transcoder,PMP_IPC_RELEASE_TRANSCODER); + transcoder = (Transcoder*)SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(WPARAM)this,PMP_IPC_GET_TRANSCODER); + if (!transcoder) return; + + wchar_t * p = supportedFormats; + while (p && *p) + { + wchar_t * np = wcschr(p,L';'); + if (np) *np = 0; + transcoder->AddAcceptableFormat(p); + if (np) + { + *np = L';'; + p=np+1; + } + else return; + } +} + +BOOL CALLBACK browseEnumProc(HWND hwnd, LPARAM lParam) +{ + wchar_t cl[32] = {0}; + GetClassNameW(hwnd, cl, ARRAYSIZE(cl)); + if (!lstrcmpiW(cl, WC_TREEVIEW)) + { + PostMessage(hwnd, TVM_ENSUREVISIBLE, 0, (LPARAM)TreeView_GetSelection(hwnd)); + return FALSE; + } + + return TRUE; +} + +wchar_t pldir[MAX_PATH] = {0}; +int CALLBACK WINAPI BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) +{ + if (uMsg == BFFM_INITIALIZED) + { + SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, (LPARAM)pldir); + + // this is not nice but it fixes the selection not working correctly on all OSes + EnumChildWindows(hwnd, browseEnumProc, 0); + } + return 0; +} + +static INT_PTR CALLBACK prefs_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) +{ + static AndroidDevice * dev; + switch (uMsg) + { + case WM_INITDIALOG: + { + prefsParam* p = (prefsParam*)lParam; + dev = (AndroidDevice*)p->dev; + p->config_tab_init(hwndDlg,p->parent); + SetDlgItemTextW(hwndDlg,IDC_NAMEFORMAT,dev->songFormat); + SetDlgItemTextW(hwndDlg,IDC_PLDIR,dev->pldir); + SetDlgItemTextW(hwndDlg,IDC_SUPPORTEDFORMATS,dev->supportedFormats); + if (dev->purgeFolders[0]=='1') CheckDlgButton(hwndDlg,IDC_PURGEFOLDERS,BST_CHECKED); + else CheckDlgButton(hwndDlg,IDC_PURGEFOLDERS,BST_UNCHECKED); + + SendDlgItemMessageW(hwndDlg,IDC_PL_WRITE_COMBO,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_SLASH_AT_START)); + SendDlgItemMessageW(hwndDlg,IDC_PL_WRITE_COMBO,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_DOT_AT_START)); + SendDlgItemMessageW(hwndDlg,IDC_PL_WRITE_COMBO,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_NO_SLASH_OR_DOT)); + SendDlgItemMessage(hwndDlg,IDC_PL_WRITE_COMBO,CB_SETCURSEL,dev->pl_write_mode,0); + SetDlgItemTextW(hwndDlg,IDC_PL_WRITE_EG,WASABI_API_LNGSTRINGW(IDS_EG_SLASH+dev->pl_write_mode)); + } + break; + + case WM_COMMAND: + switch (LOWORD(wParam)) + { + case IDC_NAMEFORMAT: + if (HIWORD(wParam)==EN_CHANGE) + { + GetDlgItemTextW(hwndDlg,IDC_NAMEFORMAT,dev->songFormat,sizeof(dev->songFormat)/sizeof(wchar_t)); + WritePrivateProfileStringW(L"pmp_android",L"songFormat",dev->songFormat,dev->iniFile); + } + break; + + case IDC_PLDIR: + if (HIWORD(wParam)==EN_CHANGE) + { + GetDlgItemTextW(hwndDlg,IDC_PLDIR,dev->pldir,sizeof(dev->pldir)/sizeof(wchar_t)); + WritePrivateProfileStringW(L"pmp_android",L"pldir",dev->pldir,dev->iniFile); + } + break; + + case IDC_SUPPORTEDFORMATS: + if (HIWORD(wParam)==EN_CHANGE) + { + GetDlgItemTextW(hwndDlg,IDC_SUPPORTEDFORMATS,dev->supportedFormats,sizeof(dev->supportedFormats)/sizeof(wchar_t)); + WritePrivateProfileStringW(L"pmp_android",L"supportedFormats",dev->supportedFormats,dev->iniFile); + } + break; + + case IDC_REFRESHCACHE: + { + char titleStr[32] = {0}; + dev->refreshNDECache(); + MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_CACHE_UPDATED), + WASABI_API_LNGSTRING_BUF(IDS_SUCCESS,titleStr,32),MB_OK); + break; + } + + case IDC_PL_WRITE_COMBO: + { + dev->pl_write_mode = (int)SendMessage((HWND)lParam,CB_GETCURSEL,0,0); + SetDlgItemTextW(hwndDlg,IDC_PL_WRITE_EG,WASABI_API_LNGSTRINGW(IDS_EG_SLASH+dev->pl_write_mode)); + + wchar_t tmp[16] = {0}; + StringCchPrintf(tmp, 16, L"%d", dev->pl_write_mode); + WritePrivateProfileStringW(L"pmp_android",L"pl_write_mode",tmp,dev->iniFile); + break; + } + + case IDC_FILENAMEHELP: + { + char titleStr[64] = {0}; + MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_FILENAME_FORMATTING_INFO), + WASABI_API_LNGSTRING_BUF(IDS_FILENAME_FORMAT_HELP,titleStr,64),MB_OK); + } + break; + + case IDC_PLBROWSE: + { + wchar_t *tempWS = 0; + BROWSEINFO bi = {0}; + LPMALLOC lpm = 0; + wchar_t bffFileName[MAX_PATH] = {0}; + + bi.hwndOwner = hwndDlg; + bi.pszDisplayName = bffFileName; + bi.lpszTitle = WASABI_API_LNGSTRINGW(IDS_SELECT_FOLDER_TO_LOAD_PLAYLISTS); + bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_EDITBOX | BIF_NEWDIALOGSTYLE; + bi.lpfn = BrowseCallbackProc; + lstrcpynW(pldir, dev->pldir, MAX_PATH); + LPITEMIDLIST iil = SHBrowseForFolder(&bi); + if (iil) + { + SHGetPathFromIDListW(iil,bffFileName); + SHGetMalloc(&lpm); + // path is now in bffFileName + } + + tempWS = _wcsdup(bffFileName); + if (tempWS[0] == dev->drive) + { + lstrcpynW(dev->pldir, tempWS, MAX_PATH); + SetDlgItemText(hwndDlg,IDC_PLDIR,tempWS); + } + else + { + if (bffFileName[0] != 0) //dont print error if the user selected 'cancel' + { + char titleStr[32] = {0}; + MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_ERR_SELECTED_PATH_NOT_ON_DEVICE), + WASABI_API_LNGSTRING_BUF(IDS_ERROR,titleStr,32), MB_OK); + } + } + free(tempWS); + } + break; + + case IDC_FORMATSHELP: + { + char titleStr[64] = {0}; + MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_SUPPORTED_FORMAT_INFO), + WASABI_API_LNGSTRING_BUF(IDS_SUPPORTED_FORMAT_HELP,titleStr,64),MB_OK); + } + break; + + case IDC_PURGEFOLDERS: + { + if (IsDlgButtonChecked(hwndDlg,IDC_PURGEFOLDERS) == BST_CHECKED) + { + wcsncpy(dev->purgeFolders,L"1",2); + } + else + { + wcsncpy(dev->purgeFolders,L"0",2); + } + WritePrivateProfileStringW(L"pmp_android",L"purgeFolders",dev->purgeFolders,dev->iniFile); + } + break; + + case IDC_RESCAN: + { + //update changes + SetFileAttributesW(dev->iniFile,FILE_ATTRIBUTE_HIDDEN); + + wchar_t driveletter = dev->drive; //hold on to driveletter before it goes away + //disconnect + dev->Close(); + + //connect + pmpDeviceLoading load; + dev = new AndroidDevice(driveletter,&load); + char titleStr[64] = {0}; + MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_RESCAN_COMPLETE_SAVED), + WASABI_API_LNGSTRING_BUF(IDS_RESCAN_COMPLETE,titleStr,64),MB_OK); + } + break; + } + } + return 0; +} + +// update a track with new metadata (string) +void AndroidDevice::updateTrackField(AndroidSong* song, unsigned int col, const void* newValue, int fieldType) +{ + if (!song) return; + + Nullsoft::Utility::AutoLock lock(dbcs); + openDeviceTable(); + nde_scanner_t s = NDE_Table_CreateScanner(deviceTable); + + if (NDE_Scanner_LocateFilename(s, DEVICEVIEW_COL_FILENAME, FIRST_RECORD, song->filename)) + { + switch (fieldType) + { + case FIELD_STRING: + db_setFieldString(s, col, (wchar_t *)(newValue)); + break; + case FIELD_INTEGER: + db_setFieldInt(s, col, *((int *)newValue)); + default: + break; + } + } + + NDE_Scanner_Post(s); + NDE_Table_DestroyScanner(deviceTable, s); + // NDE_Table_Sync(deviceTable); + //closeDeviceTable(); +} + +AndroidSong::AndroidSong() +{ + filename[0]=artist[0]=album[0]=title[0]=genre[0]=albumartist[0]=publisher[0]=composer[0]=0; + filled=year=track=length=discnum=bitrate=playcount=(int)(size=0); +} + + +AndroidArt::AndroidArt(ARGB32 *bits, int w, int h) :bits(bits), w(w), h(h) +{ +} + +AndroidArt::~AndroidArt() +{ + if (bits) + WASABI_API_MEMMGR->sysFree(bits); +}
\ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_android/androiddevice.h b/Src/Plugins/Portable/pmp_android/androiddevice.h new file mode 100644 index 00000000..331a808f --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/androiddevice.h @@ -0,0 +1,240 @@ +#pragma once +#include <windows.h> +#include <Dbt.h> + +#include "../../Library/ml_pmp/transcoder.h" +#include "../../Library/ml_pmp/pmp.h" + +#include "../nde/nde_c.h" +#include "../nu/AutoLock.h" + +#include <vector> + +#define WINAMP_ANDROID_MARKER_FILE L"\\.winamp\\winamp.info" + +//Filename="E:\Howling Bells - Into The Chaos.MP3" +//Artist="Howling Bells" +//Album="E:" +//Title="Into The Chaos" +//Genre="" +//AlbumArtist="" +//Publisher="" +//Composer="" +//Year="0" +//Track="0" +//Bitrate="0" +//Playcount="0" +//Discnum="0" +//Length="0" +//Size="7767879" +enum +{ + NDE_ANDROID_FAILURE=0, + NDE_ANDROID_SUCCESS=1, +}; + +enum +{ + DEVICEVIEW_COL_FILENAME = 0, + DEVICEVIEW_COL_ARTIST=1, + DEVICEVIEW_COL_ALBUM=2, + DEVICEVIEW_COL_TITLE=3, + DEVICEVIEW_COL_GENRE=4, + DEVICEVIEW_COL_ALBUM_ARTIST=5, + DEVICEVIEW_COL_PUBLISHER=6, + DEVICEVIEW_COL_COMPOSER=7, + DEVICEVIEW_COL_YEAR=8, + DEVICEVIEW_COL_TRACK=9, + DEVICEVIEW_COL_BITRATE=10, + DEVICEVIEW_COL_DISC_NUMBER=11, + DEVICEVIEW_COL_LENGTH=12, + DEVICEVIEW_COL_SIZE=13, + DEVICEVIEW_COL_PLAY_COUNT=14, +}; + +#define TAG_CACHE L"winamp_metadata.dat" +#define FIELD_LENGTH 1024 + +class AndroidSong { +public: + AndroidSong(); + wchar_t filename[MAX_PATH]; + wchar_t artist[FIELD_LENGTH]; + wchar_t album[FIELD_LENGTH]; + wchar_t title[FIELD_LENGTH]; + wchar_t genre[FIELD_LENGTH]; + wchar_t albumartist[FIELD_LENGTH]; + wchar_t publisher[FIELD_LENGTH]; + wchar_t composer[FIELD_LENGTH]; + int year,track,length,discnum,bitrate,playcount; + __int64 size; + BOOL filled; + wchar_t ext[ 6 ]; +}; + +enum DeviceType { + TYPE_OTHER, + TYPE_PSP, +}; + +class AndroidPlaylist; + +class AndroidArt +{ +public: + AndroidArt(ARGB32 *bits, int w, int h); + ~AndroidArt(); + ARGB32 *bits; + int w,h; +}; + +class AndroidDevice : public Device +{ +public: + AndroidDevice(wchar_t drive, pmpDeviceLoading * load); + ~AndroidDevice(); + AndroidDevice(); + void fileProbe(wchar_t * indir); + void tag(void); //load ID3 tags from cache or mp3 file + void createDeviceFields(); + int openDeviceDatabase(); + int openDeviceTable(); + void closeDeviceTable(); + + static void CloseDatabase(); + void SeekToBegininngOfDevice(nde_scanner_t s); + void refreshNDECache(void); + void fillMetaData(AndroidSong *s); + static int getFileInfoW(const wchar_t *filename, const wchar_t *metadata, wchar_t *dest, size_t len); + void setupTranscoder(); + AndroidPlaylist* getMasterPlaylist(); + AndroidSong* findSongInMasterPlaylist(const wchar_t *songfn); + void writeRecordToDB(AndroidSong* songToPrint); + ////////////////////////////////////////// + + virtual __int64 getDeviceCapacityAvailable(); // in bytes + virtual __int64 getDeviceCapacityTotal(); // in bytes + + virtual void Eject(); // if you ejected successfully, you MUST call PMP_IPC_DEVICEDISCONNECTED and delete this; + virtual void Close(); // save any changes, and call PMP_IPC_DEVICEDISCONNECTED AND delete this; + + // return 0 for success, -1 for failed or cancelled + virtual int 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 + ); + virtual int trackAddedToTransferQueue(const itemRecordW *track); // return 0 to accept, -1 for "not enough space", -2 for "incorrect format" + virtual void trackRemovedFromTransferQueue(const itemRecordW *track); + + // 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. + virtual __int64 getTrackSizeOnDevice(const itemRecordW *track); + + virtual void deleteTrack(songid_t songid); // physically remove from device. Be sure to remove it from all the playlists! + + virtual void commitChanges(); // optional. Will be called at a good time to save changes + + virtual int getPlaylistCount(); // always at least 1. playlistnumber 0 is the Master Playlist containing all tracks. + // PlaylistName(0) should return the name of the device. + virtual void getPlaylistName(int playlistnumber, wchar_t *buf, int len); + virtual int getPlaylistLength(int playlistnumber); + virtual songid_t getPlaylistTrack(int playlistnumber,int songnum); // returns a songid + + virtual void setPlaylistName(int playlistnumber, const wchar_t *buf); // with playlistnumber==0, set the name of the device. + virtual void playlistSwapItems(int playlistnumber, int posA, int posB); // swap the songs at position posA and posB + virtual void sortPlaylist(int playlistnumber, int sortBy); + virtual void addTrackToPlaylist(int playlistnumber, songid_t songid); // adds songid to the end of the playlist + virtual void removeTrackFromPlaylist(int playlistnumber, int songnum); //where songnum is the position of the track in the playlist + + virtual void deletePlaylist(int playlistnumber); + virtual int newPlaylist(const wchar_t *name); // create empty playlist, returns playlistnumber. -1 for failed. + + virtual void getTrackArtist(songid_t songid, wchar_t *buf, int len); + virtual void getTrackAlbum(songid_t songid, wchar_t *buf, int len); + virtual void getTrackTitle(songid_t songid, wchar_t *buf, int len); + virtual int getTrackTrackNum(songid_t songid); + virtual int getTrackDiscNum(songid_t songid); + virtual void getTrackGenre(songid_t songid, wchar_t * buf, int len); + virtual int getTrackYear(songid_t songid); + virtual __int64 getTrackSize(songid_t songid); // in bytes + virtual int getTrackLength(songid_t songid); // in millisecs + virtual int getTrackBitrate(songid_t songid); // in kbps + virtual int getTrackPlayCount(songid_t songid); + virtual int getTrackRating(songid_t songid); //0-5 + virtual __time64_t getTrackLastPlayed(songid_t songid); // in unix time format + virtual __time64_t getTrackLastUpdated(songid_t songid); // in unix time format + virtual void getTrackAlbumArtist(songid_t songid, wchar_t *buf, int len); + virtual void getTrackPublisher(songid_t songid, wchar_t *buf, int len); + virtual void getTrackComposer(songid_t songid, wchar_t *buf, int len); + virtual int getTrackType(songid_t songid); + virtual void getTrackExtraInfo(songid_t songid, const wchar_t *field, wchar_t *buf, int len) ; //optional + + // feel free to ignore any you don't support + virtual void setTrackArtist(songid_t songid, const wchar_t *value); + virtual void setTrackAlbum(songid_t songid, const wchar_t *value); + virtual void setTrackTitle(songid_t songid, const wchar_t *value); + virtual void setTrackTrackNum(songid_t songid, int value); + virtual void setTrackDiscNum(songid_t songid, int value); + virtual void setTrackGenre(songid_t songid, const wchar_t *value); + virtual void setTrackYear(songid_t songid, int year); + virtual void setTrackPlayCount(songid_t songid, int value); + virtual void setTrackRating(songid_t songid, int value); + virtual void setTrackLastPlayed(songid_t songid, __time64_t value); // in unix time format + virtual void setTrackLastUpdated(songid_t songid, __time64_t value); // in unix time format + virtual void setTrackAlbumArtist(songid_t songid, const wchar_t *value); + virtual void setTrackPublisher(songid_t songid, const wchar_t *value); + virtual void setTrackComposer(songid_t songid, const wchar_t *value); + virtual void setTrackExtraInfo(songid_t songid, const wchar_t *field, const wchar_t *value) ; //optional + + virtual bool playTracks(songid_t * songidList, int listLength, int startPlaybackAt, bool enqueue); // return false if unsupported + + virtual intptr_t extraActions(intptr_t param1, intptr_t param2, intptr_t param3,intptr_t param4); + + virtual bool copyToHardDriveSupported(); + + virtual __int64 songSizeOnHardDrive(songid_t song); // how big a song will be when copied back. Return -1 for not supported. + + virtual int 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 + ); // -1 for failed/not supported. 0 for success. + + // art functions + virtual void setArt(songid_t songid, void *buf, int w, int h); //buf is in format ARGB32* + virtual pmpart_t getArt(songid_t songid); + virtual void releaseArt(pmpart_t art); + virtual int drawArt(pmpart_t art, HDC dc, int x, int y, int w, int h); + virtual void getArtNaturalSize(pmpart_t art, int *w, int *h); + virtual void setArtNaturalSize(pmpart_t art, int w, int h); + virtual void getArtData(pmpart_t art, void* data); // data ARGB32* is at natural size + virtual bool artIsEqual(pmpart_t a, pmpart_t b); + + // Additional attributes + Transcoder *transcoder; + wchar_t drive; + wchar_t iniFile[MAX_PATH]; + wchar_t pldir[MAX_PATH]; + wchar_t songFormat[MAX_PATH]; + wchar_t supportedFormats[MAX_PATH]; + wchar_t purgeFolders[2]; + int pl_write_mode; // used to determine how the playlists are stored + __int64 transferQueueLength; + std::vector<AndroidPlaylist*> androidPlaylists; + bool loadedUpToDate; //whether or not songs in memory are tagged and correct + + static nde_database_t discDB; + nde_table_t deviceTable; + Nullsoft::Utility::LockGuard dbcs; + wchar_t ndeDataFile[100]; + wchar_t ndeIndexFile[100]; + +private: + // update a track with new metadata (string) + void updateTrackField(AndroidSong* song, unsigned int col, const void* newValue, int fieldType); + bool readRecordFromDB(AndroidSong* song); + bool songChanged(AndroidSong* song); +};
\ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_android/androidplaylist.cpp b/Src/Plugins/Portable/pmp_android/androidplaylist.cpp new file mode 100644 index 00000000..ac4a3076 --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/androidplaylist.cpp @@ -0,0 +1,134 @@ +#include "./androiddevice.h" +#include "./AndroidPlaylist.h" + +#include <shlwapi.h> +#include <strsafe.h> + +// dtor +// cleanup the memory allocated for the vector of songs +AndroidPlaylist::~AndroidPlaylist() +{ + +} + +// this is the constructor that gets called +AndroidPlaylist::AndroidPlaylist(AndroidDevice& d, LPCTSTR fileName, BOOL m) +: device(d), master(m), dirty(false) +{ + StringCbCopyW(filename, sizeof(filename), fileName); + StringCbCopyW(playlistName, sizeof(playlistName), PathFindFileName(fileName)); + StringCbCopyW(playlistPath, sizeof(playlistName), fileName); + PathRemoveFileSpec(playlistPath); +} + + +void AndroidPlaylist::OnFile( const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info ) +{ + if ( filename == NULL ) + return; + + AndroidSong *song = NULL; + //Nullsoft::Utility::AutoLock songs_lock(songs_guard); + song = device.findSongInMasterPlaylist( filename ); + songs.push_back( song ); +} + +size_t AndroidPlaylist::size() +{ + //Nullsoft::Utility::AutoLock songs_lock(songs_guard); + return songs.size(); +} + +AndroidSong *&AndroidPlaylist::at(size_t index) +{ + //Nullsoft::Utility::AutoLock songs_lock(songs_guard); + return songs.at(index); +} + +void AndroidPlaylist::push_back(AndroidSong *callback) +{ + //Nullsoft::Utility::AutoLock songs_lock(songs_guard); + songs.push_back(callback); + dirty=TRUE; +} + +void AndroidPlaylist::RemoveSong(AndroidSong *song) +{ + //Nullsoft::Utility::AutoLock songs_lock(songs_guard); + size_t old_size = songs.size(); + + //songs.eraseAll(song); + auto it = songs.begin(); + while (it != songs.end()) + { + if (*it != song) + { + it++; + continue; + } + + it = songs.erase(it); + } + + if (old_size != songs.size()) + dirty=TRUE; +} + +void AndroidPlaylist::swap(size_t index1, size_t index2) +{ + //Nullsoft::Utility::AutoLock songs_lock(songs_guard); + AndroidSong *temp = songs[index1]; + songs[index1] = songs[index2]; + songs[index2] = temp; + dirty = true; +} + +void AndroidPlaylist::eraseAt(size_t index) +{ +// Nullsoft::Utility::AutoLock songs_lock(songs_guard); + songs.erase(songs.begin() + index); + dirty=true; +} + +static int filenamecmp( const wchar_t *f1, const wchar_t *f2 ) +{ + for ( ;;) + { + wchar_t c1 = *f1++; + wchar_t c2 = *f2++; + if ( !c1 && !c2 ) + return 0; + if ( !c1 ) + return -1; + if ( !c2 ) + return 1; + c1 = towupper( c1 ); + c2 = towupper( c2 ); + if ( c1 == '\\' ) + c1 = '/'; + if ( c2 == '\\' ) + c2 = '/'; + if ( c1 < c2 ) + return -1; + else if ( c1 > c2 ) + return 1; + } +} + +AndroidSong *AndroidPlaylist::FindSong(const wchar_t *filename) +{ + //Nullsoft::Utility::AutoLock songs_lock(songs_guard); + for (SongList::iterator e = songs.begin(); e != songs.end(); e++) + { + if (filenamecmp(filename, (*e)->filename) == 0) + { + return (*e); + } + } + return 0; +} + +#define CBCLASS AndroidPlaylist +START_DISPATCH; +VCB(IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile) +END_DISPATCH;
\ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_android/androidplaylist.h b/Src/Plugins/Portable/pmp_android/androidplaylist.h new file mode 100644 index 00000000..67511736 --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/androidplaylist.h @@ -0,0 +1,49 @@ +#pragma once + +#include <vector> +#include "../playlist/ifc_playlistloadercallback.h" +#include "../nu/AutoLock.h" + +class AndroidDevice; +class AndroidSong; + +class AndroidPlaylist: public ifc_playlistloadercallback +{ +public: + AndroidPlaylist(AndroidDevice& d, LPCTSTR pszPlaylist, BOOL master); + ~AndroidPlaylist(); + +public: + /*** ifc_playlistloadercallback ***/ + void OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info); + +public: + // utility + BOOL isMaster() { return master; } + wchar_t* getFilename() { return filename; } + size_t size(); + AndroidSong *&at(size_t index); + void push_back(AndroidSong *callback); + void RemoveSong(AndroidSong *song); + void swap(size_t index1, size_t index2); + void eraseAt(size_t index); + AndroidSong *FindSong(const wchar_t *filename); + +protected: + RECVS_DISPATCH; +private: + + AndroidDevice &device; + typedef std::vector<AndroidSong*> SongList; + SongList songs; +public: + //Nullsoft::Utility::LockGuard songs_guard; + wchar_t playlistName[MAX_PATH]; + wchar_t playlistPath[MAX_PATH]; + + wchar_t filename[MAX_PATH]; + BOOL master; + BOOL dirty; +}; + + diff --git a/Src/Plugins/Portable/pmp_android/androidplaylistsaver.cpp b/Src/Plugins/Portable/pmp_android/androidplaylistsaver.cpp new file mode 100644 index 00000000..148f2767 --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/androidplaylistsaver.cpp @@ -0,0 +1,74 @@ +#include "./androidplaylistsaver.h" +#include "./androidplaylist.h" +#include "./androiddevice.h" +#include "./api.h" + +#include <strsafe.h> + + +AndroidPlaylistSaver::AndroidPlaylistSaver(LPCTSTR iFilename, LPCTSTR iPlaylistName, AndroidPlaylist *iPlaylist) + : filename((LPTSTR)iFilename), title((LPTSTR)iPlaylistName), playlist(iPlaylist) +{ +} + +AndroidPlaylistSaver::~AndroidPlaylistSaver() +{ +} + +HRESULT AndroidPlaylistSaver::Save() +{ + INT result = WASABI_API_PLAYLISTMNGR->Save(filename, this); + + return (PLAYLISTMANAGER_SUCCESS == result) ? S_OK : E_FAIL; +} + +size_t AndroidPlaylistSaver::GetNumItems() +{ + return playlist->size(); +} + +size_t AndroidPlaylistSaver::GetItem(size_t item, wchar_t *filename, size_t filenameCch) +{ + AndroidSong* song = (AndroidSong *) playlist->at(item); + if (!song) return 0; + + HRESULT hr = StringCchCopyEx(filename, filenameCch, song->filename, NULL, NULL, STRSAFE_IGNORE_NULLS); + if (FAILED(hr)) + *filename = L'\0'; + + return SUCCEEDED(hr); +} + +size_t AndroidPlaylistSaver::GetItemTitle(size_t item, wchar_t *title, size_t titleCch) +{ + AndroidSong* song = (AndroidSong *) playlist->at(item); + if (!song) return 0; + + HRESULT hr = StringCchCopyEx(title, titleCch, song->title, NULL, NULL, STRSAFE_IGNORE_NULLS); + if (FAILED(hr)) + *title = L'\0'; + + return SUCCEEDED(hr); +} + +int AndroidPlaylistSaver::GetItemLengthMs(size_t item) +{ + AndroidSong* song = (AndroidSong *) playlist->at(item); + if (!song) return 0; + + return song->length ? song->length: -1; +} + +size_t AndroidPlaylistSaver::GetItemExtendedInfo(size_t item, const wchar_t *metadata, wchar_t *info, size_t infoCch) +{ + return 0; +} + +#define CBCLASS AndroidPlaylistSaver +START_DISPATCH; +CB(IFC_PLAYLIST_GETNUMITEMS, GetNumItems) +CB(IFC_PLAYLIST_GETITEM, GetItem) +CB(IFC_PLAYLIST_GETITEMTITLE, GetItemTitle) +CB(IFC_PLAYLIST_GETITEMLENGTHMILLISECONDS, GetItemLengthMs) +CB(IFC_PLAYLIST_GETITEMEXTENDEDINFO, GetItemExtendedInfo) +END_DISPATCH;
\ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_android/androidplaylistsaver.h b/Src/Plugins/Portable/pmp_android/androidplaylistsaver.h new file mode 100644 index 00000000..7925fc79 --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/androidplaylistsaver.h @@ -0,0 +1,33 @@ +#pragma once + +#include <wtypes.h> +#include "../playlist/ifc_playlist.h" + +class AndroidPlaylist; + +class AndroidPlaylistSaver : public ifc_playlist +{ + +public: + AndroidPlaylistSaver(LPCTSTR iFilename, LPCTSTR iTitle, AndroidPlaylist * iPlaylist); + virtual ~AndroidPlaylistSaver(); + +public: + /*** ifc_playlist ***/ + size_t GetNumItems(); + size_t GetItem(size_t item, wchar_t *filename, size_t filenameCch); + size_t GetItemTitle(size_t item, wchar_t *title, size_t titleCch); + int GetItemLengthMs(size_t item); // TODO: maybe microsecond for better resolution? + size_t GetItemExtendedInfo(size_t item, const wchar_t *metadata, wchar_t *info, size_t infoCch); + + HRESULT Save(); +protected: + RECVS_DISPATCH; + +protected: + LPTSTR title; + LPTSTR filename; + AndroidPlaylist *playlist; +}; + + diff --git a/Src/Plugins/Portable/pmp_android/api.cpp b/Src/Plugins/Portable/pmp_android/api.cpp new file mode 100644 index 00000000..da21b0a9 --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/api.cpp @@ -0,0 +1,64 @@ +#include "../../Library/ml_pmp/pmp.h" +#include "api.h" +#include <api/service/waservicefactory.h> + +api_language *WASABI_API_LNG = 0; +HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0; + +// Metadata service +api_metadata *AGAVE_API_METADATA=0; +api_playlistmanager *WASABI_API_PLAYLISTMNGR=0; +api_threadpool *WASABI_API_THREADPOOL=0; +api_albumart *AGAVE_API_ALBUMART=0; +api_memmgr *WASABI_API_MEMMGR=0; +api_application *WASABI_API_APP=0; +api_devicemanager *AGAVE_API_DEVICEMANAGER = 0; + +extern PMPDevicePlugin plugin; + +template <class api_T> +void ServiceBuild(api_T *&api_t, GUID factoryGUID_t) +{ + if (plugin.service) + { + waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t); + if (factory) + api_t = reinterpret_cast<api_T *>( factory->getInterface() ); + } +} + +template <class api_T> +void ServiceRelease(api_T *api_t, GUID factoryGUID_t) +{ + if (plugin.service && api_t) + { + waServiceFactory *factory = plugin.service->service_getServiceByGuid(factoryGUID_t); + if (factory) + factory->releaseInterface(api_t); + } + api_t = NULL; +} + +void WasabiInit() +{ + ServiceBuild(WASABI_API_LNG, languageApiGUID); + ServiceBuild(AGAVE_API_METADATA, api_metadataGUID); + ServiceBuild(WASABI_API_PLAYLISTMNGR, api_playlistmanagerGUID); + ServiceBuild(WASABI_API_THREADPOOL, ThreadPoolGUID); + ServiceBuild(AGAVE_API_ALBUMART, albumArtGUID); + ServiceBuild(WASABI_API_MEMMGR, memMgrApiServiceGuid); + ServiceBuild(WASABI_API_APP, applicationApiServiceGuid); + ServiceBuild(AGAVE_API_DEVICEMANAGER, DeviceManagerGUID); +} + +void WasabiQuit() +{ + ServiceRelease(WASABI_API_LNG, languageApiGUID); + ServiceRelease(WASABI_API_PLAYLISTMNGR, api_playlistmanagerGUID); + ServiceRelease(AGAVE_API_METADATA, api_metadataGUID); + ServiceRelease(WASABI_API_THREADPOOL, ThreadPoolGUID); + ServiceRelease(AGAVE_API_ALBUMART, albumArtGUID); + ServiceRelease(WASABI_API_MEMMGR, memMgrApiServiceGuid); + ServiceRelease(WASABI_API_APP, applicationApiServiceGuid); + ServiceRelease(AGAVE_API_DEVICEMANAGER, DeviceManagerGUID); +}
\ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_android/api.h b/Src/Plugins/Portable/pmp_android/api.h new file mode 100644 index 00000000..f7cfd662 --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/api.h @@ -0,0 +1,34 @@ +#pragma once + +#include "../Agave/Language/api_language.h" + +#include "../Agave/Metadata/api_metadata.h" +extern api_metadata *metadataApi; +#define AGAVE_API_METADATA metadataApi + +#include "../playlist/api_playlistmanager.h" +extern api_playlistmanager *playlistManagerApi; +#define WASABI_API_PLAYLISTMNGR playlistManagerApi + +#include "../nu/threadpool/api_threadpool.h" +extern api_threadpool *threadPoolApi; +#define WASABI_API_THREADPOOL threadPoolApi + +#include "../Agave/AlbumArt/api_albumart.h" +extern api_albumart *albumArtApi; +#define AGAVE_API_ALBUMART albumArtApi + +#include <api/memmgr/api_memmgr.h> +extern api_memmgr *memmgr; +#define WASABI_API_MEMMGR memmgr + +#include <api/application/api_application.h> +extern api_application *applicationApi; +#define WASABI_API_APP applicationApi + +#include "../devices/api_devicemanager.h" +extern api_devicemanager *deviceManagerApi; +#define AGAVE_API_DEVICEMANAGER deviceManagerApi + +void WasabiInit(); +void WasabiQuit(); diff --git a/Src/Plugins/Portable/pmp_android/deviceprovider.cpp b/Src/Plugins/Portable/pmp_android/deviceprovider.cpp new file mode 100644 index 00000000..05e73d21 --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/deviceprovider.cpp @@ -0,0 +1,343 @@ +#include "api.h" +#include "./deviceprovider.h" +#include "../devices/api_devicemanager.h" + +extern PMPDevicePlugin plugin; +void connectDrive(wchar_t drive, bool checkSize, bool checkBlacklist); + +static size_t tlsIndex = (size_t)-1; + +static BOOL +DiscoveryProvider_RegisterCancelSwitch(BOOL *cancelSwitch) +{ + if ((size_t)-1 != tlsIndex && + NULL != WASABI_API_APP) + { + WASABI_API_APP->SetThreadStorage(tlsIndex, cancelSwitch); + return TRUE; + } + + return FALSE; +} + +static BOOL +DiscoveryProvider_GetCancelSwitchOn() +{ + if ((size_t)-1 != tlsIndex && + NULL != WASABI_API_APP) + { + BOOL *cancelSwitch = (BOOL*)WASABI_API_APP->GetThreadStorage(tlsIndex); + if (NULL != cancelSwitch && + FALSE != *cancelSwitch) + { + return TRUE; + } + } + return FALSE; +} + +static void +DeviceProvider_DriverEnumCb(wchar_t drive, unsigned int type) +{ + if (DRIVE_REMOVABLE == type && + FALSE == DiscoveryProvider_GetCancelSwitchOn()) + { + connectDrive(drive,true,true); + } +} + +DeviceProvider::DeviceProvider() + : ref(1), activity(0), manager(NULL), readyEvent(NULL), cancelDiscovery(FALSE) +{ + InitializeCriticalSection(&lock); + enumerator = (ENUMDRIVES)SendMessageW(plugin.hwndPortablesParent, + WM_PMP_IPC, 0, PMP_IPC_ENUM_ACTIVE_DRIVES); +} + +DeviceProvider::~DeviceProvider() +{ + CancelDiscovery(); + + if (NULL != readyEvent) + CloseHandle(readyEvent); + + DeleteCriticalSection(&lock); +} + +HRESULT DeviceProvider::CreateInstance(DeviceProvider **instance) +{ + if (NULL == instance) + return E_POINTER; + + *instance = new DeviceProvider(); + + if (NULL == *instance) + return E_OUTOFMEMORY; + + return S_OK; +} + +size_t DeviceProvider::AddRef() +{ + return InterlockedIncrement((LONG*)&ref); +} + +size_t DeviceProvider::Release() +{ + if (0 == ref) + return ref; + + LONG r = InterlockedDecrement((LONG*)&ref); + if (0 == r) + delete(this); + + return r; +} + +int DeviceProvider::QueryInterface(GUID interface_guid, void **object) +{ + if (NULL == object) + return E_POINTER; + + if (IsEqualIID(interface_guid, IFC_DeviceProvider)) + *object = static_cast<ifc_deviceprovider*>(this); + else + { + *object = NULL; + return E_NOINTERFACE; + } + + if (NULL == *object) + return E_UNEXPECTED; + + AddRef(); + return S_OK; +} + +void DeviceProvider::Lock() +{ + EnterCriticalSection(&lock); +} + +void DeviceProvider::Unlock() +{ + LeaveCriticalSection(&lock); +} + +DWORD DeviceProvider::DiscoveryThread() +{ + IncrementActivity(); + + if (NULL != enumerator && + FALSE == cancelDiscovery) + { + DiscoveryProvider_RegisterCancelSwitch(&cancelDiscovery); + + enumerator(DeviceProvider_DriverEnumCb); + + DiscoveryProvider_RegisterCancelSwitch(NULL); + } + + DecrementActivity(); + + Lock(); + + if (NULL != readyEvent) + SetEvent(readyEvent); + + Unlock(); + + + return 0; +} + +static int DeviceProvider_DiscoveryThreadStarter(HANDLE handle, void *user, intptr_t id) +{ + DeviceProvider *self; + DWORD result; + + self = (DeviceProvider*)user; + + if (NULL != self) + result = self->DiscoveryThread(); + else + result = -2; + + return result; +} + +HRESULT DeviceProvider::BeginDiscovery(api_devicemanager *manager) +{ + HRESULT hr; + + if (NULL == enumerator) + return E_UNEXPECTED; + + Lock(); + + if (NULL != readyEvent && + WAIT_TIMEOUT == WaitForSingleObject(readyEvent, 0)) + { + hr = E_PENDING; + } + else + { + hr = S_OK; + + if (NULL == readyEvent) + { + readyEvent = CreateEvent(NULL, TRUE, TRUE, NULL); + if (NULL == readyEvent) + hr = E_FAIL; + } + + if ((size_t)-1 == tlsIndex && + NULL != WASABI_API_APP) + { + tlsIndex = WASABI_API_APP->AllocateThreadStorage(); + } + + if (SUCCEEDED(hr)) + { + + cancelDiscovery = FALSE; + ResetEvent(readyEvent); + + if (0 != WASABI_API_THREADPOOL->RunFunction(0, DeviceProvider_DiscoveryThreadStarter, + this, 0, api_threadpool::FLAG_LONG_EXECUTION)) + { + + SetEvent(readyEvent); + hr = E_FAIL; + } + } + } + + Unlock(); + + return hr; +} + +HRESULT DeviceProvider::CancelDiscovery() +{ + HRESULT hr; + + hr = S_FALSE; + + Lock(); + + if (NULL != readyEvent) + { + cancelDiscovery = TRUE; + if (WAIT_OBJECT_0 == WaitForSingleObject(readyEvent, 0)) + hr = S_OK; + + cancelDiscovery = FALSE; + } + + Unlock(); + + return hr; +} + +HRESULT DeviceProvider::GetActive() +{ + HRESULT hr; + + Lock(); + + if (0 != activity) + hr = S_OK; + else + hr = S_FALSE; + + Unlock(); + + return hr; +} + +HRESULT DeviceProvider::Register(api_devicemanager *manager) +{ + HRESULT hr; + + if (NULL != this->manager) + return E_UNEXPECTED; + + if (NULL == manager) + return E_POINTER; + + hr = manager->RegisterProvider(this); + if (SUCCEEDED(hr)) + { + this->manager = manager; + manager->AddRef(); + } + return hr; +} + +HRESULT DeviceProvider::Unregister() +{ + HRESULT hr; + + if (NULL == manager) + return E_UNEXPECTED; + + hr = manager->UnregisterProvider(this); + manager->Release(); + manager = NULL; + return hr; +} + +size_t DeviceProvider::IncrementActivity() +{ + size_t a; + + Lock(); + + activity++; + if (1 == activity && + NULL != manager) + { + manager->SetProviderActive(this, TRUE); + } + + a = activity; + + Unlock(); + + return a; +} + +size_t DeviceProvider::DecrementActivity() +{ + size_t a; + + Lock(); + + if (0 != activity) + { + activity--; + if (0 == activity && + NULL != manager) + { + manager->SetProviderActive(this, FALSE); + } + } + + a = activity; + + Unlock(); + + return a; +} + +#define CBCLASS DeviceProvider +START_DISPATCH; +CB(ADDREF, AddRef) +CB(RELEASE, Release) +CB(QUERYINTERFACE, QueryInterface) +CB(API_BEGINDISCOVERY, BeginDiscovery) +CB(API_CANCELDISCOVERY, CancelDiscovery) +CB(API_GETACTIVE, GetActive) +END_DISPATCH; +#undef CBCLASS
\ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_android/deviceprovider.h b/Src/Plugins/Portable/pmp_android/deviceprovider.h new file mode 100644 index 00000000..a771e82e --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/deviceprovider.h @@ -0,0 +1,50 @@ +#pragma once + +#include <wtypes.h> +#include "../devices/ifc_deviceprovider.h" +#include "../../Library/ml_pmp/pmp.h" + +class DeviceProvider : public ifc_deviceprovider +{ +protected: + DeviceProvider(); + ~DeviceProvider(); + +public: + static HRESULT CreateInstance(DeviceProvider **instance); + +public: + /* Dispatchable */ + size_t AddRef(); + size_t Release(); + int QueryInterface(GUID interface_guid, void **object); + + /* ifc_deviceprovider */ + HRESULT BeginDiscovery(api_devicemanager *manager); + HRESULT CancelDiscovery(); + HRESULT GetActive(); + +public: + HRESULT Register(api_devicemanager *manager); + HRESULT Unregister(); + size_t IncrementActivity(); + size_t DecrementActivity(); + +private: + void Lock(); + void Unlock(); + DWORD DiscoveryThread(); + friend static int DeviceProvider_DiscoveryThreadStarter(HANDLE handle, void *user_data, intptr_t id); + +protected: + size_t ref; + size_t activity; + CRITICAL_SECTION lock; + api_devicemanager *manager; + ENUMDRIVES enumerator; + HANDLE readyEvent; + BOOL cancelDiscovery; + +protected: + RECVS_DISPATCH; +};
\ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_android/eject.cpp b/Src/Plugins/Portable/pmp_android/eject.cpp new file mode 100644 index 00000000..fd9dd9c1 --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/eject.cpp @@ -0,0 +1,138 @@ +// this file almost totally copied from MSDN + +#include <windows.h> +#include <stdio.h> +#include <winioctl.h> + +#define LOCK_TIMEOUT 3000 // 10 Seconds +#define LOCK_RETRIES 20 + +static HANDLE OpenVolume(TCHAR cDriveLetter) +{ + HANDLE hVolume; + UINT uDriveType; + wchar_t szVolumeName[8] = {0}; + wchar_t szRootName[5] = {0}; + DWORD dwAccessFlags = 0; + + wsprintf(szRootName, L"%c:\\", cDriveLetter); + + uDriveType = GetDriveType(szRootName); + switch(uDriveType) { + case DRIVE_REMOVABLE: + dwAccessFlags = GENERIC_READ | GENERIC_WRITE; + break; + case DRIVE_CDROM: + dwAccessFlags = GENERIC_READ; + break; + default: + printf("Cannot eject. Drive type is incorrect.\n"); + return INVALID_HANDLE_VALUE; + } + + wsprintf(szVolumeName, L"\\\\.\\%c:", cDriveLetter); + + hVolume = CreateFile( szVolumeName, + dwAccessFlags, + FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + 0, + NULL ); + + if (hVolume == INVALID_HANDLE_VALUE) + printf("CreateFile error %d\n", GetLastError()); + return hVolume; +} + +static BOOL CloseVolume(HANDLE hVolume) +{ + return CloseHandle(hVolume); +} + +static BOOL LockVolume(HANDLE hVolume) +{ + DWORD dwBytesReturned; + DWORD dwSleepAmount; + int nTryCount; + + dwSleepAmount = LOCK_TIMEOUT / LOCK_RETRIES; + + // Do this in a loop until a timeout period has expired + for (nTryCount = 0; nTryCount < LOCK_RETRIES; nTryCount++) { + if (DeviceIoControl(hVolume, + FSCTL_LOCK_VOLUME, + NULL, 0, + NULL, 0, + &dwBytesReturned, + NULL)) + return TRUE; + + Sleep(dwSleepAmount); + } + return FALSE; +} + +static BOOL DismountVolume(HANDLE hVolume) +{ + DWORD dwBytesReturned; + + return DeviceIoControl( hVolume, + FSCTL_DISMOUNT_VOLUME, + NULL, 0, + NULL, 0, + &dwBytesReturned, + NULL); +} + +static BOOL PreventRemovalOfVolume(HANDLE hVolume, BOOL fPreventRemoval) +{ + DWORD dwBytesReturned; + PREVENT_MEDIA_REMOVAL PMRBuffer; + + PMRBuffer.PreventMediaRemoval = fPreventRemoval; + + return DeviceIoControl( hVolume, + IOCTL_STORAGE_MEDIA_REMOVAL, + &PMRBuffer, sizeof(PREVENT_MEDIA_REMOVAL), + NULL, 0, + &dwBytesReturned, + NULL); +} + +static int AutoEjectVolume(HANDLE hVolume) +{ + DWORD dwBytesReturned; + + return DeviceIoControl( hVolume, + IOCTL_STORAGE_EJECT_MEDIA, + NULL, 0, + NULL, 0, + &dwBytesReturned, + NULL); +} + +BOOL EjectVolume(TCHAR cDriveLetter) +{ + HANDLE hVolume; + + BOOL fAutoEject = FALSE; + + hVolume = OpenVolume(cDriveLetter); + if (hVolume == INVALID_HANDLE_VALUE) + return FALSE; + + // Lock and dismount the volume. + if (LockVolume(hVolume) && DismountVolume(hVolume)) { + // Set prevent removal to false and eject the volume. + if (PreventRemovalOfVolume(hVolume, FALSE) && AutoEjectVolume(hVolume)) + fAutoEject = TRUE; + } + + // Close the volume so other processes can use the drive. + if (!CloseVolume(hVolume)) + return FALSE; + + if (fAutoEject) return TRUE; + else return FALSE; +}
\ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_android/filecopy.cpp b/Src/Plugins/Portable/pmp_android/filecopy.cpp new file mode 100644 index 00000000..aecf9422 --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/filecopy.cpp @@ -0,0 +1,67 @@ +#include "api.h" +#include <windows.h> +#include <stdio.h> +#include <wchar.h> +#include "resource.h" +#include <strsafe.h> + +typedef struct CopyData +{ + void * callbackContext; + void (*callback)(void * callbackContext, wchar_t * status); +} CopyData; + +DWORD CALLBACK CopyToIpodProgressRoutine(LARGE_INTEGER TotalFileSize, LARGE_INTEGER TotalBytesTransferred, + LARGE_INTEGER StreamSize, LARGE_INTEGER StreamBytesTransferred, + DWORD dwStreamNumber, + DWORD dwCallbackReason, + HANDLE hSourceFile, HANDLE hDestinationFile, + LPVOID lpData) +{ + CopyData *inst = (CopyData *)lpData; + if (inst && inst->callback) + { + wchar_t status[100] = {0}; + wchar_t langtemp[100] = {0}; + StringCbPrintf(status, sizeof(status), WASABI_API_LNGSTRINGW_BUF(IDS_TRANSFERING_PERCENT, langtemp, 100), (int)(100ULL * TotalBytesTransferred.QuadPart / (TotalFileSize.QuadPart))); + inst->callback(inst->callbackContext,status); + } + return PROGRESS_CONTINUE; +} + +int CopyFile(const wchar_t *infile, const wchar_t *outfile, void * callbackContext, void (*callback)(void * callbackContext, wchar_t * status), int * killswitch) +{ + wchar_t langtemp[100] = {0}; + + CopyData c; + c.callback = callback; + c.callbackContext = callbackContext; + + if (CopyFileEx(infile, outfile, CopyToIpodProgressRoutine, &c, killswitch,0)) + { + if (callback) + { + callback(callbackContext, WASABI_API_LNGSTRINGW_BUF(IDS_DONE, langtemp, 100)); + } + return 0; + } + else + { + switch(GetLastError()) + { + case ERROR_REQUEST_ABORTED: + DeleteFile(outfile); + if (callback) + { + callback(callbackContext, WASABI_API_LNGSTRINGW_BUF(IDS_CANCELLED, langtemp, 100)); + } + + default: + if (callback) + { + callback(callbackContext, WASABI_API_LNGSTRINGW_BUF(IDS_TRANSFER_FAILED, langtemp, 100)); + } + } + return -1; + } +}
\ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_android/main.cpp b/Src/Plugins/Portable/pmp_android/main.cpp new file mode 100644 index 00000000..2aea1066 --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/main.cpp @@ -0,0 +1,601 @@ +#include "../../Library/ml_pmp/pmp.h" +#include "../Winamp/wa_ipc.h" +#include <vector> +#include "../nu/AutoWide.h" +#include "../nu/AutoChar.h" +#include "../nu/Alias.h" + +#include <api/service/waServiceFactory.h> + +#include "api.h" +#include "resource.h" +#include "androiddevice.h" +#include "deviceprovider.h" + +#include <devguid.h> +#include <shlobj.h> +#include <shlwapi.h> +#include <strsafe.h> + +#define PLUGIN_VERSION L"1.72" +static int Init(); +static void Quit(); +static bool doRegisterForDevNotification(void); +static intptr_t MessageProc(int msg, intptr_t param1, intptr_t param2, intptr_t param3); + +extern PMPDevicePlugin plugin = {PMPHDR_VER,0,Init,Quit,MessageProc}; + +// start-android +static const wchar_t *winampini; +static std::vector<wchar_t*> blacklist; +bool loading_devices[26] = {0,}; + +static HDEVNOTIFY hDevNotify; + +std::vector<AndroidDevice*> devices; +HWND config; + +static DeviceProvider *deviceProvider = NULL; +static UINT_PTR rescanTimer = 0; + +static void blacklistLoad() { + wchar_t keyname[64] = {0}; + int l = GetPrivateProfileIntW(L"pmp_android", L"blacklistnum", 0, winampini); + for(int i=l>100?l-100:0; i<l; i++) { + wchar_t buf[100] = {0}; + StringCchPrintfW(keyname, 64, L"blacklist-%d", i); + GetPrivateProfileStringW(L"pmp_android", keyname, L"", buf, 100, winampini); + if(buf[0]) + { + blacklist.push_back(_wcsdup(buf)); + } + } +} + +static void blacklistSave() { + wchar_t buf[64] = {0}; + StringCchPrintfW(buf, 64, L"%d", blacklist.size()); + WritePrivateProfileStringW(L"pmp_android", L"blacklistnum", buf, winampini); + for(size_t i=0; i<blacklist.size(); i++) + { + StringCchPrintfW(buf, 64, L"blacklist-%d", i); + WritePrivateProfileStringW(L"pmp_android", buf, (const wchar_t*)blacklist.at(i), winampini); + } +} + +static wchar_t *makeBlacklistString(wchar_t drive) { + wchar_t path[4]={drive,L":\\"}; + wchar_t name[100]=L""; + wchar_t buf[FIELD_LENGTH]=L""; + DWORD serial=0; + + UINT olderrmode=SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS); + GetVolumeInformation(path,name,100,&serial,NULL,NULL,NULL,0); + + if(serial) + { + StringCchPrintf(buf, FIELD_LENGTH, L"s:%d",serial); + SetErrorMode(olderrmode); + return _wcsdup(buf); + } + + { + ULARGE_INTEGER tfree={0,}, total={0,}, freeb={0,}; + GetDiskFreeSpaceEx(path, &tfree, &total, &freeb); + StringCchPrintf(buf, FIELD_LENGTH, L"n:%s,%d,%d", name, total.HighPart, total.LowPart); + SetErrorMode(olderrmode); + return _wcsdup(buf); + } +} + +static bool blacklistCheck(wchar_t drive) +{ + wchar_t *s = makeBlacklistString(drive); + if (s) + { + for(size_t i=0; i<blacklist.size(); i++) + { + if(!wcscmp(s,(wchar_t*)blacklist.at(i))) + { + free(s); + return true; + } + } + free(s); + } + return false; +} + +static bool blacklistAdd(const wchar_t drive) +{ + wchar_t *s = makeBlacklistString(drive); + if (s) + { + for(size_t i=0; i<blacklist.size(); i++) + { + if(!wcscmp(s,(wchar_t*)blacklist.at(i))) + { + free(s); + return false; + } + } + blacklist.push_back(s); + } + return true; +} + +static BOOL +Device_IsiPod(const wchar_t drive) +{ + const wchar_t test[] = {drive, L":\\iPod_Control"}; + WIN32_FIND_DATAW findData; + HANDLE file = FindFirstFileW(test, &findData); + if (INVALID_HANDLE_VALUE != file) + { + FindClose(file); + if (0 != (FILE_ATTRIBUTE_DIRECTORY & findData.dwFileAttributes)) + return TRUE; + } + return FALSE; +} + +static BOOL +Device_IsAndroid(const wchar_t drive) +{ + const wchar_t test[] = {drive, L":\\Android"}; + WIN32_FIND_DATAW findData; + HANDLE file = FindFirstFileW(test, &findData); + if (INVALID_HANDLE_VALUE != file) + { + FindClose(file); + if (0 != (FILE_ATTRIBUTE_DIRECTORY & findData.dwFileAttributes)) + return TRUE; + } + return FALSE; +} + +static BOOL +Device_IsSizeOk(const wchar_t drive) +{ + const wchar_t test[] = {drive, L":\\"}; + ULARGE_INTEGER total; + + if (0 == GetDiskFreeSpaceExW(test, NULL, &total, NULL) || + total.HighPart == 0 && total.LowPart == 0) + { + return FALSE; + } + + return TRUE; +} + +static BOOL +Device_IsOkToConnect(const wchar_t drive) +{ + const wchar_t test[] = {drive, TEXT(":\\Winamp\\")TAG_CACHE}; + wchar_t title[128] = {0}; + wchar_t message[1024] = {0}; + int result; + + if (FALSE != PathFileExistsW(test)) + return TRUE; + + StringCbPrintfW(message, sizeof(message), WASABI_API_LNGSTRINGW(IDS_REMOVEABLE_DRIVE_DETECTED), + towupper(drive)); + + WASABI_API_LNGSTRINGW_BUF(IDS_WINAMP_PMP_SUPPORT,title,ARRAYSIZE(title)); + + result = MessageBoxW(NULL, message, title, + MB_YESNO | MB_SETFOREGROUND | MB_TOPMOST | + MB_ICONINFORMATION); + + if(IDNO == result) + { + return FALSE; + } + + return TRUE; +} + + +static Nullsoft::Utility::LockGuard connect_guard; + +static int ThreadFunc_Load(HANDLE handle, void *user_data, intptr_t id) +{ + wchar_t drive = (wchar_t)id; + + if (FALSE == Device_IsOkToConnect(drive)) + { + Nullsoft::Utility::AutoLock connect_lock(connect_guard); + blacklistAdd(drive); + blacklistSave(); + } + else + { + pmpDeviceLoading load; + Device * d = new AndroidDevice(drive,&load); + } + + loading_devices[drive-'A'] = false; + deviceProvider->DecrementActivity(); + + return 0; +} + +//#include <WinIoCtl.h> + +void connectDrive(wchar_t drive, bool checkSize=true, bool checkBlacklist=true) +{ + Nullsoft::Utility::AutoLock connect_lock(connect_guard); + // capitalize + if (drive >= 'a' && drive <= 'z') + drive = drive - 32; + + // reject invalid drive letters + if (drive < 'A' || drive > 'Z') + return; + + if(checkBlacklist && blacklistCheck(drive)) + return; + + // if device is taken already ignore + for (std::vector<AndroidDevice*>::const_iterator e = devices.begin(); e != devices.end(); e++) + { + if ((*e)->drive == drive) + return; + } + + if (loading_devices[drive-'A']) + return; + + loading_devices[drive-'A'] = true; + + if(FALSE == checkSize || FALSE != Device_IsSizeOk(drive)) + { + if (FALSE != Device_IsAndroid(drive) && + FALSE == Device_IsiPod(drive)) + { + + deviceProvider->IncrementActivity(); + if (NULL != WASABI_API_THREADPOOL && + 0 == WASABI_API_THREADPOOL->RunFunction(0, ThreadFunc_Load, 0, + (int)drive, api_threadpool::FLAG_LONG_EXECUTION)) + { + return; + } + deviceProvider->DecrementActivity(); + } + } + + loading_devices[drive-'A'] = false; +} + +static void autoDetectCallback(wchar_t drive,UINT type) +{ + if(type == DRIVE_REMOVABLE) + { + connectDrive(drive, true, true); + } +} + + +// end-android +static int Init() +{ + WasabiInit(); + + // start-android + winampini = (const wchar_t*)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GETINIFILEW); + // need to have this initialised before we try to do anything with localisation features + WASABI_API_START_LANG(plugin.hDllInstance,PmpAndroidLangGUID); + // end-android + + static wchar_t szDescription[256]; + StringCchPrintfW(szDescription, ARRAYSIZE(szDescription), + WASABI_API_LNGSTRINGW(IDS_NULLSOFT_ANDROID_DEVICE_PLUGIN), PLUGIN_VERSION); + plugin.description = szDescription; + + /** load up the backlist */ + blacklistLoad(); + + if (NULL != AGAVE_API_DEVICEMANAGER && + NULL == deviceProvider) + { + if (SUCCEEDED(DeviceProvider::CreateInstance(&deviceProvider)) && + FAILED(deviceProvider->Register(AGAVE_API_DEVICEMANAGER))) + { + deviceProvider->Release(); + deviceProvider = NULL; + } + } + + /* Our device shows up as a normal drive */ + if (NULL == deviceProvider || + FAILED(deviceProvider->BeginDiscovery(AGAVE_API_DEVICEMANAGER))) + { + SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(WPARAM)autoDetectCallback,PMP_IPC_ENUM_ACTIVE_DRIVES); + } + + return 0; +} + +static void Quit() +{ + if (NULL != deviceProvider) + { + deviceProvider->Unregister(); + deviceProvider->Release(); + deviceProvider = NULL; + } + + WasabiQuit(); + UnregisterDeviceNotification(hDevNotify); + + AndroidDevice::CloseDatabase(); +} + +static wchar_t FirstDriveFromMask(ULONG *unitmask) { + char i; + ULONG adj = 0x1, mask = *unitmask; + for(i=0; i<26; ++i) { + if(mask & 0x1) { + *unitmask -= adj; + break; + } + adj = adj << 1; + mask = mask >> 1; + } + return (i+L'A'); +} + +static int GetNumberOfDrivesFromMask(ULONG unitmask) { + int count = 0; + for(int i=0; i<26; ++i) + { + if(unitmask & 0x1) + count++; + + unitmask = unitmask >> 1; + } + return count; +} + +static void CALLBACK RescanOnTimer(HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) +{ + KillTimer(hwnd, idEvent); + if (idEvent == rescanTimer) + rescanTimer = 0; + + if (NULL == deviceProvider || + FAILED(deviceProvider->BeginDiscovery(AGAVE_API_DEVICEMANAGER))) + { + PostMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(WPARAM)autoDetectCallback,PMP_IPC_ENUM_ACTIVE_DRIVES); + } +} + + +int wmDeviceChange(WPARAM wParam, LPARAM lParam) +{ + UINT olderrmode=SetErrorMode(SEM_FAILCRITICALERRORS); + if(wParam==DBT_DEVICEARRIVAL || wParam==DBT_DEVICEREMOVECOMPLETE) + { // something has been inserted or removed + PDEV_BROADCAST_HDR lpdb = (PDEV_BROADCAST_HDR)lParam; + if(lpdb->dbch_devicetype == DBT_DEVTYP_VOLUME) + { // its a volume + PDEV_BROADCAST_VOLUME lpdbv = (PDEV_BROADCAST_VOLUME)lpdb; + if((!(lpdbv->dbcv_flags & DBTF_MEDIA) && !(lpdbv->dbcv_flags & DBTF_NET))) + { // its not a network drive or a CD/floppy, game on! + ULONG dbcv_unitmask = lpdbv->dbcv_unitmask; + + // see just how many drives have been flagged on the action + // eg one android drive could have multiple partitions that we handle + int count = GetNumberOfDrivesFromMask(dbcv_unitmask); + for(int j = 0; j < count; j++) + { + wchar_t drive = FirstDriveFromMask(&dbcv_unitmask); + if((wParam == DBT_DEVICEARRIVAL) && !blacklistCheck(drive)) + { // connected + connectDrive(drive); + //send a message as if the user just selected a drive from the combo box, this way the fields are refreshed to the correct device's settings + SendMessage(config, WM_COMMAND,MAKEWPARAM(IDC_DRIVESELECT,CBN_SELCHANGE),0); + } + else + { // removal + for(size_t i=0; i < devices.size(); i++) + { + AndroidDevice * d = (AndroidDevice*)devices.at(i); + if(d->drive == drive) + { + devices.erase(devices.begin() + i); + if(config) SendMessage(config,WM_USER,0,0); //refresh fields + if(config) SendMessage(config,WM_COMMAND, MAKEWPARAM(IDC_DRIVESELECT,CBN_SELCHANGE),0); //update to correct device change as if the user had clicked on the combo box themself + SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)d,PMP_IPC_DEVICEDISCONNECTED); + delete d; + i--; + } + } + } + } + } + } + } + else + { + rescanTimer = SetTimer(NULL, rescanTimer, 10000, RescanOnTimer); + } + SetErrorMode(olderrmode); + return 0; +} + +static int IsDriveConnectedToPMP(wchar_t drive) +{ + for(size_t i = 0; i < devices.size(); i++) + { + if(((AndroidDevice*)devices.at(i))->drive == drive) + { + return 1; + } + } + return 0; +} + +static INT_PTR CALLBACK config_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) +{ + switch(uMsg) + { + case WM_INITDIALOG: + { + for(wchar_t d=L'A'; d<='Z'; d++) + { + wchar_t drive[3] = {d,L':',0}, drv[4] = {d,L':','\\',0}; + UINT uDriveType = GetDriveType(drv); + if(uDriveType == DRIVE_REMOVABLE || uDriveType == DRIVE_CDROM || uDriveType == DRIVE_FIXED) { + int position = (int)SendDlgItemMessageW(hwndDlg,IDC_COMBO_MANUALCONNECT,CB_ADDSTRING,0,(LPARAM)drive); + SendDlgItemMessage(hwndDlg,IDC_COMBO_MANUALCONNECT,CB_SETITEMDATA,position,d); + } + } + SendDlgItemMessage(hwndDlg,IDC_COMBO_MANUALCONNECT,CB_SETCURSEL,0,0); + SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_COMBO_MANUALCONNECT,CBN_SELCHANGE),0); + } + break; + case WM_CLOSE: + EndDialog(hwndDlg,0); + break; + case WM_COMMAND: + switch(LOWORD(wParam)) { + case IDC_COMBO_MANUALCONNECT: + { + if(HIWORD(wParam)==CBN_SELCHANGE) { + int indx = (int)SendDlgItemMessageW(hwndDlg,IDC_COMBO_MANUALCONNECT,CB_GETCURSEL,0,0); + wchar_t drive = (wchar_t)SendDlgItemMessage(hwndDlg,IDC_COMBO_MANUALCONNECT,CB_GETITEMDATA,indx,0); + if(indx >= 0) + { + int connected = IsDriveConnectedToPMP(drive), isblacklisted = blacklistCheck(drive); + EnableWindow(GetDlgItem(hwndDlg, IDC_MANUALCONNECT), !connected && !isblacklisted); + EnableWindow(GetDlgItem(hwndDlg, IDC_MANUALDISCONNECT), connected); + EnableWindow(GetDlgItem(hwndDlg, IDC_MANUALBLACKLIST), TRUE); + SetDlgItemText(hwndDlg, IDC_MANUALBLACKLIST, WASABI_API_LNGSTRINGW(isblacklisted ? IDS_UNBLACKLIST_DRIVE : IDS_BLACKLIST_DRIVE)); + } + } + } + break; + case IDC_MANUALCONNECT: + { + char titleStr[32] = {0}; + if(MessageBoxA(hwndDlg, WASABI_API_LNGSTRING(IDS_MANUAL_CONNECT_PROMPT), + WASABI_API_LNGSTRING_BUF(IDS_WARNING,titleStr,32), MB_YESNO) == IDYES) + { + int indx = (int)SendDlgItemMessageW(hwndDlg,IDC_COMBO_MANUALCONNECT,CB_GETCURSEL,0,0); + wchar_t drive = (wchar_t)SendDlgItemMessage(hwndDlg,IDC_COMBO_MANUALCONNECT,CB_GETITEMDATA,indx,0); + if(drive >= L'A' && drive <= L'Z') { + wchar_t *bl = makeBlacklistString(drive); + if (bl) + { + for(size_t i=0; i<blacklist.size(); i++) + { + if(!wcscmp(bl,(wchar_t*)blacklist.at(i))) + { + free(blacklist.at(i)); + blacklist.erase(blacklist.begin() + i); + break; + } + } + free(bl); + } + connectDrive(drive,false); + // should do a better check here incase of failure, etc + EnableWindow(GetDlgItem(hwndDlg, IDC_MANUALCONNECT), FALSE); + EnableWindow(GetDlgItem(hwndDlg, IDC_MANUALDISCONNECT), TRUE); + } + } + } + break; + case IDC_MANUALDISCONNECT: + { + int indx = (int)SendDlgItemMessageW(hwndDlg,IDC_COMBO_MANUALCONNECT,CB_GETCURSEL,0,0); + wchar_t drive = (wchar_t)SendDlgItemMessage(hwndDlg,IDC_COMBO_MANUALCONNECT,CB_GETITEMDATA,indx,0); + if(drive >= L'A' && drive <= L'Z') + { + for(size_t i=0; i < devices.size(); i++) + { + AndroidDevice * d = (AndroidDevice*)devices.at(i); + if(d->drive == drive) + { + devices.erase(devices.begin() + i); + if(config) SendMessage(config,WM_USER,0,0); //refresh fields + if(config) SendMessage(config,WM_COMMAND, MAKEWPARAM(IDC_DRIVESELECT,CBN_SELCHANGE),0); //update to correct device change as if the user had clicked on the combo box themself + SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)d,PMP_IPC_DEVICEDISCONNECTED); + SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_COMBO_MANUALCONNECT,CBN_SELCHANGE),0); + delete d; + i--; + } + } + } + } + break; + case IDC_MANUALBLACKLIST: + { + int indx = (int)SendDlgItemMessageW(hwndDlg,IDC_COMBO_MANUALCONNECT,CB_GETCURSEL,0,0); + wchar_t drive = (wchar_t)SendDlgItemMessage(hwndDlg,IDC_COMBO_MANUALCONNECT,CB_GETITEMDATA,indx,0); + if(drive >= L'A' && drive <= L'Z') { + wchar_t *bl = makeBlacklistString(drive); + if (bl) + { + if(!blacklistCheck(drive)) { + blacklist.push_back(bl); + // see if we've got a connected drive and prompt to remove it or wait till restart + if(IsDriveConnectedToPMP(drive)) { + wchar_t title[96] = {0}; + GetWindowText(hwndDlg, title, 96); + if(MessageBox(hwndDlg,WASABI_API_LNGSTRINGW(IDS_DRIVE_CONNECTED_DISCONNECT_Q),title,MB_YESNO)==IDYES){ + SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_MANUALDISCONNECT,0),0); + } + } + } + else + { + for(size_t i=0; i < blacklist.size(); i++) + { + if(!wcscmp(bl,(wchar_t*)blacklist.at(i))) + { + free(blacklist.at(i)); + blacklist.erase(blacklist.begin() + i); + break; + } + } + free(bl); + } + } + SendMessage(hwndDlg,WM_COMMAND,MAKEWPARAM(IDC_COMBO_MANUALCONNECT,CBN_SELCHANGE),0); + } + } + break; + case IDOK: + case IDCANCEL: + blacklistSave(); + EndDialog(hwndDlg,0); + break; + } + break; + } + return 0; +} + + +static intptr_t MessageProc(int msg, intptr_t param1, intptr_t param2, intptr_t param3) +{ + switch(msg) { + case PMP_DEVICECHANGE: + return wmDeviceChange(param1,param2); + case PMP_CONFIG: + WASABI_API_DIALOGBOXW(IDD_CONFIG_GLOBAL,(HWND)param1,config_dialogProc); + return 1; + } + return 0; +} + +extern "C" __declspec(dllexport) PMPDevicePlugin *winampGetPMPDevicePlugin() +{ + return &plugin; +} + diff --git a/Src/Plugins/Portable/pmp_android/pmp_android.rc b/Src/Plugins/Portable/pmp_android/pmp_android.rc new file mode 100644 index 00000000..2d312229 --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/pmp_android.rc @@ -0,0 +1,209 @@ +// Microsoft Visual C++ generated resource script. +// +#include "resource.h" + +#define APSTUDIO_READONLY_SYMBOLS +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 2 resource. +// +#include "afxres.h" + +///////////////////////////////////////////////////////////////////////////// +#undef APSTUDIO_READONLY_SYMBOLS + +///////////////////////////////////////////////////////////////////////////// +// English (U.S.) resources + +#if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) +#ifdef _WIN32 +LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US +#pragma code_page(1252) +#endif //_WIN32 + +#ifdef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// TEXTINCLUDE +// + +1 TEXTINCLUDE +BEGIN + "resource.h\0" +END + +2 TEXTINCLUDE +BEGIN + "#include ""afxres.h""\r\n" + "\0" +END + +3 TEXTINCLUDE +BEGIN + "#include ""version.rc2""\r\n" + "\0" +END + +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// PNG +// + +IDB_ANDROID_160 PNG "resources\\generic_android.png" +IDR_ANDROID_ICON PNG "resources\\androidIcon.png" + +///////////////////////////////////////////////////////////////////////////// +// +// Dialog +// + +IDD_CONFIG DIALOGEX 0, 0, 264, 226 +STYLE DS_SETFONT | DS_FIXEDSYS | WS_CHILD | WS_VISIBLE | WS_SYSMENU +EXSTYLE WS_EX_CONTROLPARENT +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + LTEXT "File Name Format",IDC_STATIC,7,10,56,8 + EDITTEXT IDC_NAMEFORMAT,80,7,127,14,ES_AUTOHSCROLL + PUSHBUTTON "Format Help",IDC_FILENAMEHELP,211,7,46,14 + LTEXT "Playlist Directory",IDC_STATIC,7,26,54,8 + EDITTEXT IDC_PLDIR,80,24,128,14,ES_AUTOHSCROLL + PUSHBUTTON "Browse...",IDC_PLBROWSE,211,23,46,14 + LTEXT "Supported Formats",IDC_STATIC,7,42,62,8 + EDITTEXT IDC_SUPPORTEDFORMATS,80,40,128,14,ES_AUTOHSCROLL + PUSHBUTTON "Syntax Help",IDC_FORMATSHELP,211,40,46,14 + LTEXT "Delete Empty Folders",IDC_STATIC,7,55,69,8 + CONTROL "",IDC_PURGEFOLDERS,"Button",BS_AUTOCHECKBOX | WS_TABSTOP,80,56,8,8 + PUSHBUTTON "Save and Rescan",IDC_RESCAN,7,71,66,14 + PUSHBUTTON "Refresh Cache",IDC_REFRESHCACHE,79,71,66,14 + GROUPBOX "Playlist Writing Options",IDC_STATIC,7,95,250,49 + COMBOBOX IDC_PL_WRITE_COMBO,12,108,120,35,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + LTEXT "Changing how Winamp saves its playlists may improve compatability with other portable devices.",IDC_STATIC,137,104,113,34 + LTEXT "",IDC_PL_WRITE_EG,12,126,120,10 +END + +IDD_CONFIG_GLOBAL DIALOGEX 0, 0, 196, 90 +STYLE DS_SETFONT | DS_MODALFRAME | DS_FIXEDSYS | WS_POPUP | WS_CAPTION | WS_SYSMENU +CAPTION "Android Device Support Configuration" +FONT 8, "MS Shell Dlg", 400, 0, 0x1 +BEGIN + GROUPBOX "Select the Drive to use from the available list below",IDC_STATIC,5,3,186,67 + COMBOBOX IDC_COMBO_MANUALCONNECT,31,17,40,242,CBS_DROPDOWNLIST | CBS_SORT | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Connect Drive",IDC_MANUALCONNECT,75,17,90,13 + PUSHBUTTON "Disconnect Drive",IDC_MANUALDISCONNECT,75,34,90,13 + PUSHBUTTON "Blacklist Drive",IDC_MANUALBLACKLIST,75,51,90,13 + DEFPUSHBUTTON "Close",IDOK,75,73,45,13 +END + + +///////////////////////////////////////////////////////////////////////////// +// +// DESIGNINFO +// + +#ifdef APSTUDIO_INVOKED +GUIDELINES DESIGNINFO +BEGIN + IDD_CONFIG, DIALOG + BEGIN + LEFTMARGIN, 7 + RIGHTMARGIN, 257 + TOPMARGIN, 7 + BOTTOMMARGIN, 218 + END + + IDD_CONFIG_GLOBAL, DIALOG + BEGIN + LEFTMARGIN, 5 + RIGHTMARGIN, 191 + TOPMARGIN, 3 + BOTTOMMARGIN, 86 + END +END +#endif // APSTUDIO_INVOKED + + +///////////////////////////////////////////////////////////////////////////// +// +// String Table +// + +STRINGTABLE +BEGIN + IDS_NULLSOFT_ANDROID_DEVICE_PLUGIN "Nullsoft Android Device Plug-in v%s" + 65535 "{EBFF6E00-39D8-45e6-B3EC-E3B07A45E6B0}" +END + +STRINGTABLE +BEGIN + IDS_CANNOT_OPEN_FILE "Cannot open file" + IDS_CANNOT_CREATE_FILE "Cannot create file" + IDS_TRANSFERING_PERCENT "Transferring %d%%" + IDS_CANCELLED "Cancelled" + IDS_DONE "Done" + IDS_TRANSFER_FAILED "Transfer failed" + IDS_REMOVEABLE_DRIVE_DETECTED + "Winamp has detected an Android device on %c:.\nIs this a device that you want to manage with Winamp?" + IDS_WINAMP_PMP_SUPPORT "Winamp Portable Music Player Support" + IDS_MANUAL_CONNECT_PROMPT + "Manually connecting the wrong device could cause problems.\nPlease be sure that you have entered a valid Android device letter.\n\nAre you sure you wish to continue?" + IDS_WARNING "Warning" + IDS_LOADING_DRIVE_X "Loading Drive X:" + IDS_FAILED_TO_EJECT_DRIVE + "Failed to eject device. Is something else using it?" + IDS_ERROR "Error" + IDS_ANDROID_DRIVE_X "ANDROID Drive X:" + IDS_TRACK_IN_USE "Track is in use - Could not delete!" +END + +STRINGTABLE +BEGIN + IDS_CACHE_UPDATED "Cache updated." + IDS_SUCCESS "Success" + IDS_FILENAME_FORMATTING_INFO + "You may enter a filename format string for your files.\nIt can contain \\ or / to delimit a path, and the following keywords:\n\n <Artist> - inserts the artist with the default capitalization\n <ARTIST> - inserts the artist in all uppercase\n <artist> - inserts the artist in all lowercase\n <Albumartist>/<ALBUMARTIST>/<albumartist> - inserts the album artist\n <Album>/<ALBUM>/<album> - inserts the album\n <year> - inserts the album year\n <Genre>/<GENRE>/<genre> - inserts the album genre\n <Title>/<TITLE>/<title> - inserts the track title\n <filename> - inserts the original filename (extension safe)\n <disc> - inserts the disc number\n #, ##, or ### - inserts the track number, with leading 0s if ## or ###\n\n For Example: E:\\Music\\<Artist>\\<Album>\\## - <Title>\n" + IDS_FILENAME_FORMAT_HELP "Filename Format Help" + IDS_SELECT_FOLDER_TO_LOAD_PLAYLISTS + "Please select a folder to load playlists from the device." + IDS_ERR_SELECTED_PATH_NOT_ON_DEVICE + "Error: selected path is not on device!" + IDS_SUPPORTED_FORMAT_INFO + "You may enter supported extensions in the form of\nextensions separated by semicolons.\n\nFor Example: mp3;wav;wma" + IDS_SUPPORTED_FORMAT_HELP "Supported Formats Help" + IDS_RESCAN_COMPLETE_SAVED "Rescan complete, device settings saved." + IDS_RESCAN_COMPLETE "Rescan complete" + IDS_ADVANCED "Advanced" + IDS_UNBLACKLIST_DRIVE "Un-blacklist Drive" + IDS_BLACKLIST_DRIVE "Blacklist Drive" + IDS_DRIVE_CONNECTED_DISCONNECT_Q + "The Drive you have selected to blacklist is currently connected and being managed.\n\nWould you like to disconnect the Drive now or you can wait until you restart Winamp." + IDS_SLASH_AT_START "Slash at start of paths (default)" + IDS_DOT_AT_START "Dot at start of paths" +END + +STRINGTABLE +BEGIN + IDS_NO_SLASH_OR_DOT "No slash or dot at start" + IDS_EG_SLASH "e.g. \\path\\to\\file.mp3" + IDS_EG_DOT "e.g. .\\path\\to\\file.mp3" + IDS_EG_NEITHER "e.g. path\\to\\file.mp3" + IDS_DELAYLOAD_FAILURE "Android plug-in cannot load the database to write the cache file" +END + +#endif // English (U.S.) resources +///////////////////////////////////////////////////////////////////////////// + + + +#ifndef APSTUDIO_INVOKED +///////////////////////////////////////////////////////////////////////////// +// +// Generated from the TEXTINCLUDE 3 resource. +// +#include "version.rc2" + +///////////////////////////////////////////////////////////////////////////// +#endif // not APSTUDIO_INVOKED + diff --git a/Src/Plugins/Portable/pmp_android/pmp_android.sln b/Src/Plugins/Portable/pmp_android/pmp_android.sln new file mode 100644 index 00000000..3e21ea33 --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/pmp_android.sln @@ -0,0 +1,130 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29509.3 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pmp_android", "pmp_android.vcxproj", "{491665B2-6437-4D30-9AEC-DE7D1CB20E1E}" + ProjectSection(ProjectDependencies) = postProject + {57C90706-B25D-4ACA-9B33-95CDB2427C27} = {57C90706-B25D-4ACA-9B33-95CDB2427C27} + {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F} + {4D25C321-7F8B-424E-9899-D80A364BAF1A} = {4D25C321-7F8B-424E-9899-D80A364BAF1A} + {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1} = {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1} + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} = {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915} + {E105A0A2-7391-47C5-86AC-718003524C3D} = {E105A0A2-7391-47C5-86AC-718003524C3D} + {255B68B5-7EF8-45EF-A675-2D6B88147909} = {255B68B5-7EF8-45EF-A675-2D6B88147909} + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "bfc", "..\Wasabi\bfc\bfc.vcxproj", "{D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "tataki", "..\tataki\tataki.vcxproj", "{255B68B5-7EF8-45EF-A675-2D6B88147909}" + ProjectSection(ProjectDependencies) = postProject + {DABE6307-F8DD-416D-9DAC-673E2DECB73F} = {DABE6307-F8DD-416D-9DAC-673E2DECB73F} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nsutil", "..\nsutil\nsutil.vcxproj", "{DABE6307-F8DD-416D-9DAC-673E2DECB73F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nde", "..\nde\nde.vcxproj", "{4D25C321-7F8B-424E-9899-D80A364BAF1A}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nx", "..\replicant\nx\nx.vcxproj", "{57C90706-B25D-4ACA-9B33-95CDB2427C27}" + ProjectSection(ProjectDependencies) = postProject + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "nu", "..\replicant\nu\nu.vcxproj", "{F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "jnetlib", "..\replicant\jnetlib\jnetlib.vcxproj", "{E105A0A2-7391-47C5-86AC-718003524C3D}" + ProjectSection(ProjectDependencies) = postProject + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} = {0F9730E4-45DA-4BD2-A50A-403A4BC9751A} + EndProjectSection +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "zlib", "..\replicant\zlib\zlib.vcxproj", "{0F9730E4-45DA-4BD2-A50A-403A4BC9751A}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {491665B2-6437-4D30-9AEC-DE7D1CB20E1E}.Debug|Win32.ActiveCfg = Debug|Win32 + {491665B2-6437-4D30-9AEC-DE7D1CB20E1E}.Debug|Win32.Build.0 = Debug|Win32 + {491665B2-6437-4D30-9AEC-DE7D1CB20E1E}.Debug|x64.ActiveCfg = Debug|x64 + {491665B2-6437-4D30-9AEC-DE7D1CB20E1E}.Debug|x64.Build.0 = Debug|x64 + {491665B2-6437-4D30-9AEC-DE7D1CB20E1E}.Release|Win32.ActiveCfg = Release|Win32 + {491665B2-6437-4D30-9AEC-DE7D1CB20E1E}.Release|Win32.Build.0 = Release|Win32 + {491665B2-6437-4D30-9AEC-DE7D1CB20E1E}.Release|x64.ActiveCfg = Release|x64 + {491665B2-6437-4D30-9AEC-DE7D1CB20E1E}.Release|x64.Build.0 = Release|x64 + {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|Win32.ActiveCfg = Debug|Win32 + {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|Win32.Build.0 = Debug|Win32 + {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|x64.ActiveCfg = Debug|x64 + {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Debug|x64.Build.0 = Debug|x64 + {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|Win32.ActiveCfg = Release|Win32 + {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|Win32.Build.0 = Release|Win32 + {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|x64.ActiveCfg = Release|x64 + {D0EC862E-DDDD-4F4F-934F-B75DC9062DC1}.Release|x64.Build.0 = Release|x64 + {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|Win32.ActiveCfg = Debug|Win32 + {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|Win32.Build.0 = Debug|Win32 + {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|x64.ActiveCfg = Debug|x64 + {255B68B5-7EF8-45EF-A675-2D6B88147909}.Debug|x64.Build.0 = Debug|x64 + {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|Win32.ActiveCfg = Release|Win32 + {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|Win32.Build.0 = Release|Win32 + {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|x64.ActiveCfg = Release|x64 + {255B68B5-7EF8-45EF-A675-2D6B88147909}.Release|x64.Build.0 = Release|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.ActiveCfg = Debug|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|Win32.Build.0 = Debug|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.ActiveCfg = Debug|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Debug|x64.Build.0 = Debug|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.ActiveCfg = Release|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|Win32.Build.0 = Release|Win32 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.ActiveCfg = Release|x64 + {DABE6307-F8DD-416D-9DAC-673E2DECB73F}.Release|x64.Build.0 = Release|x64 + {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|Win32.ActiveCfg = Debug|Win32 + {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|Win32.Build.0 = Debug|Win32 + {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|x64.ActiveCfg = Debug|x64 + {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Debug|x64.Build.0 = Debug|x64 + {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|Win32.ActiveCfg = Release|Win32 + {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|Win32.Build.0 = Release|Win32 + {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|x64.ActiveCfg = Release|x64 + {4D25C321-7F8B-424E-9899-D80A364BAF1A}.Release|x64.Build.0 = Release|x64 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.ActiveCfg = Debug|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|Win32.Build.0 = Debug|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.ActiveCfg = Debug|x64 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Debug|x64.Build.0 = Debug|x64 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.ActiveCfg = Release|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|Win32.Build.0 = Release|Win32 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.ActiveCfg = Release|x64 + {57C90706-B25D-4ACA-9B33-95CDB2427C27}.Release|x64.Build.0 = Release|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.ActiveCfg = Debug|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|Win32.Build.0 = Debug|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.ActiveCfg = Debug|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Debug|x64.Build.0 = Debug|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.ActiveCfg = Release|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|Win32.Build.0 = Release|Win32 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.ActiveCfg = Release|x64 + {F1F5CD60-0D5B-4CEA-9EEB-2F87FF9AA915}.Release|x64.Build.0 = Release|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.ActiveCfg = Debug|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|Win32.Build.0 = Debug|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.ActiveCfg = Debug|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Debug|x64.Build.0 = Debug|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.ActiveCfg = Release|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|Win32.Build.0 = Release|Win32 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.ActiveCfg = Release|x64 + {E105A0A2-7391-47C5-86AC-718003524C3D}.Release|x64.Build.0 = Release|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.ActiveCfg = Debug|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|Win32.Build.0 = Debug|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.ActiveCfg = Debug|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Debug|x64.Build.0 = Debug|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.ActiveCfg = Release|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|Win32.Build.0 = Release|Win32 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.ActiveCfg = Release|x64 + {0F9730E4-45DA-4BD2-A50A-403A4BC9751A}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {C4E799C1-027B-487B-8E1A-31F2D26A1AFE} + EndGlobalSection +EndGlobal diff --git a/Src/Plugins/Portable/pmp_android/pmp_android.vcxproj b/Src/Plugins/Portable/pmp_android/pmp_android.vcxproj new file mode 100644 index 00000000..34236c88 --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/pmp_android.vcxproj @@ -0,0 +1,332 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup Label="ProjectConfigurations"> + <ProjectConfiguration Include="Debug|Win32"> + <Configuration>Debug</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Debug|x64"> + <Configuration>Debug</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|Win32"> + <Configuration>Release</Configuration> + <Platform>Win32</Platform> + </ProjectConfiguration> + <ProjectConfiguration Include="Release|x64"> + <Configuration>Release</Configuration> + <Platform>x64</Platform> + </ProjectConfiguration> + </ItemGroup> + <PropertyGroup Label="Globals"> + <ProjectGuid>{491665B2-6437-4D30-9AEC-DE7D1CB20E1E}</ProjectGuid> + <RootNamespace>pmp_android</RootNamespace> + <WindowsTargetPlatformVersion>10.0.19041.0</WindowsTargetPlatformVersion> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration"> + <ConfigurationType>DynamicLibrary</ConfigurationType> + <PlatformToolset>v142</PlatformToolset> + <CharacterSet>Unicode</CharacterSet> + </PropertyGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" /> + <ImportGroup Label="ExtensionSettings"> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets"> + <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" /> + </ImportGroup> + <PropertyGroup Label="UserMacros" /> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir> + <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir> + <IncludePath>$(IncludePath)</IncludePath> + <LibraryPath>$(LibraryPath)</LibraryPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir> + <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir> + <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir> + <IncludePath>$(IncludePath)</IncludePath> + <LibraryPath>$(LibraryPath)</LibraryPath> + </PropertyGroup> + <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <LinkIncremental>false</LinkIncremental> + <OutDir>$(PlatformShortName)_$(Configuration)\</OutDir> + <IntDir>$(PlatformShortName)_$(Configuration)\</IntDir> + </PropertyGroup> + <PropertyGroup Label="Vcpkg"> + <VcpkgEnableManifest>false</VcpkgEnableManifest> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgConfiguration>Debug</VcpkgConfiguration> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + <VcpkgConfiguration>Debug</VcpkgConfiguration> + </PropertyGroup> + <PropertyGroup Label="Vcpkg" Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <VcpkgInstalledDir> + </VcpkgInstalledDir> + <VcpkgUseStatic>false</VcpkgUseStatic> + <VcpkgTriplet>x86-windows-static-md</VcpkgTriplet> + </PropertyGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..;..\..\..\;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_WINDOWS;_UNICODE;UNICODE;_USRDLL;ML_ex_EXPORTS;_CRT_SECURE_NO_WARNINGS;WIN32;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <FunctionLevelLinking>true</FunctionLevelLinking> + <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> + <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4244;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x040c</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>wsock32.lib;shlwapi.lib;fmtd.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalIncludeDirectories)</AdditionalLibraryDirectories> + <MapFileName>$(IntDir)$(TargetName).map</MapFileName> + </Link> + <PostBuildEvent> + <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ +xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command> + <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message> + </PostBuildEvent> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'"> + <ClCompile> + <Optimization>Disabled</Optimization> + <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..;..\..\..\;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_WINDOWS;_UNICODE;UNICODE;_USRDLL;ML_ex_EXPORTS;_CRT_SECURE_NO_WARNINGS;WIN64;_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MinimalRebuild>false</MinimalRebuild> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks> + <RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <FunctionLevelLinking>true</FunctionLevelLinking> + <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> + <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>ProgramDatabase</DebugInformationFormat> + <DisableSpecificWarnings>4244;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x040c</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>true</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <MapFileName>$(IntDir)$(TargetName).map</MapFileName> + </Link> + <PostBuildEvent> + <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ +xcopy /Y /D $(IntDir)$(TargetName).pdb ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command> + <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message> + </PostBuildEvent> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'"> + <ClCompile> + <Optimization>MinSpace</Optimization> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..;..\..\..\;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_WINDOWS;_UNICODE;UNICODE;_USRDLL;ML_ex_EXPORTS;_CRT_SECURE_NO_WARNINGS;WIN32;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> + <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4244;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x040c</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <MapFileName>$(IntDir)$(TargetName).map</MapFileName> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <TargetMachine>MachineX86</TargetMachine> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </Link> + <PostBuildEvent> + <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command> + <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message> + </PostBuildEvent> + </ItemDefinitionGroup> + <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'"> + <ClCompile> + <Optimization>MinSpace</Optimization> + <FavorSizeOrSpeed>Size</FavorSizeOrSpeed> + <AdditionalIncludeDirectories>.;..\..\..\Wasabi;..;..\..\..\;..\..\..\replicant;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories> + <PreprocessorDefinitions>_WINDOWS;_UNICODE;UNICODE;_USRDLL;ML_ex_EXPORTS;_CRT_SECURE_NO_WARNINGS;WIN64;NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <StringPooling>true</StringPooling> + <MultiProcessorCompilation>true</MultiProcessorCompilation> + <RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary> + <BufferSecurityCheck>true</BufferSecurityCheck> + <TreatWChar_tAsBuiltInType>true</TreatWChar_tAsBuiltInType> + <ForceConformanceInForLoopScope>true</ForceConformanceInForLoopScope> + <AssemblerListingLocation>$(IntDir)</AssemblerListingLocation> + <ObjectFileName>$(IntDir)</ObjectFileName> + <ProgramDataBaseFileName>$(IntDir)$(TargetName).pdb</ProgramDataBaseFileName> + <WarningLevel>Level3</WarningLevel> + <DebugInformationFormat>None</DebugInformationFormat> + <DisableSpecificWarnings>4244;4996;%(DisableSpecificWarnings)</DisableSpecificWarnings> + </ClCompile> + <ResourceCompile> + <PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions> + <Culture>0x040c</Culture> + </ResourceCompile> + <Link> + <AdditionalDependencies>wsock32.lib;shlwapi.lib;%(AdditionalDependencies)</AdditionalDependencies> + <OutputFile>$(OutDir)$(TargetName)$(TargetExt)</OutputFile> + <GenerateDebugInformation>false</GenerateDebugInformation> + <ProgramDatabaseFile>$(IntDir)$(TargetName).pdb</ProgramDatabaseFile> + <MapFileName>$(IntDir)$(TargetName).map</MapFileName> + <OptimizeReferences>true</OptimizeReferences> + <EnableCOMDATFolding>true</EnableCOMDATFolding> + <RandomizedBaseAddress>false</RandomizedBaseAddress> + <ImportLibrary>$(IntDir)$(TargetName).lib</ImportLibrary> + <AdditionalLibraryDirectories>%(AdditionalLibraryDirectories)</AdditionalLibraryDirectories> + <ImageHasSafeExceptionHandlers>false</ImageHasSafeExceptionHandlers> + </Link> + <PostBuildEvent> + <Command>xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\ </Command> + <Message>Post build event: 'xcopy /Y /D $(OutDir)$(TargetName)$(TargetExt) ..\..\..\..\Build\Winamp_$(PlatformShortName)_$(Configuration)\Plugins\'</Message> + </PostBuildEvent> + </ItemDefinitionGroup> + <ItemGroup> + <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp" /> + <ClCompile Include="albumart.cpp" /> + <ClCompile Include="androiddevice.cpp" /> + <ClCompile Include="androidplaylist.cpp" /> + <ClCompile Include="androidplaylistsaver.cpp" /> + <ClCompile Include="api.cpp" /> + <ClCompile Include="deviceprovider.cpp" /> + <ClCompile Include="eject.cpp" /> + <ClCompile Include="filecopy.cpp" /> + <ClCompile Include="main.cpp" /> + <ClCompile Include="utils.cpp" /> + </ItemGroup> + <ItemGroup> + <ClInclude Include="..\..\General\gen_ml\ml.h" /> + <ClInclude Include="..\..\Library\ml_pmp\pmp.h" /> + <ClInclude Include="androiddevice.h" /> + <ClInclude Include="androidplaylist.h" /> + <ClInclude Include="androidplaylistsaver.h" /> + <ClInclude Include="api.h" /> + <ClInclude Include="deviceprovider.h" /> + <ClInclude Include="resource.h" /> + </ItemGroup> + <ItemGroup> + <Image Include="resources\androidIcon.png" /> + <Image Include="resources\generic_android.png" /> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="pmp_android.rc" /> + </ItemGroup> + <ItemGroup> + <ProjectReference Include="..\..\..\nde\nde.vcxproj"> + <Project>{4d25c321-7f8b-424e-9899-d80a364baf1a}</Project> + </ProjectReference> + <ProjectReference Include="..\..\..\replicant\nu\nu.vcxproj"> + <Project>{f1f5cd60-0d5b-4cea-9eeb-2f87ff9aa915}</Project> + </ProjectReference> + <ProjectReference Include="..\..\..\tataki\tataki.vcxproj"> + <Project>{255b68b5-7ef8-45ef-a675-2d6b88147909}</Project> + </ProjectReference> + <ProjectReference Include="..\..\..\Wasabi\Wasabi.vcxproj"> + <Project>{3e0bfa8a-b86a-42e9-a33f-ec294f823f7f}</Project> + </ProjectReference> + <ProjectReference Include="..\..\..\WAT\WAT.vcxproj"> + <Project>{c5714908-a71f-4644-bd95-aad8ee7914da}</Project> + </ProjectReference> + </ItemGroup> + <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" /> + <ImportGroup Label="ExtensionTargets"> + </ImportGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_android/pmp_android.vcxproj.filters b/Src/Plugins/Portable/pmp_android/pmp_android.vcxproj.filters new file mode 100644 index 00000000..ead498ac --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/pmp_android.vcxproj.filters @@ -0,0 +1,91 @@ +<?xml version="1.0" encoding="utf-8"?> +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> + <ItemGroup> + <ClCompile Include="albumart.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="androiddevice.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="androidplaylist.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="androidplaylistsaver.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="api.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="deviceprovider.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="eject.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="filecopy.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="main.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="utils.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\General\gen_ml\ml_lib.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + </ItemGroup> + <ItemGroup> + <ClInclude Include="androiddevice.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="androidplaylist.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="androidplaylistsaver.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="api.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="deviceprovider.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="resource.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\General\gen_ml\ml.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\Library\ml_pmp\pmp.h"> + <Filter>Header Files</Filter> + </ClInclude> + </ItemGroup> + <ItemGroup> + <Image Include="resources\generic_android.png"> + <Filter>Image Files</Filter> + </Image> + <Image Include="resources\androidIcon.png"> + <Filter>Image Files</Filter> + </Image> + </ItemGroup> + <ItemGroup> + <Filter Include="Header Files"> + <UniqueIdentifier>{82d6647a-a3b9-4ade-b2cc-4a360f5ed80d}</UniqueIdentifier> + </Filter> + <Filter Include="Ressource Files"> + <UniqueIdentifier>{8731ac77-4c42-42fc-8ff8-f64922843c99}</UniqueIdentifier> + </Filter> + <Filter Include="Source Files"> + <UniqueIdentifier>{06318ce7-b270-4a39-833a-4ab306c760ed}</UniqueIdentifier> + </Filter> + <Filter Include="Image Files"> + <UniqueIdentifier>{d2febc64-2e0e-4a50-8994-0dcaab423352}</UniqueIdentifier> + </Filter> + </ItemGroup> + <ItemGroup> + <ResourceCompile Include="pmp_android.rc"> + <Filter>Ressource Files</Filter> + </ResourceCompile> + </ItemGroup> +</Project>
\ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_android/resource.h b/Src/Plugins/Portable/pmp_android/resource.h new file mode 100644 index 00000000..4ca89908 --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/resource.h @@ -0,0 +1,75 @@ +//{{NO_DEPENDENCIES}} +// Microsoft Visual C++ generated include file. +// Used by pmp_android.rc +// +#define IDS_CANNOT_OPEN_FILE 1 +#define IDS_CANNOT_CREATE_FILE 2 +#define IDS_TRANSFERING_PERCENT 3 +#define IDS_CANCELLED 4 +#define IDS_DONE 5 +#define IDS_TRANSFER_FAILED 6 +#define IDS_REMOVEABLE_DRIVE_DETECTED 7 +#define IDS_WINAMP_PMP_SUPPORT 8 +#define IDS_MANUAL_CONNECT_PROMPT 9 +#define IDS_WARNING 10 +#define IDS_LOADING_DRIVE_X 11 +#define IDS_FAILED_TO_EJECT_DRIVE 12 +#define IDS_ERROR 13 +#define IDS_STRING14 14 +#define IDS_ANDROID_DRIVE_X 14 +#define IDS_TRACK_IN_USE 15 +#define IDS_CACHE_UPDATED 16 +#define IDS_SUCCESS 17 +#define IDS_FILENAME_FORMATTING_INFO 18 +#define IDS_FILENAME_FORMAT_HELP 19 +#define IDS_SELECT_FOLDER_TO_LOAD_PLAYLISTS 20 +#define IDS_ERR_SELECTED_PATH_NOT_ON_DEVICE 21 +#define IDS_SUPPORTED_FORMAT_INFO 22 +#define IDS_SUPPORTED_FORMAT_HELP 23 +#define IDS_RESCAN_COMPLETE_SAVED 24 +#define IDS_RESCAN_COMPLETE 25 +#define IDS_ADVANCED 26 +#define IDS_UNBLACKLIST_DRIVE 27 +#define IDS_BLACKLIST_DRIVE 28 +#define IDS_DRIVE_CONNECTED_DISCONNECT_Q 29 +#define IDS_SLASH_AT_START 30 +#define IDS_DOT_AT_START 31 +#define IDS_NO_SLASH_OR_DOT 32 +#define IDS_EG_SLASH 33 +#define IDS_EG_DOT 34 +#define IDS_STRING35 35 +#define IDS_EG_NEITHER 35 +#define IDS_DELAYLOAD_FAILURE 36 +#define IDD_CONFIG 102 +#define IDD_CONFIG_GLOBAL 105 +#define IDB_ANDROID_160 110 +#define IDB_PNG1 111 +#define IDR_ANDROID_ICON 111 +#define IDC_PLDIR 1001 +#define IDC_NAMEFORMAT 1002 +#define IDC_SUPPORTEDFORMATS 1003 +#define IDC_DRIVESELECT 1004 +#define IDC_COMBO_MANUALCONNECT 1005 +#define IDC_MANUALCONNECT 1006 +#define IDC_MANUALDISCONNECT 1007 +#define IDC_MANUALBLACKLIST 1008 +#define IDC_REFRESHCACHE 1011 +#define IDC_FILENAMEHELP 1012 +#define IDC_PLBROWSE 1013 +#define IDC_FORMATSHELP 1014 +#define IDC_RESCAN 1015 +#define IDC_PURGEFOLDERS 1016 +#define IDC_PL_WRITE_COMBO 1017 +#define IDC_PL_WRITE_EG 1018 +#define IDS_NULLSOFT_ANDROID_DEVICE_PLUGIN 65534 + +// Next default values for new objects +// +#ifdef APSTUDIO_INVOKED +#ifndef APSTUDIO_READONLY_SYMBOLS +#define _APS_NEXT_RESOURCE_VALUE 113 +#define _APS_NEXT_COMMAND_VALUE 40001 +#define _APS_NEXT_CONTROL_VALUE 1019 +#define _APS_NEXT_SYMED_VALUE 101 +#endif +#endif diff --git a/Src/Plugins/Portable/pmp_android/resources/androidIcon.png b/Src/Plugins/Portable/pmp_android/resources/androidIcon.png Binary files differnew file mode 100644 index 00000000..674c4774 --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/resources/androidIcon.png diff --git a/Src/Plugins/Portable/pmp_android/resources/generic_android.png b/Src/Plugins/Portable/pmp_android/resources/generic_android.png Binary files differnew file mode 100644 index 00000000..bafdb3ce --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/resources/generic_android.png diff --git a/Src/Plugins/Portable/pmp_android/utils.cpp b/Src/Plugins/Portable/pmp_android/utils.cpp new file mode 100644 index 00000000..b81093dc --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/utils.cpp @@ -0,0 +1,334 @@ +#include <windows.h> +#include "../../General/gen_ml/ml.h" +#include "../../Library/ml_pmp/pmp.h" +#include "AndroidDevice.h" +#include <strsafe.h> +#include <shlwapi.h> + +BOOL RecursiveCreateDirectory(wchar_t* buf1); +__int64 fileSize(wchar_t * filename); +bool supportedFormat(wchar_t * file, wchar_t * supportedFormats); +DeviceType detectDeviceType(wchar_t drive); +void removebadchars(wchar_t *s); +wchar_t * FixReplacementVars(wchar_t *str, int str_size, Device * dev, songid_t song); + +void removebadchars(wchar_t *s) { + while (s && *s) + { + if (*s == L'?' || *s == L'/' || *s == L'\\' || *s == L':' || *s == L'*' || *s == L'\"' || *s == L'<' || *s == L'>' || *s == L'|') + *s = L'_'; + s = CharNextW(s); + } +} + +__int64 fileSize(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; +} + +// RecursiveCreateDirectory: creates all non-existent folders in a path +BOOL RecursiveCreateDirectory(wchar_t* buf1) { + wchar_t *p=buf1; + int errors = 0; + if (*p) { + p = PathSkipRoot(buf1); + if (!p) return true ; + + wchar_t ch='c'; + while (ch) { + while (p && *p != '\\' && *p) p = CharNext(p); + ch = (p ? *p : 0); + if (p) *p = 0; + int pp = (int)wcslen(buf1)-1; + + while(buf1[pp] == '.' || buf1[pp] == ' ' || + (buf1[pp] == '\\' && (buf1[pp-1] == '.' || buf1[pp-1] == ' ' || buf1[pp-1] == '/')) || + buf1[pp] == '/' && buf1) + { + if(buf1[pp] == '\\') + { + buf1[pp-1] = '_'; + pp -= 2; + } else { + buf1[pp] = '_'; + pp--; + } + } + + WIN32_FIND_DATA fd = {0}; + // Avoid a "There is no disk in the drive" error box on empty removable drives + UINT prevErrorMode = SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS); + HANDLE h = FindFirstFile(buf1,&fd); + SetErrorMode(prevErrorMode); + if (h == INVALID_HANDLE_VALUE) + { + if (!CreateDirectory(buf1,NULL)) errors++; + } else { + FindClose(h); + if ((fd.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) == 0) errors++; + } + if (p) *p++ = ch; + } + } + + return errors != 0; +} + +bool supportedFormat(wchar_t * file, wchar_t * supportedFormats) { + wchar_t * ext = wcsrchr(file,'.'); + if(!ext) return false; + ext++; + wchar_t * p = supportedFormats; + while(p && *p) { + bool ret=false; + wchar_t * np = wcschr(p,L';'); + if(np) *np = 0; + if(!_wcsicmp(ext,p)) ret=true; + if(np) { *np = L';'; p=np+1; } + else return ret; + if(ret) return true; + } + return false; +} + +// FixReplacementVars: replaces <Artist>, <Title>, <Album>, and #, ##, ##, with appropriate data +// DOES NOT add a file extention!! +wchar_t * fixReplacementVars(wchar_t *str, int str_size, Device * dev, songid_t song) +{ + #define ADD_STR(x) { x(song,outp,str_size-1-(outp-str)); removebadchars(outp); while (outp && *outp) outp++; } + #define ADD_STR_U(x) { x(song,outp,str_size-1-(outp-str)); removebadchars(outp); while (outp && *outp) { *outp=towupper(*outp); outp++; } } + #define ADD_STR_L(x) { x(song,outp,str_size-1-(outp-str)); removebadchars(outp); while (outp && *outp) { *outp=towlower(*outp); outp++; } } + + wchar_t tmpsrc[4096] = {0}; + lstrcpyn(tmpsrc,str,sizeof(tmpsrc)/sizeof(wchar_t)); //lstrcpyn is nice enough to make sure it's null terminated. + + wchar_t *inp = tmpsrc; + wchar_t *outp = str; + int slash = 0; + + while (inp && *inp && outp-str < str_size-2) + { + if (*inp == L'<') + { + if (!wcsncmp(inp,L"<TITLE>",7)) + { + ADD_STR_U(dev->getTrackTitle); + inp+=7; + } + else if (!wcsncmp(inp,L"<title>",7)) + { + ADD_STR_L(dev->getTrackTitle); + inp+=7; + } + else if (!_wcsnicmp(inp,L"<Title>",7)) + { + ADD_STR(dev->getTrackTitle); + inp+=7; + } + else if (!wcsncmp(inp,L"<ALBUM>",7)) + { + ADD_STR_U(dev->getTrackAlbum); + inp+=7; + } + else if (!wcsncmp(inp,L"<album>",7)) + { + ADD_STR_L(dev->getTrackAlbum); + inp+=7; + } + else if (!_wcsnicmp(inp,L"<Album>",7)) + { + ADD_STR(dev->getTrackAlbum); + inp+=7; + } + else if (!wcsncmp(inp,L"<GENRE>",7)) + { + ADD_STR_U(dev->getTrackGenre); + inp+=7; + } + else if (!wcsncmp(inp,L"<genre>",7)) + { + ADD_STR_L(dev->getTrackGenre); + inp+=7; + } + else if (!_wcsnicmp(inp,L"<Genre>",7)) + { + ADD_STR(dev->getTrackGenre); + inp+=7; + } + else if (!wcsncmp(inp,L"<ARTIST>",8)) + { + ADD_STR_U(dev->getTrackArtist); + inp+=8; + } + else if (!wcsncmp(inp,L"<artist>",8)) + { + ADD_STR_L(dev->getTrackArtist); + inp+=8; + } + else if (!_wcsnicmp(inp,L"<Artist>",8)) + { + ADD_STR(dev->getTrackArtist); + inp+=8; + } + else if (!wcsncmp(inp,L"<ALBUMARTIST>",13)) + { + wchar_t temp[128] = {0}; + + dev->getTrackAlbumArtist(song, temp, 128); + if (temp[0] == 0) + dev->getTrackArtist(song, temp, 128); + + lstrcpyn(outp,temp,str_size-1-(outp-str)); + removebadchars(outp); + while (outp && *outp) { *outp=towupper(*outp); outp++; } + + inp+=13; + } + else if (!wcsncmp(inp,L"<albumartist>",13)) + { + wchar_t temp[128] = {0}; + + dev->getTrackAlbumArtist(song, temp, 128); + if (temp[0] == 0) + dev->getTrackArtist(song, temp, 128); + + lstrcpyn(outp,temp,str_size-1-(outp-str)); + removebadchars(outp); + while (outp && *outp) { *outp=towlower(*outp); outp++; } + inp+=13; + } + else if (!_wcsnicmp(inp,L"<Albumartist>",13)) + { + wchar_t temp[128] = {0}; + + dev->getTrackAlbumArtist(song, temp, 128); + if (temp[0] == 0) + dev->getTrackArtist(song, temp, 128); + + lstrcpyn(outp,temp, (int)str_size-1-(outp-str)); + removebadchars(outp); + while (outp && *outp) outp++; + inp+=13; + } + else if (!_wcsnicmp(inp,L"<year>",6)) + { + wchar_t year[64] = {0}; + int y = dev->getTrackYear(song); + if (y) StringCchPrintf(year, ARRAYSIZE(year), L"%d", y); + lstrcpyn(outp,year, (int)str_size-1-(outp-str)); while (outp && *outp) outp++; + inp+=6; + } + else if (!_wcsnicmp(inp,L"<disc>",6)) + { + wchar_t disc[16] = {0}; + int d = dev->getTrackDiscNum(song); + if (d) StringCchPrintf(disc, ARRAYSIZE(disc), L"%d", d); + lstrcpyn(outp,disc, (int)str_size-1-(outp-str)); while (outp && *outp) outp++; + inp+=6; + } + else if (!_wcsnicmp(inp,L"<filename>",10)) + { + wchar_t tfn[MAX_PATH], *ext, *fn; + StringCchCopy(tfn,MAX_PATH,((AndroidSong*)song)->filename); + ext = wcsrchr(tfn, L'.'); + *ext = 0; //kill extension since its added later + fn = wcsrchr(tfn, L'\\'); + fn++; + lstrcpyn(outp,fn, (int)str_size-1-(outp-str)); + while (outp && *outp) outp++; + inp+=10; + } + else + { + // use this to skip over unknown tags + while (inp && *inp && *inp != L'>') inp++; + if (inp) inp++; + } + } + else if (*inp == L'#') + { + int nd=0; + wchar_t tmp[64] = {0}; + while (inp && *inp == L'#') nd++,inp++; + int track = dev->getTrackTrackNum(song); + if (!track) + { + while (inp && *inp == L' ') inp++; + while (inp && *inp == L'\\') inp++; + if (inp && (*inp == L'-' || *inp == L'.' || *inp == L'_')) // separator + { + inp++; + while (inp && *inp == L' ') inp++; + } + } + else + { + if (nd > 1) + { + wchar_t tmp2[32] = {0}; + if (nd > 5) nd=5; + StringCchPrintf(tmp2, ARRAYSIZE(tmp2), L"%%%02dd",nd); + StringCchPrintf(tmp, ARRAYSIZE(tmp), tmp2,track); + } + else StringCchPrintf(tmp, ARRAYSIZE(tmp), L"%d",track); + } + lstrcpyn(outp,tmp, (int)str_size-1-(outp-str)); while (outp && *outp) outp++; + } + else + { + if (*inp == L'\\') slash += 1; + *outp++=*inp++; + } + } + + if (outp) *outp = 0; + + // if we end up with something like U:\\\ then this + // will set it to be just <filename> so it'll not + // end up making a load of bad files e.g. U:\.mp3 + int out_len = lstrlen(str); + if (out_len) + { + if (out_len - 2 == slash) + { + outp = str + 3; + wchar_t tfn[MAX_PATH], *ext, *fn; + StringCchCopy(tfn,MAX_PATH,((AndroidSong*)song)->filename); + ext = wcsrchr(tfn, L'.'); + *ext = 0; //kill extension since its added later + fn = wcsrchr(tfn, L'\\'); + fn++; + lstrcpyn(outp,fn, (int)str_size-1-(outp-str)); + while (outp && *outp) outp++; + if (outp) *outp = 0; + } + } + + inp=str; + outp=str; + wchar_t lastc=0; + while (inp && *inp) + { + wchar_t ch=*inp++; + if (ch == L'\t') ch=L' '; + + if (ch == L' ' && (lastc == L' ' || lastc == L'\\' || lastc == L'/')) continue; // ignore space after slash, or another space + + if ((ch == L'\\' || ch == L'/') && lastc == L' ') outp--; // if we have a space then slash, back up to write the slash where the space was + *outp++=ch; + lastc=ch; + } + if (outp) *outp=0; + #undef ADD_STR + #undef ADD_STR_L + #undef ADD_STR_U + + return str; +}
\ No newline at end of file diff --git a/Src/Plugins/Portable/pmp_android/version.rc2 b/Src/Plugins/Portable/pmp_android/version.rc2 new file mode 100644 index 00000000..0bc218ca --- /dev/null +++ b/Src/Plugins/Portable/pmp_android/version.rc2 @@ -0,0 +1,39 @@ + +///////////////////////////////////////////////////////////////////////////// +// +// Version +// +#include "../../../Winamp/buildType.h" +VS_VERSION_INFO VERSIONINFO + FILEVERSION 1,72,0,0 + PRODUCTVERSION WINAMP_PRODUCTVER + FILEFLAGSMASK 0x17L +#ifdef _DEBUG + FILEFLAGS 0x1L +#else + FILEFLAGS 0x0L +#endif + FILEOS 0x4L + FILETYPE 0x2L + FILESUBTYPE 0x0L +BEGIN + BLOCK "StringFileInfo" + BEGIN + BLOCK "040904b0" + BEGIN + VALUE "CompanyName", "Winamp SA" + VALUE "FileDescription", "Winamp Portable Device Plug-in" + VALUE "FileVersion", "1,72,0,0" + VALUE "InternalName", "Nullsoft Android Device" + VALUE "LegalCopyright", "Copyright © 2010-2023 Winamp SA" + VALUE "LegalTrademarks", "Nullsoft and Winamp are trademarks of Winamp SA" + VALUE "OriginalFilename", "pmp_android.dll" + VALUE "ProductName", "Winamp" + VALUE "ProductVersion", STR_WINAMP_PRODUCTVER + END + END + BLOCK "VarFileInfo" + BEGIN + VALUE "Translation", 0x409, 1200 + END +END |