1
0

transfer_thread.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. #include "DeviceView.h"
  2. #include <time.h>
  3. #include <shlwapi.h>
  4. #include "SkinnedListView.h"
  5. #include "metadata_utils.h"
  6. #include "IconStore.h"
  7. #include "api__ml_pmp.h"
  8. #include "resource1.h"
  9. #include "main.h"
  10. static void TransferCallback(void * callBackContext, wchar_t * status);
  11. extern void TransfersListUpdateItem(CopyInst * item);
  12. void TransfersListUpdateItem(CopyInst * item, DeviceView *view);
  13. extern void TransfersListPushPopItem(CopyInst * item);
  14. void TransfersListPushPopItem(CopyInst * item, DeviceView *view);
  15. extern HWND mainMessageWindow;
  16. extern HANDLE hMainThread;
  17. /*
  18. How to add new ways of copying files.
  19. Subclass CopyInst, over-ride CopyAction and Equals
  20. Optionally over-ride PreCopyAction, PostCopyAction and Cancelled
  21. Add to transfer queue as normal.
  22. */
  23. SongCopyInst::SongCopyInst(DeviceView * dev, itemRecordW * song0)
  24. {
  25. usesPreCopy = false;
  26. usesPostCopy = true;
  27. this->dev = dev;
  28. equalsType = 0;
  29. res = 0;
  30. copyRecord(&song, song0);
  31. songid = NULL;
  32. status = STATUS_WAITING;
  33. // status caption
  34. WASABI_API_LNGSTRINGW_BUF(IDS_WAITING, statusCaption, sizeof(statusCaption)/sizeof(wchar_t));
  35. SYSTEMTIME system_time;
  36. GetLocalTime(&system_time);
  37. GetTimeFormat(LOCALE_INVARIANT, NULL, &system_time, NULL, lastChanged, sizeof(lastChanged)/sizeof(wchar_t));
  38. // make the itemRecord a little safer
  39. if(!song.album) song.album = _wcsdup(L"");
  40. if(!song.artist) song.artist = _wcsdup(L"");
  41. if(!song.title) song.title = _wcsdup(L"");
  42. if(!song.genre) song.genre = _wcsdup(L"");
  43. if(!song.filename) song.filename = _wcsdup(L"");
  44. if(!song.comment) song.comment = _wcsdup(L"");
  45. if(!song.albumartist) song.albumartist = _wcsdup(L"");
  46. if(!song.publisher) song.publisher = _wcsdup(L"");
  47. if(!song.composer) song.composer = _wcsdup(L"");
  48. // track caption
  49. lstrcpyn(trackCaption, song.artist, 128);
  50. int l = lstrlen(trackCaption);
  51. if(128 - l > 1) lstrcpyn(trackCaption + l, L" - ", 128-l);
  52. l = lstrlen(trackCaption);
  53. if(128 - l > 1) lstrcpyn(trackCaption + l, song.title, 128 - l);
  54. // type caption
  55. WASABI_API_LNGSTRINGW_BUF(IDS_TRANSFER, typeCaption, sizeof(typeCaption)/sizeof(wchar_t));
  56. // TODO fill out when other actions become done
  57. // source and destination details
  58. //this->dev->GetDisplayName(sourceDevice, sizeof(sourceDevice)/sizeof(wchar_t));
  59. WASABI_API_LNGSTRINGW_BUF(IDS_LOCAL_MACHINE, sourceDevice, ARRAYSIZE(sourceDevice));
  60. this->dev->GetDisplayName(destDevice, ARRAYSIZE(destDevice));
  61. lstrcpynW(sourceFile, song.filename, sizeof(sourceFile)/sizeof(wchar_t));
  62. }
  63. SongCopyInst::~SongCopyInst()
  64. {
  65. freeRecord(&song);
  66. }
  67. void SongCopyInst::Cancelled() {
  68. // helps us to do appropriate handling
  69. if (status == STATUS_TRANSFERRING)
  70. {
  71. dev->threadKillswitch = -2;
  72. }
  73. status = STATUS_CANCELLED;
  74. WASABI_API_LNGSTRINGW_BUF(IDS_UPLOAD_CANCELLED, statusCaption, ARRAYSIZE(statusCaption));
  75. SYSTEMTIME system_time = {0};
  76. GetLocalTime(&system_time);
  77. GetTimeFormat(LOCALE_INVARIANT, NULL, &system_time, NULL, lastChanged, ARRAYSIZE(lastChanged));
  78. dev->dev->trackRemovedFromTransferQueue(&song);
  79. }
  80. static void TransferCallback(void * callBackContext, wchar_t * status)
  81. {
  82. CopyInst * c = (CopyInst *)callBackContext;
  83. if(!wcscmp(status, c->statusCaption)) return;
  84. if (status && *status) lstrcpyn(c->statusCaption, status, sizeof(c->statusCaption)/sizeof(wchar_t));
  85. TransfersListUpdateItem(c);
  86. TransfersListUpdateItem(c, c->dev);
  87. int pc=0;
  88. // copes with 'transferring %'
  89. if(swscanf(status,L"%*s %d%%",&pc))
  90. {
  91. if (c->dev->isCloudDevice) cloudTransferProgress = pc;
  92. else c->dev->currentTransferProgress = pc;
  93. }
  94. // copes with 'transferring (%)'
  95. else if(swscanf(status,L"%*s %*1c %d%%",&pc))
  96. {
  97. if (c->dev->isCloudDevice) cloudTransferProgress = pc;
  98. else c->dev->currentTransferProgress = pc;
  99. }
  100. c->dev->UpdateSpaceInfo(TRUE, TRUE);
  101. }
  102. bool SongCopyInst::CopyAction()
  103. {
  104. int r = dev->dev->transferTrackToDevice(&song,this,TransferCallback,&songid,&dev->threadKillswitch);
  105. if (r==0 && AGAVE_API_STATS)
  106. AGAVE_API_STATS->IncrementStat(api_stats::PMP_TRANSFER_COUNT);
  107. dev->dev->trackRemovedFromTransferQueue(&song);
  108. return r!=0;
  109. }
  110. void SongCopyInst::PostCopyAction()
  111. {
  112. if(status == STATUS_DONE && songid)
  113. {
  114. dev->dev->addTrackToPlaylist(0, songid);
  115. if (dev->metadata_fields & SUPPORTS_ALBUMART)
  116. {
  117. int w,h;
  118. ARGB32 *bits;
  119. if (AGAVE_API_ALBUMART->GetAlbumArt_NoAMG(song.filename, L"cover", &w, &h, &bits) == ALBUMART_SUCCESS)
  120. {
  121. dev->dev->setArt(songid,bits,w,h);
  122. WASABI_API_MEMMGR->sysFree(bits);
  123. }
  124. }
  125. }
  126. }
  127. bool SongCopyInst::Equals(CopyInst * b) {
  128. if(this->equalsType == b->equalsType) {
  129. SongCopyInst * c = (SongCopyInst*)b;
  130. bool ret = (compareItemRecords(&this->song,&c->song) == 0);
  131. // for cloud then we do some extra checks to allow different formats
  132. // and also for sending the same file to a different cloud device...
  133. if (c->dev->isCloudDevice)
  134. {
  135. const wchar_t * mime_1 = getRecordExtendedItem(&c->song, L"mime");
  136. const wchar_t * mime_2 = getRecordExtendedItem(&this->song, L"mime");
  137. int mime_match = lstrcmpiW(mime_1 ? mime_1 : L"", mime_2 ? mime_2 : L"");
  138. int device_match = lstrcmpiW(c->destDevice ? c->destDevice : L"", this->destDevice ? this->destDevice : L"");
  139. if (!device_match)
  140. {
  141. if (!mime_match)
  142. {
  143. return ret;
  144. }
  145. else
  146. {
  147. return false;
  148. }
  149. }
  150. else
  151. {
  152. return false;
  153. }
  154. }
  155. return ret;
  156. }
  157. return false;
  158. }
  159. PlaylistCopyInst::PlaylistCopyInst(DeviceView * dev, itemRecordW * song, wchar_t * plName0, int plid0) : SongCopyInst(dev,song)
  160. {
  161. lstrcpyn(plName,plName0,255);
  162. plid=plid0;
  163. plAddSongs = NULL;
  164. usesPreCopy = false;
  165. usesPostCopy = true;
  166. }
  167. PlaylistCopyInst::~PlaylistCopyInst()
  168. {
  169. SongCopyInst::~SongCopyInst();
  170. if(plAddSongs) delete plAddSongs;
  171. }
  172. bool PlaylistCopyInst::PreCopyAction() {return false;}
  173. void PlaylistCopyInst::PostCopyAction() {
  174. SongCopyInst::PostCopyAction();
  175. if(!plAddSongs || plid == -1) return;
  176. if(plid >= dev->dev->getPlaylistCount()) return;
  177. int l = plAddSongs->GetSize();
  178. wchar_t pln[256] = {0};
  179. dev->dev->getPlaylistName(plid,pln,255);
  180. if(wcscmp(pln,plName) == 0) {
  181. if(status == STATUS_DONE && songid) dev->dev->addTrackToPlaylist(plid,songid);
  182. for(int i=0; i < l; i++) {
  183. songid_t s = (songid_t)plAddSongs->Get(i);
  184. if(s) dev->dev->addTrackToPlaylist(plid,s);
  185. }
  186. }
  187. }
  188. ReverseCopyInst::ReverseCopyInst(DeviceView * dev, const wchar_t * filepath, const wchar_t * format, songid_t song, bool addToLibrary, bool uppercaseext) : uppercaseext(uppercaseext) {
  189. usesPreCopy = false;
  190. usesPostCopy = addToLibrary;
  191. this->dev = dev;
  192. equalsType = 1;
  193. this->songid = song;
  194. status = STATUS_WAITING;
  195. WASABI_API_LNGSTRINGW_BUF(IDS_WAITING, statusCaption, sizeof(statusCaption)/sizeof(wchar_t));
  196. WASABI_API_LNGSTRINGW_BUF(IDS_COPY_TO_LIBRARY, typeCaption, sizeof(typeCaption)/sizeof(wchar_t));
  197. dev->dev->getTrackArtist(song,trackCaption,128);
  198. int l = lstrlen(trackCaption);
  199. if(128 - l > 1) lstrcpyn(trackCaption + l,L" - ",128-l);
  200. l = lstrlen(trackCaption);
  201. if(128 - l > 1) dev->dev->getTrackTitle(song,trackCaption + l,128 - l);
  202. // find path for song
  203. lstrcpyn(path,format,2036);
  204. FixReplacementVars(path,2036,dev->dev,song);
  205. PathCombine(path,filepath,path);
  206. }
  207. bool ReverseCopyInst::Equals(CopyInst *b) {
  208. if(this->equalsType == b->equalsType) {
  209. ReverseCopyInst* c = (ReverseCopyInst*)b;
  210. return (c->dev == this->dev) && (c->songid == this->songid);
  211. }
  212. return false;
  213. }
  214. bool ReverseCopyInst::CopyAction() { //Return true if failed.
  215. wchar_t * lastslash = wcsrchr(path,L'\\');
  216. if(!lastslash) {
  217. WASABI_API_LNGSTRINGW_BUF(IDS_INVALID_PATH, statusCaption, sizeof(statusCaption)/sizeof(wchar_t));
  218. return true;
  219. }
  220. *lastslash=0;
  221. if(RecursiveCreateDirectory(path)) {
  222. WASABI_API_LNGSTRINGW_BUF(IDS_INVALID_PATH, statusCaption, sizeof(statusCaption)/sizeof(wchar_t));
  223. return true;
  224. }
  225. *lastslash=L'\\';
  226. // path created, copy file over.
  227. int r = dev->dev->copyToHardDrive(songid, path, this, TransferCallback, &dev->threadKillswitch);
  228. return r!=0;
  229. }
  230. void ReverseCopyInst::PostCopyAction()
  231. {
  232. itemRecordW ice={0};
  233. filenameToItemRecord(path,&ice);
  234. ice.rating = dev->dev->getTrackRating(songid);
  235. ice.playcount = dev->dev->getTrackPlayCount(songid);
  236. ice.lastplay = dev->dev->getTrackLastPlayed(songid);
  237. ice.lastupd = dev->dev->getTrackLastUpdated(songid);
  238. ice.type = dev->dev->getTrackType(songid);
  239. SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&ice,ML_IPC_DB_ADDORUPDATEITEMW);
  240. freeRecord(&ice);
  241. }
  242. ReversePlaylistCopyInst::ReversePlaylistCopyInst(DeviceView * dev, const wchar_t * filepath, const wchar_t * format, songid_t song, wchar_t * playlistFile0, wchar_t * playlistName0, bool last,bool addToLibrary)
  243. : ReverseCopyInst(dev,filepath,format,song,addToLibrary,false), last(last) {
  244. lstrcpyn(playlistFile,playlistFile0,MAX_PATH);
  245. lstrcpyn(playlistName,playlistName0,128);
  246. }
  247. bool ReversePlaylistCopyInst::CopyAction()
  248. {
  249. bool r = ReverseCopyInst::CopyAction();
  250. return r;
  251. }
  252. void ReversePlaylistCopyInst::PostCopyAction()
  253. {
  254. ReverseCopyInst::PostCopyAction();
  255. FILE * f = _wfopen(playlistFile,L"at");
  256. if(f) {
  257. fputws(L"#EXTINF:",f);
  258. wchar_t buf[100] = {0};
  259. wsprintf(buf,L"%d",dev->dev->getTrackLength(songid)/1000);
  260. fputws(buf,f);
  261. fputws(L",",f);
  262. wchar_t title[2048] = {0};
  263. getTitle(dev->dev,songid,path,title,2048);
  264. fputws(title,f);
  265. fputws(L"\n",f);
  266. fputws(path,f);
  267. fputws(L"\n",f);
  268. fclose(f);
  269. }
  270. if(last) {
  271. mlAddPlaylist a = {sizeof(mlAddPlaylist),playlistName,playlistFile,PL_FLAG_SHOW | PL_FLAGS_IMPORT,-1,-1};
  272. SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&a,ML_IPC_PLAYLIST_ADD);
  273. _wunlink(playlistFile);
  274. }
  275. }
  276. // HERE BE DRAGONS
  277. static VOID CALLBACK APC_PreCopy(ULONG_PTR dwParam) {
  278. CopyInst * a = (CopyInst *)dwParam;
  279. a->res = a->PreCopyAction()?2:1;
  280. }
  281. static VOID CALLBACK APC_PostCopy(ULONG_PTR dwParam) {
  282. CopyInst * a = (CopyInst *)dwParam;
  283. a->PostCopyAction();
  284. a->res = 1;
  285. }
  286. void CALLBACK TransferNavTimer(HWND hwnd, UINT uMsg, UINT_PTR eventId, ULONG elapsed)
  287. {
  288. TransferContext *context = (TransferContext *)eventId;
  289. if (context && context->dev->queueTreeItem)
  290. {
  291. NAVITEM item;
  292. item.cbSize = sizeof(NAVITEM);
  293. item.hItem = context->dev->queueTreeItem;
  294. item.iSelectedImage = item.iImage = icon_store.GetQueueIcon(context->dev->queueActiveIcon);
  295. context->dev->queueActiveIcon = (context->dev->queueActiveIcon + 1) % 4;
  296. item.mask = NIMF_IMAGE | NIMF_IMAGESEL;
  297. MLNavItem_SetInfo(plugin.hwndLibraryParent, &item);
  298. }
  299. }
  300. void TransferContext::DoOneTransfer(HANDLE handle)
  301. {
  302. if (TryEnterCriticalSection(&transfer_lock))
  303. {
  304. if(dev->threadKillswitch == 1)
  305. {
  306. WASABI_API_THREADPOOL->RemoveHandle(transfer_thread, handle);
  307. dev->threadKillswitch = 100;
  308. SetEvent(killer);
  309. LeaveCriticalSection(&transfer_lock);
  310. return;
  311. }
  312. if (IsPaused())
  313. {
  314. LeaveCriticalSection(&transfer_lock);
  315. return;
  316. }
  317. LinkedQueue * txQueue = getTransferQueue(this->dev);
  318. CopyInst * c = (txQueue ? (CopyInst *)txQueue->Peek() : NULL);
  319. if (c)
  320. {
  321. if (c->res != 2 && c->status != STATUS_CANCELLED)
  322. {
  323. c->status = STATUS_TRANSFERRING;
  324. start = time(NULL);
  325. TransferCallback(c, WASABI_API_LNGSTRINGW_BUF(IDS_STARTING_TRANSFER, c->statusCaption, sizeof(c->statusCaption)/sizeof(wchar_t)));
  326. c->res = 0;
  327. if(c->usesPreCopy)
  328. SynchronousProcedureCall(APC_PreCopy,(ULONG_PTR)c);
  329. }
  330. if(dev->threadKillswitch)
  331. {
  332. WASABI_API_THREADPOOL->RemoveHandle(transfer_thread, handle);
  333. dev->threadKillswitch = 100;
  334. SetEvent(killer);
  335. LeaveCriticalSection(&transfer_lock);
  336. return;
  337. }
  338. if(c->res == 2)
  339. { // dupe
  340. WASABI_API_LNGSTRINGW_BUF((dev->isCloudDevice ? IDS_ALREADY_UPLOADED : IDS_DUPLICATE), c->statusCaption, sizeof(c->statusCaption)/sizeof(wchar_t));
  341. c->status = STATUS_DONE;
  342. }
  343. else if (c->status != STATUS_CANCELLED)
  344. {
  345. // do the transfer
  346. int r = c->CopyAction();
  347. c->status = (r == -1 ? STATUS_ERROR : STATUS_DONE);
  348. SYSTEMTIME system_time = {0};
  349. GetLocalTime(&system_time);
  350. GetTimeFormat(LOCALE_INVARIANT, NULL, &system_time, NULL, c->lastChanged, sizeof(c->lastChanged)/sizeof(wchar_t));
  351. // Now do whatever needs to be done post-copy (add to playlist or whatever)
  352. c->res = 0;
  353. if(c->usesPostCopy && c->status == STATUS_DONE)
  354. SynchronousProcedureCall(APC_PostCopy,(ULONG_PTR)c);
  355. // now work out the moving average time per transfer
  356. end = time(NULL);
  357. if(c->status == STATUS_DONE)
  358. {
  359. times[numTransfers % AVERAGEBASIS] = (int)((long)end - (long)start);
  360. numTransfers++;
  361. }
  362. int n = min(AVERAGEBASIS,numTransfers);
  363. if(n > 0)
  364. {
  365. int t = 0;
  366. for(int i = 0; i < n; i++) t += times[i];
  367. dev->transferRate = ((double)t) / ((double)n);
  368. }
  369. }
  370. if(dev->threadKillswitch == 2)
  371. { // a transfer has been cancelled part way through
  372. dev->threadKillswitch = 0;
  373. delete txQueue->Poll();
  374. }
  375. else
  376. {
  377. LinkedQueue * finishedTX = getFinishedTransferQueue(this->dev);
  378. if (finishedTX)
  379. {
  380. txQueue->lock();
  381. finishedTX->lock();
  382. finishedTX->Offer(txQueue->Poll());
  383. finishedTX->unlock();
  384. txQueue->unlock();
  385. TransfersListPushPopItem(c);
  386. TransfersListPushPopItem(c, dev);
  387. }
  388. }
  389. dev->commitNeeded = true;
  390. if (dev->isCloudDevice) cloudTransferProgress = 0;
  391. else dev->currentTransferProgress = 0;
  392. LeaveCriticalSection(&transfer_lock);
  393. SetEvent(handle);
  394. }
  395. else
  396. {
  397. if(dev->commitNeeded)
  398. PostMessage(mainMessageWindow,WM_TIMER,COMMITTIMERID,0);
  399. if (dev->isCloudDevice) cloudTransferProgress = 0;
  400. else dev->currentTransferProgress = 0;
  401. dev->UpdateActivityState();
  402. LeaveCriticalSection(&transfer_lock);
  403. }
  404. }
  405. }
  406. int TransferThreadPoolFunc(HANDLE handle, void *user_data, intptr_t id)
  407. {
  408. TransferContext *context = (TransferContext *)user_data;
  409. SetTimer(plugin.hwndLibraryParent, (UINT_PTR)user_data, 1000, TransferNavTimer);
  410. // allows cancels to continue even if a cloud device upload had been cancelled
  411. if (context->dev->threadKillswitch == -2) context->dev->threadKillswitch = 0;
  412. context->DoOneTransfer(handle);
  413. KillTimer(plugin.hwndLibraryParent, (UINT_PTR)user_data);
  414. return 0;
  415. }
  416. bool TransferContext::IsPaused()
  417. {
  418. return (paused_all || paused);
  419. }
  420. void TransferContext::Pause()
  421. {
  422. if (1 == InterlockedIncrement(&paused))
  423. {
  424. if(dev->commitNeeded)
  425. PostMessage(mainMessageWindow,WM_TIMER,COMMITTIMERID,0);
  426. SetEvent(notifier);
  427. }
  428. }
  429. void TransferContext::Resume()
  430. {
  431. if (0 == InterlockedDecrement(&paused))
  432. {
  433. SetEvent(notifier);
  434. }
  435. }
  436. bool TransferContext::IsAllPaused()
  437. {
  438. return paused_all?true:false;
  439. }
  440. void TransferContext::PauseAll()
  441. {
  442. InterlockedIncrement(&paused_all);
  443. }
  444. void TransferContext::ResumeAll()
  445. {
  446. InterlockedDecrement(&paused_all);
  447. }