aboutsummaryrefslogtreecommitdiff
path: root/Src/Plugins/Portable/pmp_android
diff options
context:
space:
mode:
authorJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
committerJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
commit20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch)
tree12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/Plugins/Portable/pmp_android
parent537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff)
downloadwinamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz
Initial community commit
Diffstat (limited to 'Src/Plugins/Portable/pmp_android')
-rw-r--r--Src/Plugins/Portable/pmp_android/albumart.cpp58
-rw-r--r--Src/Plugins/Portable/pmp_android/androiddevice.cpp1869
-rw-r--r--Src/Plugins/Portable/pmp_android/androiddevice.h240
-rw-r--r--Src/Plugins/Portable/pmp_android/androidplaylist.cpp134
-rw-r--r--Src/Plugins/Portable/pmp_android/androidplaylist.h49
-rw-r--r--Src/Plugins/Portable/pmp_android/androidplaylistsaver.cpp74
-rw-r--r--Src/Plugins/Portable/pmp_android/androidplaylistsaver.h33
-rw-r--r--Src/Plugins/Portable/pmp_android/api.cpp64
-rw-r--r--Src/Plugins/Portable/pmp_android/api.h34
-rw-r--r--Src/Plugins/Portable/pmp_android/deviceprovider.cpp343
-rw-r--r--Src/Plugins/Portable/pmp_android/deviceprovider.h50
-rw-r--r--Src/Plugins/Portable/pmp_android/eject.cpp138
-rw-r--r--Src/Plugins/Portable/pmp_android/filecopy.cpp67
-rw-r--r--Src/Plugins/Portable/pmp_android/main.cpp601
-rw-r--r--Src/Plugins/Portable/pmp_android/pmp_android.rc209
-rw-r--r--Src/Plugins/Portable/pmp_android/pmp_android.sln130
-rw-r--r--Src/Plugins/Portable/pmp_android/pmp_android.vcxproj332
-rw-r--r--Src/Plugins/Portable/pmp_android/pmp_android.vcxproj.filters91
-rw-r--r--Src/Plugins/Portable/pmp_android/resource.h75
-rw-r--r--Src/Plugins/Portable/pmp_android/resources/androidIcon.pngbin0 -> 197 bytes
-rw-r--r--Src/Plugins/Portable/pmp_android/resources/generic_android.pngbin0 -> 5721 bytes
-rw-r--r--Src/Plugins/Portable/pmp_android/utils.cpp334
-rw-r--r--Src/Plugins/Portable/pmp_android/version.rc239
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
new file mode 100644
index 00000000..674c4774
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_android/resources/androidIcon.png
Binary files differ
diff --git a/Src/Plugins/Portable/pmp_android/resources/generic_android.png b/Src/Plugins/Portable/pmp_android/resources/generic_android.png
new file mode 100644
index 00000000..bafdb3ce
--- /dev/null
+++ b/Src/Plugins/Portable/pmp_android/resources/generic_android.png
Binary files differ
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