2185 lines
72 KiB
C++
2185 lines
72 KiB
C++
#include "P4SDevice.h"
|
|
#include <time.h>
|
|
#include "msWMDM_i.c"
|
|
#include "../nu/AutoWide.h"
|
|
#include "../nu/AutoChar.h"
|
|
#include "../WAT/wa_logger.h"
|
|
#include "MyProgress.h"
|
|
|
|
#include "WMDRMDeviceApp_i.c"
|
|
|
|
extern C_ItemList devices;
|
|
extern HANDLE killEvent;
|
|
extern CRITICAL_SECTION csTransfers;
|
|
|
|
#define plext L"pla"
|
|
|
|
BOOL FormatResProtocol(const wchar_t *resourceName, const wchar_t *resourceType, wchar_t *buffer, size_t bufferMax);
|
|
|
|
static BYTE* GetMetadataItem(IWMDMStorage4 * store, const WCHAR * name);
|
|
static IWMDMStorage4* GetOrCreateFolder(IWMDMStorage4 * store, wchar_t * name, P4SDevice * dev=NULL, bool album=false, const itemRecordW * item=NULL);
|
|
|
|
// from http://msdn.microsoft.com/library/default.asp?url=/library/en-us/wmdm/htm/wmdm_format_capability.asp
|
|
void FreeFormatCapability(WMDM_FORMAT_CAPABILITY formatCap)
|
|
{
|
|
// Loop through all configurations.
|
|
for (UINT i=0; i < formatCap.nPropConfig; i++)
|
|
{
|
|
// Loop through all descriptions of a configuration and delete
|
|
// the values particular to that description type.
|
|
for (UINT j=0; j < formatCap.pConfigs[i].nPropDesc; j++)
|
|
{
|
|
switch (formatCap.pConfigs[i].pPropDesc[j].ValidValuesForm)
|
|
{
|
|
case WMDM_ENUM_PROP_VALID_VALUES_ENUM:
|
|
for (UINT k=0; k < formatCap.pConfigs[i].pPropDesc[j].ValidValues.EnumeratedValidValues.cEnumValues; k++)
|
|
{
|
|
PropVariantClear (&(formatCap.pConfigs[i].pPropDesc[j].ValidValues.EnumeratedValidValues.pValues[k]));
|
|
}
|
|
CoTaskMemFree(formatCap.pConfigs[i].pPropDesc[j].ValidValues.EnumeratedValidValues.pValues);
|
|
break;
|
|
case WMDM_ENUM_PROP_VALID_VALUES_RANGE:
|
|
PropVariantClear (&(formatCap.pConfigs[i].pPropDesc[j].ValidValues.ValidValuesRange.rangeMin));
|
|
PropVariantClear (&(formatCap.pConfigs[i].pPropDesc[j].ValidValues.ValidValuesRange.rangeMax));
|
|
PropVariantClear (&(formatCap.pConfigs[i].pPropDesc[j].ValidValues.ValidValuesRange.rangeStep));
|
|
break;
|
|
case WMDM_ENUM_PROP_VALID_VALUES_ANY:
|
|
// No dynamically allocated memory for this value.
|
|
default:
|
|
break;
|
|
}
|
|
|
|
// Free the memory for the description name.
|
|
CoTaskMemFree(formatCap.pConfigs[i].pPropDesc[j].pwszPropName);
|
|
}
|
|
// Free the memory holding the array of description items for this configuration.
|
|
CoTaskMemFree(formatCap.pConfigs[i].pPropDesc);
|
|
}
|
|
|
|
// Free the memory pointing to the array of configurations.
|
|
CoTaskMemFree(formatCap.pConfigs);
|
|
formatCap.nPropConfig = 0;
|
|
}
|
|
|
|
static HRESULT GetFormatCaps(WMDM_FORMATCODE formatCode, IWMDMDevice3* pDevice)
|
|
{
|
|
// Get a list of supported configurations for the format.
|
|
WMDM_FORMAT_CAPABILITY formatCapList;
|
|
HRESULT hr = pDevice->GetFormatCapability(formatCode, &formatCapList);
|
|
if (FAILED(hr)) return E_FAIL;
|
|
if (formatCapList.nPropConfig == 0)
|
|
{
|
|
FreeFormatCapability(formatCapList);
|
|
return E_FAIL; // operation succeeded, but format not supported.
|
|
}
|
|
|
|
// TODO: Display the format name.
|
|
// Loop through the configurations and examine each one.
|
|
for (UINT iConfig = 0; iConfig < formatCapList.nPropConfig; iConfig++)
|
|
{
|
|
WMDM_PROP_CONFIG formatConfig = formatCapList.pConfigs[iConfig];
|
|
|
|
// Preference level for this configuration (lower number means more preferred).
|
|
// TODO: Display the preference level for this format configuration.
|
|
|
|
// Loop through all properties for this configuration and get supported
|
|
// values for the property. Values can be a single value, a range,
|
|
// or a list of enumerated values.
|
|
for (UINT iDesc = 0; iDesc < formatConfig.nPropDesc; iDesc++)
|
|
{
|
|
WMDM_PROP_DESC propDesc = formatConfig.pPropDesc[iDesc];
|
|
// TODO: Display the property name.
|
|
|
|
// Three ways a value can be represented: any, a range, or a list.
|
|
switch (propDesc.ValidValuesForm)
|
|
{
|
|
case WMDM_ENUM_PROP_VALID_VALUES_ANY:
|
|
// TODO: Display a message indicating that all values are valid.
|
|
break;
|
|
case WMDM_ENUM_PROP_VALID_VALUES_RANGE:
|
|
{
|
|
// List these in the docs as the propvariants set.
|
|
WMDM_PROP_VALUES_RANGE rng =
|
|
propDesc.ValidValues.ValidValuesRange;
|
|
// TODO: Display the min, max, and step values.
|
|
}
|
|
break;
|
|
case WMDM_ENUM_PROP_VALID_VALUES_ENUM:
|
|
{
|
|
// TODO: Display a banner for the list of valid values.
|
|
/*
|
|
WMDM_PROP_VALUES_ENUM list = propDesc.ValidValues.EnumeratedValidValues;
|
|
PROPVARIANT pVal;
|
|
for (UINT iValue = 0; iValue < list.cEnumValues; iValue++)
|
|
{
|
|
pVal = list.pValues[iValue];
|
|
// TODO: Display each valid value.
|
|
PropVariantClear(&pVal);
|
|
PropVariantInit(&pVal);
|
|
}*/
|
|
}
|
|
|
|
break;
|
|
default:
|
|
FreeFormatCapability(formatCapList);
|
|
return E_FAIL;
|
|
//break;
|
|
}
|
|
}
|
|
}
|
|
// Now clear the memory used by WMDM_FORMAT_CAPABILITY.
|
|
FreeFormatCapability(formatCapList);
|
|
return hr;
|
|
}
|
|
|
|
static __time64_t wmdmDateTimeToUnixTime(_WMDMDATETIME * t) {
|
|
tm m={0};
|
|
m.tm_hour = t->wHour;
|
|
m.tm_min = t->wMinute;
|
|
m.tm_sec = t->wSecond;
|
|
m.tm_mday = t->wDay;
|
|
m.tm_mon = t->wMonth;
|
|
m.tm_year = t->wYear;
|
|
return _mktime64(&m);
|
|
}
|
|
|
|
HRESULT getMetadata(IWMDMStorage4 *store2,IWMDMMetaData ** meta, bool noMetadata) {
|
|
const wchar_t ** propnames = (const wchar_t**)calloc(15,sizeof(void*));
|
|
propnames[0] = g_wszWMDMFormatCode;
|
|
propnames[1] = g_wszWMDMTitle;
|
|
propnames[2] = g_wszWMDMAuthor;
|
|
propnames[3] = g_wszWMDMAlbumTitle;
|
|
propnames[4] = g_wszWMDMGenre;
|
|
propnames[5] = g_wszWMDMTrack;
|
|
propnames[6] = g_wszWMDMYear;
|
|
propnames[7] = g_wszWMDMFileSize;
|
|
propnames[8] = g_wszWMDMDuration;
|
|
propnames[9] = g_wszWMDMPlayCount;
|
|
propnames[10] = g_wszWMDMUserRating;
|
|
propnames[11] = g_wszWMDMUserLastPlayTime;
|
|
propnames[12] = g_wszWMDMLastModifiedDate;
|
|
propnames[13] = g_wszWMDMAlbumArtist;
|
|
propnames[14] = g_wszWMDMComposer;
|
|
HRESULT h;
|
|
if(noMetadata) {
|
|
h = store2->GetSpecifiedMetadata(1,(LPCWSTR*)propnames,meta);
|
|
if(h == WMDM_S_NOT_ALL_PROPERTIES_RETRIEVED) h = S_OK;
|
|
if(h != S_OK) { // ugh. Guess that this is an AAC/M4A. Dirty workaround hack!
|
|
if (SUCCEEDED(store2->CreateEmptyMetadataObject(meta))) {
|
|
h = S_OK;
|
|
DWORD type = WMDM_FORMATCODE_UNDEFINEDAUDIO;
|
|
(*meta)->AddItem(WMDM_TYPE_DWORD,g_wszWMDMFormatCode,(BYTE*)&type,sizeof(type));
|
|
}
|
|
}
|
|
} else {
|
|
h = store2->GetSpecifiedMetadata(13,(LPCWSTR*)propnames,meta);
|
|
if(h == WMDM_S_NOT_ALL_PROPERTIES_RETRIEVED) h = S_OK;
|
|
}
|
|
free(propnames);
|
|
return h;
|
|
}
|
|
|
|
void P4SDevice::foundSong(IWMDMStorage4 * store, IWMDMMetaData * meta,bool video,int pl,wchar_t * artist, wchar_t * album, IWMDMStorage4 * alb, IWMDMMetaData * albmeta) {
|
|
Playlist * pls = (Playlist *)playlists.Get(pl);
|
|
Song * song = new Song;
|
|
song->video = video;
|
|
song->meta = meta;
|
|
song->modified = false;
|
|
song->storage = store;
|
|
song->artist = artist;
|
|
song->album = album;
|
|
if(alb && albmeta) {
|
|
song->alb = alb;
|
|
song->albmeta = albmeta;
|
|
alb->AddRef();
|
|
albmeta->AddRef();
|
|
}
|
|
if(song->artist) song->artist = _wcsdup(song->artist);
|
|
if(song->album) song->album = _wcsdup(song->album);
|
|
pls->songs.Add(song);
|
|
store->AddRef();
|
|
meta->AddRef();
|
|
if(noMetadata || video) {
|
|
wchar_t buf[2048]=L"";
|
|
getTrackAlbum((songid_t)song,buf,2048);
|
|
if(video) meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMAlbumTitle,(BYTE*)L"~",4);
|
|
else {
|
|
meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMAlbumTitle,(BYTE*)buf,wcslen(buf)*2 + 2);
|
|
buf[0]=0;
|
|
getTrackArtist((songid_t)song,buf,2048);
|
|
}
|
|
meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMAuthor,(BYTE*)buf,wcslen(buf)*2 + 2);
|
|
buf[0]=0;
|
|
getTrackTitle((songid_t)song,buf,2048);
|
|
meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMTitle,(BYTE*)buf,wcslen(buf)*2 + 2);
|
|
int n = getTrackTrackNum((songid_t)song);
|
|
meta->AddItem(WMDM_TYPE_DWORD,g_wszWMDMTrack,(BYTE*)&n,sizeof(DWORD));
|
|
__int64 s = (__int64)getTrackSize((songid_t)song);
|
|
meta->AddItem(WMDM_TYPE_QWORD,g_wszWMDMFileSize,(BYTE*)&s,sizeof(__int64));
|
|
}
|
|
}
|
|
|
|
void P4SDevice::foundPlaylist(IWMDMStorage4 * store, IWMDMMetaData * meta) {
|
|
DWORD count = 0;
|
|
IWMDMStorage ** stores;
|
|
wchar_t buf[100] = {0};
|
|
store->GetName(buf,100);
|
|
//OutputDebugString(buf);
|
|
if(store->GetReferences(&count,&stores) == S_OK) {
|
|
if(count > 0) {
|
|
Playlist * pl = new Playlist;
|
|
StringCchCopy(pl->name, ARRAYSIZE(pl->name), buf);
|
|
{wchar_t * ext = wcsrchr(pl->name,L'.'); if(ext) *ext=0;}
|
|
pl->storage = store;
|
|
pl->meta = meta;
|
|
meta->AddRef();
|
|
store->AddRef();
|
|
playlists.Add(pl);
|
|
int num = playlists.GetSize()-1;
|
|
for(unsigned int i=0; i<count; i++) {
|
|
IWMDMStorage4 * song=NULL;
|
|
/*{
|
|
wchar_t buf[100] = {0};
|
|
stores[i]->GetName(buf,100);
|
|
OutputDebugString(buf);
|
|
}*/
|
|
if(stores[i]->QueryInterface(&song) == S_OK) if(song) {
|
|
/*
|
|
Song * s = new Song;
|
|
s->modified=false;
|
|
s->storage=song;
|
|
HRESULT h = getMetadata(song,&s->meta);
|
|
if (SUCCEEDED(h)) pl->songs.Add(s);
|
|
else delete s;
|
|
*/
|
|
//pl->songs.Add(song);
|
|
IWMDMMetaData * meta;
|
|
if (SUCCEEDED(getMetadata(song,&meta,noMetadata))) {
|
|
foundSong(song,meta,false,num); meta->Release();
|
|
}
|
|
song->Release();
|
|
}
|
|
stores[i]->Release();
|
|
}
|
|
} else {
|
|
//OutputDebugString(L"ref count zero");
|
|
}
|
|
CoTaskMemFree(stores);
|
|
} else {
|
|
//OutputDebugString(L"can't get playlist refs");
|
|
}
|
|
}
|
|
|
|
void P4SDevice::traverseStorage(IWMDMStorage * store, int level, wchar_t * artist, wchar_t * album) {
|
|
if(!store) return;
|
|
IWMDMStorage4 * store2=NULL;
|
|
ULONG num;
|
|
//OutputDebugStringA("1A");
|
|
C_ItemList storages;
|
|
{
|
|
IWMDMEnumStorage * enstore=NULL;
|
|
IWMDMStorage * storeold=NULL;
|
|
HRESULT hr = store->EnumStorage(&enstore);
|
|
while(SUCCEEDED(hr)) {
|
|
if (WaitForSingleObject(killEvent,0) == WAIT_OBJECT_0)
|
|
break;
|
|
hr = enstore->Next(1,&storeold,&num);
|
|
if(SUCCEEDED(hr) && storeold) {
|
|
hr = storeold->QueryInterface(&store2);
|
|
storeold->Release();
|
|
if(SUCCEEDED(hr) && store2) {
|
|
storages.Add(store2);
|
|
}
|
|
else break;
|
|
}
|
|
else break;
|
|
}
|
|
if(enstore) enstore->Release();
|
|
}
|
|
//OutputDebugStringA("2A");
|
|
//while (SUCCEEDED(hr) && enstore) {
|
|
IWMDMStorage4 * alb=NULL;
|
|
IWMDMMetaData * albmeta=NULL;
|
|
|
|
for(int i=0; i<storages.GetSize(); i++) {
|
|
wchar_t buf[256]=L"";
|
|
store2 = (IWMDMStorage4*)storages.Get(i);
|
|
store2->GetName(buf,256);
|
|
wchar_t *ext = wcsrchr(buf,L'.');
|
|
if(ext && _wcsicmp(ext,L".alb")==0) {
|
|
alb = store2;
|
|
alb->AddRef();
|
|
alb->GetMetadata(&albmeta);
|
|
break;
|
|
}
|
|
}
|
|
|
|
//albfiles.Add(new AlbFile(store2,meta));
|
|
for(int i=0; i<storages.GetSize(); i++) {
|
|
if (WaitForSingleObject(killEvent,0) == WAIT_OBJECT_0)
|
|
break;
|
|
//OutputDebugStringA("1");
|
|
store2 = (IWMDMStorage4*)storages.Get(i);
|
|
|
|
if(store2 == alb) continue;
|
|
|
|
wchar_t buf[256]=L"";
|
|
//OutputDebugStringA("2");
|
|
store2->GetName(buf,256);
|
|
if(playlistsDir == NULL && _wcsicmp(buf,L"My Playlists")==0) { playlistsDir = store2; store2->AddRef(); }
|
|
if(playlistsDir == NULL && _wcsicmp(buf,L"Playlists")==0) { playlistsDir = store2; store2->AddRef(); }
|
|
|
|
DWORD attribs=NULL;
|
|
store2->GetAttributes(&attribs,NULL);
|
|
//OutputDebugStringA("3");
|
|
if(attribs & WMDM_FILE_ATTR_FOLDER) {
|
|
if(level==0) artist=buf;
|
|
else if(level==1) album=buf;
|
|
//OutputDebugStringA("5");
|
|
WMDM_STORAGE_ENUM_MODE mode = ENUM_MODE_RAW;
|
|
store2->SetEnumPreference(&mode,0,NULL);
|
|
traverseStorage(store2,level+1,artist,album);
|
|
} else if(attribs & WMDM_FILE_ATTR_FILE || !attribs) {
|
|
IWMDMMetaData * meta=NULL;
|
|
//OutputDebugStringA("1");
|
|
HRESULT h = getMetadata(store2,&meta,noMetadata);
|
|
//OutputDebugStringA("2");
|
|
//OutputDebugStringA("4");
|
|
/*{
|
|
wchar_t buf2[400] = {0};
|
|
wsprintf(buf2,L"name: %s atr: 0x%x, hr: 0x%x %s",buf,attribs,h,h == E_INVALIDARG?L"POOT":L"");
|
|
OutputDebugString(buf2);
|
|
}*/
|
|
if(meta && h == S_OK) {
|
|
wchar_t * name=NULL;
|
|
WMDM_TAG_DATATYPE type;
|
|
BYTE * value=NULL;
|
|
UINT valuelen;
|
|
if(meta->QueryByName(g_wszWMDMFormatCode,&type,&value,&valuelen) == S_OK && type == WMDM_TYPE_DWORD)
|
|
{
|
|
switch(*(DWORD*)value) { // find out what it is...
|
|
case WMDM_FORMATCODE_ABSTRACTAUDIOVIDEOPLAYLIST:
|
|
case WMDM_FORMATCODE_WPLPLAYLIST:
|
|
case WMDM_FORMATCODE_M3UPLAYLIST:
|
|
case WMDM_FORMATCODE_MPLPLAYLIST:
|
|
case WMDM_FORMATCODE_ASXPLAYLIST:
|
|
case WMDM_FORMATCODE_PLSPLAYLIST:
|
|
foundPlaylist(store2,meta);
|
|
break;
|
|
case WMDM_FORMATCODE_ASF:
|
|
case WMDM_FORMATCODE_AVI:
|
|
case WMDM_FORMATCODE_MPEG:
|
|
case WMDM_FORMATCODE_WMV:
|
|
case WMDM_FORMATCODE_MP2:
|
|
case WMDM_FORMATCODE_3GP:
|
|
case WMDM_FORMATCODE_UNDEFINEDVIDEO:
|
|
foundSong(store2,meta,true);
|
|
break;
|
|
case WMDM_FORMATCODE_UNDEFINED:
|
|
{ //ugh. nokiahack.
|
|
wchar_t * ext = wcsrchr(buf,'.');
|
|
if(!ext) break;
|
|
if(!_wcsicmp(ext,L".mp4") || !_wcsicmp(ext,L".m4a")) foundSong(store2,meta,false);
|
|
}
|
|
break;
|
|
case WMDM_FORMATCODE_AIFF:
|
|
case WMDM_FORMATCODE_WAVE:
|
|
case WMDM_FORMATCODE_MP3:
|
|
case WMDM_FORMATCODE_WMA:
|
|
case WMDM_FORMATCODE_OGG:
|
|
case WMDM_FORMATCODE_AAC:
|
|
case WMDM_FORMATCODE_MP4:
|
|
case WMDM_FORMATCODE_AUDIBLE:
|
|
case WMDM_FORMATCODE_FLAC:
|
|
case WMDM_FORMATCODE_UNDEFINEDAUDIO:
|
|
foundSong(store2,meta,false,0,0,0,alb,albmeta);
|
|
break;
|
|
}
|
|
} else {
|
|
wchar_t * ext = wcsrchr(buf,'.');
|
|
if(ext) {
|
|
bool m = noMetadata;
|
|
noMetadata = true;
|
|
if(!_wcsicmp(ext,L".mp3") || !_wcsicmp(ext,L".wma")) foundSong(store2,meta,false,0,artist,album);
|
|
else if(!_wcsicmp(ext,L".wmv") || !_wcsicmp(ext,L".avi")) foundSong(store2,meta,true,0,artist,album);
|
|
else if(!_wcsicmp(ext,L".asx") || !_wcsicmp(ext,L".pla")) foundPlaylist(store2,meta);
|
|
noMetadata = m;
|
|
}
|
|
}
|
|
meta->Release();
|
|
if(name) CoTaskMemFree(name);
|
|
if(value) CoTaskMemFree(value);
|
|
}
|
|
}
|
|
if(store2) store2->Release();
|
|
}
|
|
|
|
if(alb) alb->Release();
|
|
if(albmeta) albmeta->Release();
|
|
}
|
|
|
|
bool P4SDevice::songsEqual(songid_t a, songid_t b) {
|
|
wchar_t ba[1024] = {0};
|
|
wchar_t bb[1024] = {0};
|
|
getTrackTitle(a,ba,1024);
|
|
getTrackTitle(b,bb,1024);
|
|
if(wcscmp(ba,bb)) return false;
|
|
getTrackAlbum(a,ba,1024);
|
|
getTrackAlbum(b,bb,1024);
|
|
if(wcscmp(ba,bb)) return false;
|
|
getTrackArtist(a,ba,1024);
|
|
getTrackArtist(b,bb,1024);
|
|
if(wcscmp(ba,bb)) return false;
|
|
return true;
|
|
}
|
|
|
|
int P4SDevice::songsCmp(songid_t a, songid_t b) {
|
|
int q=0;
|
|
wchar_t ba[1024] = {0};
|
|
wchar_t bb[1024] = {0};
|
|
getTrackTitle(a,ba,1024);
|
|
getTrackTitle(b,bb,1024);
|
|
q=wcscmp(ba,bb); if(q) return q;
|
|
getTrackAlbum(a,ba,1024);
|
|
getTrackAlbum(b,bb,1024);
|
|
q=wcscmp(ba,bb); if(q) return q;
|
|
getTrackArtist(a,ba,1024);
|
|
getTrackArtist(b,bb,1024);
|
|
return wcscmp(ba,bb);
|
|
}
|
|
|
|
P4SDevice * sortDev;
|
|
|
|
static int song_sortfunc(const void *elem1, const void *elem2) {
|
|
songid_t a = *(songid_t *)elem1;
|
|
songid_t b = *(songid_t *)elem2;
|
|
return sortDev->songsCmp(a,b);
|
|
}
|
|
|
|
extern IWMDRMDeviceApp * DRMDeviceApp;
|
|
|
|
static songid_t BinaryChopFind(songid_t find,Playlist * mpl, P4SDevice * dev) {
|
|
sortDev = dev;
|
|
songid_t * ret = (songid_t*)bsearch(&find,mpl->songs.GetAll(),mpl->songs.GetSize(),sizeof(songid_t),song_sortfunc);
|
|
return ret?*ret:NULL;
|
|
}
|
|
|
|
P4SDevice::P4SDevice(IWMDMDevice3* pIDevice, bool noMetadata) : transcoder(NULL) {
|
|
error=0;
|
|
musicDir = L"Music";
|
|
videoDir = L"Video";
|
|
lastChange=NULL;
|
|
this->noMetadata = noMetadata;
|
|
if(DRMDeviceApp && DRMDeviceApp->SynchronizeLicenses(pIDevice,NULL,0,0) == S_OK)
|
|
{
|
|
//OutputDebugString(L"sync!");
|
|
}
|
|
playlistsDir=NULL;
|
|
transferQueueSize=0;
|
|
Playlist * mpl = new Playlist;
|
|
playlists.Add(mpl);
|
|
|
|
WMDevice = pIDevice;
|
|
if (NULL != WMDevice)
|
|
{
|
|
WMDevice->AddRef();
|
|
if (FAILED(WMDevice->GetName(name,100)))
|
|
name[0] = L'\0';
|
|
}
|
|
else
|
|
name[0] = L'\0';
|
|
|
|
// give loading indication to the user....
|
|
pmpDeviceLoading load;
|
|
load.dev = this;
|
|
load.UpdateCaption = NULL;
|
|
SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)&load,PMP_IPC_DEVICELOADING);
|
|
if(load.UpdateCaption) {
|
|
wchar_t buf[200]=L"";
|
|
wsprintf(buf,WASABI_API_LNGSTRINGW(IDS_LOADING),name);
|
|
load.UpdateCaption(buf,load.context);
|
|
}
|
|
Load();
|
|
}
|
|
|
|
void P4SDevice::Load() {
|
|
Playlist * mpl = (Playlist*)playlists.Get(0);
|
|
HRESULT hr;
|
|
IWMDMEnumStorage * enstore=0;
|
|
IWMDMStorage * store0=0;
|
|
IWMDMStorage4 * store=0;
|
|
hr = WMDevice->EnumStorage(&enstore);
|
|
ULONG num;
|
|
if(!enstore) {delete this; return;}
|
|
hr = enstore->Next(1,&store0,&num);
|
|
if (SUCCEEDED(hr)) hr = store0->QueryInterface(&store);
|
|
if(store0) store0->Release();
|
|
else error=1;
|
|
if(enstore) enstore->Release();
|
|
else error=1;
|
|
if (SUCCEEDED(hr) && !error) {
|
|
wchar_t buf[100] = {0};
|
|
store->GetName(buf,100);
|
|
mpl->storage = store;
|
|
store->AddRef();
|
|
WMDM_STORAGE_ENUM_MODE mode = ENUM_MODE_RAW;
|
|
store->SetEnumPreference(&mode,0,NULL);
|
|
|
|
//traverseStorage(store);
|
|
IWMDMStorage * playlists;
|
|
if(store->GetStorage(L"Playlists",&playlists) == S_FALSE) {
|
|
if(store->GetStorage(L"My Playlists",&playlists) == S_FALSE)
|
|
{
|
|
playlists = NULL;
|
|
}
|
|
}
|
|
if(playlists)
|
|
{
|
|
traverseStorage(playlists);
|
|
playlists->QueryInterface(&playlistsDir);
|
|
playlists->Release();
|
|
}
|
|
|
|
IWMDMStorage * songs = NULL;
|
|
if(store->GetStorage(L"Music",&songs) == S_OK)
|
|
{
|
|
traverseStorage(songs);
|
|
songs->Release();
|
|
}
|
|
else if(store->GetStorage(L"My Music",&songs) == S_OK)
|
|
{
|
|
traverseStorage(songs);
|
|
songs->Release();
|
|
musicDir = L"My Music";
|
|
}
|
|
else if(store->GetStorage(L"Music Files",&songs) == S_OK)
|
|
{
|
|
traverseStorage(songs);
|
|
songs->Release();
|
|
musicDir = L"Music Files";
|
|
}
|
|
else { // create a music folder
|
|
IWMDMStorageControl3 * storeControl=NULL;
|
|
store->QueryInterface(&storeControl);
|
|
if(storeControl) {
|
|
IWMDMStorage * newdir=NULL;
|
|
IWMDMMetaData * meta=NULL;
|
|
store->CreateEmptyMetadataObject(&meta);
|
|
storeControl->Insert3(WMDM_MODE_BLOCK|WMDM_CONTENT_FOLDER,WMDM_FILE_ATTR_FOLDER,NULL,musicDir,NULL,NULL,meta,NULL,&newdir);
|
|
if(newdir) newdir->Release();
|
|
else error=1;
|
|
if(meta) meta->Release();
|
|
else error=1;
|
|
storeControl->Release();
|
|
} else error=1;
|
|
}
|
|
|
|
if(store->GetStorage(L"Video",&songs) == S_OK) { traverseStorage(songs); songs->Release(); supportsVideo=true; }
|
|
if(store->GetStorage(L"TV",&songs) == S_OK) { traverseStorage(songs); songs->Release(); supportsVideo=true; }
|
|
|
|
if(!playlists && !songs) traverseStorage(store);
|
|
}
|
|
if(!store) error=1;
|
|
if(error) {delete this; return;}
|
|
if(!playlistsDir) playlistsDir = GetOrCreateFolder(store,L"Playlists");
|
|
if(!playlistsDir) {delete this; return;}//MessageBox(plugin.hwndWinampParent,L"An error has occured whilst trying to initialise the playlists on your device.\nPlaylists will not work correctly.",L"Playlists Error",0);
|
|
|
|
sortDev = this;
|
|
qsort(mpl->songs.GetAll(),mpl->songs.GetSize(),sizeof(void*),song_sortfunc);
|
|
|
|
// Now to recombobulate the playlists (this SUCKS slightly less now)
|
|
for(int i=1; i<playlists.GetSize(); i++) {
|
|
Playlist * pl = (Playlist *)playlists.Get(i);
|
|
int l = pl->songs.GetSize();
|
|
for(int j=0; j<l; j++) {
|
|
songid_t plsong = (songid_t)pl->songs.Get(j);
|
|
songid_t mplsong = BinaryChopFind(plsong,mpl,this);
|
|
if(mplsong) pl->songs.Set(j,(void*)mplsong);
|
|
else { pl->songs.Del(j--); l--; }
|
|
|
|
Song * p = (Song *)plsong;
|
|
p->meta->Release();
|
|
p->storage->Release();
|
|
delete p;
|
|
}
|
|
}
|
|
|
|
requiresALB=false;
|
|
{ // check for .alb support
|
|
WMDM_FORMAT_CAPABILITY formatCapList;
|
|
HRESULT hr = WMDevice->GetFormatCapability(WMDM_FORMATCODE_ABSTRACTAUDIOALBUM, &formatCapList);
|
|
bool size=false,data=false,format=false;
|
|
if(!FAILED(hr)) {
|
|
for(unsigned int i=0; i<formatCapList.nPropConfig; i++) {
|
|
WMDM_PROP_CONFIG formatConfig = formatCapList.pConfigs[i];
|
|
for(unsigned int j=0; j<formatConfig.nPropDesc; j++) {
|
|
if(!formatConfig.pPropDesc[j].pwszPropName) continue;
|
|
if(wcscmp(formatConfig.pPropDesc[j].pwszPropName,g_wszWMDMAlbumCoverSize)==0) size=true;
|
|
else if(wcscmp(formatConfig.pPropDesc[j].pwszPropName,g_wszWMDMAlbumCoverData)==0) data=true;
|
|
else if(wcscmp(formatConfig.pPropDesc[j].pwszPropName,g_wszWMDMAlbumCoverFormat)==0) format=true;
|
|
}
|
|
}
|
|
}
|
|
if(size && data && format) requiresALB=true;
|
|
FreeFormatCapability(formatCapList);
|
|
}
|
|
|
|
devices.Add(this);
|
|
SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICECONNECTED);
|
|
|
|
//transcoder = NULL;
|
|
transcoder = (Transcoder*)SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(WPARAM)this,PMP_IPC_GET_TRANSCODER);
|
|
if(transcoder) {
|
|
if(SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_MP3,WMDevice)))
|
|
transcoder->AddAcceptableFormat(L"mp3");
|
|
//transcoder->AddAcceptableFormat(L"wav");
|
|
if(SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_WMA,WMDevice)))
|
|
transcoder->AddAcceptableFormat(L"wma");
|
|
if(SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_ASF,WMDevice)))
|
|
transcoder->AddAcceptableFormat(L"asf");
|
|
if(SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_AVI,WMDevice)))
|
|
transcoder->AddAcceptableFormat(L"avi");
|
|
if(SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_OGG,WMDevice)))
|
|
transcoder->AddAcceptableFormat(L"ogg");
|
|
if(SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_FLAC,WMDevice)))
|
|
transcoder->AddAcceptableFormat(L"flac");
|
|
if(SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_AUDIBLE,WMDevice)))
|
|
transcoder->AddAcceptableFormat(L"aa");
|
|
if(SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_MPEG,WMDevice))) {
|
|
transcoder->AddAcceptableFormat(L"mpeg");
|
|
transcoder->AddAcceptableFormat(L"mpg");
|
|
}
|
|
if(SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_WMV,WMDevice)))
|
|
transcoder->AddAcceptableFormat(L"wmv");
|
|
if(SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_3GP,WMDevice)))
|
|
transcoder->AddAcceptableFormat(L"3gp");
|
|
if(SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_MP4,WMDevice))) {
|
|
transcoder->AddAcceptableFormat(L"mp4");
|
|
transcoder->AddAcceptableFormat(L"m4a");
|
|
}
|
|
if(noMetadata || SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_AAC,WMDevice))) {
|
|
transcoder->AddAcceptableFormat(L"m4a");
|
|
transcoder->AddAcceptableFormat(L"mp4");
|
|
transcoder->AddAcceptableFormat(L"aac");
|
|
}
|
|
}
|
|
}
|
|
|
|
static void commitPlaylist(Playlist * pl) {
|
|
int l = pl->songs.GetSize();
|
|
IWMDMStorage ** newOrder = (IWMDMStorage **)calloc(l, sizeof(IWMDMStorage *));
|
|
for(int j=0; j<l; j++) ((Song *)pl->songs.Get(j))->storage->QueryInterface(&newOrder[j]);
|
|
pl->storage->SetReferences(l, newOrder);
|
|
for(int i=0; i<l; i++) newOrder[i]->Release();
|
|
free(newOrder);
|
|
pl->modified = false;
|
|
}
|
|
|
|
P4SDevice::~P4SDevice() {
|
|
SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICEDISCONNECTED);
|
|
if(playlistsDir) playlistsDir->Release();
|
|
for(int i=1; i<playlists.GetSize(); i++) {
|
|
Playlist * pl = (Playlist*)playlists.Get(i);
|
|
if(pl->modified) commitPlaylist(pl);
|
|
pl->storage->Release();
|
|
pl->meta->Release();
|
|
}
|
|
if(playlists.GetSize()) {
|
|
Playlist * pl = (Playlist*)playlists.Get(0);
|
|
pl->storage->Release();
|
|
for(int j=0; j < pl->songs.GetSize(); j++) {
|
|
Song * s = (Song *)pl->songs.Get(j);
|
|
if (s)
|
|
{
|
|
if(s->modified) s->storage->SetMetadata(s->meta);
|
|
s->meta->Release();
|
|
s->storage->Release();
|
|
delete s;
|
|
}
|
|
}
|
|
}
|
|
|
|
for(int i=0; i<devices.GetSize(); i++) {
|
|
if(devices.Get(i) == (void*)this) {
|
|
devices.Del(i);
|
|
return;
|
|
}
|
|
}
|
|
WMDevice->Release();
|
|
if(transcoder) SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(WPARAM)transcoder,PMP_IPC_RELEASE_TRANSCODER);
|
|
}
|
|
|
|
void P4SDevice::Close()
|
|
{
|
|
delete this;
|
|
}
|
|
|
|
__int64 P4SDevice::getDeviceCapacityAvailable() {
|
|
static __int64 prev;
|
|
IWMDMStorageGlobals * sg = NULL;
|
|
if(!playlistsDir) return 0;
|
|
playlistsDir->GetStorageGlobals(&sg);
|
|
if(sg) {
|
|
ULARGE_INTEGER s;
|
|
sg->GetTotalFree(&s.LowPart,&s.HighPart);
|
|
sg->Release();
|
|
if(s.LowPart == 0 && s.HighPart == 0) return prev;
|
|
return prev = s.QuadPart;
|
|
}
|
|
return prev;
|
|
}
|
|
|
|
__int64 P4SDevice::getDeviceCapacityTotal() {
|
|
static __int64 prev;
|
|
IWMDMStorageGlobals * sg = NULL;
|
|
if(!playlistsDir) return 0;
|
|
playlistsDir->GetStorageGlobals(&sg);
|
|
if(sg) {
|
|
ULARGE_INTEGER s;
|
|
sg->GetTotalSize(&s.LowPart,&s.HighPart);
|
|
sg->Release();
|
|
if(s.LowPart == 0 && s.HighPart == 0) return prev;
|
|
return prev = s.QuadPart;
|
|
}
|
|
return prev;
|
|
}
|
|
|
|
void P4SDevice::deleteTrack(songid_t songid) {
|
|
lastChange=NULL;
|
|
Song * s = (Song*)songid;
|
|
IWMDMStorageControl3 * storeControl=NULL;
|
|
s->storage->QueryInterface(&storeControl);
|
|
if(!storeControl) return;
|
|
for(int i=0; i<playlists.GetSize(); i++) {
|
|
Playlist * pl = (Playlist *)playlists.Get(i);
|
|
int j = pl->songs.GetSize();
|
|
while(j-- > 0) if((Song *)pl->songs.Get(j) == s) { pl->songs.Del(j); pl->modified=true; }
|
|
}
|
|
if(storeControl->Delete(WMDM_MODE_BLOCK,NULL) != S_OK) return;
|
|
s->meta->Release();
|
|
s->storage->Release();
|
|
storeControl->Release();
|
|
delete s;
|
|
}
|
|
|
|
void P4SDevice::commitChanges() {
|
|
for(int i=1; i<playlists.GetSize(); i++) {
|
|
Playlist * pl = (Playlist*)playlists.Get(i);
|
|
if(pl->modified) commitPlaylist(pl);
|
|
}
|
|
|
|
Playlist * pl = (Playlist*)playlists.Get(0);
|
|
for(int j=0; j < pl->songs.GetSize(); j++) {
|
|
Song * s = (Song *)pl->songs.Get(j);
|
|
if(s->modified) {
|
|
s->storage->SetMetadata(s->meta);
|
|
s->meta->Release();
|
|
s->storage->GetMetadata(&s->meta);
|
|
s->modified = false;
|
|
}
|
|
}
|
|
lastChange=NULL;
|
|
}
|
|
|
|
static int fileSizeA(char * filename)
|
|
{
|
|
FILE * fh = fopen(filename,"rb");
|
|
if(!fh) return -1;
|
|
fseek(fh,0,2); //seek to end;
|
|
int l = ftell(fh);
|
|
fclose(fh);
|
|
return l;
|
|
}
|
|
|
|
#define MKVALIDFN(x) { wchar_t * n = x; while(n && *n == L'.') *(n++)=L'_'; while(n && *n) { if(*n == L'|' || *n == L'\\' || *n == L'/' || *n == L'?' || *n == L'<' || *n == L'>' || *n == L':' || *n == L'*' || *n == L'"') *n=L'_'; n++; } n = x+wcslen(x)-1; while(n && *n==L'.' && n>=x) *(n--)=0; }
|
|
|
|
static IWMDMStorage4* GetOrCreateFolder(IWMDMStorage4 * store, wchar_t * name, P4SDevice * dev, bool album, const itemRecordW * item) {
|
|
if(!name[0]) name=L"Blank";
|
|
MKVALIDFN(name);
|
|
if(!name[0]) name=L"Blank";
|
|
if(!store) return NULL;
|
|
IWMDMEnumStorage * enstore=NULL;
|
|
HRESULT hr = store->EnumStorage(&enstore);
|
|
IWMDMStorage * store0;
|
|
IWMDMStorage4 * store4;
|
|
ULONG num;
|
|
if(!enstore) return NULL;
|
|
enstore->Reset();
|
|
hr = enstore->Next(1,&store0,&num);
|
|
while(hr == S_OK) {
|
|
store4=NULL;
|
|
store0->QueryInterface(&store4);
|
|
store0->Release();
|
|
wchar_t buf[100] = {0};
|
|
store4->GetName(buf,100);
|
|
if(_wcsicmp(buf,name) == 0) {
|
|
return store4;
|
|
}
|
|
store4->Release();
|
|
hr = enstore->Next(1,&store0,&num);
|
|
}
|
|
if(enstore) enstore->Release();
|
|
// we must create it!
|
|
store0=store4=NULL;
|
|
IWMDMStorageControl3 * storeControl=NULL;
|
|
store->QueryInterface(&storeControl);
|
|
if(!storeControl) return NULL;
|
|
IWMDMMetaData * meta;
|
|
store->CreateEmptyMetadataObject(&meta);
|
|
storeControl->Insert3(WMDM_MODE_BLOCK|WMDM_CONTENT_FOLDER,WMDM_FILE_ATTR_FOLDER,NULL,name,NULL,NULL,meta,NULL,&store0);
|
|
meta->Release();
|
|
if(!store0) return NULL;
|
|
store0->QueryInterface(&store4);
|
|
storeControl->Release();
|
|
store0->Release();
|
|
store->Release();
|
|
if(album) {
|
|
wchar_t buffer[MAX_PATH];
|
|
IWMDMStorageControl3 * storeControl;
|
|
wsprintf(buffer,L"%s.alb",name);
|
|
store4->QueryInterface(&storeControl);
|
|
IWMDMStorage * newpl=NULL;
|
|
IWMDMStorage4 * newpl4=NULL;
|
|
IWMDMMetaData * meta=NULL;
|
|
store4->CreateEmptyMetadataObject(&meta);
|
|
if (meta)
|
|
{
|
|
DWORD formatCode = WMDM_FORMATCODE_ABSTRACTAUDIOALBUM;
|
|
meta->AddItem(WMDM_TYPE_DWORD,g_wszWMDMFormatCode,(BYTE*)&formatCode,sizeof(DWORD));
|
|
meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMAlbumTitle,(BYTE*)name,(wcslen(name)*2)+2);
|
|
meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMAlbumArtist,(BYTE*)((wchar_t*)item->albumartist),(wcslen(item->albumartist)*2)+2);
|
|
meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMTitle,(BYTE*)name,(wcslen(name)*2)+2);
|
|
meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMAuthor,(BYTE*)((wchar_t*)item->artist),(wcslen(item->artist)*2)+2);
|
|
meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMGenre,(BYTE*)((wchar_t*)item->genre),(wcslen(item->genre)*2)+2);
|
|
storeControl->Insert3(WMDM_MODE_BLOCK | WMDM_CONTENT_FILE,0,NULL,buffer,NULL,NULL,meta,NULL,&newpl);
|
|
storeControl->Release();
|
|
if (newpl)
|
|
{
|
|
newpl->QueryInterface(&newpl4);
|
|
newpl->Release();
|
|
}
|
|
if (newpl4)
|
|
{
|
|
newpl4->SetReferences(0,NULL);
|
|
newpl4->SetMetadata(meta);
|
|
newpl4->Release();
|
|
}
|
|
meta->Release();
|
|
}
|
|
}
|
|
return store4;
|
|
}
|
|
|
|
// Disabled for 5.64
|
|
// Fixes issues with exit failures and duplicates on transfer (view issue only) and other memory corruption issues
|
|
/*typedef struct {
|
|
C_ItemList * playlists;
|
|
void * item;
|
|
} addTrackStruct;
|
|
|
|
void CALLBACK addTrack(ULONG_PTR dwParam) {
|
|
addTrackStruct * a = (addTrackStruct *) dwParam;
|
|
((Playlist*)a->playlists->Get(0))->songs.Add(a->item);
|
|
delete a;
|
|
}*/
|
|
|
|
static void getTime(__time64_t value, _WMDMDATETIME * time) {
|
|
if(!time) return;
|
|
ZeroMemory(time,sizeof(_WMDMDATETIME));
|
|
struct tm * t = _localtime64(&value);
|
|
if(!t) return;
|
|
time->wYear = t->tm_year;
|
|
time->wMonth = t->tm_mon;
|
|
time->wDay = t->tm_mday;
|
|
time->wHour = t->tm_hour;
|
|
time->wMinute = t->tm_min;
|
|
time->wSecond = t->tm_sec;
|
|
}
|
|
|
|
static int atoi_nullok(char * str) {
|
|
if(str) return atoi(str);
|
|
return 0;
|
|
}
|
|
|
|
static IWMDMStorage * storefoo;
|
|
|
|
#define PHASE_START 1
|
|
#define PHASE_INPROGRESS 2
|
|
#define PHASE_FINISH 3
|
|
#define PHASE_DONE 4
|
|
#define PHASE_ERROR 5
|
|
|
|
extern CSecureChannelClient SAC;
|
|
extern int SynchronousProcedureCall(void * p, ULONG_PTR dwParam);
|
|
|
|
void P4SDevice::doTransfer(TransferItem * t) {
|
|
static wchar_t buf[256];
|
|
static IWMDMStorage4 * store;
|
|
static IWMDMStorageControl3 * control;
|
|
|
|
switch(t->phase) {
|
|
case PHASE_START:
|
|
{
|
|
bool video = false;
|
|
DWORD formatCode=0;
|
|
wchar_t * point = wcsrchr(t->file,L'.');
|
|
if(point) {
|
|
if(_wcsicmp(point,L".wma")==0) formatCode = WMDM_FORMATCODE_WMA;
|
|
else if(_wcsicmp(point,L".wav")==0) formatCode = WMDM_FORMATCODE_WAVE;
|
|
else if(_wcsicmp(point,L".ogg")==0) formatCode = WMDM_FORMATCODE_OGG;
|
|
else if(_wcsicmp(point,L".m4a")==0) formatCode = WMDM_FORMATCODE_MP4;
|
|
else if(_wcsicmp(point,L".aac")==0) formatCode = WMDM_FORMATCODE_AAC;
|
|
else if(_wcsicmp(point,L".aa")==0) formatCode = WMDM_FORMATCODE_AUDIBLE;
|
|
else if(_wcsicmp(point,L".flac")==0 || _wcsicmp(point,L".fla")==0) formatCode = WMDM_FORMATCODE_FLAC;
|
|
else if(_wcsicmp(point,L".asf")==0) { video=true; formatCode = WMDM_FORMATCODE_ASF; }
|
|
else if(_wcsicmp(point,L".avi")==0) { video=true; formatCode = WMDM_FORMATCODE_AVI; }
|
|
else if(_wcsicmp(point,L".mpg")==0) { video=true; formatCode = WMDM_FORMATCODE_MPEG; }
|
|
else if(_wcsicmp(point,L".mpeg")==0) { video=true; formatCode = WMDM_FORMATCODE_MPEG; }
|
|
else if(_wcsicmp(point,L".wmv")==0) { video=true; formatCode = WMDM_FORMATCODE_WMV; }
|
|
else if(_wcsicmp(point,L".m4v")==0) { video=true; formatCode = WMDM_FORMATCODE_MP4; }
|
|
else if(_wcsicmp(point,L".mp2")==0) { video=true; formatCode = WMDM_FORMATCODE_MP2; }
|
|
else if(_wcsicmp(point,L".mp4")==0) {
|
|
wchar_t buf[10]=L"0";
|
|
extendedFileInfoStructW m = {t->file,L"type",buf,10};
|
|
SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&m,IPC_GET_EXTENDED_FILE_INFOW);
|
|
formatCode = WMDM_FORMATCODE_MP4;
|
|
video = (buf[0]==L'1');
|
|
}
|
|
else formatCode = WMDM_FORMATCODE_MP3; // mp3 or whatever
|
|
}
|
|
t->video = video;
|
|
|
|
store = ((Playlist *)playlists.Get(0))->storage;
|
|
store->AddRef();
|
|
|
|
if(video) {
|
|
store = GetOrCreateFolder(store,videoDir,this);
|
|
if(_wcsicmp(t->track->artist,L"")) store = GetOrCreateFolder(store,t->track->artist);
|
|
} else {
|
|
store = GetOrCreateFolder(store,musicDir,this);
|
|
if(_wcsicmp(t->track->artist,L"")) store = GetOrCreateFolder(store,t->track->artist);
|
|
else store = GetOrCreateFolder(store,L"No Artist");
|
|
if(_wcsicmp(t->track->album,L"")) store = GetOrCreateFolder(store,t->track->album,this,requiresALB,t->track);
|
|
else store = GetOrCreateFolder(store,L"No Album");
|
|
}
|
|
/*
|
|
DWORD dw;
|
|
do {
|
|
WMDevice->GetStatus(&dw);
|
|
SleepEx(50,true);
|
|
} while(!(dw & WMDM_STATUS_READY));
|
|
*/
|
|
if(!store) {
|
|
t->callback(t->callbackContext,WASABI_API_LNGSTRINGW(IDS_COUND_NOT_CREATE_FOLDER));
|
|
*(t->songid)=NULL;
|
|
t->phase=PHASE_ERROR;
|
|
return;
|
|
}
|
|
|
|
// create and fill in metadata...
|
|
|
|
HRESULT hr;
|
|
hr = store->CreateEmptyMetadataObject(&t->meta);
|
|
if(hr != S_OK) {
|
|
wchar_t buf[100] = {0};
|
|
wsprintf(buf,WASABI_API_LNGSTRINGW(IDS_COULD_NOT_CREATE_METADATA),hr);
|
|
t->callback(t->callbackContext,buf);
|
|
*(t->songid)=NULL;
|
|
t->phase=PHASE_ERROR;
|
|
return;
|
|
}
|
|
t->meta->AddItem(WMDM_TYPE_DWORD,g_wszWMDMFormatCode,(BYTE*)&formatCode,sizeof(DWORD));
|
|
if(t->track->artist) t->meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMAuthor,(BYTE*)((wchar_t*)(t->track->artist)),(wcslen(t->track->artist)*2)+2);
|
|
if(t->track->albumartist) t->meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMAlbumArtist,(BYTE*)((wchar_t*)(t->track->albumartist)),(wcslen(t->track->albumartist)*2)+2);
|
|
if(t->track->composer) t->meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMComposer,(BYTE*)((wchar_t*)(t->track->composer)),(wcslen(t->track->composer)*2)+2);
|
|
if(t->track->album) t->meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMAlbumTitle,(BYTE*)((wchar_t*)(t->track->album)),(wcslen(t->track->album)*2)+2);
|
|
if(t->track->genre) t->meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMGenre,(BYTE*)((wchar_t*)(t->track->genre)),(wcslen(t->track->genre)*2)+2);
|
|
if(t->track->title) t->meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMTitle,(BYTE*)((wchar_t*)(t->track->title)),(wcslen(t->track->title)*2)+2);
|
|
if(t->track->track> 0) t->meta->AddItem(WMDM_TYPE_DWORD,g_wszWMDMTrack,(BYTE*)&t->track->track,sizeof(DWORD));
|
|
__int64 len = t->track->length;
|
|
len *= 10000000;
|
|
wchar_t buf2[256] = {0};
|
|
t->meta->AddItem(WMDM_TYPE_QWORD,g_wszWMDMDuration,(BYTE*)&len,sizeof(__int64));
|
|
int fs = fileSizeA(AutoChar(t->file));
|
|
len = fs;
|
|
t->meta->AddItem(WMDM_TYPE_QWORD,g_wszWMDMFileSize,(BYTE*)&len,sizeof(__int64));
|
|
wsprintf(buf2,L"%d",t->track->year);
|
|
if(t->track->year > 0) t->meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMYear,(BYTE*)buf2,(wcslen(buf2)*2) + 2);
|
|
int v;
|
|
if (t->track->length)
|
|
{
|
|
v = 8*(fs/t->track->length); //atoi_nullok(getRecordExtendedItem(t->track,"BITRATE")) * 1000;
|
|
t->meta->AddItem(WMDM_TYPE_DWORD,g_wszWMDMBitrate,(BYTE*)&v,sizeof(DWORD));
|
|
}
|
|
v = t->track->playcount;
|
|
t->meta->AddItem(WMDM_TYPE_DWORD,g_wszWMDMPlayCount,(BYTE*)&v,sizeof(DWORD));
|
|
v = t->track->rating;
|
|
if(v>=0 && v<=5) t->meta->AddItem(WMDM_TYPE_DWORD,g_wszWMDMUserRating,(BYTE*)&v,sizeof(DWORD));
|
|
_WMDMDATETIME time1={0}, time2={0};
|
|
getTime(t->track->lastplay,&time1);
|
|
t->meta->AddItem(WMDM_TYPE_DWORD,g_wszWMDMUserLastPlayTime,(BYTE*)&time1,sizeof(DWORD));
|
|
getTime(t->track->lastupd,&time2);
|
|
t->meta->AddItem(WMDM_TYPE_DWORD,g_wszWMDMLastModifiedDate,(BYTE*)&time2,sizeof(DWORD));
|
|
|
|
control = NULL;
|
|
store->QueryInterface(&control);
|
|
IWMDMStorage * newstore=NULL;
|
|
|
|
if(video) wsprintf(buf,L"%s%s",(wchar_t*)(t->track->title),(wchar_t*)(wcsrchr(t->track->filename,'.')));
|
|
else wsprintf(buf,L"%02d - %s%s",t->track->track,(wchar_t*)(t->track->title),(wchar_t*)(wcsrchr(t->track->filename,'.')));
|
|
|
|
if(video) wsprintf(buf,L"%s%s",(wchar_t*)(t->track->title),wcsrchr(t->file,L'.'));
|
|
else wsprintf(buf,L"%02d - %s%s",t->track->track,(wchar_t*)(t->track->title),wcsrchr(t->file,L'.'));
|
|
MKVALIDFN(buf);
|
|
|
|
if(!control) {
|
|
t->callback(t->callbackContext,WASABI_API_LNGSTRINGW(IDS_INCOMPATABLE_DEVICE));
|
|
*(t->songid)=NULL;
|
|
t->phase = PHASE_ERROR;
|
|
return;
|
|
}
|
|
|
|
t->phase = PHASE_INPROGRESS;
|
|
t->progress = new MyProgress(t);
|
|
storefoo = NULL;
|
|
|
|
hr = control->Insert3(WMDM_MODE_BLOCK|WMDM_MODE_TRANSFER_PROTECTED|WMDM_CONTENT_FILE|WMDM_STORAGECONTROL_INSERTAFTER,
|
|
WMDM_FILE_ATTR_FILE,
|
|
t->file,
|
|
buf,
|
|
NULL,
|
|
t->progress,
|
|
t->meta,
|
|
NULL,
|
|
&storefoo);
|
|
//OutputDebugString(L"finished insert");
|
|
|
|
if (FAILED(hr)) {
|
|
wchar_t buf1[100] = {0};
|
|
wsprintf(buf1,WASABI_API_LNGSTRINGW(IDS_ERROR_IN_INSERT),hr);
|
|
t->callback(t->callbackContext,buf1);
|
|
*(t->songid)=NULL;
|
|
t->phase = PHASE_ERROR;
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
case PHASE_FINISH:
|
|
//OutputDebugString(L"phase finish start");
|
|
{
|
|
/*
|
|
DWORD dw;
|
|
WMDevice->GetStatus(&dw);
|
|
while(!(dw == WMDM_STATUS_READY)) {
|
|
SleepEx(50,true);
|
|
WMDevice->GetStatus(&dw);
|
|
}
|
|
*/
|
|
}
|
|
|
|
t->progress->Release();
|
|
control->Release();
|
|
|
|
if(storefoo) { /*OutputDebugString(L"storefoo");*/ storefoo->Release(); storefoo=NULL; }
|
|
if(store) {
|
|
IWMDMStorage * store0 = NULL;
|
|
store->GetStorage(buf,&store0);
|
|
store->Release();
|
|
if(store0) {
|
|
IWMDMStorage4 * store4 = NULL;
|
|
store0->QueryInterface(&store4);
|
|
if(store4) {
|
|
store4->AddRef();
|
|
store0->Release();
|
|
store4->SetMetadata(t->meta);
|
|
//t->meta->Release();
|
|
Song * song = new Song;
|
|
song->modified=false;
|
|
song->storage = store4;
|
|
song->meta = t->meta;
|
|
song->video = t->video;
|
|
//((Playlist*)playlists.Get(0))->songs.Add(song);
|
|
|
|
// Disabled for 5.64
|
|
// Fixes issues with exit failures and duplicates on transfer (view issue only) and other memory corruption issues
|
|
/*
|
|
addTrackStruct * a = new addTrackStruct;
|
|
a->item=song;
|
|
a->playlists = &playlists;
|
|
//PostMessage(plugin.hwndPortablesParent,WM_USER+3,(WPARAM)addTrack,(LPARAM)a);
|
|
SynchronousProcedureCall((void*)addTrack,(ULONG_PTR)a);
|
|
*/
|
|
|
|
*(t->songid) = (songid_t)song;
|
|
// sort out the album group...
|
|
if(t->track->album && requiresALB) {
|
|
IWMDMStorage * album0=NULL;
|
|
IWMDMStorage * parent=NULL;
|
|
IWMDMStorage4 * parent4=NULL;
|
|
IWMDMStorage4 * album4;
|
|
wchar_t buf[512] = {0};
|
|
wsprintf(buf,L"%s.alb",(wchar_t*)(t->track->album));
|
|
store4->GetParent(&parent);
|
|
if (parent)
|
|
{
|
|
parent->QueryInterface(&parent4);
|
|
parent->Release();
|
|
if (parent4)
|
|
{
|
|
parent4->GetStorage(buf,&album0);
|
|
parent4->Release();
|
|
if(album0) {
|
|
album0->QueryInterface(&album4);
|
|
album0->Release();
|
|
DWORD refc;
|
|
IWMDMStorage ** refs;
|
|
IWMDMStorage ** newrefs;
|
|
album4->GetReferences(&refc,&refs);
|
|
newrefs = (IWMDMStorage **)calloc((refc + 1), sizeof(void*));
|
|
for(DWORD i=0; i<refc; i++) newrefs[i] = refs[i];
|
|
newrefs[refc] = store4;
|
|
refc++;
|
|
album4->SetReferences(refc,newrefs);
|
|
refc--;
|
|
for(DWORD i=0; i<refc; i++) refs[i]->Release();
|
|
free(newrefs);
|
|
CoTaskMemFree(refs);
|
|
song->alb = album4;
|
|
album4->GetMetadata(&song->albmeta);
|
|
//album4->Release();
|
|
|
|
int w,h;
|
|
ARGB32 *bits;
|
|
if (AGAVE_API_ALBUMART->GetAlbumArt(t->file, L"cover", &w, &h, &bits) == ALBUMART_SUCCESS)
|
|
{
|
|
setArt((songid_t)song,bits,w,h);
|
|
WASABI_API_MEMMGR->sysFree(bits);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if(!*t->songid) t->callback(t->callbackContext,WASABI_API_LNGSTRINGW(IDS_UNSPECIFIED_ERROR));
|
|
t->phase = PHASE_DONE;
|
|
//OutputDebugString(L"phase finish finished");
|
|
break;
|
|
}
|
|
}
|
|
|
|
int P4SDevice::transferTrackToDevice(const itemRecordW * track,void * callbackContext,void (*callback)(void * callbackContext, wchar_t * status),songid_t * songid,int * killswitch) {
|
|
wchar_t file[2048] = {0};
|
|
StringCchCopy(file, ARRAYSIZE(file), track->filename);
|
|
bool deletefile = false;
|
|
if(transcoder) if(transcoder->ShouldTranscode(file)) {
|
|
wchar_t newfile[MAX_PATH] = {0};
|
|
wchar_t ext[10] = {0};
|
|
transcoder->CanTranscode(file,ext);
|
|
transcoder->GetTempFilePath(ext,newfile);
|
|
if(transcoder->TranscodeFile(file,newfile,killswitch,callback,callbackContext)) return -1;
|
|
StringCchCopy(file, ARRAYSIZE(file), newfile);
|
|
deletefile=true;
|
|
}
|
|
|
|
callback(callbackContext,WASABI_API_LNGSTRINGW(IDS_WAITING_FOR_OTHER_TRANSFERS));
|
|
EnterCriticalSection(&csTransfers); // only one transfer at once, globally :(
|
|
callback(callbackContext,WASABI_API_LNGSTRINGW(IDS_TRANSFERRING));
|
|
TransferItem t;
|
|
t.file = file;
|
|
t.track = track;
|
|
t.callback = callback;
|
|
t.callbackContext = callbackContext;
|
|
t.killswitch = killswitch;
|
|
t.songid = songid;
|
|
t.dev = this;
|
|
t.phase = PHASE_START;
|
|
t.pc = 0;
|
|
*songid = NULL;
|
|
this->doTransfer(&t); // do the transfer
|
|
if(t.phase == PHASE_FINISH) this->doTransfer(&t); // finish it, if needs be.
|
|
|
|
int ret = (*songid)?0:-1;
|
|
if(ret==0) callback(callbackContext,WASABI_API_LNGSTRINGW(IDS_DONE));
|
|
trackRemovedFromTransferQueue(track);
|
|
LeaveCriticalSection(&csTransfers);
|
|
if(deletefile) _wunlink(file);
|
|
return ret;
|
|
}
|
|
|
|
bool extentionSupported(IWMDMDevice3* WMDevice, wchar_t * ext,bool aac_and_m4a_support, bool video_supported) {
|
|
if(!ext) return false;
|
|
bool supported=false;
|
|
|
|
if(!_wcsicmp(ext,L".mp3")) supported = SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_MP3,WMDevice));
|
|
else if(!_wcsicmp(ext,L".wma")) supported = SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_WMA,WMDevice));
|
|
else if(!_wcsicmp(ext,L".wav")) supported = SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_WAVE,WMDevice));
|
|
else if(!_wcsicmp(ext,L".aa")) supported = SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_AUDIBLE,WMDevice));
|
|
else if(!_wcsicmp(ext,L".m4a") || !_wcsicmp(ext,L".aac")) supported = SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_AAC,WMDevice)) || SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_MP4,WMDevice));
|
|
else if(!_wcsicmp(ext,L".ogg")) supported = SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_OGG,WMDevice));
|
|
else if(!_wcsicmp(ext,L".flac") || !_wcsicmp(ext,L".fla")) supported = SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_FLAC,WMDevice));
|
|
else if(!_wcsicmp(ext,L".avi")) supported = SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_AVI,WMDevice));
|
|
else if(!_wcsicmp(ext,L".mpg") || !_wcsicmp(ext,L".mpeg")) supported = SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_MPEG,WMDevice));
|
|
else if(!_wcsicmp(ext,L".asf")) supported = SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_ASF,WMDevice));
|
|
else if(!_wcsicmp(ext,L".wmv")) supported = SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_WMV,WMDevice));
|
|
else if(!_wcsicmp(ext,L".mp4")) supported = SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_MP4,WMDevice)) || SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_AAC,WMDevice));
|
|
else if(!_wcsicmp(ext,L".m4v")) supported = SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_MP4,WMDevice));
|
|
else if(!_wcsicmp(ext,L".mp2")) supported = SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_MP2,WMDevice));
|
|
else if(!_wcsicmp(ext,L".3gp")) supported = SUCCEEDED(GetFormatCaps(WMDM_FORMATCODE_3GP,WMDevice));
|
|
else return false;
|
|
|
|
if(!supported) {
|
|
if(!_wcsicmp(ext,L".mp3") || !_wcsicmp(ext,L".wma") || !_wcsicmp(ext,L".wav")) supported=true;
|
|
if(aac_and_m4a_support && (!_wcsicmp(ext,L".m4a") || !_wcsicmp(ext,L".aac"))) supported=true;
|
|
if(video_supported && (!_wcsicmp(ext,L".asf") || !_wcsicmp(ext,L".avi") || !_wcsicmp(ext,L".mpeg") || !_wcsicmp(ext,L".mpg") || !_wcsicmp(ext,L".wmv"))) supported=true;
|
|
}
|
|
return supported;
|
|
}
|
|
/*
|
|
bool extentionSupported(wchar_t * ext,bool aac_and_m4a_support) {
|
|
if(!ext) return false;
|
|
if(_wcsicmp(ext,L".mp3") && _wcsicmp(ext,L".wma") && _wcsicmp(ext,L".wav")
|
|
&& (aac_and_m4a_support || (_wcsicmp(ext,L".m4a") && _wcsicmp(ext,L".aac")))
|
|
) return false;
|
|
return true;
|
|
}
|
|
*/
|
|
static __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;
|
|
}
|
|
|
|
int P4SDevice::trackAddedToTransferQueue(const itemRecordW * track) {
|
|
__int64 l;
|
|
if(transcoder && transcoder->ShouldTranscode(track->filename)) {
|
|
int k = transcoder->CanTranscode(track->filename);
|
|
if(k == -1) return -2;
|
|
if(k == 0) l = (__int64)fileSize(track->filename);
|
|
else l = (__int64)k;
|
|
} else {
|
|
wchar_t * ext = wcsrchr(track->filename,'.');
|
|
if(!extentionSupported(WMDevice,ext,noMetadata,supportsVideo)) return -2; // fucko: assumes all noMetadata devices are nokia (which is true for now)
|
|
l = (__int64)fileSize(track->filename);
|
|
}
|
|
__int64 test = l;
|
|
__int64 avail = getDeviceCapacityAvailable();
|
|
test += transferQueueSize;
|
|
//test += (__int64)3000000;
|
|
if(test > avail) return -1;
|
|
transferQueueSize += l;
|
|
return 0;
|
|
}
|
|
|
|
void P4SDevice::trackRemovedFromTransferQueue(const itemRecordW * track) {
|
|
__int64 l = (__int64)fileSize(track->filename);
|
|
if(transcoder && transcoder->ShouldTranscode(track->filename)) {
|
|
int k = transcoder->CanTranscode(track->filename);
|
|
if(k != -1 && k != 0) l = (__int64)k;
|
|
}
|
|
transferQueueSize -= l;
|
|
}
|
|
|
|
__int64 P4SDevice::getTrackSizeOnDevice(const itemRecordW * track) {
|
|
if(transcoder && transcoder->ShouldTranscode(track->filename)) {
|
|
int k = transcoder->CanTranscode(track->filename);
|
|
if(k != -1 && k != 0) return k;
|
|
}
|
|
wchar_t * ext = wcsrchr(track->filename,'.');
|
|
if(!extentionSupported(WMDevice,ext,noMetadata,supportsVideo)) return 0; // fucko: assumes all noMetadata devices are nokia (which is true for now)
|
|
return fileSize(track->filename);
|
|
}
|
|
|
|
int P4SDevice::getPlaylistCount() {
|
|
return playlists.GetSize();
|
|
}
|
|
|
|
static BYTE* GetMetadataItem(IWMDMMetaData *meta, const WCHAR * name) {
|
|
WMDM_TAG_DATATYPE type;
|
|
BYTE * value=NULL;
|
|
UINT len;
|
|
if(!meta) { return (BYTE*)""; } // OutputDebugString(L"no meta");
|
|
if((meta->QueryByName(name,&type,&value,&len)) != S_OK) { return NULL; /*value;*/ } //wchar_t buf[100]; wsprintf(buf,L"meta fail: %x %s",hr,name); OutputDebugString(buf);
|
|
return value;
|
|
}
|
|
|
|
static BYTE* GetMetadataItem(Song * song, const WCHAR * name) {
|
|
WMDM_TAG_DATATYPE type;
|
|
BYTE * value=NULL;
|
|
UINT len;
|
|
if(!song || !(song->meta)) { return (BYTE*)""; } // OutputDebugString(L"no meta");
|
|
if((song->meta->QueryByName(name,&type,&value,&len)) != S_OK) { return NULL; /*value;*/ } //wchar_t buf[100]; wsprintf(buf,L"meta fail: %x %s",hr,name); OutputDebugString(buf);
|
|
return value;
|
|
}
|
|
|
|
void P4SDevice::getPlaylistName(int playlistnumber, wchar_t * buf, int len)
|
|
{
|
|
if (NULL == buf)
|
|
return;
|
|
|
|
if(playlistnumber == 0)
|
|
{
|
|
if (NULL == WMDevice)
|
|
buf[0] = L'\0';
|
|
else
|
|
{
|
|
HRESULT hr;
|
|
hr = WMDevice->GetName(buf, len);
|
|
if (FAILED(hr))
|
|
{
|
|
buf[0] = L'\0';
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
StringCchCopy(buf, len, ((Playlist *)playlists.Get(playlistnumber))->name);
|
|
}
|
|
}
|
|
|
|
int P4SDevice::getPlaylistLength(int playlistnumber) {
|
|
if(playlistnumber == -1) return 0;
|
|
return ((Playlist*)playlists.Get(playlistnumber))->songs.GetSize();
|
|
}
|
|
|
|
songid_t P4SDevice::getPlaylistTrack(int playlistnumber,int songnum) {
|
|
if(playlistnumber == -1) return NULL;
|
|
return (songid_t)((Playlist*)playlists.Get(playlistnumber))->songs.Get(songnum);
|
|
}
|
|
|
|
void P4SDevice::setPlaylistName(int playlistnumber, const wchar_t *buf) {
|
|
if(playlistnumber == -1) return;
|
|
IWMDMStorageControl3 * storeControl=NULL;
|
|
Playlist * pl = (Playlist *)playlists.Get(playlistnumber);
|
|
lstrcpyn(pl->name,buf,128);
|
|
pl->storage->QueryInterface(&storeControl);
|
|
pl->meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMTitle,(BYTE*)buf,(wcslen(buf)*2)+2);
|
|
pl->storage->SetMetadata(pl->meta);
|
|
wchar_t buffer[256] = {0};
|
|
wsprintf(buffer,L"%s.%s",buf,plext);
|
|
if(storeControl) {
|
|
storeControl->Rename(WMDM_MODE_BLOCK,buffer,NULL);
|
|
storeControl->Release();
|
|
}
|
|
}
|
|
|
|
int sortby;
|
|
//Device * sortDev;
|
|
|
|
#define SKIP_THE_AND_WHITESPACE(x) { while (!iswalnum(*x) && *x) x++; if (!_wcsnicmp(x,L"the ",4)) x+=4; while (*x == L' ') x++; }
|
|
int STRCMP_NULLOK(const wchar_t *pa, const wchar_t *pb) {
|
|
if (!pa) pa=L"";
|
|
else SKIP_THE_AND_WHITESPACE(pa)
|
|
if (!pb) pb=L"";
|
|
else SKIP_THE_AND_WHITESPACE(pb)
|
|
return lstrcmpi(pa,pb);
|
|
}
|
|
#undef SKIP_THE_AND_WHITESPACE
|
|
|
|
static int sortFunc(const void *elem1, const void *elem2)
|
|
{
|
|
int use_by = sortby;
|
|
songid_t a=(songid_t)*(songid_t *)elem1;
|
|
songid_t b=(songid_t)*(songid_t *)elem2;
|
|
|
|
#define RETIFNZ(v) if ((v)!=0) return v;
|
|
|
|
// this might be too slow, but it'd be nice
|
|
int x;
|
|
for (x = 0; x < 5; x ++)
|
|
{
|
|
if (use_by == SORTBY_TITLE) // title -> artist -> album -> disc -> track
|
|
{
|
|
wchar_t bufa[2048] = {0};
|
|
wchar_t bufb[2048] = {0};
|
|
sortDev->getTrackTitle(a,bufa,2048);
|
|
sortDev->getTrackTitle(b,bufb,2048);
|
|
int v=STRCMP_NULLOK(bufa,bufb);
|
|
RETIFNZ(v)
|
|
use_by=SORTBY_ARTIST;
|
|
}
|
|
else if (use_by == SORTBY_ARTIST) // artist -> album -> disc -> track -> title
|
|
{
|
|
wchar_t bufa[2048] = {0};
|
|
wchar_t bufb[2048] = {0};
|
|
sortDev->getTrackArtist(a,bufa,2048);
|
|
sortDev->getTrackArtist(b,bufb,2048);
|
|
int v=STRCMP_NULLOK(bufa,bufb);
|
|
RETIFNZ(v)
|
|
use_by=SORTBY_ALBUM;
|
|
}
|
|
else if (use_by == SORTBY_ALBUM) // album -> disc -> track -> title -> artist
|
|
{
|
|
wchar_t bufa[2048] = {0};
|
|
wchar_t bufb[2048] = {0};
|
|
sortDev->getTrackAlbum(a,bufa,2048);
|
|
sortDev->getTrackAlbum(b,bufb,2048);
|
|
int v=STRCMP_NULLOK(bufa,bufb);
|
|
RETIFNZ(v)
|
|
use_by=SORTBY_DISCNUM;
|
|
}
|
|
else if (use_by == SORTBY_DISCNUM) // disc -> track -> title -> artist -> album
|
|
{
|
|
int v1=sortDev->getTrackDiscNum(a);
|
|
int v2=sortDev->getTrackDiscNum(b);
|
|
if (v1<0)v1=0;
|
|
if (v2<0)v2=0;
|
|
RETIFNZ(v1-v2)
|
|
use_by=SORTBY_TRACKNUM;
|
|
}
|
|
else if (use_by == SORTBY_TRACKNUM) // track -> title -> artist -> album -> disc
|
|
{
|
|
int v1=sortDev->getTrackTrackNum(a);
|
|
int v2=sortDev->getTrackTrackNum(b);
|
|
if (v1<0)v1=0;
|
|
if (v2<0)v2=0;
|
|
RETIFNZ(v1-v2)
|
|
use_by=SORTBY_TITLE;
|
|
}
|
|
else if (use_by == SORTBY_GENRE) // genre -> artist -> album -> disc -> track
|
|
{
|
|
wchar_t bufa[2048] = {0};
|
|
wchar_t bufb[2048] = {0};
|
|
sortDev->getTrackGenre(a,bufa,2048);
|
|
sortDev->getTrackGenre(b,bufb,2048);
|
|
int v=STRCMP_NULLOK(bufa,bufb);
|
|
RETIFNZ(v)
|
|
use_by=SORTBY_ARTIST;
|
|
}
|
|
else if (use_by == SORTBY_PLAYCOUNT) // size -> artist -> album -> disc -> track
|
|
{
|
|
int v1=sortDev->getTrackPlayCount(a);
|
|
int v2=sortDev->getTrackPlayCount(b);
|
|
if (v1<0)v1=0;
|
|
if (v2<0)v2=0;
|
|
RETIFNZ(v1-v2)
|
|
use_by=SORTBY_ARTIST;
|
|
}
|
|
else if (use_by == SORTBY_RATING) // size -> artist -> album -> disc -> track
|
|
{
|
|
int v1=sortDev->getTrackRating(a);
|
|
int v2=sortDev->getTrackRating(b);
|
|
if (v1<0)v1=0;
|
|
if (v2<0)v2=0;
|
|
RETIFNZ(v1-v2)
|
|
use_by=SORTBY_ARTIST;
|
|
}
|
|
else if (use_by == SORTBY_LASTPLAYED)
|
|
{
|
|
__time64_t la = sortDev->getTrackLastPlayed(a);
|
|
__time64_t lb = sortDev->getTrackLastPlayed(b);
|
|
double t = difftime((time_t)la,(time_t)lb);
|
|
int v = t>0?1:(t<0?-1:0);
|
|
RETIFNZ(v)
|
|
use_by=SORTBY_ARTIST;
|
|
}
|
|
else break; // no sort order?
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
void P4SDevice::sortPlaylist(int playlistnumber, int sortBy) {
|
|
if(playlistnumber == -1) return;
|
|
sortby = sortBy;
|
|
sortDev = this;
|
|
Playlist * pl = (Playlist *)playlists.Get(playlistnumber);
|
|
qsort(pl->songs.GetAll(),pl->songs.GetSize(),sizeof(void*),sortFunc);
|
|
pl->modified = true;
|
|
}
|
|
|
|
void P4SDevice::playlistSwapItems(int playlistnumber, int posA, int posB) {
|
|
if(playlistnumber == -1) return;
|
|
Playlist * pl = (Playlist*)playlists.Get(playlistnumber);
|
|
if(posA >= pl->songs.GetSize() || posB >= pl->songs.GetSize()) return;
|
|
void * a = pl->songs.Get(posA);
|
|
void * b = pl->songs.Get(posB);
|
|
pl->songs.Set(posA,b);
|
|
pl->songs.Set(posB,a);
|
|
pl->modified=true;
|
|
}
|
|
|
|
void P4SDevice::addTrackToPlaylist(int playlistnumber, songid_t songid) {
|
|
if(playlistnumber == -1) return;
|
|
Playlist * pl = (Playlist*)playlists.Get(playlistnumber);
|
|
pl->songs.Add((void*)songid);
|
|
pl->modified=true;
|
|
}
|
|
|
|
void P4SDevice::removeTrackFromPlaylist(int playlistnumber, int songnum) {
|
|
if(playlistnumber == -1) return;
|
|
Playlist * pl = (Playlist*)playlists.Get(playlistnumber);
|
|
pl->songs.Del(songnum);
|
|
pl->modified=true;
|
|
}
|
|
|
|
void P4SDevice::deletePlaylist(int playlistnumber) {
|
|
if(playlistnumber == -1) return;
|
|
IWMDMStorageControl3 * storeControl;
|
|
Playlist * pl = (Playlist *)playlists.Get(playlistnumber);
|
|
pl->storage->QueryInterface(&storeControl);
|
|
|
|
storeControl->Delete(WMDM_MODE_BLOCK,NULL);
|
|
pl->meta->Release();
|
|
pl->storage->Release();
|
|
playlists.Del(playlistnumber);
|
|
|
|
storeControl->Release();
|
|
}
|
|
|
|
int P4SDevice::newPlaylist(const wchar_t *name) {
|
|
IWMDMStorageControl3 * storeControl;
|
|
if(!playlistsDir) return -1;
|
|
playlistsDir->QueryInterface(&storeControl);
|
|
DWORD dw=WMDM_FORMATCODE_ABSTRACTAUDIOVIDEOPLAYLIST;
|
|
IWMDMMetaData* meta = NULL;
|
|
int ret = -1;
|
|
if (SUCCEEDED(playlistsDir->CreateEmptyMetadataObject(&meta)) && meta)
|
|
{
|
|
meta->AddItem(WMDM_TYPE_DWORD, g_wszWMDMFormatCode, (BYTE *)&dw, sizeof(dw));
|
|
meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMTitle,(BYTE*)name,(wcslen(name)*2)+2);
|
|
IWMDMStorage * newpl=NULL;
|
|
wchar_t buffer[MAX_PATH] = {0};
|
|
wsprintf(buffer,L"%s.%s",name,plext);
|
|
storeControl->Insert3(WMDM_MODE_BLOCK | WMDM_CONTENT_FILE,0,NULL,buffer,NULL,NULL,meta,NULL,&newpl);
|
|
|
|
if(newpl) {
|
|
IWMDMStorage4 * newpl4=NULL;
|
|
newpl->QueryInterface(&newpl4);
|
|
Playlist * pl = new Playlist;
|
|
lstrcpyn(pl->name,name,128);
|
|
pl->storage = newpl4;
|
|
pl->modified = false;
|
|
pl->meta = meta;
|
|
playlists.Add(pl);
|
|
ret = playlists.GetSize() - 1;
|
|
newpl->Release();
|
|
}
|
|
}
|
|
storeControl->Release();
|
|
|
|
return ret;
|
|
}
|
|
|
|
void P4SDevice::getTrackArtist(songid_t songid, wchar_t * buf, int len) {
|
|
buf[0]=0;
|
|
BYTE * b = GetMetadataItem((Song *)songid,g_wszWMDMAuthor);
|
|
if(b) { lstrcpyn(buf,(wchar_t*)b,len); CoTaskMemFree(b); }
|
|
if(noMetadata || !b) {
|
|
if(!buf[0]) { // guess based upon file path
|
|
Song *s = (Song *)songid;
|
|
if(s->artist) {lstrcpyn(buf,s->artist,len);
|
|
return;
|
|
}
|
|
IWMDMStorage * p = NULL;
|
|
if(s->storage->GetParent(&p) == S_OK) {
|
|
IWMDMStorage * p2 = NULL;
|
|
IWMDMStorage4 * p3 = NULL;
|
|
if (SUCCEEDED(p->QueryInterface(&p3))) {
|
|
if(p3->GetParent(&p2) == S_OK) {
|
|
p2->GetName(buf,len);
|
|
p2->Release();
|
|
}
|
|
p3->Release();
|
|
}
|
|
p->Release();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void P4SDevice::getTrackAlbum(songid_t songid, wchar_t * buf, int len) {
|
|
buf[0]=0;
|
|
BYTE * b = GetMetadataItem((Song *)songid,g_wszWMDMAlbumTitle);
|
|
if(b) { lstrcpyn(buf,(wchar_t*)b,len); CoTaskMemFree(b); }
|
|
if(!b || noMetadata || ((Song*)songid)->video) {
|
|
if(!buf[0]) { // guess based upon file path
|
|
Song *s = (Song *)songid;
|
|
if(s->album) {lstrcpyn(buf,s->album,len); return;}
|
|
IWMDMStorage * p = NULL;
|
|
if(s->storage->GetParent(&p) == S_OK) {
|
|
p->GetName(buf,len);
|
|
p->Release();
|
|
}
|
|
}
|
|
}
|
|
if(buf[0] == L'~' && buf[1] == 0) buf[0]=0;
|
|
}
|
|
void P4SDevice::getTrackTitle(songid_t songid, wchar_t * buf, int len) {
|
|
buf[0]=0;
|
|
BYTE * b = GetMetadataItem((Song *)songid,g_wszWMDMTitle);
|
|
if(b) { lstrcpyn(buf,(wchar_t*)b,len); CoTaskMemFree(b); }
|
|
if(!b || noMetadata || ((Song*)songid)->video) {
|
|
if(!buf[0]) { // guess based upon file name
|
|
Song *s = (Song *)songid;
|
|
wchar_t buf2[256]=L"";
|
|
s->storage->GetName(buf2,256);
|
|
wchar_t * n = wcsrchr(buf2,L'-');
|
|
if(n) {
|
|
while(n && (*n == L'-' || *n == L' ' || *n == L'_' || *n == L'.')) n++;
|
|
lstrcpyn(buf,n,len);
|
|
} else lstrcpyn(buf,buf2,len);
|
|
}
|
|
wchar_t * ext = wcsrchr(buf,L'.');
|
|
if(ext) *ext=0;
|
|
}
|
|
}
|
|
void P4SDevice::getTrackGenre(songid_t songid, wchar_t * buf, int len) {
|
|
buf[0]=0;
|
|
BYTE * b = GetMetadataItem((Song *)songid,g_wszWMDMGenre);
|
|
if(b) { lstrcpyn(buf,(wchar_t*)b,len); CoTaskMemFree(b); }
|
|
}
|
|
|
|
int P4SDevice::getTrackTrackNum(songid_t songid) {
|
|
BYTE * b = GetMetadataItem((Song *)songid,g_wszWMDMTrack);
|
|
int r = b?(int)*((DWORD*)b):0; if(b) CoTaskMemFree(b);
|
|
if(r) return r;
|
|
if(!b || noMetadata) { // guess based upon file name
|
|
Song *s = (Song *)songid;
|
|
wchar_t buf2[256]=L"";
|
|
s->storage->GetName(buf2,256);
|
|
|
|
wchar_t * n = buf2; //wcschr(buf2,L'-');
|
|
while(n) {
|
|
while((*n == L'-' || *n == L' ' || *n == L'_' || *n == L'.') && *n) n++;
|
|
if(!n) break;
|
|
int m=0; while(*(n+m)>=L'0' && *(n+m)<=L'9') m++;
|
|
if(m == 2) { *(n+m)=0; return _wtoi(n); }
|
|
n = wcschr(n,L'-');
|
|
}
|
|
/*
|
|
wchar_t * n = wcschr(buf2,L'-');
|
|
if(n) {
|
|
*(n--)=0;
|
|
while((*n == L'-' || *n == L' ' || *n == L'_' || *n == L'.') && n > buf2) *(n--)=0;
|
|
return _wtoi(buf2);
|
|
}
|
|
*/
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int P4SDevice::getTrackYear(songid_t songid) {
|
|
BYTE * b = GetMetadataItem((Song *)songid,g_wszWMDMYear);
|
|
int r = b?_wtoi((wchar_t*)b):0; if(b) CoTaskMemFree(b); return r;
|
|
}
|
|
__int64 P4SDevice::getTrackSize(songid_t songid) {
|
|
BYTE * b = GetMetadataItem((Song *)songid,g_wszWMDMFileSize);
|
|
int r = b?(int)*((__int64*)b):0; if(b) CoTaskMemFree(b);
|
|
if(r) return r;
|
|
DWORD high, low;
|
|
((Song *)songid)->storage->GetSize(&low,&high);
|
|
ULARGE_INTEGER u;
|
|
u.HighPart = high;
|
|
u.LowPart = low;
|
|
return u.QuadPart;
|
|
}
|
|
|
|
int P4SDevice::getTrackLength(songid_t songid) {
|
|
BYTE * b = GetMetadataItem((Song *)songid,g_wszWMDMDuration);
|
|
int r = b?(int)(*((__int64*)b)/10000):0; if(b) CoTaskMemFree(b); return r;
|
|
}
|
|
|
|
int P4SDevice::getTrackBitrate(songid_t songid) {
|
|
int len = getTrackLength(songid) / 8000;
|
|
return len?(int)(getTrackSize(songid)/1024) / len:0;
|
|
}
|
|
|
|
int P4SDevice::getTrackPlayCount(songid_t songid) {
|
|
BYTE * b = GetMetadataItem((Song *)songid,g_wszWMDMPlayCount);
|
|
int r = b?(int)*((DWORD*)b):0; if(b) CoTaskMemFree(b); return r;
|
|
}
|
|
|
|
int P4SDevice::getTrackRating(songid_t songid) {
|
|
BYTE * b = GetMetadataItem((Song *)songid,g_wszWMDMUserRating);
|
|
int r = b?(int)*((DWORD*)b):0; if(b) CoTaskMemFree(b); return r;
|
|
}
|
|
|
|
__time64_t P4SDevice::getTrackLastPlayed(songid_t songid) {
|
|
BYTE * b = GetMetadataItem((Song *)songid,g_wszWMDMUserLastPlayTime);
|
|
__time64_t r = b?wmdmDateTimeToUnixTime((_WMDMDATETIME *)b):0; if(b) CoTaskMemFree(b); return r;
|
|
}
|
|
|
|
__time64_t P4SDevice::getTrackLastUpdated(songid_t songid) {
|
|
BYTE * b = GetMetadataItem((Song *)songid,g_wszWMDMLastModifiedDate);
|
|
__time64_t r = b?wmdmDateTimeToUnixTime((_WMDMDATETIME *)b):0; if(b) CoTaskMemFree(b); return r;
|
|
}
|
|
|
|
void P4SDevice::getTrackAlbumArtist(songid_t songid, wchar_t * buf, int len) {
|
|
buf[0]=0;
|
|
BYTE * b = GetMetadataItem((Song *)songid,g_wszWMDMAlbumArtist);
|
|
if(b) { lstrcpyn(buf,(wchar_t*)b,len); CoTaskMemFree(b); }
|
|
if(!buf[0]) getTrackArtist(songid,buf,len);
|
|
}
|
|
|
|
void P4SDevice::getTrackComposer(songid_t songid, wchar_t * buf, int len) {
|
|
buf[0]=0;
|
|
BYTE * b = GetMetadataItem((Song *)songid,g_wszWMDMComposer);
|
|
if(b) { lstrcpyn(buf,(wchar_t*)b,len); CoTaskMemFree(b); }
|
|
}
|
|
|
|
int P4SDevice::getTrackType(songid_t songid) {
|
|
Song * s = (Song *)songid;
|
|
return s->video;
|
|
}
|
|
|
|
void P4SDevice::getTrackExtraInfo(songid_t songid, const wchar_t * field, wchar_t * buf, int len) {
|
|
if(!wcscmp(field,FIELD_EXTENSION)) {
|
|
Song * s = (Song *)songid;
|
|
wchar_t buf2[2048] = {0};
|
|
s->storage->GetName(buf2,2048);
|
|
wchar_t * ext = wcsrchr(buf2,L'.');
|
|
if(ext) { ext++; lstrcpyn(buf,ext,len); }
|
|
}
|
|
}
|
|
|
|
void P4SDevice::PreCommit(Song * s) {
|
|
if(!lastChange) lastChange = s;
|
|
else if(s != lastChange) {
|
|
if(lastChange->modified) {
|
|
lastChange->storage->SetMetadata(lastChange->meta);
|
|
lastChange->meta->Release();
|
|
lastChange->storage->GetMetadata(&lastChange->meta);
|
|
lastChange->modified = false;
|
|
}
|
|
lastChange = s;
|
|
}
|
|
}
|
|
|
|
void P4SDevice::setTrackArtist(songid_t songid, const wchar_t * value) {
|
|
((Song*)songid)->meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMAuthor,(BYTE*)value,wcslen(value)*2+2);
|
|
((Song*)songid)->modified=true;
|
|
PreCommit((Song*)songid);
|
|
}
|
|
|
|
void P4SDevice::setTrackAlbum(songid_t songid, const wchar_t * value) {
|
|
((Song*)songid)->meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMAlbumTitle,(BYTE*)value,wcslen(value)*2+2);
|
|
((Song*)songid)->modified=true;
|
|
PreCommit((Song*)songid);
|
|
}
|
|
|
|
void P4SDevice::setTrackTitle(songid_t songid, const wchar_t * value) {
|
|
((Song*)songid)->meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMTitle,(BYTE*)value,wcslen(value)*2+2);
|
|
((Song*)songid)->modified=true;
|
|
PreCommit((Song*)songid);
|
|
}
|
|
|
|
void P4SDevice::setTrackGenre(songid_t songid, const wchar_t * value) {
|
|
((Song*)songid)->meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMGenre,(BYTE*)value,wcslen(value)*2+2);
|
|
((Song*)songid)->modified=true;
|
|
PreCommit((Song*)songid);
|
|
}
|
|
|
|
void P4SDevice::setTrackTrackNum(songid_t songid, int value) {
|
|
((Song*)songid)->meta->AddItem(WMDM_TYPE_DWORD,g_wszWMDMTrack,(BYTE*)&value,sizeof(DWORD));
|
|
((Song*)songid)->modified=true;
|
|
PreCommit((Song*)songid);
|
|
}
|
|
|
|
void P4SDevice::setTrackYear(songid_t songid, int value) {
|
|
wchar_t buf[10] = {0}; wsprintf(buf,L"%d",value);
|
|
((Song*)songid)->meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMYear,(BYTE*)buf,wcslen(buf)*2+2);
|
|
((Song*)songid)->modified=true;
|
|
PreCommit((Song*)songid);
|
|
}
|
|
|
|
void P4SDevice::setTrackPlayCount(songid_t songid, int value) {
|
|
((Song*)songid)->meta->AddItem(WMDM_TYPE_DWORD,g_wszWMDMPlayCount,(BYTE*)&value,sizeof(DWORD));
|
|
((Song*)songid)->modified=true;
|
|
PreCommit((Song*)songid);
|
|
}
|
|
|
|
void P4SDevice::setTrackRating(songid_t songid, int value) {
|
|
((Song*)songid)->meta->AddItem(WMDM_TYPE_DWORD,g_wszWMDMUserRating,(BYTE*)&value,sizeof(DWORD));
|
|
((Song*)songid)->modified=true;
|
|
PreCommit((Song*)songid);
|
|
}
|
|
|
|
void P4SDevice::setTrackLastPlayed(songid_t songid, __time64_t value) {
|
|
_WMDMDATETIME time={0};
|
|
getTime(value,&time);
|
|
((Song*)songid)->meta->AddItem(WMDM_TYPE_DATE,g_wszWMDMUserLastPlayTime,(BYTE*)&time,sizeof(_WMDMDATETIME));
|
|
((Song*)songid)->modified=true;
|
|
PreCommit((Song*)songid);
|
|
}
|
|
|
|
void P4SDevice::setTrackLastUpdated(songid_t songid, __time64_t value) {
|
|
_WMDMDATETIME time={0};
|
|
getTime(value,&time);
|
|
((Song*)songid)->meta->AddItem(WMDM_TYPE_DATE,g_wszWMDMLastModifiedDate,(BYTE*)&time,sizeof(_WMDMDATETIME));
|
|
((Song*)songid)->modified=true;
|
|
PreCommit((Song*)songid);
|
|
}
|
|
|
|
void P4SDevice::setTrackAlbumArtist(songid_t songid, const wchar_t * value) {
|
|
((Song*)songid)->meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMAlbumArtist,(BYTE*)value,wcslen(value)*2+2);
|
|
((Song*)songid)->modified=true;
|
|
PreCommit((Song*)songid);
|
|
}
|
|
|
|
void P4SDevice::setTrackComposer(songid_t songid, const wchar_t * value) {
|
|
((Song*)songid)->meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMComposer,(BYTE*)value,wcslen(value)*2+2);
|
|
((Song*)songid)->modified=true;
|
|
PreCommit((Song*)songid);
|
|
}
|
|
|
|
int P4SDevice::copyToHardDrive(songid_t s,wchar_t * path,void * callbackContext,void (*callback)(void * callbackContext, wchar_t * status),int * killswitch)
|
|
{
|
|
callback(callbackContext,WASABI_API_LNGSTRINGW(IDS_WAITING));
|
|
EnterCriticalSection(&csTransfers);
|
|
Song * song = (Song*)s;
|
|
IWMDMStorageControl * control;
|
|
if (!SUCCEEDED(song->storage->QueryInterface(&control))) {
|
|
callback(callbackContext,WASABI_API_LNGSTRINGW(IDS_FAILED));
|
|
return -1;
|
|
}
|
|
wchar_t fn[2084] = {0};
|
|
wchar_t *ext = 0;
|
|
if(SUCCEEDED(song->storage->GetName(fn,2084)) && (ext=wcsrchr(fn,L'.'))!=0)
|
|
wcscat(path,ext);
|
|
int ret=-1;
|
|
callback(callbackContext,WASABI_API_LNGSTRINGW(IDS_TRANSFERRING));
|
|
TransferItem t={0};
|
|
t.callback = callback;
|
|
t.callbackContext = callbackContext;
|
|
t.killswitch = killswitch;
|
|
t.progress = new MyProgress(&t);
|
|
if(SUCCEEDED(control->Read(WMDM_MODE_BLOCK | WMDM_CONTENT_FILE,path,t.progress,NULL))) ret=0;
|
|
control->Release();
|
|
t.progress->Release();
|
|
callback(callbackContext,WASABI_API_LNGSTRINGW((ret==0?IDS_DONE:IDS_FAILED)));
|
|
LeaveCriticalSection(&csTransfers);
|
|
return ret;
|
|
}
|
|
|
|
static IWMDMStorage4* getAlb(P4SDevice * dev, songid_t songid, bool create) {
|
|
wchar_t alb[1024] = {0};
|
|
dev->getTrackAlbum(songid,alb,1020);
|
|
StringCchCat(alb, ARRAYSIZE(alb), L".alb");
|
|
Song * song = (Song*)songid;
|
|
IWMDMStorage * album0=NULL;
|
|
IWMDMStorage * parent=NULL;
|
|
IWMDMStorage4 * parent4=NULL;
|
|
IWMDMStorage4 * album4=NULL;
|
|
song->storage->GetParent(&parent);
|
|
if (parent)
|
|
{
|
|
parent->QueryInterface(&parent4);
|
|
parent->Release();
|
|
if (parent4)
|
|
{
|
|
parent4->GetStorage(alb,&album0);
|
|
//parent4->Release();
|
|
if(album0) {
|
|
album0->QueryInterface(&album4);
|
|
album0->Release();
|
|
}
|
|
}
|
|
}
|
|
if(album4 || !create) { parent4->Release(); return album4; }
|
|
if(!parent4) return NULL;
|
|
// create my own
|
|
album0=0;
|
|
IWMDMStorageControl3 * storeControl=0;
|
|
parent4->QueryInterface(&storeControl);
|
|
if(storeControl) {
|
|
IWMDMMetaData * meta=0;
|
|
parent4->CreateEmptyMetadataObject(&meta);
|
|
if(meta) {
|
|
DWORD formatCode = WMDM_FORMATCODE_ABSTRACTAUDIOALBUM;
|
|
wchar_t album[256]=L"";
|
|
wchar_t artist[256]=L"";
|
|
wchar_t genre[256]=L"";
|
|
dev->getTrackAlbumArtist(songid,artist,256);
|
|
dev->getTrackAlbum(songid,album,256);
|
|
dev->getTrackGenre(songid,genre,256);
|
|
meta->AddItem(WMDM_TYPE_DWORD,g_wszWMDMFormatCode,(BYTE*)&formatCode,sizeof(DWORD));
|
|
meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMAlbumTitle,(BYTE*)album,(wcslen(album)*2)+2);
|
|
meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMAlbumArtist,(BYTE*)artist,(wcslen(artist)*2)+2);
|
|
meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMTitle,(BYTE*)album,(wcslen(album)*2)+2);
|
|
meta->AddItem(WMDM_TYPE_STRING,g_wszWMDMGenre,(BYTE*)genre,(wcslen(genre)*2)+2);
|
|
|
|
storeControl->Insert3(WMDM_MODE_BLOCK | WMDM_CONTENT_FILE,0,NULL,alb,NULL,NULL,meta,NULL,&album0);
|
|
meta->Release();
|
|
}
|
|
storeControl->Release();
|
|
}
|
|
parent4->Release();
|
|
if(album0) {
|
|
album0->QueryInterface(&album4);
|
|
album0->Release();
|
|
return album4;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void P4SDevice::setArt(songid_t songid, void *buf, int w, int h) { //buf is in format ARGB32*
|
|
if(!songid) return;
|
|
Song * s = (Song *)songid;
|
|
IWMDMStorage4 * album = s->alb;
|
|
if(!album) {
|
|
album = getAlb(this,songid,true);
|
|
if(!album) return;
|
|
s->alb = album;
|
|
s->albmeta = NULL;
|
|
s->alb->GetMetadata(&s->albmeta);
|
|
if(!s->albmeta) return;
|
|
}
|
|
|
|
IWMDMMetaData *meta=s->albmeta;
|
|
if(!meta) return;
|
|
IWMDMMetaData *meta2 = ((Song*)songid)->meta;
|
|
|
|
if(meta || meta2) {
|
|
if(buf) {
|
|
SkinBitmap art((ARGB32*)buf,w,h);
|
|
w=h=120;
|
|
BltCanvas artc(w,h);
|
|
art.stretch(&artc,0,0,w,h);
|
|
|
|
const GUID JPEGwriteguid = { 0x7bc27468, 0x475, 0x4c0d, { 0xae, 0xed, 0xc, 0x51, 0x19, 0x5d, 0xc2, 0xea } };
|
|
svc_imageWriter* jpgWrite=NULL;
|
|
waServiceFactory *sf = plugin.service->service_getServiceByGuid(JPEGwriteguid);
|
|
if(sf) jpgWrite = (svc_imageWriter*)sf->getInterface();
|
|
if(jpgWrite) {
|
|
int length=0;
|
|
void *jpeg = jpgWrite->convert(artc.getBits(),32,w,h,&length);
|
|
if(jpeg) {
|
|
DWORD fmt = WMDM_FORMATCODE_IMAGE_EXIF; // this is the formatcode for jpeg, apparently.
|
|
if(meta) {
|
|
meta->AddItem(WMDM_TYPE_BINARY,g_wszWMDMAlbumCoverData,(BYTE*)jpeg,length);
|
|
meta->AddItem(WMDM_TYPE_DWORD,g_wszWMDMAlbumCoverWidth,(BYTE*)&w,sizeof(DWORD));
|
|
meta->AddItem(WMDM_TYPE_DWORD,g_wszWMDMAlbumCoverHeight,(BYTE*)&h,sizeof(DWORD));
|
|
meta->AddItem(WMDM_TYPE_DWORD,g_wszWMDMAlbumCoverSize,(BYTE*)&length,sizeof(DWORD));
|
|
meta->AddItem(WMDM_TYPE_DWORD,g_wszWMDMAlbumCoverFormat,(BYTE*)&fmt,sizeof(DWORD));
|
|
}
|
|
if(meta2) {
|
|
meta2->AddItem(WMDM_TYPE_BINARY,g_wszWMDMAlbumCoverData,(BYTE*)jpeg,length);
|
|
meta2->AddItem(WMDM_TYPE_DWORD,g_wszWMDMAlbumCoverWidth,(BYTE*)&w,sizeof(DWORD));
|
|
meta2->AddItem(WMDM_TYPE_DWORD,g_wszWMDMAlbumCoverHeight,(BYTE*)&h,sizeof(DWORD));
|
|
meta2->AddItem(WMDM_TYPE_DWORD,g_wszWMDMAlbumCoverSize,(BYTE*)&length,sizeof(DWORD));
|
|
meta2->AddItem(WMDM_TYPE_DWORD,g_wszWMDMAlbumCoverFormat,(BYTE*)&fmt,sizeof(DWORD));
|
|
}
|
|
WASABI_API_MEMMGR->sysFree(jpeg);
|
|
}
|
|
if (sf) sf->releaseInterface(jpgWrite);
|
|
}
|
|
} else { // remove art
|
|
if(meta) {
|
|
meta->AddItem(WMDM_TYPE_BINARY,g_wszWMDMAlbumCoverData,(BYTE*)0,0);
|
|
meta->AddItem(WMDM_TYPE_DWORD,g_wszWMDMAlbumCoverWidth,(BYTE*)0,0);
|
|
meta->AddItem(WMDM_TYPE_DWORD,g_wszWMDMAlbumCoverHeight,(BYTE*)0,0);
|
|
meta->AddItem(WMDM_TYPE_DWORD,g_wszWMDMAlbumCoverSize,(BYTE*)0,0);
|
|
meta->AddItem(WMDM_TYPE_DWORD,g_wszWMDMAlbumCoverFormat,(BYTE*)0,0);
|
|
}
|
|
if(meta2) {
|
|
meta2->AddItem(WMDM_TYPE_BINARY,g_wszWMDMAlbumCoverData,(BYTE*)0,0);
|
|
meta2->AddItem(WMDM_TYPE_DWORD,g_wszWMDMAlbumCoverWidth,(BYTE*)0,0);
|
|
meta2->AddItem(WMDM_TYPE_DWORD,g_wszWMDMAlbumCoverHeight,(BYTE*)0,0);
|
|
meta2->AddItem(WMDM_TYPE_DWORD,g_wszWMDMAlbumCoverSize,(BYTE*)0,0);
|
|
meta2->AddItem(WMDM_TYPE_DWORD,g_wszWMDMAlbumCoverFormat,(BYTE*)0,0);
|
|
}
|
|
}
|
|
if(meta) {
|
|
album->SetMetadata(meta);
|
|
}
|
|
}
|
|
((Song*)songid)->modified=true;
|
|
PreCommit((Song*)songid);
|
|
/*
|
|
g_wszWMDMAlbumCoverData Album art JPEG byte blob WMDM_TYPE_BINARY BYTE*
|
|
g_wszWMDMAlbumCoverDuration Album cover duration WMDM_TYPE_DWORD DWORD
|
|
g_wszWMDMAlbumCoverFormat Album art format WMDM_TYPE_DWORD DWORD
|
|
g_wszWMDMAlbumCoverHeight Album art height WMDM_TYPE_DWORD DWORD
|
|
g_wszWMDMAlbumCoverSize Album art size WMDM_TYPE_DWORD DWORD
|
|
g_wszWMDMAlbumCoverWidth Album art width WMDM_TYPE_DWORD DWORD
|
|
*/
|
|
}
|
|
|
|
class Art {
|
|
public:
|
|
Art(void * jpegData, int jpegDataLen, int w, int h) : jpegData(jpegData), jpegDataLen(jpegDataLen), w(w), h(h), data(0), resized(0) {
|
|
}
|
|
~Art() {
|
|
CoTaskMemFree(jpegData);
|
|
if(data) WASABI_API_MEMMGR->sysFree(data); data=0;
|
|
}
|
|
ARGB32 * GetImage() {
|
|
if(data) return data;
|
|
const GUID JPEGguid = { 0xae04fb30, 0x53f5, 0x4032, { 0xbd, 0x29, 0x3, 0x2b, 0x87, 0xec, 0x34, 0x04 } };
|
|
svc_imageLoader* jpgLoad=NULL;
|
|
waServiceFactory *sf = plugin.service->service_getServiceByGuid(JPEGguid);
|
|
if(sf) jpgLoad = (svc_imageLoader*)sf->getInterface();
|
|
if(jpgLoad) {
|
|
data = jpgLoad->loadImage(jpegData,jpegDataLen,&w,&h);
|
|
if (sf) sf->releaseInterface(jpgLoad);
|
|
}
|
|
resized=0;
|
|
return data;
|
|
}
|
|
int resized;
|
|
void Resize(int width, int height) {
|
|
if(w == width && h == height) return;
|
|
if(resized) {
|
|
if(data) WASABI_API_MEMMGR->sysFree(data);
|
|
data=0;
|
|
resized=0;
|
|
}
|
|
GetImage();
|
|
if(!data) return;
|
|
SkinBitmap temp(data,w,h);
|
|
BltCanvas newImage(width,height);
|
|
temp.stretch(&newImage,0,0,width,height);
|
|
w=width;
|
|
h=height;
|
|
WASABI_API_MEMMGR->sysFree(data);
|
|
data = (ARGB32*)WASABI_API_MEMMGR->sysMalloc(w*h*sizeof(ARGB32));
|
|
memcpy(data,newImage.getBits(),w*h*sizeof(ARGB32));
|
|
resized=1;
|
|
}
|
|
int getWidth() {return w;}
|
|
int getHeight() {return h;}
|
|
int cmp(Art * art) {
|
|
if(art->jpegDataLen != jpegDataLen) return art->jpegDataLen - jpegDataLen;
|
|
return memcmp(art->jpegData,jpegData,jpegDataLen);
|
|
}
|
|
protected:
|
|
void * jpegData;
|
|
int jpegDataLen;
|
|
int w,h;
|
|
ARGB32 *data;
|
|
};
|
|
|
|
pmpart_t P4SDevice::getArt(songid_t songid) {
|
|
if(!songid) return NULL;
|
|
Song* s = (Song*)songid;
|
|
WMDM_TAG_DATATYPE type;
|
|
BYTE * data=NULL;
|
|
UINT length=0;
|
|
IWMDMMetaData *meta = s->albmeta;
|
|
if(!meta) return NULL;
|
|
|
|
HRESULT hr = meta->QueryByName(g_wszWMDMAlbumCoverData,&type,&data,&length);
|
|
|
|
if(hr == S_OK && data && length) {
|
|
BYTE * b = GetMetadataItem(meta,g_wszWMDMAlbumCoverWidth);
|
|
int w = b?(int)*((DWORD*)b):0;
|
|
if(b) CoTaskMemFree(b);
|
|
|
|
b = GetMetadataItem(meta,g_wszWMDMAlbumCoverHeight);
|
|
int h = b?(int)*((DWORD*)b):0;
|
|
if(b) CoTaskMemFree(b);
|
|
//meta->Release();
|
|
if(!w) w=120; // this happens if the device doesn't store the w and h of the image.
|
|
if(!h) h=120; // but it's ok, cause the real values are in the jpeg data, and these can just be a guide.
|
|
return (pmpart_t) new Art(data,length,w,h);
|
|
}
|
|
//meta->Release();
|
|
if(data) CoTaskMemFree(data);
|
|
return NULL;
|
|
}
|
|
|
|
void P4SDevice::releaseArt(pmpart_t art) {
|
|
if(art) delete ((Art *)art);
|
|
}
|
|
|
|
int P4SDevice::drawArt(pmpart_t art0, HDC dc, int x, int y, int w, int h) {
|
|
Art* art = (Art*)art0;
|
|
if(!art) return 0;
|
|
ARGB32 * d = art->GetImage();
|
|
if(!d) return 0;
|
|
SkinBitmap(d, art->getWidth(), art->getHeight()).stretch(&DCCanvas(dc),x,y,w,h); // wrap into a SkinBitmap (no copying involved)
|
|
return 1;
|
|
}
|
|
|
|
void P4SDevice::getArtNaturalSize(pmpart_t art0, int *w, int *h) {
|
|
Art* art = (Art*)art0;
|
|
*w=art->getWidth();
|
|
*h=art->getWidth();
|
|
if(*w==0 || *h==0) *w=*h=120;
|
|
}
|
|
|
|
void P4SDevice::setArtNaturalSize(pmpart_t art0, int w, int h) {
|
|
Art* art = (Art*)art0;
|
|
art->Resize(w,h);
|
|
}
|
|
|
|
void P4SDevice::getArtData(pmpart_t art0, void* data) { // data ARGB32* is at natural size
|
|
Art* art = (Art*)art0;
|
|
int w,h;
|
|
getArtNaturalSize(art0,&w,&h);
|
|
setArtNaturalSize(art0,w,h);
|
|
ARGB32 * d = art->GetImage();
|
|
if(d) memcpy(data,d,w*h*sizeof(ARGB32));
|
|
}
|
|
|
|
bool P4SDevice::artIsEqual(pmpart_t a, pmpart_t b) {
|
|
if(!a || !b) return false;
|
|
return ((Art*)a)->cmp((Art*)b) == 0;
|
|
}
|
|
|
|
extern void checkForDevices();
|
|
|
|
intptr_t P4SDevice::extraActions(intptr_t param1, intptr_t param2, intptr_t param3,intptr_t param4) {
|
|
switch(param1) {
|
|
case DEVICE_SET_ICON: // icons
|
|
{
|
|
MLTREEIMAGE * i = (MLTREEIMAGE*)param2;
|
|
if(wcsstr(name,L"Zen")) {
|
|
i->hinst = plugin.hDllInstance;
|
|
i->resourceId = IDR_CREATIVE_ZEN_ICON;
|
|
} else if (wcsstr(name,L"Nokia")) {
|
|
i->hinst = plugin.hDllInstance;
|
|
i->resourceId = IDR_NOKIA_ICON;
|
|
}
|
|
break;
|
|
}
|
|
case DEVICE_SUPPORTED_METADATA:
|
|
return noMetadata ? 0x8f : (0xffef | (requiresALB?SUPPORTS_ALBUMART:0));
|
|
case DEVICE_DOES_NOT_SUPPORT_EDITING_METADATA:
|
|
return noMetadata ? 1 : 0;
|
|
case DEVICE_REFRESH:
|
|
{
|
|
bool nm = noMetadata;
|
|
IWMDMDevice3* d = WMDevice;
|
|
d->AddRef();
|
|
Close();
|
|
new P4SDevice(d,nm);
|
|
d->Release();
|
|
}
|
|
return 0;
|
|
case DEVICE_SUPPORTS_VIDEO:
|
|
return 1;
|
|
case DEVICE_GET_ICON:
|
|
{
|
|
if (param2 <= 16 && param3 <= 16)
|
|
{
|
|
int resourceId;
|
|
wchar_t *buffer;
|
|
|
|
if(wcsstr(name,L"Zen"))
|
|
resourceId = IDR_CREATIVE_ZEN_ICON;
|
|
else if (wcsstr(name,L"Nokia"))
|
|
resourceId = IDR_NOKIA_ICON;
|
|
else
|
|
resourceId = 0;
|
|
|
|
buffer = (wchar_t *)param4;
|
|
if (NULL != buffer &&
|
|
FALSE == FormatResProtocol(MAKEINTRESOURCE(resourceId), RT_RCDATA, buffer, 260))
|
|
{
|
|
buffer[0] = L'\0';
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
return 0;
|
|
} |