1
0

androiddevice.cpp 50 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869
  1. #include "androiddevice.h"
  2. #include "resource.h"
  3. #include "androidplaylist.h"
  4. #include "androidplaylistsaver.h"
  5. #include "api.h"
  6. #include "../winamp/wa_ipc.h"
  7. #include <tataki/bitmap/bitmap.h>
  8. #include <tataki/canvas/bltcanvas.h>
  9. #include <shlobj.h>
  10. #include <strsafe.h>
  11. #include <shlwapi.h>
  12. // from main.cpp
  13. extern PMPDevicePlugin plugin;
  14. extern std::vector<AndroidDevice*> devices;
  15. extern bool loading_devices[26];
  16. // from utils.cpp
  17. extern BOOL RecursiveCreateDirectory(wchar_t* buf);
  18. extern bool supportedFormat(wchar_t * file, wchar_t * supportedFormats);
  19. extern DeviceType detectDeviceType(wchar_t drive);
  20. extern __int64 fileSize(wchar_t * filename);
  21. extern void removebadchars(wchar_t *s);
  22. extern wchar_t * fixReplacementVars(wchar_t *str, int str_size, Device * dev, songid_t song);
  23. static INT_PTR CALLBACK prefs_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
  24. int CopyFile(const wchar_t *infile, const wchar_t *outfile, void * callbackContext, void (*callback)(void * callbackContext, wchar_t * status), int * killswitch);
  25. extern BOOL EjectVolume(TCHAR cDriveLetter);
  26. static int connected_drives[26] = {0, };
  27. // from albumart.cpp
  28. void CopyAlbumArt(const wchar_t *source, const wchar_t *destination);
  29. // called from ml_pmp
  30. __int64 AndroidDevice::getDeviceCapacityAvailable() // in bytes
  31. {
  32. ULARGE_INTEGER tfree={0,}, total={0,}, freeb={0,};
  33. wchar_t path[4]=L"x:\\";
  34. path[0]=drive;
  35. GetDiskFreeSpaceEx(path, &tfree, &total, &freeb);
  36. return freeb.QuadPart;
  37. }
  38. // called from ml_pmp
  39. __int64 AndroidDevice::getDeviceCapacityTotal()
  40. {
  41. // in bytes
  42. ULARGE_INTEGER tfree={0,}, total={0,}, freeb={0,};
  43. wchar_t path[4]=L"x:\\";
  44. path[0]=drive;
  45. GetDiskFreeSpaceEx(path, &tfree, &total, &freeb);
  46. return total.QuadPart;
  47. }
  48. // called from ml_pmp
  49. void AndroidDevice::Eject()
  50. {
  51. // if you ejected successfully, you MUST call PMP_IPC_DEVICEDISCONNECTED and delete this
  52. for(size_t i=0; i < devices.size(); i++)
  53. {
  54. AndroidDevice *device = devices.at(i);
  55. if (device == this)
  56. {
  57. if (EjectVolume(drive))
  58. {
  59. devices.erase(devices.begin() + i);
  60. SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICEDISCONNECTED);
  61. delete this;
  62. }
  63. else
  64. {
  65. wchar_t titleStr[128] = {0};
  66. MessageBox(plugin.hwndLibraryParent,WASABI_API_LNGSTRINGW(IDS_FAILED_TO_EJECT_DRIVE), WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,titleStr,128),0);
  67. }
  68. break;
  69. }
  70. }
  71. }
  72. // called from ml_pmp
  73. void AndroidDevice::Close()
  74. {
  75. // save any changes, and call PMP_IPC_DEVICEDISCONNECTED AND delete this
  76. for (size_t i=0; i < devices.size(); i++)
  77. {
  78. if (((AndroidDevice*)devices.at(i)) == this)
  79. {
  80. devices.erase(devices.begin() + i);
  81. SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICEDISCONNECTED);
  82. delete this;
  83. break;
  84. }
  85. }
  86. }
  87. // called from ml_pmp
  88. // return 0 for success, -1 for failed or cancelled
  89. int AndroidDevice::transferTrackToDevice(const itemRecordW * track, // the track to transfer
  90. void * callbackContext, //pass this to the callback
  91. void (*callback)(void *callbackContext, wchar_t *status), // call this every so often so the GUI can be updated. Including when finished!
  92. songid_t * songid, // fill in the songid when you are finished
  93. int * killswitch) // if this gets set to anything other than zero, the transfer has been cancelled by the user
  94. {
  95. wchar_t fn[MAX_PATH] = L"X:\\";
  96. lstrcpyn(fn, songFormat, MAX_PATH);
  97. fn[0] = drive;
  98. wchar_t * src = track->filename;
  99. wchar_t ext[10] = {0};
  100. wchar_t *e = wcsrchr(src,L'.');
  101. if (e) lstrcpyn(ext, e, 10);
  102. bool transcodefile = false;
  103. if (transcoder && transcoder->ShouldTranscode(src))
  104. {
  105. int r = transcoder->CanTranscode(src, ext);
  106. if (r != 0 && r != -1) transcodefile = true;
  107. }
  108. AndroidSong *s = new AndroidSong();
  109. lstrcpyn(s->filename, src, MAX_PATH); //this will get written over, but for now we have this so that the user can keep the old filename
  110. fillMetaData(s); // TODO: benski> used cached info inside track (itemRecordW) if available
  111. fixReplacementVars(fn, MAX_PATH, this, (songid_t)s);
  112. StringCchCat(fn, MAX_PATH, ext); //place extension
  113. StringCchCopy(s->filename, MAX_PATH, fn);
  114. wchar_t * dir = wcsrchr(fn,L'\\');
  115. wchar_t * dir2 = wcsrchr(fn,L'/');
  116. wchar_t slash;
  117. if (dir2 > dir)
  118. {
  119. dir = dir2;
  120. slash=L'/';
  121. }
  122. else slash = L'\\';
  123. if (dir) *dir = 0;
  124. RecursiveCreateDirectory(fn);
  125. if (dir) *dir = slash;
  126. int r;
  127. if (transcodefile)
  128. {
  129. r = transcoder->TranscodeFile(src, fn, killswitch, callback, callbackContext);
  130. }
  131. else
  132. {
  133. r = CopyFile(src, fn, callbackContext, callback, killswitch);
  134. }
  135. if (r == 0)
  136. {
  137. // TODO: benski> do we need to update any fields from the transcoded filed?
  138. CopyAlbumArt(src, fn);
  139. writeRecordToDB(s);
  140. callback(callbackContext, WASABI_API_LNGSTRINGW(IDS_DONE));
  141. *songid = (songid_t)s;
  142. }
  143. else
  144. {
  145. callback(callbackContext, WASABI_API_LNGSTRINGW(IDS_TRANSFER_FAILED));
  146. delete s;
  147. }
  148. return r;
  149. }
  150. // called from ml_pmp
  151. int AndroidDevice::trackAddedToTransferQueue(const itemRecordW *track)
  152. {
  153. // return 0 to accept, -1 for "not enough space", -2 for "incorrect format"
  154. __int64 k = getTrackSizeOnDevice(track);
  155. if (!k) return -2;
  156. __int64 l = (__int64)k;
  157. __int64 avail = getDeviceCapacityAvailable();
  158. __int64 cmp = transferQueueLength;
  159. cmp += l;
  160. if (cmp > avail) return -1;
  161. else
  162. {
  163. transferQueueLength += l;
  164. return 0;
  165. }
  166. }
  167. // called from ml_pmp
  168. void AndroidDevice::trackRemovedFromTransferQueue(const itemRecordW *track)
  169. {
  170. transferQueueLength -= (__int64)getTrackSizeOnDevice(track);
  171. }
  172. // called from ml_pmp
  173. // return the amount of space that will be taken up on the device by the track (once it has been tranferred)
  174. // or 0 for incompatable. This is usually the filesize, unless you are transcoding. An estimate is acceptable.
  175. __int64 AndroidDevice::getTrackSizeOnDevice(const itemRecordW *track)
  176. {
  177. if (transcoder)
  178. {
  179. if (transcoder->ShouldTranscode(track->filename))
  180. {
  181. int k = transcoder->CanTranscode(track->filename);
  182. if (k != -1 && k != 0) return k;
  183. return 0;
  184. }
  185. else return fileSize(track->filename);
  186. }
  187. else
  188. {
  189. if (!supportedFormat(track->filename,supportedFormats)) return 0;
  190. return fileSize(track->filename);
  191. }
  192. }
  193. // called from ml_pmp
  194. void AndroidDevice::deleteTrack( songid_t songid )
  195. {
  196. // physically remove from device. Be sure to remove it from all the playlists!
  197. AndroidSong *s = (AndroidSong *)songid;
  198. //errno == 2 is ENOENT
  199. if ( !_wunlink( s->filename ) || errno == 2 ) //will continue delete if file was deleted successfully or file path does not exist in the first place (errno==2)
  200. {
  201. for ( size_t i = 0; i < androidPlaylists.size(); i++ )
  202. {
  203. AndroidPlaylist *pl = androidPlaylists.at( i );
  204. pl->RemoveSong( s );
  205. }
  206. if ( purgeFolders[ 0 ] == '1' )
  207. {
  208. RemoveDirectory( s->filename );
  209. }
  210. delete s;
  211. }
  212. else
  213. {
  214. char titleStr[ 32 ] = { 0 };
  215. MessageBoxA( plugin.hwndLibraryParent, WASABI_API_LNGSTRING( IDS_TRACK_IN_USE ), WASABI_API_LNGSTRING_BUF( IDS_ERROR, titleStr, 32 ), 0 );
  216. }
  217. }
  218. // called from ml_pmp
  219. // optional. Will be called at a good time to save changes
  220. void AndroidDevice::commitChanges()
  221. {
  222. Nullsoft::Utility::AutoLock lock(dbcs);
  223. if (deviceTable && androidPlaylists.size() && androidPlaylists[0]->dirty)
  224. {
  225. NDE_Table_Sync(deviceTable);
  226. androidPlaylists[0]->dirty=false;
  227. }
  228. for (size_t i=1;i<androidPlaylists.size();i++)
  229. {
  230. AndroidPlaylist *pl = androidPlaylists[i];
  231. if (pl->dirty)
  232. {
  233. // Lets delete the current playlist file
  234. _wunlink(pl->filename);
  235. AndroidPlaylistSaver playlistSaver(pl->filename, L"autosaved", pl);
  236. playlistSaver.Save();
  237. pl->dirty = false;
  238. }
  239. }
  240. }
  241. // called from ml_pmp
  242. int AndroidDevice::getPlaylistCount()
  243. {
  244. // always at least 1. playlistnumber 0 is the Master Playlist containing all tracks.
  245. return (int)androidPlaylists.size();
  246. }
  247. // called from ml_pmp
  248. // PlaylistName(0) should return the name of the device.
  249. void AndroidDevice::getPlaylistName(int playlistnumber, wchar_t *buf, int len)
  250. {
  251. wchar_t * pathName = androidPlaylists.at(playlistnumber)->filename;
  252. if (playlistnumber != 0)
  253. {
  254. if (pathName[0])
  255. {
  256. wchar_t * playlistName = PathFindFileNameW(pathName);
  257. lstrcpyn(buf,playlistName,len);
  258. PathRemoveExtension(buf);
  259. }
  260. }
  261. else //playlist number = 0 -> this is the device
  262. {
  263. if (pathName[0])
  264. {
  265. //if we have a custom device name
  266. lstrcpyn(buf,pathName,len);
  267. }
  268. else
  269. {
  270. WASABI_API_LNGSTRINGW_BUF(IDS_ANDROID_DRIVE_X,buf,len);
  271. wchar_t * x = wcsrchr(buf,L'X');
  272. if (x) *x = drive;
  273. }
  274. }
  275. }
  276. // called from ml_pmp
  277. int AndroidDevice::getPlaylistLength(int playlistnumber)
  278. {
  279. return (int)androidPlaylists.at(playlistnumber)->size();
  280. }
  281. // called from ml_pmp
  282. songid_t AndroidDevice::getPlaylistTrack(int playlistnumber,int songnum)
  283. {
  284. // returns a songid
  285. return (songid_t) androidPlaylists.at(playlistnumber)->at(songnum);
  286. }
  287. // called from ml_pmp
  288. void AndroidDevice::setPlaylistName(int playlistnumber, const wchar_t *buf)
  289. {
  290. // with playlistnumber==0, set the name of the device.
  291. AndroidPlaylist * pl = androidPlaylists.at(playlistnumber);
  292. if (playlistnumber==0)
  293. {
  294. WritePrivateProfileString(L"pmp_android",L"customName",buf,iniFile);
  295. lstrcpyn(pl->filename,buf,sizeof(pl->filename)/sizeof(wchar_t));
  296. }
  297. else
  298. {
  299. wchar_t currentFilename[MAX_PATH] = {0};
  300. lstrcpynW(currentFilename, pl->filename, MAX_PATH);
  301. wchar_t * newFilename = const_cast<wchar_t *>(buf);
  302. if (wcslen(buf) >= MAX_PATH-1) newFilename[MAX_PATH-1]=0;
  303. while (newFilename && *newFilename && *newFilename == L'.') newFilename++;
  304. removebadchars(newFilename);
  305. StringCchPrintf(pl->filename,MAX_PATH,L"%s\\%s.m3u",pldir,newFilename);
  306. pl->filename[0]=drive;
  307. MoveFile(currentFilename, pl->filename);
  308. pl->dirty=true;
  309. }
  310. }
  311. // called from ml_pmp
  312. void AndroidDevice::playlistSwapItems(int playlistnumber, int posA, int posB)
  313. {
  314. // swap the songs at position posA and posB
  315. AndroidPlaylist * pl = (AndroidPlaylist*)androidPlaylists.at(playlistnumber);
  316. pl->swap(posA, posB);
  317. }
  318. // called from ml_pmp
  319. void AndroidDevice::sortPlaylist(int playlistnumber, int sortBy)
  320. {
  321. // TODO: implement
  322. }
  323. // called from ml_pmp
  324. void AndroidDevice::addTrackToPlaylist(int playlistnumber, songid_t songid)
  325. {
  326. // adds songid to the end of the playlist
  327. AndroidSong* song = (AndroidSong *) songid;
  328. AndroidPlaylist * pl = (AndroidPlaylist*)androidPlaylists.at(playlistnumber);
  329. pl->push_back(song);
  330. }
  331. // called from ml_pmp
  332. void AndroidDevice::removeTrackFromPlaylist(int playlistnumber, int songnum)
  333. {
  334. //where songnum is the position of the track in the playlist
  335. AndroidPlaylist * pl = (AndroidPlaylist*)androidPlaylists.at(playlistnumber);
  336. pl->eraseAt(songnum);
  337. }
  338. // called from ml_pmp
  339. void AndroidDevice::deletePlaylist(int playlistnumber)
  340. {
  341. AndroidPlaylist * pl = (AndroidPlaylist*)androidPlaylists.at(playlistnumber);
  342. _wunlink(pl->filename);
  343. androidPlaylists.erase(androidPlaylists.begin() + playlistnumber);
  344. delete pl;
  345. }
  346. // called from ml_pmp
  347. int AndroidDevice::newPlaylist(const wchar_t *name)
  348. {
  349. wchar_t plname[MAX_PATH] = {0};
  350. StringCchCopy(plname, MAX_PATH, name);
  351. removebadchars(plname);
  352. // create empty playlist, returns playlistnumber. -1 for failed.
  353. for (std::vector<AndroidPlaylist*>::iterator itr=androidPlaylists.begin();itr!=androidPlaylists.end();itr++)
  354. {
  355. AndroidPlaylist *p = *itr;
  356. if (!_wcsicmp(p->playlistName, plname))
  357. return -1;
  358. }
  359. wchar_t buff[MAX_PATH] = {0};
  360. StringCchPrintf(buff, MAX_PATH, L"%s\\%s.m3u",pldir,plname);
  361. AndroidPlaylist * pl = new AndroidPlaylist(*this, buff, false);
  362. pl->filename[0]=drive;
  363. //Lets save the playlist right away
  364. AndroidPlaylistSaver playlistSaver(pl->filename, L"autosaved", pl);
  365. playlistSaver.Save();
  366. androidPlaylists.push_back(pl);
  367. return (int)androidPlaylists.size()-1;
  368. }
  369. // called from ml_pmp
  370. void AndroidDevice::getTrackArtist(songid_t songid, wchar_t *buf, int len)
  371. {
  372. AndroidSong* song = (AndroidSong*)songid;
  373. if (!song) return;
  374. buf[0] = L'\0';
  375. StringCchCopy(buf, len, song->artist);
  376. }
  377. // called from ml_pmp
  378. void AndroidDevice::getTrackAlbum(songid_t songid, wchar_t *buf, int len)
  379. {
  380. AndroidSong* song = (AndroidSong*)songid;
  381. if (!song) return;
  382. buf[0] = L'\0';
  383. StringCchCopy(buf, len, song->album);
  384. }
  385. // called from ml_pmp
  386. void AndroidDevice::getTrackTitle(songid_t songid, wchar_t *buf, int len)
  387. {
  388. AndroidSong* song = (AndroidSong*)songid;
  389. if (!song) return;
  390. StringCchCopy(buf, len, song->title);
  391. }
  392. // called from ml_pmp
  393. int AndroidDevice::getTrackTrackNum(songid_t songid)
  394. {
  395. AndroidSong* song = (AndroidSong*)songid;
  396. if (!song) return 0;
  397. return song->track;
  398. }
  399. // called from ml_pmp
  400. int AndroidDevice::getTrackDiscNum(songid_t songid)
  401. {
  402. AndroidSong* song = (AndroidSong*)songid;
  403. if (!song) return 0;
  404. return song->discnum;
  405. }
  406. // called from ml_pmp
  407. void AndroidDevice::getTrackGenre(songid_t songid, wchar_t * buf, int len)
  408. {
  409. AndroidSong* song = (AndroidSong*)songid;
  410. if (!song) return;
  411. StringCchCopy(buf, len, song->genre);
  412. }
  413. // called from ml_pmp
  414. int AndroidDevice::getTrackYear(songid_t songid)
  415. {
  416. AndroidSong* song = (AndroidSong*)songid;
  417. if (!song) return 0;
  418. return song->year;
  419. }
  420. // called from ml_pmp
  421. __int64 AndroidDevice::getTrackSize(songid_t songid)
  422. {
  423. // in bytes
  424. AndroidSong* song = (AndroidSong*)songid;
  425. if (!song) return 0;
  426. return song->size;
  427. }
  428. // called from ml_pmp
  429. int AndroidDevice::getTrackLength(songid_t songid)
  430. {
  431. // in millisecs
  432. AndroidSong* song = (AndroidSong*)songid;
  433. if (!song) return 0;
  434. return song->length;
  435. }
  436. // called from ml_pmp
  437. int AndroidDevice::getTrackBitrate(songid_t songid)
  438. {
  439. // in kbps
  440. AndroidSong* song = (AndroidSong*)songid;
  441. if (!song) return 0;
  442. return song->bitrate;
  443. }
  444. // called from ml_pmp
  445. int AndroidDevice::getTrackPlayCount(songid_t songid)
  446. {
  447. AndroidSong* song = (AndroidSong*)songid;
  448. if (!song) return 0;
  449. return song->playcount;
  450. }
  451. // called from ml_pmp
  452. int AndroidDevice::getTrackRating(songid_t songid)
  453. {
  454. //0-5
  455. // TODO: implement
  456. return 0;
  457. }
  458. // called from ml_pmp
  459. __time64_t AndroidDevice::getTrackLastPlayed(songid_t songid)
  460. {
  461. // in unix time format
  462. // TODO: implement
  463. return 0;
  464. }
  465. // called from ml_pmp
  466. __time64_t AndroidDevice::getTrackLastUpdated(songid_t songid)
  467. {
  468. // in unix time format
  469. // TODO: implement
  470. return 0;
  471. }
  472. // called from ml_pmp
  473. void AndroidDevice::getTrackAlbumArtist(songid_t songid, wchar_t *buf, int len)
  474. {
  475. AndroidSong* song = (AndroidSong*)songid;
  476. if (!song) return;
  477. StringCchCopy(buf, len, song->albumartist);
  478. }
  479. // called from ml_pmp
  480. void AndroidDevice::getTrackPublisher(songid_t songid, wchar_t *buf, int len)
  481. {
  482. AndroidSong* song = (AndroidSong*)songid;
  483. if (!song) return;
  484. StringCchCopy(buf, len, song->publisher);
  485. }
  486. // called from ml_pmp
  487. void AndroidDevice::getTrackComposer(songid_t songid, wchar_t *buf, int len)
  488. {
  489. AndroidSong* song = (AndroidSong*)songid;
  490. if (!song) return;
  491. StringCchCopy(buf, len, song->composer);
  492. }
  493. // called from ml_pmp
  494. int AndroidDevice::getTrackType(songid_t songid)
  495. {
  496. // TODO: implement
  497. return 0;
  498. }
  499. // called from ml_pmp
  500. void AndroidDevice::getTrackExtraInfo(songid_t songid, const wchar_t *field, wchar_t *buf, int len)
  501. {
  502. // TODO: implement
  503. //optional
  504. }
  505. // called from ml_pmp
  506. // feel free to ignore any you don't support
  507. void AndroidDevice::setTrackArtist(songid_t songid, const wchar_t *value)
  508. {
  509. AndroidSong *song = (AndroidSong *) songid;
  510. if (song)
  511. {
  512. updateTrackField(song, DEVICEVIEW_COL_ARTIST, value, FIELD_STRING);
  513. StringCchCopy(song->artist, FIELD_LENGTH, value);
  514. AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"artist", value);
  515. }
  516. }
  517. // called from ml_pmp
  518. void AndroidDevice::setTrackAlbum(songid_t songid, const wchar_t *value)
  519. {
  520. AndroidSong *song = (AndroidSong *) songid;
  521. if (song)
  522. {
  523. updateTrackField(song, DEVICEVIEW_COL_ALBUM, value, FIELD_STRING);
  524. StringCchCopy(song->album, FIELD_LENGTH, value);
  525. AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"album", value);
  526. }
  527. }
  528. // called from ml_pmp
  529. void AndroidDevice::setTrackTitle(songid_t songid, const wchar_t *value)
  530. {
  531. AndroidSong *song = (AndroidSong *) songid;
  532. if (song)
  533. {
  534. updateTrackField(song, DEVICEVIEW_COL_TITLE, value, FIELD_STRING);
  535. StringCchCopy(song->title, FIELD_LENGTH, value);
  536. AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"title", value);
  537. }
  538. }
  539. // called from ml_pmp
  540. void AndroidDevice::setTrackTrackNum(songid_t songid, int value)
  541. {
  542. AndroidSong *song = (AndroidSong *) songid;
  543. if (song)
  544. {
  545. wchar_t track[FIELD_LENGTH] = {0};
  546. updateTrackField(song, DEVICEVIEW_COL_TRACK, &value, FIELD_INTEGER);
  547. song->track = value;
  548. StringCchPrintf(track, FIELD_LENGTH, L"%d", value);
  549. AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"track", track);
  550. }
  551. }
  552. // called from ml_pmp
  553. void AndroidDevice::setTrackDiscNum(songid_t songid, int value)
  554. {
  555. AndroidSong *song = (AndroidSong *) songid;
  556. if (song)
  557. {
  558. wchar_t discNum[FIELD_LENGTH] = {0};
  559. updateTrackField(song, DEVICEVIEW_COL_DISC_NUMBER, &value, FIELD_INTEGER);
  560. song->discnum = value;
  561. StringCchPrintf(discNum, FIELD_LENGTH, L"%d", value);
  562. AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"disc", discNum);
  563. }
  564. }
  565. // called from ml_pmp
  566. void AndroidDevice::setTrackGenre(songid_t songid, const wchar_t *value)
  567. {
  568. AndroidSong *song = (AndroidSong *) songid;
  569. if (song)
  570. {
  571. updateTrackField(song, DEVICEVIEW_COL_GENRE, value, FIELD_STRING);
  572. StringCchCopy(song->genre, FIELD_LENGTH, value);
  573. AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"genre", value);
  574. }
  575. }
  576. // called from ml_pmp
  577. void AndroidDevice::setTrackYear(songid_t songid, int year)
  578. {
  579. AndroidSong *song = (AndroidSong *) songid;
  580. if (song)
  581. {
  582. wchar_t yearStr[FIELD_LENGTH] = {0};
  583. updateTrackField(song, DEVICEVIEW_COL_YEAR, &year, FIELD_INTEGER);
  584. song->year = year;
  585. StringCchPrintf(yearStr, FIELD_LENGTH, L"%d", year);
  586. AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"year", yearStr);
  587. }
  588. }
  589. // called from ml_pmp
  590. void AndroidDevice::setTrackPlayCount(songid_t songid, int value)
  591. {
  592. AndroidSong *song = (AndroidSong *) songid;
  593. if (song)
  594. {
  595. wchar_t playCount[FIELD_LENGTH] = {0};
  596. updateTrackField(song, DEVICEVIEW_COL_PLAY_COUNT, &value, FIELD_INTEGER);
  597. song->playcount = value;
  598. StringCchPrintf(playCount, FIELD_LENGTH, L"%d", value);
  599. AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"playcount", playCount);
  600. }
  601. }
  602. // called from ml_pmp
  603. void AndroidDevice::setTrackRating(songid_t songid, int value)
  604. {
  605. AndroidSong *song = (AndroidSong *) songid;
  606. if (song)
  607. {
  608. wchar_t rating[FIELD_LENGTH] = {0};
  609. updateTrackField(song, DEVICEVIEW_COL_PLAY_COUNT, &value, FIELD_INTEGER);
  610. song->playcount = value;
  611. StringCchPrintf(rating, FIELD_LENGTH, L"%d", value);
  612. AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"rating", rating);
  613. }
  614. }
  615. // called from ml_pmp
  616. void AndroidDevice::setTrackLastPlayed(songid_t songid, __time64_t value)
  617. {
  618. // TODO: implement
  619. } // in unix time format
  620. // called from ml_pmp
  621. void AndroidDevice::setTrackLastUpdated(songid_t songid, __time64_t value)
  622. {
  623. // TODO: implement
  624. } // in unix time format
  625. // called from ml_pmp
  626. void AndroidDevice::setTrackAlbumArtist(songid_t songid, const wchar_t *value)
  627. {
  628. AndroidSong *song = (AndroidSong *) songid;
  629. if (song)
  630. {
  631. updateTrackField(song, DEVICEVIEW_COL_ALBUM_ARTIST, value, FIELD_STRING);
  632. StringCchCopy(song->albumartist, FIELD_LENGTH, value);
  633. AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"albumartist", value);
  634. }
  635. }
  636. // called from ml_pmp
  637. void AndroidDevice::setTrackPublisher(songid_t songid, const wchar_t *value)
  638. {
  639. AndroidSong *song = (AndroidSong *) songid;
  640. if (song)
  641. {
  642. updateTrackField(song, DEVICEVIEW_COL_PUBLISHER, value, FIELD_STRING);
  643. StringCchCopy(song->publisher, FIELD_LENGTH, value);
  644. AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"publisher", value);
  645. }
  646. }
  647. // called from ml_pmp
  648. void AndroidDevice::setTrackComposer(songid_t songid, const wchar_t *value)
  649. {
  650. AndroidSong *song = (AndroidSong *) songid;
  651. if (song)
  652. {
  653. updateTrackField(song, DEVICEVIEW_COL_COMPOSER, value, FIELD_STRING);
  654. StringCchCopy(song->composer, FIELD_LENGTH, value);
  655. AGAVE_API_METADATA->SetExtendedFileInfo(song->filename, L"composer", value);
  656. }
  657. }
  658. // called from ml_pmp
  659. void AndroidDevice::setTrackExtraInfo(songid_t songid, const wchar_t *field, const wchar_t *value)
  660. {
  661. // TODO: implement
  662. } //optional
  663. // called from ml_pmp
  664. bool AndroidDevice::playTracks(songid_t * songidList, int listLength, int startPlaybackAt, bool enqueue)
  665. {
  666. // return false if unsupported
  667. if (!enqueue) //clear playlist
  668. {
  669. SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_DELETE);
  670. }
  671. for (int i=0; i<listLength; i++)
  672. {
  673. AndroidSong *curSong = (AndroidSong*)songidList[i];
  674. if (curSong)
  675. {
  676. enqueueFileWithMetaStructW s={0};
  677. s.filename = _wcsdup(curSong->filename);
  678. s.title = _wcsdup( curSong->title );
  679. s.ext = NULL;
  680. s.length = curSong->length/1000;
  681. SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&s, IPC_PLAYFILEW);
  682. }
  683. else
  684. {
  685. char titleStr[32] = {0};
  686. MessageBoxA(plugin.hwndWinampParent,WASABI_API_LNGSTRING(IDS_CANNOT_OPEN_FILE),
  687. WASABI_API_LNGSTRING_BUF(IDS_ERROR,titleStr,32),0);
  688. }
  689. }
  690. if (!enqueue)
  691. {
  692. //play item startPlaybackAt
  693. SendMessage(plugin.hwndWinampParent,WM_WA_IPC,startPlaybackAt,IPC_SETPLAYLISTPOS);
  694. SendMessage(plugin.hwndWinampParent,WM_COMMAND,40047,0); //stop
  695. SendMessage(plugin.hwndWinampParent,WM_COMMAND,40045,0); //play
  696. }
  697. return true;
  698. }
  699. static const intptr_t encoder_blacklist[] =
  700. {
  701. mmioFOURCC('W','M','A',' '),
  702. mmioFOURCC('A','A','C','H'),
  703. mmioFOURCC('A','A','C','P'),
  704. mmioFOURCC('A','A','C','r'),
  705. mmioFOURCC('F','L','A','C'),
  706. mmioFOURCC('M','P','2',' '),
  707. mmioFOURCC('A','D','T','S'),
  708. };
  709. // called from ml_pmp
  710. intptr_t AndroidDevice::extraActions(intptr_t param1, intptr_t param2, intptr_t param3,intptr_t param4)
  711. {
  712. switch (param1)
  713. {
  714. case DEVICE_SET_ICON:
  715. {
  716. MLTREEIMAGE * i = (MLTREEIMAGE*)param2;
  717. i->hinst = plugin.hDllInstance;
  718. i->resourceId = IDR_ANDROID_ICON;
  719. }
  720. break;
  721. case DEVICE_SUPPORTED_METADATA:
  722. {
  723. intptr_t supported = SUPPORTS_ARTIST | SUPPORTS_ALBUM | SUPPORTS_TITLE | SUPPORTS_TRACKNUM | SUPPORTS_DISCNUM | SUPPORTS_GENRE |
  724. SUPPORTS_YEAR | SUPPORTS_SIZE | SUPPORTS_LENGTH | SUPPORTS_BITRATE | SUPPORTS_LASTUPDATED | SUPPORTS_ALBUMARTIST |
  725. SUPPORTS_COMPOSER | SUPPORTS_PUBLISHER | SUPPORTS_ALBUMART;
  726. return supported;
  727. }
  728. break;
  729. case DEVICE_CAN_RENAME_DEVICE:
  730. return 1;
  731. case DEVICE_GET_INI_FILE:
  732. StringCchCopy((wchar_t*)param2, MAX_PATH, iniFile);
  733. break;
  734. case DEVICE_GET_PREFS_DIALOG:
  735. if (param3 == 0)
  736. {
  737. pref_tab * p = (pref_tab *)param2;
  738. p->hinst = WASABI_API_LNG_HINST;
  739. p->dlg_proc = prefs_dialogProc;
  740. p->res_id = IDD_CONFIG;
  741. WASABI_API_LNGSTRINGW_BUF(IDS_ADVANCED,p->title,100);
  742. }
  743. break;
  744. case DEVICE_DONE_SETTING:
  745. {
  746. AndroidSong * song = (AndroidSong *) param2;
  747. AGAVE_API_METADATA->WriteExtendedFileInfo(song->filename);
  748. return true;
  749. }
  750. break;
  751. case DEVICE_VETO_ENCODER:
  752. {
  753. for (size_t i=0;i<sizeof(encoder_blacklist)/sizeof(*encoder_blacklist);i++)
  754. {
  755. // TODO: check device info XML for aacPlus support
  756. if (param2 == encoder_blacklist[i])
  757. return 1;
  758. }
  759. }
  760. return 0;
  761. case DEVICE_GET_ICON:
  762. {
  763. if (param2 <= 16 && param3 <= 16)
  764. {
  765. // TODO: get the name of the DLL at load time
  766. StringCchPrintfW((wchar_t *)param4, 260, L"res://%s/PNG/#%u", L"pmp_android.dll", IDR_ANDROID_ICON);
  767. }
  768. else
  769. {
  770. // TODO: get the name of the DLL at load time
  771. StringCchPrintfW((wchar_t *)param4, 260, L"res://%s/PNG/#%u", L"pmp_android.dll", IDB_ANDROID_160);
  772. }
  773. }
  774. break;
  775. }
  776. return false;
  777. }
  778. // called from ml_pmp
  779. bool AndroidDevice::copyToHardDriveSupported()
  780. {
  781. return true;
  782. }
  783. // called from ml_pmp
  784. __int64 AndroidDevice::songSizeOnHardDrive(songid_t song)
  785. {
  786. // how big a song will be when copied back. Return -1 for not supported.
  787. // TODO: implement
  788. return 0;
  789. }
  790. // called from ml_pmp
  791. int AndroidDevice::copyToHardDrive(songid_t song, // the song to copy
  792. 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).
  793. void * callbackContext, //pass this to the callback
  794. void (*callback)(void * callbackContext, wchar_t * status), // call this every so often so the GUI can be updated. Including when finished!
  795. int * killswitch // if this gets set to anything other than zero, the transfer has been cancelled by the user
  796. )
  797. {
  798. // -1 for failed/not supported. 0 for success.
  799. AndroidSong* track = (AndroidSong*)song;
  800. wchar_t * ext = PathFindExtensionW(track->filename);
  801. if (ext && (lstrlen(ext)<10)) StringCchCat(path,MAX_PATH, ext); // append correct extention
  802. return CopyFile(track->filename,path,callbackContext, callback, killswitch);
  803. }
  804. // called from ml_pmp
  805. // art functions
  806. void AndroidDevice::setArt(songid_t songid, void *buf, int w, int h)
  807. {
  808. //buf is in format ARGB32*
  809. // TODO: implement
  810. }
  811. // called from ml_pmp
  812. pmpart_t AndroidDevice::getArt(songid_t songid)
  813. {
  814. AndroidSong *song = (AndroidSong *)songid;
  815. ARGB32 *bits;
  816. int w, h;
  817. if (AGAVE_API_ALBUMART && AGAVE_API_ALBUMART->GetAlbumArt(song->filename, L"cover", &w, &h, &bits) == ALBUMART_SUCCESS && bits)
  818. {
  819. return (pmpart_t) new AndroidArt(bits, w, h);
  820. }
  821. return 0;
  822. }
  823. // called from ml_pmp
  824. void AndroidDevice::releaseArt(pmpart_t art)
  825. {
  826. AndroidArt *image = (AndroidArt *)art;
  827. delete image;
  828. }
  829. // called from ml_pmp
  830. int AndroidDevice::drawArt(pmpart_t art, HDC dc, int x, int y, int w, int h)
  831. {
  832. AndroidArt *image = (AndroidArt *)art;
  833. if (image)
  834. {
  835. HQSkinBitmap temp(image->bits, image->w, image->h); // wrap into a SkinBitmap (no copying involved)
  836. DCCanvas canvas(dc);
  837. temp.stretch(&canvas,x,y,w,h);
  838. return 1;
  839. }
  840. return 0;
  841. }
  842. // called from ml_pmp
  843. void AndroidDevice::getArtNaturalSize(pmpart_t art, int *w, int *h)
  844. {
  845. AndroidArt *image = (AndroidArt *)art;
  846. if (image)
  847. {
  848. *w = image->w;
  849. *h = image->h;
  850. }
  851. }
  852. // called from ml_pmp
  853. void AndroidDevice::setArtNaturalSize(pmpart_t art, int w, int h)
  854. {
  855. // TODO: implement
  856. //DebugBreak();
  857. }
  858. // called from ml_pmp
  859. void AndroidDevice::getArtData(pmpart_t art, void* data)
  860. {
  861. AndroidArt *image = (AndroidArt *)art;
  862. if (image)
  863. memcpy(data, image->bits, image->w*image->h*sizeof(ARGB32));
  864. // data ARGB32* is at natural size
  865. }
  866. // called from ml_pmp
  867. bool AndroidDevice::artIsEqual(pmpart_t a, pmpart_t b)
  868. {
  869. if (a == b)
  870. return true;
  871. // TODO: implement
  872. return false;
  873. }
  874. //////////////////////////////////////////////////////////////////////////////////////////
  875. // Initialize class statics
  876. nde_database_t AndroidDevice::discDB = 0;
  877. // The getter that returns the master playlist
  878. // the playlist vector always carries a master playlist
  879. AndroidPlaylist* AndroidDevice::getMasterPlaylist()
  880. {
  881. for (std::vector<AndroidPlaylist*>::const_iterator e = androidPlaylists.begin(); e != androidPlaylists.end(); e++)
  882. {
  883. AndroidPlaylist* playlist = (*e);
  884. if (playlist->isMaster()) return playlist;
  885. }
  886. return NULL;
  887. }
  888. // constructor
  889. AndroidDevice::AndroidDevice(wchar_t drive, pmpDeviceLoading * load): transcoder(NULL)
  890. {
  891. deviceTable = 0;
  892. StringCchPrintf(ndeDataFile, 100, L"%c:\\Winamp\\winamp_metadata.dat", drive);
  893. StringCchPrintf(ndeIndexFile, 100, L"%c:\\Winamp\\winamp_metadata.idx", drive);
  894. load->dev = this;
  895. load->UpdateCaption = NULL;
  896. //pass load to ml_pmp, ml updates load->UpdateCaption and context
  897. SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)load,PMP_IPC_DEVICELOADING);
  898. if (load->UpdateCaption)
  899. {
  900. wchar_t buf[100] = L"";
  901. WASABI_API_LNGSTRINGW_BUF(IDS_LOADING_DRIVE_X,buf,100);
  902. wchar_t * x = wcsrchr(buf,L'X');
  903. if (x) *x = drive;
  904. load->UpdateCaption(buf,load->context);
  905. }
  906. // load settings
  907. StringCchCopy(iniFile, MAX_PATH, L"x:\\Winamp\\");
  908. iniFile[0]=drive;
  909. CreateDirectory(iniFile, NULL);
  910. StringCchCat(iniFile,MAX_PATH,L"pmp_android.ini");
  911. wchar_t customName[FIELD_LENGTH] = {0};
  912. GetPrivateProfileString(L"pmp_android",L"pldir",L"X:\\Music",pldir,sizeof(pldir)/sizeof(wchar_t),iniFile);
  913. GetPrivateProfileString(L"pmp_android",L"songFormat",L"X:\\Music\\<Artist>\\<Album>\\## - <Title>",songFormat,sizeof(songFormat)/sizeof(wchar_t),iniFile);
  914. GetPrivateProfileString(L"pmp_android",L"supportedFormats",L"mp3;wav;m4a;mp4;m4v;avi;3gp;mid;ogg",supportedFormats,sizeof(supportedFormats)/sizeof(wchar_t),iniFile);
  915. GetPrivateProfileString(L"pmp_android",L"purgeFolders",L"1",purgeFolders,sizeof(purgeFolders)/sizeof(wchar_t),iniFile);
  916. GetPrivateProfileString(L"pmp_android",L"customName",L"",customName,sizeof(customName)/sizeof(wchar_t),iniFile);
  917. pl_write_mode = GetPrivateProfileInt(L"pmp_android",L"pl_write_mode",0,iniFile);
  918. pldir[0] = drive;
  919. songFormat[0] = drive;
  920. transferQueueLength = 0;
  921. this->drive = drive;
  922. AndroidPlaylist * mpl = new AndroidPlaylist(*this, customName, true);
  923. androidPlaylists.push_back(mpl);
  924. wchar_t * pl = _wcsdup(pldir);
  925. pl[0] = drive;
  926. RecursiveCreateDirectory(pl);
  927. wchar_t root[3] = L"X:";
  928. root[0] = drive;
  929. openDeviceTable();
  930. fileProbe(root);
  931. // sort out and read playlists....
  932. if (WASABI_API_PLAYLISTMNGR != NULL && WASABI_API_PLAYLISTMNGR != (api_playlistmanager *)1)
  933. {
  934. for (std::vector<AndroidPlaylist*>::const_iterator e = androidPlaylists.begin(); e != androidPlaylists.end(); e++)
  935. {
  936. AndroidPlaylist* playlist = (*e);
  937. if (playlist->isMaster() == false)
  938. {
  939. WASABI_API_PLAYLISTMNGR->Load(playlist->getFilename(), playlist);
  940. }
  941. }
  942. }
  943. tag();
  944. devices.push_back(this);
  945. extern HWND config;
  946. if (config) PostMessage(config,WM_USER,0,0);
  947. SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(intptr_t)this,PMP_IPC_DEVICECONNECTED);
  948. setupTranscoder();
  949. }
  950. AndroidDevice::AndroidDevice()
  951. {
  952. }
  953. AndroidDevice::~AndroidDevice()
  954. {
  955. closeDeviceTable();
  956. if (transcoder)
  957. SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(WPARAM)transcoder,PMP_IPC_RELEASE_TRANSCODER);
  958. }
  959. //read files from device's folder 'indir'
  960. void AndroidDevice::fileProbe(wchar_t * indir)
  961. {
  962. wchar_t dir[MAX_PATH] = {0};
  963. WIN32_FIND_DATA FindFileData = {0};
  964. StringCchPrintf(dir,MAX_PATH,L"%s\\*",indir);
  965. HANDLE hFind = FindFirstFile(dir, &FindFileData);
  966. if (hFind == INVALID_HANDLE_VALUE) return;
  967. do
  968. {
  969. if (wcscmp(FindFileData.cFileName,L".") && wcscmp(FindFileData.cFileName,L".."))
  970. {
  971. wchar_t fullfile[MAX_PATH] = {0};
  972. StringCchPrintf(fullfile,MAX_PATH,L"%s\\%s",indir,FindFileData.cFileName);
  973. if (FindFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) //file is directory
  974. {
  975. fileProbe(fullfile); //call until we have found a file
  976. }
  977. else // found a file!
  978. {
  979. wchar_t * ext = wcsrchr(FindFileData.cFileName,'.');
  980. if (!ext) continue; //no files with extensions in the directory
  981. ext++;
  982. int isM3UPlaylistFormat = CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, ext, -1, L"m3u", -1)-2;
  983. if (isM3UPlaylistFormat == 0) // its a playlist
  984. {
  985. AndroidPlaylist *playlist = new AndroidPlaylist(*this, fullfile, false);
  986. androidPlaylists.push_back(playlist);
  987. continue;
  988. } //its a file
  989. if (supportedFormat(fullfile, supportedFormats)) //check extension
  990. {
  991. AndroidSong *s = new AndroidSong();
  992. lstrcpynW(s->filename, fullfile, MAX_PATH);
  993. this->getMasterPlaylist()->push_back(s); //add track to alltrack list (playlist 0)
  994. }
  995. }
  996. }
  997. }
  998. while (FindNextFile(hFind, &FindFileData) != 0);
  999. FindClose(hFind);
  1000. }
  1001. int AndroidDevice::getFileInfoW(const wchar_t *filename, const wchar_t *metadata, wchar_t *dest, size_t len)
  1002. {
  1003. dest[0]=0;
  1004. return AGAVE_API_METADATA->GetExtendedFileInfo(filename, metadata, dest, len);
  1005. }
  1006. // read all metadata from the metadata wasabi service
  1007. void AndroidDevice::fillMetaData(AndroidSong *t)
  1008. {
  1009. if (!t->filled)
  1010. {
  1011. wchar_t tmp[1024] = {0};
  1012. if (getFileInfoW(t->filename,L"artist",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
  1013. {
  1014. StringCchCopyW(t->artist, FIELD_LENGTH, tmp);
  1015. t->filled = true;
  1016. }
  1017. if (getFileInfoW(t->filename,L"title",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
  1018. {
  1019. StringCchCopyW(t->title, FIELD_LENGTH, tmp);
  1020. t->filled = true;
  1021. }
  1022. if (getFileInfoW(t->filename,L"album",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
  1023. {
  1024. StringCchCopyW(t->album, FIELD_LENGTH, tmp);
  1025. t->filled = true;
  1026. }
  1027. if (getFileInfoW(t->filename,L"composer",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
  1028. {
  1029. StringCchCopyW(t->composer, FIELD_LENGTH, tmp);
  1030. t->filled = true;
  1031. }
  1032. if (getFileInfoW(t->filename,L"publisher",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
  1033. {
  1034. StringCchCopyW(t->publisher, FIELD_LENGTH, tmp);
  1035. t->filled = true;
  1036. }
  1037. if (getFileInfoW(t->filename,L"albumartist",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
  1038. {
  1039. StringCchCopyW(t->albumartist, FIELD_LENGTH, tmp);
  1040. t->filled = true;
  1041. }
  1042. if (getFileInfoW(t->filename, L"length",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
  1043. {
  1044. t->length = _wtoi(tmp);
  1045. t->filled = true;
  1046. }
  1047. if (getFileInfoW(t->filename, L"track",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
  1048. {
  1049. t->track = _wtoi(tmp);
  1050. t->filled = true;
  1051. }
  1052. if (getFileInfoW(t->filename, L"disc",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
  1053. {
  1054. t->discnum = _wtoi(tmp);
  1055. t->filled = true;
  1056. }
  1057. if (getFileInfoW(t->filename, L"genre",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
  1058. {
  1059. StringCchCopyW(t->genre, FIELD_LENGTH, tmp);
  1060. t->filled = true;
  1061. }
  1062. if (getFileInfoW(t->filename, L"year",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
  1063. {
  1064. if (!wcsstr(tmp,L"__") && !wcsstr(tmp,L"/") && !wcsstr(tmp,L"\\") && !wcsstr(tmp,L"."))
  1065. {
  1066. wchar_t *p = tmp;
  1067. while (p && *p)
  1068. {
  1069. if (*p == L'_') *p=L'0';
  1070. p++;
  1071. }
  1072. t->year = _wtoi(tmp);
  1073. t->filled = true;
  1074. }
  1075. }
  1076. if (getFileInfoW(t->filename, L"bitrate",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
  1077. {
  1078. t->bitrate = _wtoi(tmp);
  1079. t->filled = true;
  1080. }
  1081. if (getFileInfoW(t->filename, L"size",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
  1082. {
  1083. t->size = _wtoi(tmp);
  1084. t->filled = true;
  1085. }
  1086. else
  1087. {
  1088. t->size = fileSize(t->filename);
  1089. t->filled = true;
  1090. }
  1091. if (getFileInfoW(t->filename, L"playcount",tmp,sizeof(tmp)/sizeof(wchar_t)) && tmp[0])
  1092. {
  1093. t->playcount = _wtoi(tmp);
  1094. t->filled = true;
  1095. }
  1096. }
  1097. }
  1098. int AndroidDevice::openDeviceDatabase()
  1099. {
  1100. Nullsoft::Utility::AutoLock lock(dbcs);
  1101. if (!discDB)
  1102. {
  1103. discDB = NDE_CreateDatabase(plugin.hDllInstance);
  1104. }
  1105. return NDE_ANDROID_SUCCESS;
  1106. }
  1107. void AndroidDevice::createDeviceFields()
  1108. {
  1109. // create defaults
  1110. NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_FILENAME, L"filename", FIELD_FILENAME);
  1111. NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_ARTIST, L"artist", FIELD_STRING);
  1112. NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_ALBUM, L"album", FIELD_STRING);
  1113. NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_TITLE, L"title", FIELD_STRING);
  1114. NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_GENRE, L"genre", FIELD_STRING);
  1115. NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_ALBUM_ARTIST, L"albumartist", FIELD_STRING);
  1116. NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_PUBLISHER, L"publisher", FIELD_STRING);
  1117. NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_COMPOSER, L"composer", FIELD_STRING);
  1118. NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_YEAR, L"year", FIELD_INTEGER);
  1119. NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_TRACK, L"track", FIELD_INTEGER);
  1120. NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_BITRATE, L"bitrate", FIELD_INTEGER);
  1121. NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_DISC_NUMBER, L"discnumber", FIELD_INTEGER);
  1122. NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_LENGTH, L"length", FIELD_INTEGER);
  1123. NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_SIZE, L"size", FIELD_INTEGER);
  1124. NDE_Table_NewColumnW(deviceTable, DEVICEVIEW_COL_PLAY_COUNT, L"playcount", FIELD_INTEGER);
  1125. NDE_Table_PostColumns(deviceTable);
  1126. NDE_Table_AddIndexByIDW(deviceTable, 0, L"filename");
  1127. }
  1128. int AndroidDevice::openDeviceTable()
  1129. {
  1130. Nullsoft::Utility::AutoLock lock(dbcs);
  1131. int ret = openDeviceDatabase();
  1132. if (ret != NDE_ANDROID_SUCCESS)
  1133. return ret;
  1134. if (!deviceTable)
  1135. {
  1136. deviceTable = NDE_Database_OpenTable(discDB, ndeDataFile, ndeIndexFile,NDE_OPEN_ALWAYS,NDE_CACHE);
  1137. if (deviceTable)
  1138. {
  1139. createDeviceFields();
  1140. }
  1141. }
  1142. return deviceTable?NDE_ANDROID_SUCCESS:NDE_ANDROID_FAILURE;
  1143. }
  1144. /* static */
  1145. void AndroidDevice::CloseDatabase()
  1146. {
  1147. if (discDB)
  1148. {
  1149. NDE_DestroyDatabase(discDB);
  1150. discDB=0;
  1151. }
  1152. }
  1153. void AndroidDevice::closeDeviceTable()
  1154. {
  1155. if (deviceTable)
  1156. {
  1157. NDE_Table_Sync(deviceTable);
  1158. NDE_Database_CloseTable(discDB, deviceTable);
  1159. deviceTable=0;
  1160. }
  1161. }
  1162. static void db_setFieldInt(nde_scanner_t s, unsigned char id, int data)
  1163. {
  1164. nde_field_t f = NDE_Scanner_GetFieldByID(s, id);
  1165. if (!f) f = NDE_Scanner_NewFieldByID(s, id);
  1166. NDE_IntegerField_SetValue(f, data);
  1167. }
  1168. static void db_setFieldString(nde_scanner_t s, unsigned char id, const wchar_t *data)
  1169. {
  1170. nde_field_t f = NDE_Scanner_GetFieldByID(s, id);
  1171. if (!f) f = NDE_Scanner_NewFieldByID(s, id);
  1172. NDE_StringField_SetString(f, data);
  1173. }
  1174. static void db_removeField(nde_scanner_t s, unsigned char id)
  1175. {
  1176. nde_field_t f = NDE_Scanner_GetFieldByID(s, id);
  1177. if (f)
  1178. {
  1179. NDE_Scanner_DeleteField(s, f);
  1180. }
  1181. }
  1182. static int db_getFieldInt(nde_scanner_t s, unsigned char id, int defaultVal)
  1183. {
  1184. nde_field_t f = NDE_Scanner_GetFieldByID(s, id);
  1185. if (f)
  1186. {
  1187. return NDE_IntegerField_GetValue(f);
  1188. }
  1189. else
  1190. {
  1191. return defaultVal;
  1192. }
  1193. }
  1194. static wchar_t* db_getFieldString(nde_scanner_t s, unsigned char id)
  1195. {
  1196. nde_field_t f = NDE_Scanner_GetFieldByID(s, id);
  1197. if (f)
  1198. {
  1199. return NDE_StringField_GetString(f);
  1200. }
  1201. else
  1202. {
  1203. return 0;
  1204. }
  1205. }
  1206. void AndroidDevice::refreshNDECache(void)
  1207. {
  1208. tag();
  1209. }
  1210. AndroidSong *AndroidDevice::findSongInMasterPlaylist(const wchar_t *songfn)
  1211. {
  1212. AndroidPlaylist* mpl = this->getMasterPlaylist();
  1213. return mpl->FindSong(songfn);
  1214. }
  1215. void AndroidDevice::tag(void)
  1216. {
  1217. /**
  1218. loop thru the newly probed disk
  1219. check for updates on each of the songs
  1220. if there is an update or if metadata does not exist for the file, re-read the metadata
  1221. if there is no update and the song is found in the master playlist, just read from the db
  1222. */
  1223. AndroidPlaylist *mpl = this->getMasterPlaylist();
  1224. int top = (int)mpl->size();
  1225. //first load in all songs data from ID3 - this is what we were trying to avoid
  1226. for (int i = 0; i < top; i++)
  1227. {
  1228. AndroidSong *t = (AndroidSong *)mpl->at(i);
  1229. // now check if this song has changed
  1230. // check if the nde cache exists in the first place
  1231. if (songChanged(t) || !readRecordFromDB(t))
  1232. {
  1233. this->fillMetaData(t);
  1234. // now since we've refreshed the metadata write to NDE
  1235. this->writeRecordToDB(t);
  1236. }
  1237. }
  1238. }
  1239. // check change in filetimes for the song
  1240. bool AndroidDevice::songChanged(AndroidSong* song)
  1241. {
  1242. if (!song) return true;
  1243. if (!PathFileExists(ndeDataFile)) return true;
  1244. //For fLastAccess/LastWrite information, use GetFileAttributesEx
  1245. WIN32_FILE_ATTRIBUTE_DATA cacheFileInfo, tempInfo;
  1246. GetFileAttributesExW(ndeDataFile, GetFileExInfoStandard, (LPVOID)&cacheFileInfo);
  1247. if (song->filename)
  1248. {
  1249. GetFileAttributesExW(song->filename, GetFileExInfoStandard, (LPVOID)&tempInfo);
  1250. }
  1251. else
  1252. {
  1253. return true;
  1254. }
  1255. //cachetime - song time
  1256. if (CompareFileTime(&cacheFileInfo.ftLastWriteTime, &tempInfo.ftLastWriteTime) < 0)
  1257. {
  1258. return true;
  1259. }
  1260. return false;
  1261. }
  1262. // read metadata for a specific song from the NDE cache
  1263. bool AndroidDevice::readRecordFromDB(AndroidSong* song)
  1264. {
  1265. if (!song) return false;
  1266. Nullsoft::Utility::AutoLock lock(dbcs);
  1267. openDeviceTable();
  1268. nde_scanner_t scanner = NDE_Table_CreateScanner(deviceTable);
  1269. if (NDE_Scanner_LocateFilename(scanner, DEVICEVIEW_COL_FILENAME, FIRST_RECORD, song->filename))
  1270. {
  1271. nde_field_t artist = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_ARTIST);
  1272. wchar_t* artistString = NDE_StringField_GetString(artist);
  1273. lstrcpyn(song->artist, artistString, FIELD_LENGTH);
  1274. nde_field_t album = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_ALBUM);
  1275. wchar_t* albumString = NDE_StringField_GetString(album);
  1276. lstrcpyn(song->album, albumString, FIELD_LENGTH);
  1277. nde_field_t albumArtist = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_ALBUM_ARTIST);
  1278. wchar_t* albumArtistString = NDE_StringField_GetString(albumArtist);
  1279. lstrcpyn(song->albumartist, albumArtistString, FIELD_LENGTH);
  1280. nde_field_t publisher = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_PUBLISHER);
  1281. wchar_t* publisherString = NDE_StringField_GetString(publisher);
  1282. lstrcpyn(song->publisher, publisherString, FIELD_LENGTH);
  1283. nde_field_t composer = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_COMPOSER);
  1284. wchar_t* composerString = NDE_StringField_GetString(composer);
  1285. lstrcpyn(song->composer, composerString, FIELD_LENGTH);
  1286. nde_field_t title = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_TITLE);
  1287. wchar_t* titleString = NDE_StringField_GetString(title);
  1288. lstrcpyn(song->title, titleString, FIELD_LENGTH);
  1289. nde_field_t genre = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_GENRE);
  1290. wchar_t* genreString = NDE_StringField_GetString(genre);
  1291. lstrcpyn(song->genre, genreString, FIELD_LENGTH);
  1292. nde_field_t track = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_TRACK);
  1293. song->track = NDE_IntegerField_GetValue(track);
  1294. nde_field_t year = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_YEAR);
  1295. song->year = NDE_IntegerField_GetValue(year);
  1296. nde_field_t discNumber = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_DISC_NUMBER);
  1297. song->discnum = NDE_IntegerField_GetValue(discNumber);
  1298. nde_field_t length = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_LENGTH);
  1299. song->length = NDE_IntegerField_GetValue(length);
  1300. nde_field_t bitrate = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_BITRATE);
  1301. int bitrateInt= NDE_IntegerField_GetValue(bitrate);
  1302. song->bitrate = bitrateInt;
  1303. nde_field_t size = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_SIZE);
  1304. int sizeInt= NDE_IntegerField_GetValue(size);
  1305. song->size = sizeInt;
  1306. nde_field_t playcount = NDE_Scanner_GetFieldByID(scanner, DEVICEVIEW_COL_PLAY_COUNT);
  1307. song->playcount = NDE_IntegerField_GetValue(playcount);
  1308. }
  1309. else
  1310. {
  1311. return false;
  1312. }
  1313. NDE_Table_DestroyScanner(deviceTable, scanner);
  1314. //closeDeviceTable();
  1315. return true;
  1316. }
  1317. // write a single record to the nde database
  1318. void AndroidDevice::writeRecordToDB(AndroidSong* songToPrint)
  1319. {
  1320. Nullsoft::Utility::AutoLock lock(dbcs);
  1321. openDeviceTable();
  1322. nde_scanner_t s = NDE_Table_CreateScanner(deviceTable);
  1323. if (! NDE_Scanner_LocateFilename(s, DEVICEVIEW_COL_FILENAME, FIRST_RECORD, songToPrint->filename))
  1324. {
  1325. NDE_Scanner_New(s);
  1326. }
  1327. if (songToPrint->filename)
  1328. {
  1329. db_setFieldString(s, DEVICEVIEW_COL_FILENAME, songToPrint->filename);
  1330. }
  1331. if (songToPrint->artist)
  1332. {
  1333. db_setFieldString(s, DEVICEVIEW_COL_ARTIST, songToPrint->artist);
  1334. }
  1335. if (songToPrint->albumartist)
  1336. {
  1337. db_setFieldString(s, DEVICEVIEW_COL_ALBUM_ARTIST, songToPrint->albumartist);
  1338. }
  1339. if (songToPrint->publisher)
  1340. {
  1341. db_setFieldString(s, DEVICEVIEW_COL_PUBLISHER, songToPrint->publisher);
  1342. }
  1343. if (songToPrint->composer)
  1344. {
  1345. db_setFieldString(s, DEVICEVIEW_COL_COMPOSER, songToPrint->composer);
  1346. }
  1347. if (songToPrint->album)
  1348. {
  1349. db_setFieldString(s, DEVICEVIEW_COL_ALBUM, songToPrint->album);
  1350. }
  1351. if (songToPrint->title)
  1352. {
  1353. db_setFieldString(s, DEVICEVIEW_COL_TITLE, songToPrint->title);
  1354. }
  1355. if (songToPrint->genre)
  1356. {
  1357. db_setFieldString(s, DEVICEVIEW_COL_GENRE, songToPrint->genre);
  1358. }
  1359. if (songToPrint->year)
  1360. {
  1361. db_setFieldInt(s, DEVICEVIEW_COL_YEAR, songToPrint->year);
  1362. }
  1363. if (songToPrint->track)
  1364. {
  1365. db_setFieldInt(s, DEVICEVIEW_COL_TRACK, songToPrint->track);
  1366. }
  1367. if (songToPrint->bitrate)
  1368. {
  1369. db_setFieldInt(s, DEVICEVIEW_COL_BITRATE, songToPrint->bitrate);
  1370. }
  1371. if (songToPrint->discnum)
  1372. {
  1373. db_setFieldInt(s, DEVICEVIEW_COL_DISC_NUMBER, songToPrint->discnum);
  1374. }
  1375. if (songToPrint->length)
  1376. {
  1377. db_setFieldInt(s, DEVICEVIEW_COL_LENGTH, songToPrint->length);
  1378. }
  1379. if (songToPrint->size)
  1380. {
  1381. db_setFieldInt(s, DEVICEVIEW_COL_SIZE, (int)songToPrint->size);
  1382. }
  1383. if (songToPrint->playcount)
  1384. {
  1385. db_setFieldInt(s, DEVICEVIEW_COL_PLAY_COUNT, songToPrint->playcount);
  1386. }
  1387. NDE_Scanner_Post(s);
  1388. NDE_Table_DestroyScanner(deviceTable, s);
  1389. // NDE_Table_Sync(deviceTable);
  1390. //closeDeviceTable();
  1391. }
  1392. void AndroidDevice::setupTranscoder()
  1393. {
  1394. if (transcoder) SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(WPARAM)transcoder,PMP_IPC_RELEASE_TRANSCODER);
  1395. transcoder = (Transcoder*)SendMessage(plugin.hwndPortablesParent,WM_PMP_IPC,(WPARAM)this,PMP_IPC_GET_TRANSCODER);
  1396. if (!transcoder) return;
  1397. wchar_t * p = supportedFormats;
  1398. while (p && *p)
  1399. {
  1400. wchar_t * np = wcschr(p,L';');
  1401. if (np) *np = 0;
  1402. transcoder->AddAcceptableFormat(p);
  1403. if (np)
  1404. {
  1405. *np = L';';
  1406. p=np+1;
  1407. }
  1408. else return;
  1409. }
  1410. }
  1411. BOOL CALLBACK browseEnumProc(HWND hwnd, LPARAM lParam)
  1412. {
  1413. wchar_t cl[32] = {0};
  1414. GetClassNameW(hwnd, cl, ARRAYSIZE(cl));
  1415. if (!lstrcmpiW(cl, WC_TREEVIEW))
  1416. {
  1417. PostMessage(hwnd, TVM_ENSUREVISIBLE, 0, (LPARAM)TreeView_GetSelection(hwnd));
  1418. return FALSE;
  1419. }
  1420. return TRUE;
  1421. }
  1422. wchar_t pldir[MAX_PATH] = {0};
  1423. int CALLBACK WINAPI BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
  1424. {
  1425. if (uMsg == BFFM_INITIALIZED)
  1426. {
  1427. SendMessageW(hwnd, BFFM_SETSELECTIONW, 1, (LPARAM)pldir);
  1428. // this is not nice but it fixes the selection not working correctly on all OSes
  1429. EnumChildWindows(hwnd, browseEnumProc, 0);
  1430. }
  1431. return 0;
  1432. }
  1433. static INT_PTR CALLBACK prefs_dialogProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
  1434. {
  1435. static AndroidDevice * dev;
  1436. switch (uMsg)
  1437. {
  1438. case WM_INITDIALOG:
  1439. {
  1440. prefsParam* p = (prefsParam*)lParam;
  1441. dev = (AndroidDevice*)p->dev;
  1442. p->config_tab_init(hwndDlg,p->parent);
  1443. SetDlgItemTextW(hwndDlg,IDC_NAMEFORMAT,dev->songFormat);
  1444. SetDlgItemTextW(hwndDlg,IDC_PLDIR,dev->pldir);
  1445. SetDlgItemTextW(hwndDlg,IDC_SUPPORTEDFORMATS,dev->supportedFormats);
  1446. if (dev->purgeFolders[0]=='1') CheckDlgButton(hwndDlg,IDC_PURGEFOLDERS,BST_CHECKED);
  1447. else CheckDlgButton(hwndDlg,IDC_PURGEFOLDERS,BST_UNCHECKED);
  1448. SendDlgItemMessageW(hwndDlg,IDC_PL_WRITE_COMBO,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_SLASH_AT_START));
  1449. SendDlgItemMessageW(hwndDlg,IDC_PL_WRITE_COMBO,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_DOT_AT_START));
  1450. SendDlgItemMessageW(hwndDlg,IDC_PL_WRITE_COMBO,CB_ADDSTRING,0,(LPARAM)WASABI_API_LNGSTRINGW(IDS_NO_SLASH_OR_DOT));
  1451. SendDlgItemMessage(hwndDlg,IDC_PL_WRITE_COMBO,CB_SETCURSEL,dev->pl_write_mode,0);
  1452. SetDlgItemTextW(hwndDlg,IDC_PL_WRITE_EG,WASABI_API_LNGSTRINGW(IDS_EG_SLASH+dev->pl_write_mode));
  1453. }
  1454. break;
  1455. case WM_COMMAND:
  1456. switch (LOWORD(wParam))
  1457. {
  1458. case IDC_NAMEFORMAT:
  1459. if (HIWORD(wParam)==EN_CHANGE)
  1460. {
  1461. GetDlgItemTextW(hwndDlg,IDC_NAMEFORMAT,dev->songFormat,sizeof(dev->songFormat)/sizeof(wchar_t));
  1462. WritePrivateProfileStringW(L"pmp_android",L"songFormat",dev->songFormat,dev->iniFile);
  1463. }
  1464. break;
  1465. case IDC_PLDIR:
  1466. if (HIWORD(wParam)==EN_CHANGE)
  1467. {
  1468. GetDlgItemTextW(hwndDlg,IDC_PLDIR,dev->pldir,sizeof(dev->pldir)/sizeof(wchar_t));
  1469. WritePrivateProfileStringW(L"pmp_android",L"pldir",dev->pldir,dev->iniFile);
  1470. }
  1471. break;
  1472. case IDC_SUPPORTEDFORMATS:
  1473. if (HIWORD(wParam)==EN_CHANGE)
  1474. {
  1475. GetDlgItemTextW(hwndDlg,IDC_SUPPORTEDFORMATS,dev->supportedFormats,sizeof(dev->supportedFormats)/sizeof(wchar_t));
  1476. WritePrivateProfileStringW(L"pmp_android",L"supportedFormats",dev->supportedFormats,dev->iniFile);
  1477. }
  1478. break;
  1479. case IDC_REFRESHCACHE:
  1480. {
  1481. char titleStr[32] = {0};
  1482. dev->refreshNDECache();
  1483. MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_CACHE_UPDATED),
  1484. WASABI_API_LNGSTRING_BUF(IDS_SUCCESS,titleStr,32),MB_OK);
  1485. break;
  1486. }
  1487. case IDC_PL_WRITE_COMBO:
  1488. {
  1489. dev->pl_write_mode = (int)SendMessage((HWND)lParam,CB_GETCURSEL,0,0);
  1490. SetDlgItemTextW(hwndDlg,IDC_PL_WRITE_EG,WASABI_API_LNGSTRINGW(IDS_EG_SLASH+dev->pl_write_mode));
  1491. wchar_t tmp[16] = {0};
  1492. StringCchPrintf(tmp, 16, L"%d", dev->pl_write_mode);
  1493. WritePrivateProfileStringW(L"pmp_android",L"pl_write_mode",tmp,dev->iniFile);
  1494. break;
  1495. }
  1496. case IDC_FILENAMEHELP:
  1497. {
  1498. char titleStr[64] = {0};
  1499. MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_FILENAME_FORMATTING_INFO),
  1500. WASABI_API_LNGSTRING_BUF(IDS_FILENAME_FORMAT_HELP,titleStr,64),MB_OK);
  1501. }
  1502. break;
  1503. case IDC_PLBROWSE:
  1504. {
  1505. wchar_t *tempWS = 0;
  1506. BROWSEINFO bi = {0};
  1507. LPMALLOC lpm = 0;
  1508. wchar_t bffFileName[MAX_PATH] = {0};
  1509. bi.hwndOwner = hwndDlg;
  1510. bi.pszDisplayName = bffFileName;
  1511. bi.lpszTitle = WASABI_API_LNGSTRINGW(IDS_SELECT_FOLDER_TO_LOAD_PLAYLISTS);
  1512. bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_EDITBOX | BIF_NEWDIALOGSTYLE;
  1513. bi.lpfn = BrowseCallbackProc;
  1514. lstrcpynW(pldir, dev->pldir, MAX_PATH);
  1515. LPITEMIDLIST iil = SHBrowseForFolder(&bi);
  1516. if (iil)
  1517. {
  1518. SHGetPathFromIDListW(iil,bffFileName);
  1519. SHGetMalloc(&lpm);
  1520. // path is now in bffFileName
  1521. }
  1522. tempWS = _wcsdup(bffFileName);
  1523. if (tempWS[0] == dev->drive)
  1524. {
  1525. lstrcpynW(dev->pldir, tempWS, MAX_PATH);
  1526. SetDlgItemText(hwndDlg,IDC_PLDIR,tempWS);
  1527. }
  1528. else
  1529. {
  1530. if (bffFileName[0] != 0) //dont print error if the user selected 'cancel'
  1531. {
  1532. char titleStr[32] = {0};
  1533. MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_ERR_SELECTED_PATH_NOT_ON_DEVICE),
  1534. WASABI_API_LNGSTRING_BUF(IDS_ERROR,titleStr,32), MB_OK);
  1535. }
  1536. }
  1537. free(tempWS);
  1538. }
  1539. break;
  1540. case IDC_FORMATSHELP:
  1541. {
  1542. char titleStr[64] = {0};
  1543. MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_SUPPORTED_FORMAT_INFO),
  1544. WASABI_API_LNGSTRING_BUF(IDS_SUPPORTED_FORMAT_HELP,titleStr,64),MB_OK);
  1545. }
  1546. break;
  1547. case IDC_PURGEFOLDERS:
  1548. {
  1549. if (IsDlgButtonChecked(hwndDlg,IDC_PURGEFOLDERS) == BST_CHECKED)
  1550. {
  1551. wcsncpy(dev->purgeFolders,L"1",2);
  1552. }
  1553. else
  1554. {
  1555. wcsncpy(dev->purgeFolders,L"0",2);
  1556. }
  1557. WritePrivateProfileStringW(L"pmp_android",L"purgeFolders",dev->purgeFolders,dev->iniFile);
  1558. }
  1559. break;
  1560. case IDC_RESCAN:
  1561. {
  1562. //update changes
  1563. SetFileAttributesW(dev->iniFile,FILE_ATTRIBUTE_HIDDEN);
  1564. wchar_t driveletter = dev->drive; //hold on to driveletter before it goes away
  1565. //disconnect
  1566. dev->Close();
  1567. //connect
  1568. pmpDeviceLoading load;
  1569. dev = new AndroidDevice(driveletter,&load);
  1570. char titleStr[64] = {0};
  1571. MessageBoxA(hwndDlg,WASABI_API_LNGSTRING(IDS_RESCAN_COMPLETE_SAVED),
  1572. WASABI_API_LNGSTRING_BUF(IDS_RESCAN_COMPLETE,titleStr,64),MB_OK);
  1573. }
  1574. break;
  1575. }
  1576. }
  1577. return 0;
  1578. }
  1579. // update a track with new metadata (string)
  1580. void AndroidDevice::updateTrackField(AndroidSong* song, unsigned int col, const void* newValue, int fieldType)
  1581. {
  1582. if (!song) return;
  1583. Nullsoft::Utility::AutoLock lock(dbcs);
  1584. openDeviceTable();
  1585. nde_scanner_t s = NDE_Table_CreateScanner(deviceTable);
  1586. if (NDE_Scanner_LocateFilename(s, DEVICEVIEW_COL_FILENAME, FIRST_RECORD, song->filename))
  1587. {
  1588. switch (fieldType)
  1589. {
  1590. case FIELD_STRING:
  1591. db_setFieldString(s, col, (wchar_t *)(newValue));
  1592. break;
  1593. case FIELD_INTEGER:
  1594. db_setFieldInt(s, col, *((int *)newValue));
  1595. default:
  1596. break;
  1597. }
  1598. }
  1599. NDE_Scanner_Post(s);
  1600. NDE_Table_DestroyScanner(deviceTable, s);
  1601. // NDE_Table_Sync(deviceTable);
  1602. //closeDeviceTable();
  1603. }
  1604. AndroidSong::AndroidSong()
  1605. {
  1606. filename[0]=artist[0]=album[0]=title[0]=genre[0]=albumartist[0]=publisher[0]=composer[0]=0;
  1607. filled=year=track=length=discnum=bitrate=playcount=(int)(size=0);
  1608. }
  1609. AndroidArt::AndroidArt(ARGB32 *bits, int w, int h) :bits(bits), w(w), h(h)
  1610. {
  1611. }
  1612. AndroidArt::~AndroidArt()
  1613. {
  1614. if (bits)
  1615. WASABI_API_MEMMGR->sysFree(bits);
  1616. }