impex.cpp 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625
  1. //------------------------------------------------------------------------
  2. //
  3. // iTunes XML Import/Export Plugin
  4. // Copyright © 2003-2014 Winamp SA
  5. //
  6. //------------------------------------------------------------------------
  7. //#define PLUGIN_NAME "Nullsoft Database Import/Export"
  8. #define PLUGIN_VERSION L"2.65"
  9. #include <windows.h>
  10. #include <commdlg.h>
  11. #include <commctrl.h>
  12. #include <stdio.h>
  13. #include "api__ml_impex.h"
  14. #include "../../General/gen_ml/ml.h"
  15. #include "../winamp/wa_ipc.h"
  16. #include "resource.h"
  17. #include <bfc/string/url.h>
  18. #include "itunesxmlwrite.h"
  19. #include "importer.h"
  20. #include "ImporterAPI.h"
  21. #include "../nu/Singleton.h"
  22. #include "../nu/AutoChar.h"
  23. #include "../nu/AutoWide.h"
  24. static ImporterAPI importAPI;
  25. static SingletonServiceFactory<api_itunes_importer, ImporterAPI> importerFactory;
  26. // -----------------------------------------------------------------------
  27. #define ID_IMPORT 35443
  28. #define ID_EXPORT 35444
  29. #define ID_CVTPLAYLISTS 35445
  30. #define ID_IMPORT_ITUNES 35446
  31. #define ID_FILE_ADDTOLIBRARY 40344
  32. #define ID_DOSHITMENU_ADDNEWVIEW 40030
  33. #define IDM_LIBRARY_CONFIG 40050
  34. #define ID_DOSHITMENU_ADDNEWPLAYLIST 40031
  35. #define WINAMP_MANAGEPLAYLISTS 40385
  36. // -----------------------------------------------------------------------
  37. api_application *WASABI_API_APP = 0;
  38. api_playlistmanager *AGAVE_API_PLAYLISTMANAGER = 0;
  39. api_playlists *AGAVE_API_PLAYLISTS = 0;
  40. // wasabi based services for localisation support
  41. api_language *WASABI_API_LNG = 0;
  42. HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
  43. wchar_t* GetFilterListString(void) { // "iTunes XML Library\0*.xml\0\0"
  44. static wchar_t filterString[128] = {0};
  45. wchar_t* end = 0;
  46. StringCchCopyEx(filterString, 128, WASABI_API_LNGSTRINGW(IDS_ITUNES_XML_LIBRARY), &end, 0, 0);
  47. StringCchCopyEx(end+1, 128, L"*.xml", 0, 0, 0);
  48. return filterString;
  49. }
  50. // -----------------------------------------------------------------------
  51. static LRESULT WINAPI ml_newParentWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
  52. static LRESULT WINAPI ml_newMlWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam);
  53. static WNDPROC ml_oldParentWndProc;
  54. static WNDPROC ml_oldMlWndProc;
  55. static HWND export_wnd, hwnd_winamp, mlWnd;
  56. extern winampMediaLibraryPlugin plugin;
  57. HMENU mlMenu=NULL;
  58. void exportDatabase();
  59. void importDatabase();
  60. // -----------------------------------------------------------------------
  61. // dummy plugin message procs, we just ignore everything
  62. // -----------------------------------------------------------------------
  63. static INT_PTR PluginMessageProc(int message_type, INT_PTR param1, INT_PTR param2, INT_PTR param3)
  64. {
  65. if (message_type == ML_MSG_NO_CONFIG)
  66. return TRUE;
  67. return FALSE;
  68. }
  69. // -----------------------------------------------------------------------
  70. // plugin, exported to gen_ml
  71. // -----------------------------------------------------------------------
  72. static int init();
  73. static void quit();
  74. extern "C" winampMediaLibraryPlugin plugin =
  75. {
  76. MLHDR_VER,
  77. "nullsoft(ml_impex.dll)",
  78. init,
  79. quit,
  80. PluginMessageProc,
  81. 0,
  82. 0,
  83. 0,
  84. };
  85. // -----------------------------------------------------------------------
  86. // export
  87. // -----------------------------------------------------------------------
  88. extern "C" {
  89. __declspec( dllexport ) winampMediaLibraryPlugin * winampGetMediaLibraryPlugin()
  90. {
  91. return &plugin;
  92. }
  93. };
  94. // -----------------------------------------------------------------------
  95. // returns the position of a command within an HMENU
  96. // -----------------------------------------------------------------------
  97. int getMenuItemPos(HMENU menu, UINT command) {
  98. for (int i=0;i<256;i++) {
  99. MENUITEMINFO mii={sizeof(mii),MIIM_ID,};
  100. if (!GetMenuItemInfo(menu, i, TRUE, &mii)) break;
  101. if (mii.wID == command) return i;
  102. }
  103. return -1;
  104. }
  105. // -----------------------------------------------------------------------
  106. // entry point, gen_ml is starting up and we've just been loaded
  107. // -----------------------------------------------------------------------
  108. int init()
  109. {
  110. waServiceFactory *sf = plugin.service->service_getServiceByGuid(languageApiGUID);
  111. if (sf) WASABI_API_LNG = reinterpret_cast<api_language*>(sf->getInterface());
  112. sf = plugin.service->service_getServiceByGuid(applicationApiServiceGuid);
  113. if (sf) WASABI_API_APP = reinterpret_cast<api_application*>(sf->getInterface());
  114. sf = plugin.service->service_getServiceByGuid(api_playlistmanagerGUID);
  115. if (sf) AGAVE_API_PLAYLISTMANAGER = reinterpret_cast<api_playlistmanager*>(sf->getInterface());
  116. sf = plugin.service->service_getServiceByGuid(api_playlistsGUID);
  117. if (sf) AGAVE_API_PLAYLISTS = reinterpret_cast<api_playlists*>(sf->getInterface());
  118. // need to have this initialised before we try to do anything with localisation features
  119. WASABI_API_START_LANG(plugin.hDllInstance,MlImpexLangGUID);
  120. importerFactory.Register(plugin.service, &importAPI);
  121. static wchar_t szDescription[256];
  122. StringCchPrintf(szDescription, ARRAYSIZE(szDescription),
  123. WASABI_API_LNGSTRINGW(IDS_ML_IMPEX_DESC), PLUGIN_VERSION);
  124. plugin.description = (char*)szDescription;
  125. HWND w = plugin.hwndWinampParent;
  126. while (GetParent(w) != NULL) w = GetParent(w);
  127. hwnd_winamp = w;
  128. ml_oldParentWndProc = (WNDPROC)SetWindowLongPtrW(plugin.hwndWinampParent, GWLP_WNDPROC, (LONG_PTR)ml_newParentWndProc);
  129. mlMenu = (HMENU)SendMessage(hwnd_winamp, WM_WA_IPC, 9, IPC_GET_HMENU);
  130. int IPC_GETMLWINDOW=(int)SendMessage(hwnd_winamp,WM_WA_IPC,(WPARAM)&"LibraryGetWnd",IPC_REGISTER_WINAMP_IPCMESSAGE);
  131. mlWnd = (HWND)SendMessage(hwnd_winamp, WM_WA_IPC, -1, IPC_GETMLWINDOW);
  132. ml_oldMlWndProc = (WNDPROC)SetWindowLongPtrW(mlWnd, GWLP_WNDPROC, (LONG_PTR)ml_newMlWndProc);
  133. int p = getMenuItemPos(mlMenu, ID_FILE_ADDTOLIBRARY);
  134. MENUITEMINFO mii={sizeof(mii),MIIM_ID|MIIM_TYPE, MFT_SEPARATOR, };
  135. InsertMenuItem(mlMenu, ++p, TRUE, &mii);
  136. if (importAPI.iTunesExists())
  137. {
  138. MENUITEMINFO mii2={sizeof(mii2),MIIM_ID|MIIM_STATE|MIIM_TYPE, MFT_STRING, MFS_ENABLED, ID_IMPORT_ITUNES, NULL, NULL, NULL, NULL, WASABI_API_LNGSTRINGW(IDS_IMPORT_ITUNES_DB), 0};
  139. InsertMenuItem(mlMenu, ++p, TRUE, &mii2);
  140. MENUITEMINFO mii5={sizeof(mii5),MIIM_ID|MIIM_STATE|MIIM_TYPE, MFT_STRING, MFS_ENABLED, ID_CVTPLAYLISTS, NULL, NULL, NULL, NULL, WASABI_API_LNGSTRINGW(IDS_IMPORT_ITUNES_PL), 0};
  141. InsertMenuItem(mlMenu, ++p, TRUE, &mii5);
  142. }
  143. MENUITEMINFO mii3={sizeof(mii),MIIM_ID|MIIM_STATE|MIIM_TYPE, MFT_STRING, MFS_ENABLED, ID_IMPORT, NULL, NULL, NULL, NULL, WASABI_API_LNGSTRINGW(IDS_IMPORT_DATABASE), 0};
  144. InsertMenuItem(mlMenu, ++p, TRUE, &mii3);
  145. MENUITEMINFO mii4={sizeof(mii),MIIM_ID|MIIM_STATE|MIIM_TYPE, MFT_STRING, MFS_ENABLED, ID_EXPORT, NULL, NULL, NULL, NULL, WASABI_API_LNGSTRINGW(IDS_EXPORT_DATABASE), 0};
  146. InsertMenuItem(mlMenu, ++p, TRUE, &mii4);
  147. int IPC_GET_ML_HMENU = (int)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&"LibraryGetHmenu", IPC_REGISTER_WINAMP_IPCMESSAGE);
  148. HMENU context_menu = (HMENU) SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_ML_HMENU);
  149. if (context_menu)
  150. {
  151. HMENU hmenuPopup = GetSubMenu(context_menu, 0);
  152. if (hmenuPopup)
  153. {
  154. int p = getMenuItemPos(hmenuPopup, WINAMP_MANAGEPLAYLISTS);
  155. if (getMenuItemPos(hmenuPopup, IDM_LIBRARY_CONFIG) != -1) // sanity check
  156. {
  157. bool end_separator=true;
  158. if (p != -1)
  159. {
  160. MENUITEMINFO mii={sizeof(mii),MIIM_ID|MIIM_TYPE, MFT_SEPARATOR, };
  161. InsertMenuItem(hmenuPopup, ++p, TRUE, &mii);
  162. end_separator=false;
  163. }
  164. if (importAPI.iTunesExists())
  165. {
  166. MENUITEMINFO mii2={sizeof(mii2),MIIM_ID|MIIM_STATE|MIIM_TYPE, MFT_STRING, MFS_ENABLED, ID_IMPORT_ITUNES, NULL, NULL, NULL, NULL, WASABI_API_LNGSTRINGW(IDS_IMPORT_ITUNES_DB), 0};
  167. InsertMenuItem(hmenuPopup, ++p, TRUE, &mii2);
  168. MENUITEMINFO mii5={sizeof(mii5),MIIM_ID|MIIM_STATE|MIIM_TYPE, MFT_STRING, MFS_ENABLED, ID_CVTPLAYLISTS, NULL, NULL, NULL, NULL, WASABI_API_LNGSTRINGW(IDS_IMPORT_ITUNES_PL), 0};
  169. InsertMenuItem(hmenuPopup, ++p, TRUE, &mii5);
  170. }
  171. MENUITEMINFO mii3={sizeof(mii3),MIIM_ID|MIIM_STATE|MIIM_TYPE, MFT_STRING, MFS_ENABLED, ID_IMPORT, NULL, NULL, NULL, NULL, WASABI_API_LNGSTRINGW(IDS_IMPORT_DATABASE), 0};
  172. InsertMenuItem(hmenuPopup, ++p, TRUE, &mii3);
  173. MENUITEMINFO mii4={sizeof(mii4),MIIM_ID|MIIM_STATE|MIIM_TYPE, MFT_STRING, MFS_ENABLED, ID_EXPORT, NULL, NULL, NULL, NULL, WASABI_API_LNGSTRINGW(IDS_EXPORT_DATABASE), 0};
  174. InsertMenuItem(hmenuPopup, ++p, TRUE, &mii4);
  175. if (end_separator)
  176. {
  177. MENUITEMINFO mii={sizeof(mii),MIIM_ID|MIIM_TYPE, MFT_SEPARATOR, };
  178. InsertMenuItem(hmenuPopup, ++p, TRUE, &mii);
  179. }
  180. }
  181. }
  182. }
  183. return ML_INIT_SUCCESS;
  184. }
  185. // -----------------------------------------------------------------------
  186. // entry point, gen_ml is shutting down and we are being unloaded
  187. // -----------------------------------------------------------------------
  188. void quit() {
  189. if (IsWindow(plugin.hwndWinampParent)) SetWindowLongPtrW(plugin.hwndWinampParent, GWLP_WNDPROC, (LONG_PTR)ml_oldParentWndProc);
  190. if (IsWindow(mlWnd)) SetWindowLongPtrW(mlWnd, GWLP_WNDPROC, (LONG_PTR)ml_oldMlWndProc);
  191. importerFactory.Deregister(plugin.service);
  192. }
  193. void handleMenuItem(int wID) {
  194. switch (wID) {
  195. case ID_EXPORT:
  196. exportDatabase();
  197. break;
  198. case ID_IMPORT:
  199. importDatabase();
  200. break;
  201. case ID_IMPORT_ITUNES:
  202. {
  203. importAPI.ImportFromiTunes(plugin.hwndLibraryParent);
  204. }
  205. break;
  206. case ID_CVTPLAYLISTS:
  207. {
  208. importAPI.ImportPlaylistsFromiTunes(plugin.hwndLibraryParent);
  209. }
  210. break;
  211. }
  212. }
  213. void setDialogIcon(HWND hwndDlg)
  214. {
  215. static HICON wa_icy;
  216. if (wa_icy)
  217. {
  218. wa_icy = (HICON)LoadImage(GetModuleHandle(L"winamp.exe"),
  219. MAKEINTRESOURCE(102), IMAGE_ICON,
  220. GetSystemMetrics(SM_CXSMICON),
  221. GetSystemMetrics(SM_CYSMICON),
  222. LR_SHARED | LR_LOADTRANSPARENT | LR_CREATEDIBSECTION);
  223. }
  224. SendMessage(hwndDlg, WM_SETICON, ICON_SMALL, (LPARAM)wa_icy);
  225. }
  226. // -----------------------------------------------------------------------
  227. // library HWND subclass
  228. // -----------------------------------------------------------------------
  229. static LRESULT WINAPI ml_newParentWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
  230. {
  231. switch (uMsg)
  232. {
  233. case WM_COMMAND:
  234. {
  235. int wID = LOWORD(wParam);
  236. handleMenuItem(wID);
  237. }
  238. break;
  239. }
  240. return CallWindowProcW(ml_oldParentWndProc,hwndDlg,uMsg,wParam,lParam);
  241. }
  242. // -----------------------------------------------------------------------
  243. static LRESULT WINAPI ml_newMlWndProc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
  244. {
  245. switch (uMsg)
  246. {
  247. case WM_COMMAND:
  248. {
  249. int wID = LOWORD(wParam);
  250. handleMenuItem(wID);
  251. }
  252. break;
  253. }
  254. return CallWindowProcW(ml_oldMlWndProc,hwndDlg,uMsg,wParam,lParam);
  255. }
  256. // {55334B63-68D5-4389-A209-797F123E207F}
  257. static const GUID ML_IMPEX =
  258. { 0x55334b63, 0x68d5, 0x4389, { 0xa2, 0x9, 0x79, 0x7f, 0x12, 0x3e, 0x20, 0x7f } };
  259. //------------------------------------------------------------------------
  260. // pick an input file
  261. //------------------------------------------------------------------------
  262. static int pickFile(HWND hwndDlg, StringW *file)
  263. {
  264. wchar_t oldCurPath[MAX_PATH] = {0};
  265. GetCurrentDirectoryW(MAX_PATH, oldCurPath);
  266. OPENFILENAME l={sizeof(l),};
  267. wchar_t *temp;
  268. const int len=256*1024-128;
  269. temp = (wchar_t *)GlobalAlloc(GPTR,len*sizeof(*temp));
  270. l.hwndOwner = hwndDlg;
  271. extern wchar_t* GetFilterListString(void);
  272. l.lpstrFilter = GetFilterListString();//L"iTunes XML Library\0*.xml\0\0"; // IDS_ITUNES_XML_LIBRARY
  273. l.lpstrFile = temp;
  274. l.nMaxFile = len-1;
  275. l.lpstrTitle = WASABI_API_LNGSTRINGW(IDS_IMPORT_DATABASE);
  276. l.lpstrDefExt = L"";
  277. l.lpstrInitialDir = WASABI_API_APP->path_getWorkingPath();;
  278. l.Flags = OFN_HIDEREADONLY|OFN_EXPLORER;
  279. if(GetOpenFileName(&l))
  280. {
  281. wchar_t newCurPath[MAX_PATH] = {0};
  282. GetCurrentDirectoryW(MAX_PATH, newCurPath);
  283. WASABI_API_APP->path_setWorkingPath(newCurPath);
  284. SetCurrentDirectoryW(oldCurPath);
  285. *file = temp;
  286. return 1;
  287. }
  288. SetCurrentDirectoryW(oldCurPath);
  289. return 0;
  290. }
  291. // -----------------------------------------------------------------------
  292. // import an iTunes XML library into gen_ml
  293. // -----------------------------------------------------------------------
  294. void importDatabase()
  295. {
  296. StringW file;
  297. // pick an inputfile
  298. if (pickFile(plugin.hwndLibraryParent, &file))
  299. {
  300. importAPI.ImportFromFile(plugin.hwndLibraryParent, file.getValue());
  301. // TODO ideally should only do this if the current view is from ml_local
  302. PostMessage(plugin.hwndLibraryParent, WM_USER + 30, 0, 0);
  303. }
  304. }
  305. // -----------------------------------------------------------------------
  306. int only_itunes = -1; // ask
  307. // -----------------------------------------------------------------------
  308. // ask the user if we should also write files unsupported by iTunes
  309. // -----------------------------------------------------------------------
  310. static BOOL CALLBACK exporttype_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
  311. {
  312. switch (uMsg)
  313. {
  314. case WM_INITDIALOG:
  315. {
  316. only_itunes = 0;
  317. setDialogIcon(hwndDlg);
  318. CheckDlgButton(hwndDlg, IDC_RADIO_ALLFILES, TRUE);
  319. CheckDlgButton(hwndDlg, IDC_RADIO_ONLYSUPPORTED, FALSE);
  320. SetForegroundWindow(hwndDlg);
  321. return 0;
  322. }
  323. case WM_COMMAND: {
  324. int wID = LOWORD(wParam);
  325. switch (wID) {
  326. case IDOK:
  327. case IDCANCEL:
  328. EndDialog(hwndDlg, wID);
  329. SetForegroundWindow(export_wnd);
  330. break;
  331. case IDC_RADIO_ONLYSUPPORTED:
  332. only_itunes = 1;
  333. break;
  334. case IDC_RADIO_ALLFILES:
  335. only_itunes = 0;
  336. break;
  337. }
  338. }
  339. return 0;
  340. }
  341. return 0;
  342. }
  343. // -----------------------------------------------------------------------
  344. // export status window proc
  345. // -----------------------------------------------------------------------
  346. static BOOL CALLBACK export_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam)
  347. {
  348. switch (uMsg)
  349. {
  350. case WM_INITDIALOG:
  351. {
  352. export_wnd = hwndDlg;
  353. SetWindowLongPtr(hwndDlg, GWLP_USERDATA, lParam);
  354. // init it
  355. ShowWindow(GetDlgItem(hwndDlg, IDC_PROGRESS_PERCENT), SW_SHOWNORMAL);
  356. ShowWindow(GetDlgItem(hwndDlg, IDC_TRACKS), SW_SHOWNORMAL);
  357. SetWindowText(hwndDlg,WASABI_API_LNGSTRINGW(IDS_EXPORTING_DATABASE));
  358. SendMessage(GetDlgItem(hwndDlg, IDC_PROGRESS_PERCENT), PBM_SETRANGE, 0, MAKELPARAM(0, 100));
  359. SendDlgItemMessage(hwndDlg, IDC_PROGRESS_PERCENT, PBM_SETPOS, 0, 0);
  360. setDialogIcon(hwndDlg);
  361. SetTimer(hwndDlg, 666, 250, 0);
  362. SetForegroundWindow(hwndDlg);
  363. return 0;
  364. }
  365. case WM_TIMER:
  366. if (wParam == 666)
  367. {
  368. int *progress = (int *)GetWindowLongPtr(hwndDlg, GWLP_USERDATA);
  369. if (progress[0] == -333)
  370. {
  371. // show "saving xml"
  372. ShowWindow(GetDlgItem(hwndDlg, IDC_PROGRESS_PERCENT), SW_HIDE);
  373. ShowWindow(GetDlgItem(hwndDlg, IDC_TRACKS), SW_HIDE);
  374. SetDlgItemText(hwndDlg,IDC_PROCESSING_STATE,WASABI_API_LNGSTRINGW(IDS_WRITINGING_XML));
  375. ShowWindow(GetDlgItem(hwndDlg, IDC_PROCESSING_STATE), SW_SHOWNORMAL);
  376. }
  377. else if (progress[0] == -666)
  378. {
  379. KillTimer(hwndDlg, 666);
  380. EndDialog(hwndDlg, 0);
  381. }
  382. else
  383. {
  384. // display progress
  385. SendDlgItemMessage(hwndDlg, IDC_PROGRESS_PERCENT, PBM_SETPOS, (int)((double)progress[0] / progress[1] * 100.0), 0);
  386. SetDlgItemText(hwndDlg, IDC_TRACKS, StringPrintfW(WASABI_API_LNGSTRINGW(IDS_TRACKS_EXPORTED_X), progress[0]));
  387. }
  388. }
  389. break;
  390. case WM_COMMAND: {
  391. int wID = LOWORD(wParam);
  392. switch (wID) {
  393. case IDOK:
  394. case IDCANCEL:
  395. EndDialog(hwndDlg, wID);
  396. break;
  397. }
  398. }
  399. return 0;
  400. }
  401. return 0;
  402. }
  403. // -----------------------------------------------------------------------
  404. // returns 1 if str ends with e
  405. // -----------------------------------------------------------------------
  406. int endsIn(const wchar_t *str, const wchar_t *e) {
  407. return !_wcsicmp(str+wcslen(str)-wcslen(e), e);
  408. }
  409. static DWORD CALLBACK ExportThread(LPVOID param)
  410. {
  411. WASABI_API_DIALOGBOXPARAMW(IDD_INFODIALOG, NULL, export_dlgproc, (LPARAM)param);
  412. return 0;
  413. }
  414. // -----------------------------------------------------------------------
  415. // exports an iTunes XML library from gen_ml's database
  416. // -----------------------------------------------------------------------
  417. void exportDatabase() {
  418. // create an iTunes XML library writer
  419. iTunesXmlWrite w;
  420. // pick an output file
  421. if (w.pickFile(plugin.hwndLibraryParent)) {
  422. // if a file unsupported by iTunes is about to be exported, ask confirmation
  423. only_itunes = -1;
  424. // create status window
  425. int progress[2] = {0};
  426. DWORD threadId = 0;
  427. HANDLE exportThread = CreateThread(0, 0, ExportThread, progress, 0, &threadId);
  428. // create an iTunes DB in memory, start with a root key
  429. plistKey *rootKey = new plistKey(L"root");
  430. // add a dictionary to it
  431. plistDict *rootDict = new plistDict();
  432. rootKey->setData(rootDict);
  433. // add a few useless fields
  434. rootDict->addKey(new plistKey(L"Major Version", new plistInteger(1)));
  435. rootDict->addKey(new plistKey(L"Minor Version", new plistInteger(1)));
  436. rootDict->addKey(new plistKey(L"Application Version", new plistString(L"7.6.1"))); // we pretend to be iTunes 7.6.1
  437. // create the Tracks key and its dictionary of tracks
  438. plistDict *dict_tracks = new plistDict();
  439. rootDict->addKey(new plistKey(L"Tracks", dict_tracks));
  440. // run an empty query, so we get all items in the db
  441. mlQueryStructW query;
  442. query.query = L"";
  443. query.max_results = 0;
  444. query.results.Size = 0;
  445. query.results.Items = NULL;
  446. query.results.Alloc = 0;
  447. SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&query, ML_IPC_DB_RUNQUERYW);
  448. // my, what a big number of items...
  449. progress[1] = query.results.Size;
  450. // ... enumerate them
  451. for (int x = 0; x < progress[1]; x ++) {
  452. // take the xth item
  453. itemRecordW ir = query.results.Items[x];
  454. // check if it's supported by iTunes, if we've already answered the question, use last answer
  455. if ((only_itunes > 0 || only_itunes < 0) && (!endsIn(ir.filename, L".mp3") && !endsIn(ir.filename, L".m4a") && !endsIn(ir.filename, L".wav") && !endsIn(ir.filename, L".aiff"))) {
  456. if (only_itunes < 0) {
  457. // prompt user, should we export files unsupported by iTunes ?
  458. WASABI_API_DIALOGBOXW(IDD_EXPORTTYPE, plugin.hwndLibraryParent, exporttype_dlgproc);
  459. }
  460. // if not, continue with the next item
  461. if (only_itunes) continue;
  462. }
  463. // create a track key, its name is the number of the track (not counting skipped tracks)
  464. plistKey *key_track = new plistKey(StringPrintfW(L"%d", progress[0]));
  465. dict_tracks->addKey(key_track);
  466. // give it a dictionary to hold its properties
  467. plistDict *dict_track = new plistDict();
  468. key_track->setData(dict_track);
  469. // create the properties as needed
  470. dict_track->addKey(new plistKey(L"Track ID", new plistInteger(progress[0])));
  471. if (ir.title) dict_track->addKey(new plistKey(L"Name", new plistString(ir.title)));
  472. if (ir.artist) dict_track->addKey(new plistKey(L"Artist", new plistString(ir.artist)));
  473. if (ir.albumartist) dict_track->addKey(new plistKey(L"Album Artist", new plistString(ir.albumartist)));
  474. if (ir.album) dict_track->addKey(new plistKey(L"Album", new plistString(ir.album)));
  475. if (ir.genre) dict_track->addKey(new plistKey(L"Genre", new plistString(ir.genre)));
  476. if (ir.comment) dict_track->addKey(new plistKey(L"Comments", new plistString(ir.comment)));
  477. dict_track->addKey(new plistKey(L"Kind", new plistString(L"MPEG audio file")));
  478. // changed in 5.64 to use the 'realsize' if it's available, otherwise map to filesize scaled to bytes (stored as kb otherwise)
  479. const wchar_t *realsize = getRecordExtendedItem(&ir, L"realsize");
  480. if (realsize) dict_track->addKey(new plistKey(L"Size", new plistInteger(_wtoi64(realsize))));
  481. else if (ir.filesize > 0) dict_track->addKey(new plistKey(L"Size", new plistInteger(ir.filesize * 1024)));
  482. if (ir.length >= 0) dict_track->addKey(new plistKey(L"Total Time", new plistInteger(ir.length * 1000)));
  483. if (ir.track >= 0) dict_track->addKey(new plistKey(L"Track Number", new plistInteger(ir.track)));
  484. if (ir.year >= 0) dict_track->addKey(new plistKey(L"Year", new plistInteger(ir.year)));
  485. if (ir.filetime> 0) dict_track->addKey(new plistKey(L"Date Modified", new plistDate((time_t)ir.filetime)));
  486. if (ir.lastupd> 0) dict_track->addKey(new plistKey(L"Date Added", new plistDate((time_t)ir.lastupd)));
  487. //if (ir.lastplay> 0) dict_track->addKey(new plistKey(L"Play Date", new plistInteger((time_t)ir.lastplay)));
  488. if (ir.lastplay > 0) dict_track->addKey(new plistKey(L"Play Date UTC", new plistDate((time_t)ir.lastplay)));
  489. if (ir.bitrate> 0) dict_track->addKey(new plistKey(L"Bit Rate", new plistInteger(ir.bitrate)));
  490. if (ir.playcount> 0) dict_track->addKey(new plistKey(L"Play Count", new plistInteger(ir.playcount)));
  491. if (ir.rating> 0) dict_track->addKey(new plistKey(L"Rating", new plistInteger(ir.rating * 20)));
  492. if (ir.composer) dict_track->addKey(new plistKey(L"Composer", new plistString(ir.composer)));
  493. if (ir.publisher) dict_track->addKey(new plistKey(L"Publisher", new plistString(ir.publisher)));
  494. if (ir.type == 1) dict_track->addKey(new plistKey(L"Has Video", new plistInteger(1)));
  495. if (ir.disc> 0) dict_track->addKey(new plistKey(L"Disc Number", new plistInteger(ir.disc)));
  496. if (ir.discs> 0) dict_track->addKey(new plistKey(L"Disc Count", new plistInteger(ir.discs)));
  497. if (ir.tracks > 0) dict_track->addKey(new plistKey(L"Track Count", new plistInteger(ir.tracks)));
  498. if (ir.bpm > 0) dict_track->addKey(new plistKey(L"BPM", new plistInteger(ir.bpm)));
  499. const wchar_t *category = getRecordExtendedItem(&ir, L"category");
  500. if (category) dict_track->addKey(new plistKey(L"Grouping", new plistString(category)));
  501. const wchar_t *producer = getRecordExtendedItem(&ir, L"producer");
  502. if (producer) dict_track->addKey(new plistKey(L"Producer", new plistString(producer)));
  503. const wchar_t *director = getRecordExtendedItem(&ir, L"director");
  504. if (director) dict_track->addKey(new plistKey(L"Director", new plistString(director)));
  505. const wchar_t *width = getRecordExtendedItem(&ir, L"width");
  506. if (width) dict_track->addKey(new plistKey(L"Video Width", new plistInteger(_wtoi64(width))));
  507. const wchar_t *height = getRecordExtendedItem(&ir, L"height");
  508. if (height) dict_track->addKey(new plistKey(L"Video Height", new plistInteger(_wtoi64(height))));
  509. // convert the filename's backslashes to slashes
  510. StringW s = ir.filename;
  511. if (WCSNICMP(s, L"http://", 7))
  512. {
  513. wchar_t *val = s.getNonConstVal();
  514. // TODO: we could do this with less malloc usage
  515. AutoChar utf8(val, CP_UTF8);
  516. String encoded = (const char *)utf8;
  517. Url::encode(encoded, 0, URLENCODE_EXCLUDEALPHANUM|URLENCODE_EXCLUDESLASH, Url::URLENCODE_STYLE_PERCENT);
  518. s = StringPrintfW(L"%s%s", ITUNES_FILENAME_HEADER, AutoWide(encoded, CP_UTF8));
  519. }
  520. // done
  521. dict_track->addKey(new plistKey(L"Location", new plistString(s)));
  522. dict_track->addKey(new plistKey(L"File Folder Count", new plistInteger(-1)));
  523. dict_track->addKey(new plistKey(L"Library Folder Count", new plistInteger(-1)));
  524. // we have one more item in our exported db
  525. progress[0]++;
  526. }
  527. // show "saving xml"
  528. progress[0]=-333;
  529. // free query results
  530. SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&query, ML_IPC_DB_FREEQUERYRESULTSW);
  531. // save the xml
  532. w.saveXml(rootKey);
  533. // done
  534. progress[0]=-666;
  535. if (exportThread)
  536. {
  537. WaitForSingleObject(exportThread, INFINITE);
  538. CloseHandle(exportThread);
  539. }
  540. // destroy the db, this deletes all the children too
  541. delete rootKey;
  542. }
  543. }
  544. //------------------------------------------------------------------------