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

1795 lines
54 KiB
C++

#include "iPodDevice.h"
//#include <assert.h>
#include "..\..\General\gen_ml/itemlist.h"
#include "../nu/AutoWide.h"
#include "../nu/AutoChar.h"
#include "../Winamp/wa_ipc.h"
#include <math.h>
#include "../Agave/Language/api_language.h"
#include "api.h"
#include <tataki/bitmap/bitmap.h>
#include <tataki/canvas/bltcanvas.h>
#include "iPodSD.h"
#include <strsafe.h>
#include <shlwapi.h>
//#include "../nu/combobox.h"
// needed to query for replaygain stuff
static const GUID playbackConfigGroupGUID =
{ 0xb6cb4a7c, 0xa8d0, 0x4c55, { 0x8e, 0x60, 0x9f, 0x7a, 0x7a, 0x23, 0xda, 0xf } };
extern PMPDevicePlugin plugin;
extern time_t mactime_to_wintime (const unsigned long mactime);
extern unsigned long wintime_to_mactime (const __time64_t time);
extern wchar_t* UTF8_to_UTF16(char *str);
extern BOOL EjectVolume(TCHAR cDriveLetter);
extern std::vector<iPodDevice*> iPods;
static __int64 fileSize(const wchar_t * filename);
iPodDevice::iPodDevice(char deviceDrive)
{
fwid=0;
info=0;
artdb=0;
gapscanner=0;
transcoder=0;
image16 = 0;
image160 = 0;
drive = deviceDrive;
driveW = ((int)(drive - 'A')) + L'A';
transferQueueLength=0;
db=NULL;
srand(GetTickCount());
dirnum = rand() % 20;
{
wchar_t artwork[] = {driveW,L":\\iPod_Control\\Artwork"};
_wmkdir(artwork);
wchar_t device[] = {driveW,L":\\iPod_Control\\Device"};
_wmkdir(device);
}
iPods.push_back(this);
pmpDeviceLoading load;
load.dev = this;
load.UpdateCaption = NULL;
SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)&load,PMP_IPC_DEVICELOADING);
if(load.UpdateCaption)
{
load.UpdateCaption(WASABI_API_LNGSTRINGW(IDS_IPOD_LOADING),load.context);
}
info = GetiPodInfo(driveW);
// Get the artwork formats that are supported
if(info && info->numberOfSupportedFormats >0)
{
// formats are already available, read from the sysinfo xml
// just use them
for (int i=0; i<info->numberOfSupportedFormats; i++)
{
thumbs.push_back(&info->supportedArtworkFormats[i]);
}
}
else
{
// revert to the static list of supported artwork formats
const ArtworkFormat* art = GetArtworkFormats(info);
if(art) for(int i=0; art[i].type != THUMB_INVALID; i++)
{
if(art[i].type >= THUMB_COVER_SMALL && art[i].type <= THUMB_COVER_LARGE)
thumbs.push_back(&art[i]);
}
}
if(!info || parseiTunesDB(thumbs.size()!=0) < 0)
{
//iPods.eraseObject(this);
auto it = std::find(iPods.begin(), iPods.end(), this);
if (it != iPods.end())
{
iPods.erase(it);
}
SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICEDISCONNECTED);
delete this;
return;
}
image16 = info->image16;
image160 = info->image160;
db->mhsdplaylists->mhlp->SortPlaylists();
int n = db->mhsdplaylists->mhlp->GetChildrenCount();
for(int i=0; i<n; i++)
playlists.Add(db->mhsdplaylists->mhlp->GetPlaylist(i));
SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICECONNECTED);
transcoder = (Transcoder*)SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(WPARAM)this,PMP_IPC_GET_TRANSCODER);
if(transcoder)
{
transcoder->AddAcceptableFormat(mmioFOURCC('M','4','A',' '));
transcoder->AddAcceptableFormat(L"mp3");
//transcoder->AddAcceptableFormat(L"wav");
transcoder->AddAcceptableFormat(L"m4v");
transcoder->AddAcceptableFormat(L"m4b");
transcoder->AddAcceptableFormat(L"aa\0\0");
transcoder->AddAcceptableFormat(L"mp4");
}
if (info->fwid)
{
fwid = (uint8_t *)malloc(8);
memcpy(fwid, info->fwid, 8);
}
}
iPodDevice::~iPodDevice() {
if(gapscanner) SendMessage(gapscanner,WM_CLOSE,0,0);
if(db) delete db; db=NULL;
char lockPath[] = {drive, ":\\iPod_Control\\iTunes\\iTunesLock"};
_unlink(lockPath);
if(transcoder) SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(WPARAM)transcoder,PMP_IPC_RELEASE_TRANSCODER);
delete info;
info=0;
free(fwid);
}
static unsigned char * readFile(char * path, int &len) {
FILE * f = fopen(path,"rb");
if(!f) return 0;
fseek(f,0,2); //seek to end
int l = ftell(f); //length of file
unsigned char * data = (unsigned char *)malloc(l);
if(!data)
{
fclose(f);
return 0;
}
fseek(f,0,0);
if(fread(data,1,l,f) != l) { fclose(f); free(data); return 0; }
fclose(f);
len = l;
return data;
}
static unsigned char * readFile(char * path) {
int l=0;
return readFile(path,l);
}
static HANDLE iTunesLock(char drive) { // returns false for unable to aquire lock.
char lockPath[] = {drive, ":\\iPod_Control\\iTunes\\iTunesLock"};
HANDLE h=CreateFileA(lockPath,GENERIC_READ,0,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);
if(h == INVALID_HANDLE_VALUE) return h;
while(!LockFile(h,0,0,0,0)) Sleep(50);
return h;
}
static void iTunesUnlock(HANDLE h) {
UnlockFile(h,0,0,0,0);
CloseHandle(h);
}
int iPodDevice::parseiTunesDB(bool parseArt) {
HANDLE hLock = iTunesLock(drive);
if(hLock == INVALID_HANDLE_VALUE) return -1;
char dbPath[] = "x:\\iPod_Control\\iTunes\\iTunesDB";
dbPath[0]=drive;
unsigned char * data = readFile(dbPath);
if(data==0) {
iTunesUnlock(hLock);
return -1;
}
db = new iPod_mhbd;
int ret = db->parse(data);
free(data);
bool changed=false;
char playcounts[] = "x:\\iPod_Control\\iTunes\\Play Counts";
playcounts[0]=drive;
data = readFile(playcounts);
if(data) {
iPod_mhdp * mhdp = new iPod_mhdp;
int l = db->mhsdsongs->mhlt->GetChildrenCount();
if(mhdp->parse(data) == l) {
changed=true;
for(int i=0; i<l; i++) {
PCEntry p = mhdp->GetPlayCount(i);
iPod_mhit * mhit = db->mhsdsongs->mhlt->GetTrack(i);
if(!mhit) continue;
mhit->bookmarktime = p.bookmarktime;
mhit->lastplayedtime = p.lastplayedtime;
mhit->stars = (unsigned char)p.stars;
mhit->playcount = p.playcount;
mhit->skipcount = p.skipcount;
mhit->skippedtime = p.skippedtime;
}
}
delete mhdp;
free(data);
}
_unlink(playcounts);
db->mhsdplaylists->mhlp->GetPlaylist(0)->mhit = &db->mhsdsongs->mhlt->mhit;
char otg[] = "x:\\iPod_Control\\iTunes\\OTGPlaylistInfo";
otg[0]=drive;
data = readFile(otg);
if(data) {
iPod_mhpo * mhpo = new iPod_mhpo;
mhpo->parse(data);
mhpo->CreatePlaylistFromOTG(db,L"On The Go");
changed=true;
delete mhpo;
free(data);
}
_unlink(otg);
iTunesUnlock(hLock);
if(changed) writeiTunesDB();
if(parseArt) {
char dbPath[] = "x:\\iPod_Control\\Artwork\\ArtworkDB";
dbPath[0]=drive;
int l=0;
unsigned char * data = readFile(dbPath,l);
bool createNew=false;
if(data) {
artdb = new ArtDB();
int r = artdb->parse(data,l,driveW);
if(r<0) {
delete artdb;
artdb=NULL;
}
free(data);
} else createNew=true;
if(createNew) {
char dir[] = {drive,":\\iPod_Control\\Artwork"};
CreateDirectoryA(dir,NULL);
artdb = new ArtDB();
artdb->makeEmptyDB(driveW);
}
}
return ret;
}
extern bool ParseSysInfoXML(wchar_t drive_letter, char * xml, int xmllen);
static unsigned char *GetFwId(wchar_t drive, unsigned char *fwid)
{
char xml[65536] = {0};
if(!ParseSysInfoXML(drive, xml, sizeof(xml)/sizeof(char))) return NULL;
char *p = strstr(xml,"<key>FireWireGUID</key>");
if(!p) return 0;
p = strstr(p,"<string>");
if(!p) return 0;
p += strlen("<string>");
for(int i=0; i<8 && *p; i++) {
char num[3]={0,0,0};
num[0] = *(p++);
num[1] = *(p++);
fwid[i] = (uint8_t)strtoul(num,NULL,16);
}
return fwid;
}
int iPodDevice::writeiTunesDB()
{
char dbPath[] = "x:\\iPod_Control\\iTunes\\iTunesDB"; dbPath[0]=drive;
char dbPathOld[] = "x:\\iPod_Control\\iTunes\\iTunesDB.old_mlpmp"; dbPathOld[0]=drive;
char dbPathNew[] = "x:\\iPod_Control\\iTunes\\iTunesDB.new_mlpmp"; dbPathNew[0]=drive;
if(!db) return -1;
HANDLE hLock = iTunesLock(drive);
if(hLock == INVALID_HANDLE_VALUE) return -1;
uint32_t allocate = (uint32_t)fileSize(AutoWide(dbPath));
int incr = 10000000;
bool done=false;
int i=0;
int ret=0;
unsigned char * data;
while(!done)
{
if(i++ > 10)
{
iTunesUnlock(hLock);
return -1;
}
allocate += incr;
data = (unsigned char*)malloc(allocate);
if(!data) return -1; //what else can we do?
// TODO: i'd like to cut this but it seems to still be causing problems to parse it from XML
unsigned char fwid[8]={0};
GetFwId(driveW, fwid);
#ifdef _DEBUG
if (memcmp(fwid, this->fwid, 8) || memcmp(fwid, info->fwid, 8))
{
DebugBreak();
}
#endif
int len = db->write(data,allocate, fwid);
if(len > 0) {
_unlink(dbPathOld);
_unlink(dbPathNew);
FILE * f = fopen(dbPathNew,"wb");
if(!f) {
iTunesUnlock(hLock);
return -1;
}
fwrite(data,1,len,f);
fclose(f);
rename(dbPath,dbPathOld);
_unlink(dbPath);
rename(dbPathNew,dbPath);
done=true;
ret=len;
} else free(data);
}
if(data)
{
if (info->shadow_db_version == 1)
{
iTunesSD1 sd;
int l = sd.write(&db->mhsdsongs->mhlt->mhit, data,allocate);
if(l>0)
{
char dbPath[] = "x:\\iPod_Control\\iTunes\\iTunesSD"; dbPath[0]=drive;
FILE * f = fopen(dbPath,"wb");
if(f) {
fwrite(data,1,l,f);
fclose(f);
}
}
}
else if (info->shadow_db_version == 2)
{
iTunesSD2 sd;
int l = sd.write(db->mhsdsongs->mhlt, db->mhsdplaylists->mhlp, data,allocate);
if(l>0)
{
char dbPath[] = "x:\\iPod_Control\\iTunes\\iTunesSD"; dbPath[0]=drive;
FILE * f = fopen(dbPath,"wb");
if(f) {
fwrite(data,1,l,f);
fclose(f);
}
}
}
}
if(artdb && data) {
int l = artdb->write(data,allocate);
if(l>0) {
char dbPath[] = "x:\\iPod_Control\\Artwork\\ArtworkDB"; dbPath[0]=drive;
FILE * f = fopen(dbPath,"wb");
if(f) {
fwrite(data,1,l,f);
fclose(f);
}
}
}
iTunesUnlock(hLock);
if(data) free(data);
return ret;
}
__int64 iPodDevice::getDeviceCapacityAvailable() {
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;
}
__int64 iPodDevice::getDeviceCapacityTotal() {
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;
}
void iPodDevice::Close()
{
SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICEDISCONNECTED);
//writeiTunesDB();
//iPods.eraseObject(this);
auto it = std::find(iPods.begin(), iPods.end(), this);
if (it != iPods.end())
{
iPods.erase(it);
}
delete this;
}
void iPodDevice::Eject()
{
//iPods.eraseObject(this);
auto it = std::find(iPods.begin(), iPods.end(), this);
if (it != iPods.end())
{
iPods.erase(it);
}
if(EjectVolume(drive))
{
SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICEDISCONNECTED);
delete this;
}
else
{
wchar_t titleStr[32] = {0};
iPods.push_back(this);
MessageBox(plugin.hwndLibraryParent,WASABI_API_LNGSTRINGW(IDS_FAILED_TO_EJECT_IPOD),
WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,titleStr,32),0);
}
}
extern int CopyFile(const wchar_t * infile, const wchar_t * outfile, void * callbackContext, void (*callback)(void * callbackContext, wchar_t * status), int * killswitch);
static __int64 fileSize(const wchar_t * filename)
{
WIN32_FIND_DATA f={0};
HANDLE h = FindFirstFileW(filename,&f);
if(h == INVALID_HANDLE_VALUE) return -1;
FindClose(h);
ULARGE_INTEGER i;
i.HighPart = f.nFileSizeHigh;
i.LowPart = f.nFileSizeLow;
return i.QuadPart;
}
void GetFileInfo(const wchar_t * file, const wchar_t * metadata, wchar_t * buf, int len) {
buf[0]=0;
extendedFileInfoStructW m = {file,metadata,buf,(size_t)len};
SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&m,IPC_GET_EXTENDED_FILE_INFOW);
}
__int64 GetFileInfoInt64(wchar_t * file, wchar_t * metadata, BOOL *w=NULL) {
wchar_t buf[100]=L"";
GetFileInfo(file,metadata,buf,100);
if(w && buf[0]==0) {(*w) = 0; return 0;}
return _wtoi64(buf);
}
int GetFileInfoInt(wchar_t * file, wchar_t * metadata, BOOL *w=NULL) {
wchar_t buf[100]=L"";
GetFileInfo(file,metadata,buf,100);
if(w && buf[0]==0) {(*w) = 0; return 0;}
return _wtoi(buf);
}
int iPodDevice::transferTrackToDevice(const itemRecordW *track,void * callbackContext,void (*callback)(void * callbackContext, wchar_t * status),songid_t * songid,int * killswitch)
{
bool transcodefile = false;
wchar_t outfile[2048] = {0};
wchar_t infile[MAX_PATH] = {0};
StringCchCopy(infile, MAX_PATH, track->filename);
bool nocopy=false;
iPod_mhit * mhit = db->mhsdsongs->mhlt->NewTrack();
dirnum = (dirnum + 1) % 20;
// create the output filename and directory
wchar_t ext[10]=L"";
const wchar_t *e = wcsrchr(infile,L'.');
if(e)
StringCbCopyW(ext, sizeof(ext), e);
if(transcoder && transcoder->ShouldTranscode(infile)) {
int r = transcoder->CanTranscode(infile,ext, track->length);
if(r != 0 && r != -1) transcodefile = true;
}
bool video = !_wcsicmp(ext,L".m4v");
if(!_wcsicmp(ext,L".mp4")) {
wchar_t buf[100]=L"0";
extendedFileInfoStructW m = {infile,L"type",buf,100};
SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&m,IPC_GET_EXTENDED_FILE_INFOW);
if(!wcscmp(buf,L"1")) { video=true; wcsncpy(ext,L".m4v",10); }
else wcsncpy(ext,L".m4a",10);
}
// and the location in the ipod naming scheme
if (infile[0] == drive && infile[1] && infile[1] == L':')
{
// file already on the ipod? add it directly
StringCbCopy(outfile, sizeof(outfile), infile);
nocopy=true;
}
else
{
StringCbPrintf(outfile,sizeof(outfile),L"%c:\\iPod_Control\\Music\\F%02d\\",(wchar_t)drive,dirnum);
CreateDirectory(outfile,NULL);
StringCbPrintf(outfile,sizeof(outfile),L"%c:\\iPod_Control\\Music\\F%02d\\w%05d%s",(wchar_t)drive,dirnum,mhit->id,ext);
}
wchar_t location[2048] = {0};
StringCbCopy(location, sizeof(location), outfile+2);
int i=0;
while(location[i] != 0) { if(location[i]==L'\\') location[i]=L':'; i++; }
{
wchar_t buf[100]=L"";
int which = AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_source", 0);
extendedFileInfoStructW m = {infile,which?L"replaygain_album_gain":L"replaygain_track_gain",buf,100};
SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(WPARAM)&m,IPC_GET_EXTENDED_FILE_INFOW);
if(buf[0]) {
double gain = _wtof(&buf[buf[0]==L'+'?1:0]);
mhit->soundcheck = (unsigned long)(1000.0 * pow(10.0,-0.1*gain));
}
}
// fill in the new MHIT (track item) with our metadata
mhit->AddString(MHOD_TITLE)->SetString(track->title);
mhit->AddString(MHOD_LOCATION)->SetString(location);
mhit->AddString(MHOD_ALBUM)->SetString(track->album);
mhit->AddString(MHOD_ARTIST)->SetString(track->artist);
mhit->AddString(MHOD_GENRE)->SetString(track->genre);
mhit->AddString(MHOD_COMMENT)->SetString(track->comment);
mhit->AddString(MHOD_ALBUMARTIST)->SetString(track->albumartist);
mhit->AddString(MHOD_COMPOSER)->SetString(track->composer);
mhit->length = (track->length>0)?track->length*1000:0;
mhit->year = (track->year>0)?track->year:0;
mhit->tracknum = (track->track>0)?track->track:0;
mhit->totaltracks = (track->tracks>0)?track->tracks:0;
mhit->stars = (unsigned char)(mhit->app_rating = track->rating);
mhit->playcount = mhit->playcount2 = track->playcount;
mhit->lastplayedtime = wintime_to_mactime(track->lastplay);
mhit->lastmodifiedtime = wintime_to_mactime(track->lastupd);
mhit->compilation = track->albumartist && !_wcsicmp(track->albumartist, L"various artists");
mhit->samplerate = 44100; // TODO: benski> we could query this from the input plugin, but we'd have to be careful with HE-AAC
mhit->samplerate2 = 44100.0f;
mhit->mediatype = video?0x02:0x01;
mhit->movie_flag = video?1:0;
mhit->cdnum = (track->disc>0)?track->disc:0;
mhit->totalcds = (track->discs>0)?track->discs:0;
mhit->BPM=(track->bpm>0)?track->bpm:0;
wchar_t *pubdate = getRecordExtendedItem(track,L"podcastpubdate");
if(pubdate && *pubdate) mhit->releasedtime=wintime_to_mactime(_wtoi(pubdate));
// copy the file over
int r;
if(transcodefile)
r = transcoder->TranscodeFile(infile,outfile,killswitch,callback,callbackContext);
else if (!nocopy)
r = CopyFile(infile,outfile,callbackContext,callback,killswitch);
else
{
if (callback)
{
wchar_t langtemp[100] = {0};
callback(callbackContext, WASABI_API_LNGSTRINGW_BUF(IDS_DONE, langtemp, 100));
}
r=0;
}
if(r == 0)
{
StringCbCopyW(ext, sizeof(ext), wcsrchr(outfile,L'.'));
if (!_wcsicmp(ext, L".m4a") || !_wcsicmp(ext, L".mp4"))
{
mhit->vbr = 0;
mhit->type = 0;
mhit->unk14 = 51;
mhit->filetype = FILETYPE_M4A;
}
else if (!_wcsicmp(ext, L".mp3"))
{
mhit->type = 1;
mhit->unk27 = 1;
mhit->unk14 = 12;
mhit->AddString(MHOD_FILETYPE)->SetString(L"MPEG audio file");
mhit->filetype = FILETYPE_MP3;
}
else if (!_wcsicmp(ext, L".wav"))
{
mhit->filetype = FILETYPE_WAV;
}
mhit->samplecount = GetFileInfoInt64(outfile,L"numsamples");
mhit->pregap = (unsigned long)GetFileInfoInt64(outfile,L"pregap");
mhit->postgap = (unsigned long)GetFileInfoInt64(outfile,L"postgap");
mhit->gaplessData = (unsigned long)GetFileInfoInt64(outfile,L"endoffset");
mhit->trackgapless = 1;
mhit->size = (unsigned long)fileSize(outfile);
if (!transcodefile && track->bitrate > 0)
mhit->bitrate = track->bitrate;
else
{
mhit->bitrate = (unsigned long)GetFileInfoInt64(outfile,L"bitrate");
if (!mhit->bitrate)
{
if (track->length > 0)
mhit->bitrate = (mhit->size / track->length)/125;
else
mhit->bitrate = 128;
}
}
*songid = (songid_t)mhit;
}
else
{
DeleteFileW(outfile);
delete mhit;
}
return r;
}
int iPodDevice::trackAddedToTransferQueue(const itemRecordW *track) {
__int64 l;
if(transcoder && transcoder->ShouldTranscode(track->filename)) {
int k = transcoder->CanTranscode(track->filename, 0, track->length);
if(k == -1) return -2;
if(k == 0) l = (__int64)fileSize(track->filename);
else l = (__int64)k;
} else {
wchar_t * ext = wcsrchr(track->filename,L'.');
if(!ext) return -2;
if(_wcsicmp(ext,L".mp3") && _wcsicmp(ext,L".wav") && _wcsicmp(ext,L".m4a") &&
_wcsicmp(ext,L".m4b") && _wcsicmp(ext,L".aa") && _wcsicmp(ext,L".m4v") &&
_wcsicmp(ext,L".mp4")) return -2;
l = (__int64)fileSize(track->filename);
}
__int64 avail = getDeviceCapacityAvailable();
__int64 cmp = transferQueueLength;
cmp += l;
cmp += (__int64)3000000;
if(cmp > avail)
return -1;
else {
transferQueueLength += l;
return 0;
}
}
void iPodDevice::trackRemovedFromTransferQueue(const itemRecordW *track) {
__int64 l = (__int64)fileSize(track->filename);
if(transcoder && transcoder->ShouldTranscode(track->filename)) {
int k = transcoder->CanTranscode(track->filename, 0, track->length);
if(k != -1 && k != 0) l = (__int64)k;
}
transferQueueLength -= l;
}
__int64 iPodDevice::getTrackSizeOnDevice(const itemRecordW *track) {
if(transcoder && transcoder->ShouldTranscode(track->filename)) {
int k = transcoder->CanTranscode(track->filename, 0, track->length);
if(k != -1 && k != 0) return k;
}
wchar_t * ext = wcsrchr(track->filename,'.');
if(!ext) return 0;
if(_wcsicmp(ext,L".mp3") && _wcsicmp(ext,L".wav") && _wcsicmp(ext,L".m4a") &&
_wcsicmp(ext,L".m4b") && _wcsicmp(ext,L".aa") && _wcsicmp(ext,L".m4v") &&
_wcsicmp(ext,L"mp4")) return 0;
return fileSize(track->filename);
}
void iPodDevice::deleteTrack(songid_t songid) {
iPod_mhit * mhit = (iPod_mhit *)songid;
iPod_mhod * mhod = mhit->FindString(MHOD_LOCATION);
if(!mhod) return;
wchar_t * t = mhod->str;
// change ':' to '\\;
wchar_t * p = t;
int l = wcslen(t);
for(int j=0; j<l; j++) if(*(p++)==L':') *(p-1)=L'\\';
// add drive onto front
wchar_t file[2048] = L"x:\\";
file[0] = driveW;
wcscat(file,t);
//check this file isn't playing...
wchar_t* curPlaying = (wchar_t*)SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_GET_PLAYING_FILENAME);
if(curPlaying && !_wcsicmp(curPlaying,file)) SendMessage(plugin.hwndWinampParent,WM_COMMAND,40047,0); //stop
//delete :)
// benski> we might have a file that has been deleted from the disk but not the DB
if(!DeleteFileW(file) // check for file failure
&& GetFileAttributes(file) != INVALID_FILE_ATTRIBUTES) // but only fail if the file actually exists
return;
setArt(songid,NULL,0,0);
l = playlists.GetSize();
for(int i=0; i<l; i++) {
iPod_mhyp * mhyp = ((iPod_mhyp*)playlists.Get(i)); //->DeletePlaylistEntryByID(mhit->id);
for(unsigned int j=0; j<mhyp->GetMhipChildrenCount(); j++) {
if(mhyp->GetPlaylistEntry(j)->songindex == mhit->id) mhyp->DeletePlaylistEntry(j);
}
}
db->mhsdsongs->mhlt->DeleteTrackByID(mhit->id);
}
int iPodDevice::getPlaylistCount() {
return playlists.GetSize();
}
static void readStringMHOD(iPod_mhod * mhod, wchar_t * buf, int len) {
if(mhod) lstrcpyn(buf,mhod->str,len);
else buf[0]=0;
}
static void setStringMHOD(iPod_mhod * mhod, const wchar_t *buf) {
mhod->SetString(buf);
}
void iPodDevice::getPlaylistName(int playlistnumber, wchar_t * buf, int len) {
iPod_mhod * name = ((iPod_mhyp*)playlists.Get(playlistnumber))->FindString(MHOD_TITLE);
readStringMHOD(name,buf,len);
}
int iPodDevice::getPlaylistLength(int playlistnumber) {
return ((iPod_mhyp*)playlists.Get(playlistnumber))->GetMhipChildrenCount();
}
static iPod_mhit blank;
songid_t iPodDevice::getPlaylistTrack(int playlistnumber,int songnum) {
int idx = ((iPod_mhyp*)playlists.Get(playlistnumber))->GetPlaylistEntry(songnum)->songindex;
iPod_mhlt::mhit_map_t::const_iterator f = db->mhsdsongs->mhlt->mhit.find(idx);
if(f != db->mhsdsongs->mhlt->mhit.end() && idx) return (songid_t)f->second;
else {
iPod_mhip* m = ((iPod_mhyp*)playlists.Get(playlistnumber))->GetPlaylistEntry(songnum);
blank.DeleteString(4);
if(m->podcastgroupflag && m->mhod[0]) {
iPod_mhod * mh = blank.AddString(4);
mh->SetString(m->mhod[0]->str);
}
return (songid_t)&blank;
}
}
void iPodDevice::setPlaylistName(int playlistnumber, const wchar_t *buf) {
iPod_mhod * name = ((iPod_mhyp*)playlists.Get(playlistnumber))->FindString(MHOD_TITLE);
if(!name) name = ((iPod_mhyp*)playlists.Get(playlistnumber))->AddString(MHOD_TITLE);
setStringMHOD(name,buf);
if(playlistnumber == 0) {
wchar_t volumename[12] = {0};
const wchar_t * p = buf;
for(int i=0; i<11;) {
if(*p!=L' ' && *p!=L'\t') volumename[i++]=*p;
if(*(p++)==0) break;
}
volumename[11]=0;
char root[] = {drive,":\\"};
SetVolumeLabel(AutoWide(root),volumename);
}
}
void iPodDevice::playlistSwapItems(int playlistnumber, int posA, int posB) {
iPod_mhyp * p = ((iPod_mhyp*)playlists.Get(playlistnumber));
iPod_mhip * a = p->mhip.at(posA);
iPod_mhip * b = p->mhip.at(posB);
if(a && b) {
p->mhip[posA] = b;
p->mhip[posB] = a;
}
}
static iPod_mhyp * sortpl;
static int sortby;
static iPod_mhbd * sortdb;
#define CMPFIELDS(x) { int v=0; iPod_mhod * am = a->FindString(x); iPod_mhod * bm = b->FindString(x); if(am && bm) v = lstrcmpi(am->str,bm->str); else if(am != bm) v = am==NULL?-1:1; if(v!=0) return v<0; }
#define CMPINTFIELDS(x,y) { int v = x-y; if(v!=0) return v<0; }
struct PlaylistItemSort {
bool operator()(iPod_mhip*& ap,iPod_mhip*& bp) {
int use_by = sortby;
iPod_mhit * a = sortdb->mhsdsongs->mhlt->mhit.find(ap->songindex)->second;
iPod_mhit * b = sortdb->mhsdsongs->mhlt->mhit.find(bp->songindex)->second;
// 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
{
CMPFIELDS(MHOD_TITLE);
use_by=SORTBY_ARTIST;
}
else if (use_by == SORTBY_ARTIST) // artist -> album -> disc -> track -> title
{
CMPFIELDS(MHOD_ARTIST);
use_by=SORTBY_ALBUM;
}
else if (use_by == SORTBY_ALBUM) // album -> disc -> track -> title -> artist
{
CMPFIELDS(MHOD_ALBUM);
use_by=SORTBY_DISCNUM;
}
else if (use_by == SORTBY_DISCNUM) // disc -> track -> title -> artist -> album
{
CMPINTFIELDS(a->cdnum,b->cdnum);
use_by=SORTBY_TRACKNUM;
}
else if (use_by == SORTBY_TRACKNUM) // track -> title -> artist -> album -> disc
{
CMPINTFIELDS(a->tracknum,b->tracknum);
use_by=SORTBY_TITLE;
}
else if (use_by == SORTBY_GENRE) // genre -> artist -> album -> disc -> track
{
CMPFIELDS(MHOD_GENRE);
use_by=SORTBY_ARTIST;
}
else if (use_by == SORTBY_PLAYCOUNT) // size -> artist -> album -> disc -> track
{
CMPINTFIELDS(a->playcount,b->playcount);
use_by=SORTBY_ARTIST;
}
else if (use_by == SORTBY_RATING) // size -> artist -> album -> disc -> track
{
CMPINTFIELDS(a->stars,b->stars);
use_by=SORTBY_ARTIST;
}
else if (use_by == SORTBY_LASTPLAYED)
{
double t = difftime(a->lastplayedtime,b->lastplayedtime);
if(t != 0) return t>0;
use_by=SORTBY_ARTIST;
}
else break; // no sort order?
}
return false;
}
};
#undef CMPFIELDS
#undef CMPINTFIELDS
void iPodDevice::sortPlaylist(int playlistnumber, int sortby0) {
sortpl = ((iPod_mhyp*)playlists.Get(playlistnumber));
sortby = sortby0;
sortdb = db;
std::sort(sortpl->mhip.begin(),sortpl->mhip.end(),PlaylistItemSort());
}
void iPodDevice::addTrackToPlaylist(int playlistnumber, songid_t songid)
{
iPod_mhit *mhit = (iPod_mhit *)songid;
if (playlistnumber == 0)
{
db->mhsdsongs->mhlt->AddTrack(mhit);
db->mhsdplaylists->mhlp->GetDefaultPlaylist()->AddPlaylistEntry(NULL, mhit->id);
}
else
{
((iPod_mhyp*)playlists.Get(playlistnumber))->AddPlaylistEntry(NULL, mhit->id);
}
}
void iPodDevice::removeTrackFromPlaylist(int playlistnumber, int songnum) {
((iPod_mhyp*)playlists.Get(playlistnumber))->DeletePlaylistEntry(songnum);
}
void iPodDevice::deletePlaylist(int playlistnumber) {
iPod_mhyp* p = ((iPod_mhyp*)playlists.Get(playlistnumber));
playlists.Del(playlistnumber);
db->mhsdplaylists->mhlp->DeletePlaylistByID(p->playlistID);
}
int iPodDevice::newPlaylist(const wchar_t *name) {
iPod_mhyp * p = db->mhsdplaylists->mhlp->AddPlaylist();
playlists.Add(p);
int ret = db->mhsdplaylists->mhlp->GetChildrenCount() - 1;
setPlaylistName(ret,name);
db->mhsdplaylists->mhlp->SortPlaylists();
return ret;
}
void iPodDevice::getTrackArtist(songid_t songid, wchar_t * buf, int len) {
iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_ARTIST);
readStringMHOD(mhod,buf,len);
}
void iPodDevice::getTrackAlbum(songid_t songid, wchar_t * buf, int len) {
iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_ALBUM);
readStringMHOD(mhod,buf,len);
}
void iPodDevice::getTrackTitle(songid_t songid, wchar_t * buf, int len) {
iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_TITLE);
readStringMHOD(mhod,buf,len);
}
void iPodDevice::getTrackGenre(songid_t songid, wchar_t * buf, int len) {
iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_GENRE);
readStringMHOD(mhod,buf,len);
}
int iPodDevice::getTrackTrackNum(songid_t songid) {
return ((iPod_mhit *)songid)->tracknum;
}
int iPodDevice::getTrackDiscNum(songid_t songid) {
return ((iPod_mhit *)songid)->cdnum;
}
int iPodDevice::getTrackYear(songid_t songid) {
return (int)(((iPod_mhit *)songid)->year);
}
__int64 iPodDevice::getTrackSize(songid_t songid) {
return ((iPod_mhit *)songid)->size;
}
int iPodDevice::getTrackLength(songid_t songid) {
return ((iPod_mhit *)songid)->length;
}
int iPodDevice::getTrackBitrate(songid_t songid) {
return ((iPod_mhit *)songid)->bitrate;
}
int iPodDevice::getTrackPlayCount(songid_t songid) {
return ((iPod_mhit *)songid)->playcount;
}
int iPodDevice::getTrackRating(songid_t songid) {
return ((iPod_mhit *)songid)->stars / 20;
}
__time64_t iPodDevice::getTrackLastPlayed(songid_t songid) {
return mactime_to_wintime(((iPod_mhit *)songid)->lastplayedtime);
}
__time64_t iPodDevice::getTrackLastUpdated(songid_t songid) {
return mactime_to_wintime(((iPod_mhit *)songid)->lastmodifiedtime);
}
void iPodDevice::getTrackAlbumArtist(songid_t songid, wchar_t * buf, int len) {
iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_ALBUMARTIST);
readStringMHOD(mhod,buf,len);
if(!mhod) getTrackArtist(songid,buf,len);
}
void iPodDevice::getTrackComposer(songid_t songid, wchar_t * buf, int len) {
iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_COMPOSER);
readStringMHOD(mhod,buf,len);
}
int iPodDevice::getTrackType(songid_t songid) {
iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_LOCATION);
if(!mhod) return 0;
wchar_t * ext = wcsrchr(mhod->str,L'.');
if(!ext) return 0;
if(!_wcsicmp(ext,L".m4v")) return 1;
return 0;
}
void iPodDevice::getTrackExtraInfo(songid_t songid, const wchar_t *field, wchar_t * buf, int len) {
if(!wcscmp(field,FIELD_EXTENSION)) {
wchar_t buf2[1024]=L"";
getFilename(buf2,1024,songid);
wchar_t * ext = wcsrchr(buf2,L'.');
if(ext) { ext++; lstrcpyn(buf,ext,len); }
}
}
void iPodDevice::setTrackArtist(songid_t songid, const wchar_t *value) {
iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_ARTIST);
if(!mhod) mhod = ((iPod_mhit *)songid)->AddString(MHOD_ARTIST);
setStringMHOD(mhod,value);
}
void iPodDevice::setTrackAlbum(songid_t songid, const wchar_t *value) {
iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_ALBUM);
if(!mhod) mhod = ((iPod_mhit *)songid)->AddString(MHOD_ALBUM);
setStringMHOD(mhod,value);
}
void iPodDevice::setTrackTitle(songid_t songid, const wchar_t *value) {
iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_TITLE);
if(!mhod) mhod = ((iPod_mhit *)songid)->AddString(MHOD_TITLE);
setStringMHOD(mhod,value);
}
void iPodDevice::setTrackTrackNum(songid_t songid, int value) {
((iPod_mhit *)songid)->tracknum = value;
}
void iPodDevice::setTrackDiscNum(songid_t songid, int value) {
((iPod_mhit *)songid)->cdnum = value;
}
void iPodDevice::setTrackGenre(songid_t songid, const wchar_t *value) {
iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_GENRE);
if(!mhod) mhod = ((iPod_mhit *)songid)->AddString(MHOD_GENRE);
setStringMHOD(mhod,value);
}
void iPodDevice::setTrackYear(songid_t songid, int value) {
((iPod_mhit *)songid)->year = (unsigned long)value;
}
void iPodDevice::setTrackPlayCount(songid_t songid, int value) {
((iPod_mhit *)songid)->playcount = value;
}
void iPodDevice::setTrackRating(songid_t songid, int value) {
((iPod_mhit *)songid)->app_rating = ((iPod_mhit *)songid)->stars = value*20;
}
void iPodDevice::setTrackLastPlayed(songid_t songid, __time64_t value) {
((iPod_mhit *)songid)->lastplayedtime = wintime_to_mactime(value);
}
void iPodDevice::setTrackLastUpdated(songid_t songid, __time64_t value) {
((iPod_mhit *)songid)->lastmodifiedtime = wintime_to_mactime(value);
}
void iPodDevice::setTrackAlbumArtist(songid_t songid, const wchar_t *value) {
iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_ALBUMARTIST);
if(!mhod) mhod = ((iPod_mhit *)songid)->AddString(MHOD_ALBUMARTIST);
setStringMHOD(mhod,value);
}
void iPodDevice::setTrackComposer(songid_t songid, const wchar_t *value) {
iPod_mhod * mhod = ((iPod_mhit *)songid)->FindString(MHOD_COMPOSER);
if(!mhod) mhod = ((iPod_mhit *)songid)->AddString(MHOD_COMPOSER);
setStringMHOD(mhod,value);
}
void iPodDevice::getFilename(char * buf, int len, songid_t song) {
iPod_mhod * mhod = ((iPod_mhit *)song)->FindString(MHOD_LOCATION);
if(!mhod) {buf[0]=0; return;}
char * filename = UTF16_to_char(mhod->str,mhod->length);
char * p = filename;
buf[0] = drive;
buf[1] = ':';
int j=2;
while(p && *p && j < len-1) { if(*p==':') buf[j]='\\'; else buf[j]=*p; p++; j++; }
buf[j]=0;
free(filename);
}
void iPodDevice::getFilename(wchar_t * buf, int len, songid_t song) {
iPod_mhod * mhod = ((iPod_mhit *)song)->FindString(MHOD_LOCATION);
if(!mhod) {buf[0]=0; return;}
wchar_t * filename = mhod->str;
wchar_t * p = filename;
buf[0] = drive;
buf[1] = L':';
int j=2;
while(p && *p && j < len-1) { if(*p==L':') buf[j]=L'\\'; else buf[j]=*p; p++; j++; }
buf[j]=0;
}
typedef struct { songid_t song; Device * dev; const wchar_t * filename; } tagItem;
static wchar_t * tagFunc(const wchar_t * tag, void * p) { //return 0 if not found, -1 for empty tag
tagItem * s = (tagItem *)p;
int len = 2048;
wchar_t * buf = (wchar_t *)malloc(sizeof(wchar_t)*len);
if (!_wcsicmp(tag, L"artist")) s->dev->getTrackArtist(s->song,buf,len);
else if (!_wcsicmp(tag, L"album")) s->dev->getTrackAlbum(s->song,buf,len);
else if (!_wcsicmp(tag, L"title")) s->dev->getTrackTitle(s->song,buf,len);
else if (!_wcsicmp(tag, L"genre")) s->dev->getTrackGenre(s->song,buf,len);
else if (!_wcsicmp(tag, L"year"))
{
int year = s->dev->getTrackYear(s->song);
if (year>0)
StringCchPrintf(buf,len,L"%d",year);
else
buf[0]=0;
}
else if (!_wcsicmp(tag, L"tracknumber") || !_wcsicmp(tag, L"track"))
{
int track = s->dev->getTrackTrackNum(s->song);
if (track>0)
StringCchPrintf(buf,len,L"%d",track);
else
buf[0]=0;
}
else if (!_wcsicmp(tag, L"discnumber"))
{
int disc = s->dev->getTrackDiscNum(s->song);
if (disc>0)
StringCchPrintf(buf,len,L"%d",disc);
else
buf[0]=0;
}
else if (!_wcsicmp(tag, L"bitrate"))
{
int bitrate = s->dev->getTrackBitrate(s->song);
if (bitrate>0)
StringCchPrintf(buf,len,L"%d",bitrate);
else
buf[0]=0;
}
else if (!_wcsicmp(tag, L"filename")) lstrcpyn(buf,s->filename,len);
else buf[0]=0;
return buf;
}
static void tagFreeFunc(wchar_t *tag, void *p) { if(tag) free(tag); }
void getTitle(Device * dev, songid_t song, const wchar_t * filename, wchar_t * buf, int len) {
buf[0]=0; buf[len-1]=0;
tagItem item = {song,dev,filename};
waFormatTitleExtended fmt={filename,0,NULL,&item,buf,len,tagFunc,tagFreeFunc};
SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&fmt, IPC_FORMAT_TITLE_EXTENDED);
}
bool iPodDevice::playTracks(songid_t * songidList, int listLength, int startPlaybackAt, bool enqueue) {
//char buf[2048]="";
wchar_t wbuf[2048]=L"";
if(!enqueue) { //clear playlist
SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_DELETE);
/*int l=SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_PE_GETINDEXTOTAL);
while(l>=0) SendMessage(plugin.hwndWinampParent,WM_WA_IPC,--l,IPC_PE_DELETEINDEX);*/
}
for(int i=0; i<listLength; i++) {
getFilename(wbuf,2048,songidList[i]);
//strcpy(buf,AutoChar(wbuf));
wchar_t title[2048] = {0};
getTitle(this,songidList[i],wbuf,title,2048);
/*enqueueFileWithMetaStruct s={buf,strdup(AutoChar(title)),getTrackLength(songidList[i])/1000};
SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILE);
free((void*)s.title);*/
enqueueFileWithMetaStructW s={wbuf,_wcsdup(title),PathFindExtensionW(wbuf),getTrackLength(songidList[i]) / 1000};
SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW);
free((void*)s.title);
}
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;
}
int iPodDevice::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.
{
wchar_t fn[2048] = {0}; // song filename on ipod
getFilename(fn,2048,song);
wchar_t * ext = wcsrchr(fn,L'.');
if(!ext) { callback(callbackContext,WASABI_API_LNGSTRINGW(IDS_INVALID_TRACK)); return -1; }
wcscat(path,ext);
return CopyFile(fn,path,callbackContext,callback,killswitch);
}
// art functions
static void fileputinhole(const wchar_t* file, unsigned int pos, int len, void* newdata) {
__int64 fs = fileSize(file);
int open_flags = OPEN_EXISTING;
if(fs <= 0) open_flags = CREATE_NEW;
HANDLE hw = CreateFile(file,GENERIC_WRITE | GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,open_flags,0,NULL);
if(hw == INVALID_HANDLE_VALUE) return;
SetFilePointer(hw,pos,NULL,FILE_BEGIN);
DWORD written=0;
WriteFile(hw,newdata,len,&written,NULL);
CloseHandle(hw);
}
ArtDataObject * makeThumbMetadata(const ArtworkFormat * thumb, wchar_t drive, Image * image, ArtDB *artdb) {
wchar_t file[MAX_PATH] = {0};
wsprintfW(file,L"%c:\\iPod_Control\\Artwork\\F%04d_1.ithmb",drive,thumb->correlation_id);
bool found=false;
ArtFile *f=NULL;
for(size_t i=0; i < artdb->fileListDS->fileList->files.size(); i++) {
f = artdb->fileListDS->fileList->files[i];
if(f->corrid == thumb->correlation_id) { found=true; break; }
}
if(!found) {
f = new ArtFile;
f->corrid = thumb->correlation_id;
f->imagesize = image->get16BitSize(thumb->row_align, thumb->image_align);
artdb->fileListDS->fileList->files.push_back(f);
f->file = _wcsdup(file);
}
ArtDataObject * ms = new ArtDataObject;
ms->type=2;
ms->image = new ArtImageName;
ms->image->corrid = thumb->correlation_id;
ms->image->imgw = thumb->width;
ms->image->imgh = thumb->height;
ms->image->imagesize = image->get16BitSize(thumb->row_align, thumb->image_align);
//__int64 fs = fileSize(file);
//ms->image->ithmboffset = fs>0?fs:0;
ms->image->ithmboffset = f->getNextHole(ms->image->imagesize);
wchar_t buf[100] = {0};
StringCchPrintf(buf,100,L":F%04d_1.ithmb",thumb->correlation_id);
ms->image->filename = new ArtDataObject;
ms->image->filename->type=3;
ms->image->filename->SetString(buf);
unsigned short *data = (unsigned short *)calloc(ms->image->imagesize,1);
image->exportToRGB565((RGB565*)data, thumb->format, thumb->row_align, thumb->image_align);
fileputinhole(file,ms->image->ithmboffset,ms->image->imagesize,data);
//writeDataToThumb(file,data,thumb->width * thumb->height);
free(data);
f->images.push_back(new ArtFileImage(ms->image->ithmboffset,ms->image->imagesize,1));
f->sortImages();
return ms;
}
void GetTempFilePath(wchar_t *path) {
wchar_t dir[MAX_PATH] = {0};
GetTempPath(MAX_PATH,dir);
GetTempFileName(dir,L"ml_pmp",0,path);
}
static void fileclosehole(const wchar_t* file, unsigned int pos, int len) {
HANDLE hw = CreateFile(file,GENERIC_WRITE | GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL);
if(hw == INVALID_HANDLE_VALUE) return;
HANDLE hr = CreateFile(file,GENERIC_WRITE | GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,NULL,OPEN_EXISTING,0,NULL);
if(hr == INVALID_HANDLE_VALUE) { CloseHandle(hw); return; }
SetFilePointer(hw,pos,NULL,FILE_BEGIN);
SetFilePointer(hr,pos+len,NULL,FILE_BEGIN);
DWORD fs = GetFileSize(hw,NULL);
if(pos == 0 && len == fs) { CloseHandle(hr); CloseHandle(hw); _wunlink(file); return; }
unsigned int p = pos;
while(1) {
BYTE buf[65536] = {0};
DWORD read=0;
ReadFile(hr,buf,sizeof(buf),&read,NULL);
if(!read) break;
DWORD written=0;
WriteFile(hw,buf,read,&written,NULL);
if(!written) break;
p+=read;
if(p>=fs) break;
}
SetFilePointer(hw,fs - len,NULL,FILE_BEGIN);
SetEndOfFile(hw);
CloseHandle(hr);
CloseHandle(hw);
}
static bool replaceart(songid_t songid, wchar_t driveW, ArtDB *artdb, std::vector<const ArtworkFormat*> * thumbs, std::vector<Image*> * images) {
//return false;
__int64 dbid = ((iPod_mhit*)songid)->dbid;
int done=0;
ArtImageList::ArtImageMapIterator art = artdb->imageListDS->imageList->images.find(dbid);
if(art != artdb->imageListDS->imageList->images.end() && art->second) { // replace old art
for(size_t i=0; i!=art->second->dataobjs.size(); i++) if(art->second->dataobjs[i]->image) {
ArtImageName * in = art->second->dataobjs[i]->image;
for(size_t j=0; j!=thumbs->size(); j++)
{
if(in->corrid == thumbs->at(j)->correlation_id) {
wchar_t file[MAX_PATH] = {0};
wsprintfW(file,L"%c:\\iPod_Control\\Artwork\\F%04d_1.ithmb",driveW,in->corrid);
int size = images->at(j)->get16BitSize(thumbs->at(j)->row_align, thumbs->at(j)->image_align);
if(size == in->imagesize) {
unsigned short *data = (unsigned short *)malloc(size);
images->at(j)->exportToRGB565((RGB565*)data, thumbs->at(j)->format, thumbs->at(j)->row_align, thumbs->at(j)->image_align);
fileputinhole(file,in->ithmboffset,in->imagesize,data);
free(data);
done++;
}
}
}
}
}
return (done == thumbs->size());
}
void iPodDevice::setArt(songid_t songid, void *bits, int w, int h) { //buf is in format ARGB32*
if(!artdb || !thumbs.size()) return; // art not supported
iPod_mhit * mhit = (iPod_mhit *)songid;
if(bits == NULL || w == 0 || h == 0) { // remove art
ArtImageList::ArtImageMapIterator arti = artdb->imageListDS->imageList->images.find(mhit->dbid);
if(arti == artdb->imageListDS->imageList->images.end() || !arti->second) return;
ArtImage * art = arti->second;
for(auto j = art->dataobjs.begin(); j!=art->dataobjs.end(); j++) {
ArtImageName *n = (*j)->image;
if(n)
{
ArtFile * f = artdb->fileListDS->fileList->getFile(n->corrid);
if(f)
{
bool found=false;
for(size_t i=0; i!=f->images.size(); i++)
{
if(!found && f->images[i]->start == n->ithmboffset && --f->images[i]->refcount==0)
{
delete f->images[i];
f->images.erase(f->images.begin() + i);
i--;
found=true;
}
}
}
}
}
mhit->mhii_link = 0;
mhit->artworkcount = 0;
mhit->hasArtwork = 0;
artdb->imageListDS->imageList->images.erase(arti);
delete art;
} else {
//setArt(songid,NULL,0,0); // clear old art first
HQSkinBitmap albumart((ARGB32*)bits, w, h); // wrap image into a bitmap object (no copying done)
std::vector<Image*> images;
for(size_t i=0; i!=thumbs.size(); i++) {
BltCanvas canvas(thumbs[i]->width,thumbs[i]->height);
albumart.stretch(&canvas, 0, 0, thumbs[i]->width,thumbs[i]->height);
images.push_back(new Image((ARGB32 *)canvas.getBits(), thumbs[i]->width,thumbs[i]->height));
}
if(!replaceart(songid,driveW,artdb,&thumbs,&images)) {
setArt(songid,NULL,0,0);
ArtImage * artimg = new ArtImage();
artimg->songid = mhit->dbid;
artimg->id = artdb->nextid++;
artimg->srcImageSize = w*h*4;//0; //fileSize(infile);
//artimg->srcImageSize = mhit->unk45 = rand();
mhit->artworksize = 0;
for(size_t i=0; i!=thumbs.size(); i++)
{
artimg->dataobjs.push_back(makeThumbMetadata(thumbs[i],driveW,images[i],artdb));
mhit->artworksize += thumbs[i]->width * thumbs[i]->height * sizeof(short);
}
artdb->imageListDS->imageList->images.insert(ArtImageList::ArtImageMapPair(artimg->songid,artimg));
mhit->artworkcount = 1;//thumbs.size();
mhit->hasArtwork = 1;//thumbs.size();
mhit->mhii_link = artimg->id;
}
//images.deleteAll();
for (auto image : images)
{
delete image;
}
images.clear();
}
}
class ipodart_t {
public:
ipodart_t(ArtImageName *in, wchar_t driveW,int w, int h, const ArtworkFormat* format): w(w),h(h),image(0),error(0),resized(0),format(format) {
wsprintf(fn,L"%c:\\iPod_Control\\Artwork",driveW);
wchar_t *p = fn+wcslen(fn);
in->filename->GetString(p,MAX_PATH - (p - fn));
while(p && *p) {if(*p == L':') *p=L'\\'; p++;}
offset = in->ithmboffset;
}
~ipodart_t() { if(image) delete image; }
Image * GetImage() {
if(image || error) return image;
int size = Image::get16BitSize(w,h,format->row_align, format->image_align);
RGB565 * r = (RGB565*)calloc(size,1);
if(!r) { return 0; error=1; }
FILE *f = _wfopen(fn,L"rb");
if(!f) { free(r); error=1; return 0; }
fseek(f,offset,0);
if(fread(r,size,1,f) != 1) { free(r); fclose(f); error=1; return 0; }
fclose(f);
image = new Image(r,w,h,format->format,format->row_align, format->image_align);
free(r);
return image;
}
Image * RegetImage() {
if(image) delete image; image=0;
return GetImage();
}
int GetError() {return error;}
int getHeight(){if(image) return image->getHeight(); else return h;}
int getWidth() {if(image) return image->getWidth(); else return w;}
int resized;
void Resize(int neww, int newh)
{
HQSkinBitmap temp(image->getData(), image->getWidth(), image->getHeight()); // wrap into a SkinBitmap (no copying involved)
BltCanvas newImage(neww,newh);
temp.stretch(&newImage, 0, 0, neww, newh);
delete image;
image = new Image((ARGB32 *)newImage.getBits(), neww, newh);
resized=1;
}
private:
wchar_t fn[MAX_PATH];
int offset;
int w,h;
Image * image;
int error;
const ArtworkFormat* format;
};
pmpart_t iPodDevice::getArt(songid_t songid) {
if(!artdb) return 0;
__int64 dbid = ((iPod_mhit*)songid)->dbid;
ArtImageList::ArtImageMapIterator art = artdb->imageListDS->imageList->images.find(dbid);
if(art == artdb->imageListDS->imageList->images.end() || !art->second) return 0;
int l = art->second->dataobjs.size();
ArtImageName * in=0;
int w=0,h=0;
const ArtworkFormat * format=0;
for(int i=0; i<l; i++) {
if(art->second->dataobjs[i]->image && (w < art->second->dataobjs[i]->image->imgw || h < art->second->dataobjs[i]->image->imgh)) {
in = art->second->dataobjs[i]->image;
w = in->imgw;
h = in->imgh;
for(size_t i=0; i < thumbs.size(); i++)
{
const ArtworkFormat *f = thumbs.at(i);
if(f->width == w && f->height == h)
format = f;
}
}
}
if(!in || !format) return 0;
return (pmpart_t)new ipodart_t(in,driveW,w,h,format);
}
void iPodDevice::releaseArt(pmpart_t art) {
if(!art) return;
ipodart_t *image = (ipodart_t *)art;
delete image;
}
int iPodDevice::drawArt(pmpart_t art, HDC dc, int x, int y, int w, int h) {
Image *image = ((ipodart_t*)art)->GetImage();
if(!image) return 0;
HQSkinBitmap temp(image->getData(), image->getWidth(), image->getHeight()); // wrap into a SkinBitmap (no copying involved)
DCCanvas canvas(dc);
temp.stretch(&canvas,x,y,w,h);
return 1;
}
void iPodDevice::getArtNaturalSize(pmpart_t art, int *w, int *h){
ipodart_t *image = (ipodart_t*)art;
if(!image) return;
*h = image->getHeight();
*w = image->getWidth();
}
void iPodDevice::setArtNaturalSize(pmpart_t art, int w, int h){
Image *image = ((ipodart_t*)art)->GetImage();
if(!image) return;
if(w == image->getWidth() && h == image->getHeight()) return;
if(((ipodart_t*)art)->resized) {
image = ((ipodart_t*)art)->RegetImage();
if(!image) return;
}
((ipodart_t*)art)->Resize(w, h);
}
void iPodDevice::getArtData(pmpart_t art, void* data){ // data ARGB32* is at natural size
Image *image = ((ipodart_t*)art)->GetImage();
if(!image) return;
image->exportToARGB32((ARGB32*)data);
}
bool iPodDevice::artIsEqual(pmpart_t at, pmpart_t bt) {
if(at == bt) return true;
if(!at || !bt) return false;
if(((ipodart_t*)at)->getWidth() != ((ipodart_t*)bt)->getWidth()) return false;
if(((ipodart_t*)at)->getHeight() != ((ipodart_t*)bt)->getHeight()) return false;
Image *a = ((ipodart_t*)at)->RegetImage();
Image *b = ((ipodart_t*)bt)->RegetImage();
if(!a && !b) return true;
if(!a || !b) return false;
if(a->getWidth() != b->getWidth()) return false;
if(b->getHeight() != b->getHeight()) return false;
return memcmp(a->getData(),b->getData(),a->getWidth()*a->getHeight()*sizeof(ARGB32)) == 0;
}
static INT_PTR CALLBACK gapscan_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
static iPodDevice * dev;
static int i;
switch(uMsg) {
case WM_INITDIALOG:
SetWindowPos(hwndDlg,HWND_TOPMOST,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE);
dev = (iPodDevice*)lParam;
i=0;
dev->gapscanner = hwndDlg;
SendDlgItemMessage(hwndDlg,IDC_PROGRESS1,PBM_SETRANGE32,0,dev->getPlaylistLength(0));
SendDlgItemMessage(hwndDlg,IDC_PROGRESS1,PBM_SETPOS,0,0);
SetTimer(hwndDlg,1,100,NULL);
break;
case WM_TIMER:
if(wParam == 1) {
KillTimer(hwndDlg,1);
int l = dev->getPlaylistLength(0);
int j=0;
for(;;) {
if(i >= l) return gapscan_dialogProc(hwndDlg,WM_CLOSE,0,0);
iPod_mhit * mhit = (iPod_mhit *)dev->getPlaylistTrack(0,i++);
if(!mhit->trackgapless && !mhit->gaplessData) {
wchar_t artist[50] = {0}, title[50] = {0}, buf[200] = {0};
dev->getTrackArtist((songid_t)mhit,artist,50);
dev->getTrackTitle((songid_t)mhit,title,50);
StringCchPrintf(buf,200,L"%d/%d: %s - %s",i+1,l,artist,title);
SetDlgItemText(hwndDlg,IDC_CAPTION,buf);
wchar_t infile[MAX_PATH]=L"";
dev->getFilename(infile,MAX_PATH,(songid_t)mhit);
BOOL worked=TRUE;
mhit->samplecount = GetFileInfoInt64(infile,L"numsamples",&worked);
mhit->pregap = (unsigned long)GetFileInfoInt64(infile,L"pregap",&worked);
mhit->postgap = (unsigned long)GetFileInfoInt64(infile,L"postgap",&worked);
mhit->gaplessData = (unsigned long)GetFileInfoInt64(infile,L"endoffset",&worked);
mhit->trackgapless = worked?1:0;
j++;
} else if(!(i%23)) SetDlgItemText(hwndDlg,IDC_CAPTION,WASABI_API_LNGSTRINGW(IDS_SCANNING));
if(j > 3 || !(i % 50)) {
SendDlgItemMessage(hwndDlg,IDC_PROGRESS1,PBM_SETPOS,i,0);
SetTimer(hwndDlg,1,25,NULL);
return 0;
}
}
}
break;
case WM_CLOSE:
dev->writeiTunesDB();
EndDialog(hwndDlg,0);
dev->gapscanner = NULL;
break;
case WM_DESTROY:
dev->gapscanner = NULL;
break;
case WM_COMMAND:
switch(LOWORD(wParam)) {
case IDCANCEL:
gapscan_dialogProc(hwndDlg,WM_CLOSE,0,0);
break;
case IDC_BG:
ShowWindow(hwndDlg,SW_HIDE);
break;
}
break;
}
return 0;
}
static INT_PTR CALLBACK config_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) {
static iPodDevice * dev;
switch(uMsg) {
case WM_INITDIALOG:
{
prefsParam* p = (prefsParam*)lParam;
p->config_tab_init(hwndDlg,p->parent);
dev = (iPodDevice*)p->dev;
if(dev->artdb && dev->thumbs.size()) {
wchar_t inifile[] = {dev->driveW,L":\\iPod_Control\\iTunes\\ml_pmp.ini"};
ShowWindow(GetDlgItem(hwndDlg,IDC_STATIC_ARTGROUP),SW_SHOWNA);
ShowWindow(GetDlgItem(hwndDlg,IDC_CHECK_USEART),SW_SHOWNA);
CheckDlgButton(hwndDlg,IDC_CHECK_USEART,GetPrivateProfileInt(L"ml_pmp",L"albumart",1,inifile));
//ShowWindow(GetDlgItem(hwndDlg,IDC_COMBO_ARTMODE),SW_SHOWNA);
/*ComboBox combo(hwndDlg,IDC_COMBO_USEART);
combo.AddString(L"Add to all tracks");
combo.AddString(L"Only add to the first track in an album");
combo.AddString(L"Don't add to any tracks");
*/
}
}
break;
case WM_COMMAND:
switch(LOWORD(wParam)) {
case IDC_SCAN:
if(dev->gapscanner) ShowWindow(dev->gapscanner,SW_SHOW);
else WASABI_API_DIALOGBOXPARAM(IDD_GAPSCAN,NULL,gapscan_dialogProc,(LPARAM)dev);
break;
case IDC_CHECK_USEART:
wchar_t inifile[] = {dev->driveW,L":\\iPod_Control\\iTunes\\ml_pmp.ini"}, s[32] = {0};
StringCchPrintf(s, 32, L"%d", (IsDlgButtonChecked(hwndDlg, IDC_CHECK_USEART)==BST_CHECKED));
WritePrivateProfileString(L"ml_pmp", L"albumart", s, inifile);
break;
}
break;
}
return 0;
}
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('O','G','G',' '),
mmioFOURCC('M','P','2',' '),
mmioFOURCC('M','4','A','H'),
mmioFOURCC('M','4','A','+'),
mmioFOURCC('A','D','T','S'),
};
intptr_t iPodDevice::extraActions(intptr_t param1, intptr_t param2, intptr_t param3,intptr_t param4) {
switch(param1) {
case DEVICE_SET_ICON: // icons
{
MLTREEIMAGE * i = (MLTREEIMAGE*)param2;
i->hinst = plugin.hDllInstance;
i->resourceId = image16;
}
break;
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_ipod.dll", image16);
}
else
{
// TODO: get the name of the DLL at load time
StringCchPrintfW((wchar_t *)param4, 260, L"res://%s/PNG/#%u", L"pmp_ipod.dll", image160);
}
}
break;
case DEVICE_SUPPORTED_METADATA:
return 0xffff | (artdb?SUPPORTS_ALBUMART:0);
case DEVICE_CAN_RENAME_DEVICE:
return 1;
case DEVICE_GET_INI_FILE:
{
wchar_t inifile[] = {driveW,L":\\iPod_Control\\iTunes\\ml_pmp.ini"};
wcsncpy((wchar_t*)param2,inifile,MAX_PATH);
}
break;
case DEVICE_GET_PREFS_DIALOG:
if(param3 == 0) {
pref_tab * p = (pref_tab *)param2;
p->hinst = WASABI_API_LNG_HINST;
p->dlg_proc = config_dialogProc;
p->res_id = IDD_CONFIG;
WASABI_API_LNGSTRINGW_BUF(IDS_ADVANCED,p->title,100);
}
break;
case DEVICE_REFRESH:
{
char drive = this->drive;
//iPods.eraseObject(this);
auto it = std::find(iPods.begin(), iPods.end(), this);
if (it != iPods.end())
{
iPods.erase(it);
}
SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICEDISCONNECTED);
delete this;
new iPodDevice(drive);
}
break;
case DEVICE_ADDPODCASTGROUP:
{
int pos = param3;
wchar_t * name = (wchar_t *)param4;
iPod_mhyp* pl = (iPod_mhyp*)playlists.Get(param2);
pl->podcastflag=1;
iPod_mhip * mhip = new iPod_mhip();
mhip->podcastgroupflag=256;
mhip->podcastgroupref=0;
iPod_mhod * d = new iPod_mhod();
d->SetString(name);
d->type=1;
mhip->mhod.push_back(d);
pl->mhip.insert(pl->mhip.begin()+pos,mhip);
}
break;
case DEVICE_ADDPODCASTGROUP_FINISH:
{
iPod_mhyp* pl = (iPod_mhyp*)playlists.Get(param2);
pl->numLibraryMHODs=0x18;
int groupref=0;
for(size_t i=0; i < pl->mhip.size(); i++) {
iPod_mhip * m = pl->mhip[i];
m->groupid = i+1000000;
if(m->podcastgroupflag & 256) groupref = m->groupid;
else m->podcastgroupref = groupref;
}
}
break;
case DEVICE_SUPPORTS_VIDEO:
return 1;
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_MODEL:
{
wchar_t *model_buffer = (wchar_t *)param2;
unsigned int cch = (unsigned int)param3;
if (!info)
{
StringCchCopyW(model_buffer, cch, L"Apple iPod");
}
else switch(info->family_id)
{
default:
StringCchCopyW(model_buffer, cch, L"Apple iPod");
break;
case 3:
StringCchCopyW(model_buffer, cch, L"Apple iPod Mini");
break;
case 4:
StringCchCopyW(model_buffer, cch, L"Apple iPod 4G");
break;
case 5:
StringCchCopyW(model_buffer, cch, L"Apple iPod Photo");
break;
case 6:
StringCchCopyW(model_buffer, cch, L"Apple iPod 5G");
break;
case 7:
StringCchCopyW(model_buffer, cch, L"Apple iPod Nano 1G");
break;
case 9:
StringCchCopyW(model_buffer, cch, L"Apple iPod Nano 2G");
break;
case 11:
StringCchCopyW(model_buffer, cch, L"Apple iPod Classic");
break;
case 12:
StringCchCopyW(model_buffer, cch, L"Apple iPod Fat Nano");
break;
case 15:
StringCchCopyW(model_buffer, cch, L"Apple iPod Nano4G");
break;
case 128:
StringCchCopyW(model_buffer, cch, L"Apple iPod Shuffle");
break;
case 130:
StringCchCopyW(model_buffer, cch, L"Apple iPod Shuffle 2G");
break;
case 132:
StringCchCopyW(model_buffer, cch, L"Apple iPod Shuffle 3G");
break;
}
}
return 1;
}
return 0;
}