//#define _WIN32_WINNT 0x0400 #include "../Winamp/buildType.h" #include "main.h" #include "DeviceView.h" //#include #include "nu/AutoWide.h" #include "nu/AutoChar.h" #include "../nu/AutoUrl.h" #include "SkinnedListView.h" #include "../playlist/api_playlistmanager.h" #include "../playlist/ifc_playlistdirectorycallback.h" #include "../playlist/ifc_playlistloadercallback.h" #include "api__ml_pmp.h" #include #include #include "metadata_utils.h" #include "../ml_wire/ifc_podcast.h" #include "./local_menu.h" #include "IconStore.h" #include "../devices/ifc_deviceevent.h" #include "metadata_utils.h" #include "../nu/sort.h" #include "resource1.h" #include #include "../nu/MediaLibraryInterface.h" extern C_ItemList devices; extern C_Config * global_config; extern INT_PTR CALLBACK pmp_artistalbum_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam); extern INT_PTR CALLBACK pmp_playlist_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam); int IPC_GET_CLOUD_HINST = -1, IPC_LIBRARY_PLAYLISTS_REFRESH = -1; HINSTANCE cloud_hinst = 0; int currentViewedPlaylist=0; HNAVITEM cloudQueueTreeItem=NULL; LinkedQueue cloudTransferQueue, cloudFinishedTransfers; int cloudTransferProgress = 0; DeviceView * currentViewedDevice=NULL; volatile size_t TransferContext::paused_all = 0; extern void UpdateTransfersListView(bool softUpdate, CopyInst * item=NULL); extern void UpdateDevicesListView(bool softUpdate); extern INT_PTR CALLBACK config_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam); extern HWND mainMessageWindow; extern prefsDlgRecW prefsPage; extern int prefsPageLoaded; static int thread_id; static bool copySettings(wchar_t * ssrc, wchar_t * sdest); static __int64 fileSize(wchar_t * filename); static void removebadchars(wchar_t *s); extern ThreadID *transfer_thread; int TransferThreadPoolFunc(HANDLE handle, void *user_data, intptr_t id); INT_PTR CALLBACK pmp_queue_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam); INT_PTR CALLBACK pmp_video_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam); DeviceView::DeviceView(Device *dev) : activityRunning(FALSE), navigationItemCreated(FALSE), usedSpace(0), totalSpace(0) { memset(name, 0, sizeof(name)); queueActiveIcon = isCloudDevice = 0; treeItem = videoTreeItem = queueTreeItem = 0; connection_type = "USB"; display_type = "Portable Media Player"; metadata_fields = (int)dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0); if(!metadata_fields) metadata_fields = -1; dev->extraActions(DEVICE_GET_CONNECTION_TYPE, (intptr_t)&connection_type, 0, 0); isCloudDevice = (!lstrcmpiA(connection_type, "cloud")); dev->extraActions(DEVICE_GET_DISPLAY_TYPE, (intptr_t)&display_type, 0, 0); ref_count = 1; if (dev->extraActions(DEVICE_GET_UNIQUE_ID, (intptr_t)name, sizeof(name), 0) == 0) { // fallback GUID name_guid; CoCreateGuid(&name_guid); StringCbPrintfA(name, sizeof(name), "%08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X", (int)name_guid.Data1, (int)name_guid.Data2, (int)name_guid.Data3, (int)name_guid.Data4[0], (int)name_guid.Data4[1], (int)name_guid.Data4[2], (int)name_guid.Data4[3], (int)name_guid.Data4[4], (int)name_guid.Data4[5], (int)name_guid.Data4[6], (int)name_guid.Data4[7] ); } wchar_t inifile[MAX_PATH] = {0}; dev->extraActions(DEVICE_GET_INI_FILE,(intptr_t)inifile,0,0); if(!inifile[0]) { wchar_t name[256] = {0}; dev->getPlaylistName(0,name,256); removebadchars(name); // build this slow so we make sure each directory exists PathCombine(inifile, WASABI_API_APP->path_getUserSettingsPath(), L"Plugins"); CreateDirectory(inifile, NULL); PathAppend(inifile, L"ml"); CreateDirectory(inifile, NULL); wchar_t ini_filespec[MAX_PATH] = {0}; StringCchPrintf(ini_filespec, MAX_PATH, L"ml_pmp_device_%s.ini",name); PathAppend(inifile, ini_filespec); } if(fileSize(inifile) <= 0) copySettings(global_config->GetIniFile(),inifile); // import old settings config = new C_Config(inifile,L"ml_pmp",global_config); currentTransferProgress = 0; transferRate=0; commitNeeded=false; this->dev = dev; wchar_t deviceName[256]=L""; dev->getPlaylistName(0,deviceName,sizeof(deviceName)/sizeof(wchar_t)); if (!isCloudDevice) videoView = config->ReadInt(L"showVideoView",dev->extraActions(DEVICE_SUPPORTS_VIDEO,0,0,0)); else videoView = 0; prefsDlgRecW *parentPrefs = (prefsDlgRecW *)dev->extraActions(DEVICE_GET_PREFS_PARENT, 0, 0, 0); if (!parentPrefs) { // only add it when we're using our own root page, otherwise skip this if (!prefsPageLoaded) { SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(intptr_t)&prefsPage,IPC_ADD_PREFS_DLGW); } prefsPageLoaded+=1; } if (lstrcmpi(deviceName, L"all_sources")) { devPrefsPage.hInst=WASABI_API_LNG_HINST; devPrefsPage.where=(parentPrefs ? (intptr_t)parentPrefs : (intptr_t)&prefsPage); devPrefsPage.dlgID=IDD_CONFIG; devPrefsPage.name=_wcsdup(deviceName); devPrefsPage.proc=config_dlgproc; SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(intptr_t)&devPrefsPage,IPC_ADD_PREFS_DLGW); } else { memset(&devPrefsPage, 0, sizeof(prefsDlgRecW)); } UpdateSpaceInfo(TRUE, FALSE); threadKillswitch = 0; transferContext.transfer_thread = WASABI_API_THREADPOOL->ReserveThread(api_threadpool::FLAG_LONG_EXECUTION); transferContext.dev = this; WASABI_API_THREADPOOL->AddHandle(transferContext.transfer_thread, transferContext.notifier, TransferThreadPoolFunc, &transferContext, thread_id, api_threadpool::FLAG_LONG_EXECUTION); thread_id++; if (AGAVE_API_DEVICEMANAGER) { ifc_device *registered_device = this; AGAVE_API_DEVICEMANAGER->DeviceRegister(®istered_device, 1); } //hTransferThread = CreateThread(NULL, 0, ThreadFunc_Transfer, (LPVOID)this, 0, &dwThreadId); /* if(dev->getDeviceCapacityTotal() > L3000000000) SyncConnectionDefault=1; else SyncConnectionDefault=2; */ SyncConnectionDefault=0; // default off for now. if (!isCloudDevice) { // ok all started. Now do any "on connect" actions... time_t lastSync = (time_t)config->ReadInt(L"syncOnConnect_time",0); time_t now = time(NULL); //int diff = now - lastSync; double diff = difftime(now,lastSync); config->WriteInt(L"syncOnConnect_time",(int)now); if(diff > config->ReadInt(L"syncOnConnect_hours",12)*3600) { switch(config->ReadInt(L"syncOnConnect",SyncConnectionDefault)) { case 1: { if (!isCloudDevice) Sync(true); //else CloudSync(true); } break; case 2: Autofill(); break; } } } if (!AGAVE_API_DEVICEMANAGER) RegisterViews(0); } HNAVITEM GetNavigationRoot(BOOL forceCreate); HNAVITEM NavigationItem_Find(HNAVITEM root, const wchar_t *name, BOOL allow_root = 0); void DeviceView::RegisterViews(HNAVITEM parent) { NAVINSERTSTRUCT nis = {0}; NAVITEM *item = 0; wchar_t buffer[128] = {0}; item = &nis.item; if(!parent) { MLTREEIMAGE devIcon; wchar_t deviceName[256]=L""; devIcon.resourceId = IDR_DEVICE_ICON; devIcon.hinst = plugin.hDllInstance; dev->extraActions(DEVICE_SET_ICON,(intptr_t)&devIcon,0,0); dev->getPlaylistName(0,deviceName,sizeof(deviceName)/sizeof(wchar_t)); nis.hParent = GetNavigationRoot(TRUE); nis.hInsertAfter = NCI_LAST; item->cbSize = sizeof(NAVITEM); item->mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_STYLE | NIMF_IMAGE | NIMF_IMAGESEL; item->pszText = deviceName; item->pszInvariant = nis.item.pszText; item->style = NIS_HASCHILDREN; item->styleMask = item->style, item->iImage = icon_store.GetResourceIcon(devIcon.hinst, MAKEINTRESOURCE(devIcon.resourceId)); item->iSelectedImage = item->iImage; treeItem = parent = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis); navigationItemCreated = TRUE; } else { treeItem = parent; navigationItemCreated = FALSE; item->cbSize = sizeof(NAVITEM); item->mask = NIMF_STYLE; item->hItem = treeItem; item->style = NIS_HASCHILDREN; item->styleMask = NIS_HASCHILDREN; MLNavItem_SetInfo(plugin.hwndLibraryParent, item); /* create transfer view */ // TODO: create this view dynamically HNAVITEM cloud = 0; if (isCloudDevice) { cloud = NavigationItem_Find(0, L"cloud_sources", TRUE); if (cloud != NULL) parent = cloud; } #if 0 int mode = gen_mlconfig->ReadInt(L"txviewpos", 0); if (mode == 1) { nis.hParent = cloud;//parent; nis.hInsertAfter = NCI_FIRST; } else if (mode == 2) { nis.hParent = NavigationItem_Find(0, L"ml_devices_root", TRUE); nis.hInsertAfter = NCI_FIRST; } #else nis.hParent = parent; #endif item->cbSize = sizeof(NAVITEM); item->mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL; item->pszText = WASABI_API_LNGSTRINGW_BUF(IDS_TRANSFERS, buffer, 128); item->pszInvariant = (isCloudDevice ? L"cloud_transfers" : L"transfers"); item->iImage = icon_store.GetQueueIcon(); item->iSelectedImage = nis.item.iImage; #if 0 if (!cloudQueueTreeItem && isCloudDevice) { NAVINSERTSTRUCT nis2 = {0}; nis2.item.cbSize = sizeof(NAVITEM); nis2.item.pszText = WASABI_API_LNGSTRINGW(IDS_ADD_SOURCE); nis2.item.pszInvariant = L"cloud_add_sources"; nis2.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL | NIMF_STYLE; nis2.item.iImage = nis2.item.iSelectedImage = mediaLibrary.AddTreeImageBmp(IDB_TREEITEM_CLOUD_ADD_SOURCE); nis2.hParent = parent; nis2.hInsertAfter = NCI_FIRST; HNAVITEM item = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis2); if (mode == 1) nis.hInsertAfter = item; queueTreeItem = cloudQueueTreeItem = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis); #if 0 nis2.item.pszText = L"BYOS";//WASABI_API_LNGSTRINGW(IDS_ADD_SOURCE); nis2.item.pszInvariant = L"cloud_byos"; nis2.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL | NIMF_STYLE; nis2.item.iImage = nis2.item.iSelectedImage = mediaLibrary.AddTreeImageBmp(IDB_TREEITEM_CLOUD_ADD_BYOS); nis2.hParent = parent; nis2.hInsertAfter = nis.hInsertAfter; MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis2); #endif } else queueTreeItem = cloudQueueTreeItem; #endif queueTreeItem = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis); } /* create video view */ if (videoView) { nis.hParent = parent; nis.hInsertAfter = NCI_LAST; item->cbSize = sizeof(NAVITEM); item->mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL; item->pszText = WASABI_API_LNGSTRINGW_BUF(IDS_VIDEO, buffer, 128); item->pszInvariant = L"video"; item->iImage = icon_store.GetVideoIcon(); item->iSelectedImage = nis.item.iImage; videoTreeItem = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis); } else videoTreeItem = 0; /* create playlists */ int l = (dev ? dev->getPlaylistCount() : 0); for(int i=1; iWriteInt(L"showVideoView",videoView); if(videoView) { /* add video before the playlists */ wchar_t buffer[128] = {0}; NAVINSERTSTRUCT nis = {0}; nis.item.cbSize = sizeof(NAVITEM); nis.item.pszText = WASABI_API_LNGSTRINGW_BUF(IDS_VIDEO, buffer, 128); nis.item.pszInvariant = L"video"; nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL; nis.hParent = treeItem; nis.hInsertAfter = NCI_FIRST; nis.item.iImage = icon_store.GetVideoIcon(); nis.item.iSelectedImage = nis.item.iImage; videoTreeItem = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis); } else { MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, videoTreeItem); videoTreeItem = 0; } } static void removebadchars(wchar_t *s) { while (s && *s) { if (*s == L'?' || *s == L'/' || *s == L'\\' || *s == L':' || *s == L'*' || *s == L'\"' || *s == L'<' || *s == L'>' || *s == L'|') *s = L'_'; s = CharNextW(s); } } static __int64 fileSize(wchar_t * filename) { WIN32_FIND_DATA f= {0}; HANDLE h = FindFirstFileW(filename,&f); if(h == INVALID_HANDLE_VALUE) return -1; FindClose(h); ULARGE_INTEGER i; i.HighPart = f.nFileSizeHigh; i.LowPart = f.nFileSizeLow; return i.QuadPart; } static bool copySettings(wchar_t * ssrc, wchar_t * sdest) { FILE * src, * dest; src=_wfopen(ssrc,L"rt"); if(!src) return false; dest=_wfopen(sdest,L"wt"); if(!dest) { fclose(src); return false; } wchar_t buf[1024]=L""; bool insection=false; while(fgetws(buf,1024,src)) { if(buf[0]==L'[' && wcslen(buf)>1) if(buf[wcslen(buf)-2]==L']') insection=false; if(wcscmp(&buf[0],L"[ml_pmp]\n")==0) insection=true; if(insection) fputws(&buf[0],dest); } fclose(src); fclose(dest); return true; } HNAVITEM DeviceView::AddPlaylistNode(int id) { NAVINSERTSTRUCT nis = {0}; wchar_t title[256] = {0}, name[128] = {0}; dev->getPlaylistName(id, title , ARRAYSIZE(title)); StringCchPrintf(name, ARRAYSIZE(name), L"ml_pmp_playlist_%d", id); nis.hParent = treeItem; nis.hInsertAfter = NCI_LAST; memset(&nis.item, 0, sizeof(nis.item)); nis.item.cbSize = sizeof(NAVITEM); nis.item.pszText = title; nis.item.pszInvariant = name; nis.item.mask = NIMF_TEXT | NIMF_TEXTINVARIANT | NIMF_IMAGE | NIMF_IMAGESEL | NIMF_STYLE; nis.item.iImage = icon_store.GetPlaylistIcon(); nis.item.iSelectedImage = nis.item.iImage; nis.item.style = NIS_ALLOWEDIT; nis.item.styleMask = nis.item.style; HNAVITEM item = MLNavCtrl_InsertItem(plugin.hwndLibraryParent, &nis); playlistTreeItems.push_back(item); return item; } int DeviceView::CreatePlaylist(wchar_t * name, bool silent) { HNAVITEM item; int playlistId; if (NULL == name) { int count, slot, length; wchar_t buffer[512] = {0}, buffer2[ARRAYSIZE(buffer)] = {0}; BOOL foundMatch; name = WASABI_API_LNGSTRINGW_BUF(IDS_NEW_PLAYLIST, buffer, ARRAYSIZE(buffer)); count = dev->getPlaylistCount(); slot = 1; length = -1; do { foundMatch = FALSE; for (int i = 1; i < count; i++) { dev->getPlaylistName(i, buffer2, ARRAYSIZE(buffer2)); if (CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT, NORM_IGNORECASE, buffer2, -1, name, -1)) { foundMatch = TRUE; if(name != buffer) { if (FAILED(StringCchCopy(buffer, ARRAYSIZE(buffer), name))) return -1; } if (-1 == length) { wchar_t *end; length = lstrlen(buffer); end = buffer + length; if(length > 2 && L')' == *(--end)) { unsigned short charType; for(wchar_t *begin = --end; begin != buffer; begin--) { if (L'(' == *begin) { if (begin > buffer && L' ' == *(--begin)) { length = (int)(intptr_t)(begin - buffer); slot = 0; } break; } else if (FALSE == GetStringTypeW(CT_CTYPE1, begin, 1, &charType) || 0 == (C1_DIGIT & charType)) { break; } } } } slot++; if (1 == slot) buffer[length] = L'\0'; else if (FAILED(StringCchPrintf(buffer + length, ARRAYSIZE(buffer) - length, L" (%d)", slot))) return false; break; } } } while(FALSE != foundMatch); } playlistId = dev->newPlaylist(name); if(playlistId == -1) return -1; // failed item = AddPlaylistNode(playlistId); if (NULL == item) { dev->deletePlaylist(playlistId); return -1; } DevicePropertiesChanges(); if(!silent) MLNavItem_EditTitle(plugin.hwndLibraryParent, item); return playlistId; } void DeviceView::RenamePlaylist(int playlistId) { HNAVITEM item; item = NULL; if (0 == playlistId) { if (0 != dev->extraActions(DEVICE_CAN_RENAME_DEVICE,0,0,0)) item = treeItem; } else { if (playlistId > 0 && playlistId <= (int)playlistTreeItems.size()) item = playlistTreeItems[playlistId - 1]; } if (NULL != item) MLNavItem_EditTitle(plugin.hwndLibraryParent, item); } size_t DeviceView::GetPlaylistName(wchar_t *buffer, size_t bufferSize, int playlistId, const wchar_t *defaultName, BOOL quoteSpaces) { size_t length; if (NULL == buffer || 0 == bufferSize) return 0; buffer[0] = L'\0'; if (NULL != dev) dev->getPlaylistName(playlistId, buffer, bufferSize); if (FAILED(StringCchLength(buffer, bufferSize, &length))) return 0; if (0 == length) { if (NULL != defaultName) { if (FALSE != IS_INTRESOURCE(defaultName)) WASABI_API_LNGSTRINGW_BUF((int)(intptr_t)defaultName, buffer, bufferSize); else { if (FAILED(StringCchCopy(buffer, bufferSize, defaultName))) return 0; } if (FAILED(StringCchLength(buffer, bufferSize, &length))) return 0; } } else { if (FALSE != quoteSpaces && (L' ' == buffer[0] || L' ' == buffer[length-1]) && (bufferSize - length) > 2) { memmove(buffer + 1, buffer, sizeof(wchar_t) * length); buffer[0] = L'\"'; buffer[length++] = L'\"'; buffer[length] = L'\0'; } } return length; } bool DeviceView::DeletePlaylist(int playlistId, bool deleteFiles, bool verbal) { int index; C_ItemList delList; if(playlistId < 1) return false; index = playlistId - 1; if (false != deleteFiles) { int length; length = dev->getPlaylistLength(playlistId); for(int i = 0; i < length; i++) { delList.Add((void*)dev->getPlaylistTrack(playlistId, i)); } } if (false != verbal) { wchar_t message[1024] = {0}, title[1024] = {0}, playlistName[256] = {0}, deviceName[256] = {0}; GetPlaylistName(playlistName, ARRAYSIZE(playlistName), playlistId, NULL, FALSE); GetPlaylistName(deviceName, ARRAYSIZE(deviceName), 0, MAKEINTRESOURCE(IDS_DEVICE_LOWERCASE), TRUE); if (0 != delList.GetSize()) { WASABI_API_LNGSTRINGW_BUF(IDS_PHYSICALLY_REMOVE_X_TRACKS, title, ARRAYSIZE(title)); StringCchPrintf(message, ARRAYSIZE(message), title, delList.GetSize(), playlistName); WASABI_API_LNGSTRINGW_BUF(IDS_DELETE_PLAYLIST_TITLE, title, ARRAYSIZE(title)); } else { WASABI_API_LNGSTRINGW_BUF(IDS_DELETE_PLAYLIST, title, ARRAYSIZE(title)); StringCchPrintf(message, ARRAYSIZE(message), title, playlistName, deviceName); WASABI_API_LNGSTRINGW_BUF(IDS_DELETE_PLAYLIST_TITLE, title, ARRAYSIZE(title)); } if(IDYES != MessageBox(plugin.hwndLibraryParent, message, title, MB_YESNO | MB_ICONWARNING)) { return false; } } if (0 != delList.GetSize()) { int result; result = DeleteTracks(&delList, CENTER_OVER_ML_VIEW); if (IDABORT == result) /* user abort */ return false; if (IDOK != result) /* error */ { } } HNAVITEM item = playlistTreeItems[index]; playlistTreeItems.erase(playlistTreeItems.begin() + index); MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, item); dev->deletePlaylist(playlistId); DevicePropertiesChanges(); return true; } bool DeviceView::GetTransferFromMlSupported(int dataType) { switch(dataType) { case ML_TYPE_ITEMRECORDLISTW: case ML_TYPE_ITEMRECORDLIST: case ML_TYPE_PLAYLIST: case ML_TYPE_PLAYLISTS: case ML_TYPE_FILENAMES: case ML_TYPE_FILENAMESW: return true; } return false; } intptr_t DeviceView::TransferFromML(int type, void* data, int unsupportedReturn, int supportedReturn, int playlist) { int r; if (AGAVE_API_STATS) { wchar_t device_name[128] = {0}; if(dev->extraActions(DEVICE_GET_MODEL, (intptr_t)device_name, 128, 0) == 1 && device_name[0]) { AGAVE_API_STATS->SetString("pmp", device_name); } } if(type == ML_TYPE_ITEMRECORDLISTW) { r=AddItemListToTransferQueue((itemRecordListW*)data,playlist); } else if(type == ML_TYPE_ITEMRECORDLIST) { itemRecordListW list= {0}; convertRecordList(&list,(itemRecordList*)data); r=AddItemListToTransferQueue(&list,playlist); freeRecordList(&list); } else if(type == ML_TYPE_FILENAMES) { C_ItemList fileList; char * filenames = (char *)data; while(filenames && *filenames) { fileList.Add(filenames); filenames+=strlen(filenames)+1; } r=AddFileListToTransferQueue((char**)fileList.GetAll(),fileList.GetSize(),playlist); } else if(type == ML_TYPE_FILENAMESW) { C_ItemList fileList; wchar_t * filenames = (wchar_t *)data; while(filenames && *filenames) { fileList.Add(filenames); filenames+=wcslen(filenames)+1; } r=AddFileListToTransferQueue((wchar_t**)fileList.GetAll(),fileList.GetSize(),playlist); } else if(type == ML_TYPE_PLAYLIST) { mlPlaylist * pl = (mlPlaylist *)data; TransferPlaylist((wchar_t*)pl->filename,(wchar_t*)pl->title); r=0; } else if(type == ML_TYPE_PLAYLISTS) { mlPlaylist **playlists = (mlPlaylist **)data; while(playlists && *playlists) { mlPlaylist *pl = *playlists; TransferPlaylist((wchar_t*)pl->filename,(wchar_t*)pl->title); playlists++; } r=0; } else return unsupportedReturn; wchar_t errStr[32] = {0}; if(r==-1) MessageBox(plugin.hwndLibraryParent, WASABI_API_LNGSTRINGW(IDS_DEVICE_OUT_OF_SPACE), WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,errStr,32),0); else if(r==-2) MessageBox(plugin.hwndLibraryParent, WASABI_API_LNGSTRINGW(IDS_INCOMPATABLE_FORMAT_NO_TX), WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,errStr,32),0); return supportedReturn; } class ItemListRefLoader : public ifc_playlistloadercallback { public: ItemListRefLoader(C_ItemList &itemList, C_ItemList &playlistItemList) : fileList(itemList), playlistList(playlistItemList) { } void OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info) { if(playlistManager->CanLoad(filename)) playlistList.Add(_wcsdup(filename)); else fileList.Add(_wcsdup(filename)); } C_ItemList &fileList, &playlistList; protected: RECVS_DISPATCH; }; #define CBCLASS ItemListRefLoader START_DISPATCH; VCB( IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile ) END_DISPATCH; #undef CBCLASS class PlaylistDirectoryCallback : public ifc_playlistdirectorycallback { public: PlaylistDirectoryCallback(const wchar_t *_extlist) : extlist(_extlist) { } bool ShouldRecurse(const wchar_t *path) { // TODO: check for recursion? return true; } bool ShouldLoad(const wchar_t *filename) { if(playlistManager->CanLoad(filename)) return true; const wchar_t *ext = PathFindExtensionW(filename); if(!*ext) return false; ext++; const wchar_t *a = extlist; while(a && *a) { if(!_wcsicmp(a, ext)) return true; a += wcslen(a) + 1; } return false; } const wchar_t *extlist; protected: RECVS_DISPATCH; }; #define CBCLASS PlaylistDirectoryCallback START_DISPATCH; CB(IFC_PLAYLISTDIRECTORYCALLBACK_SHOULDRECURSE, ShouldRecurse) CB(IFC_PLAYLISTDIRECTORYCALLBACK_SHOULDLOAD, ShouldLoad) END_DISPATCH; #undef CBCLASS intptr_t DeviceView::TransferFromDrop(HDROP hDrop, int playlist) { // benski> ugh. memory allocation hell. oh well C_ItemList fileList; C_ItemList playlistList; const wchar_t *extListW = (const wchar_t *)SendMessageW(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_EXTLISTW); PlaylistDirectoryCallback dirCB(extListW); wchar_t temp[2048] = {0}; int y = DragQueryFileW(hDrop, 0xffffffff, temp, 2048); for(int x = 0; x < y; x ++) { DragQueryFileW(hDrop, x, temp, 2048); // see if it's a directory bool isDir=false; if(!PathIsURLW(temp) && !PathIsNetworkPathW(temp)) { HANDLE h; WIN32_FIND_DATAW d; h = FindFirstFileW(temp, &d); if(h != INVALID_HANDLE_VALUE) { FindClose(h); if(d.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { ItemListRefLoader fileListCB(fileList, playlistList); playlistManager->LoadDirectory(temp, &fileListCB, &dirCB); isDir=true; } } } if(!isDir) { if(playlistManager->CanLoad(temp)) playlistList.Add(_wcsdup(temp)); else fileList.Add(_wcsdup(temp)); } } int r=0, r2=0; if(fileList.GetSize()) r=AddFileListToTransferQueue((wchar_t**)fileList.GetAll(),fileList.GetSize(),playlist); #if 0 if(playlistList.GetSize()) r2=AddFileListToTransferQueue((wchar_t**)playlistList.GetAll(), playlistList.GetSize(),1/*playlists*/); #endif wchar_t errStr[32] = {0}; if(r==-1 || r2==-1) MessageBox(plugin.hwndLibraryParent, WASABI_API_LNGSTRINGW(IDS_DEVICE_OUT_OF_SPACE), WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,errStr,32),0); if(r==-2 || r2 == -2) MessageBox(plugin.hwndLibraryParent, WASABI_API_LNGSTRINGW(IDS_INCOMPATABLE_FORMAT_NO_TX), WASABI_API_LNGSTRINGW_BUF(IDS_ERROR,errStr,32),0); // benski> my CS301 professor would be proud! fileList.for_all(free); playlistList.for_all(free); GlobalFree((HGLOBAL)extListW); return 0; } HWND DeviceView::CreateView(HWND parent) { currentViewedDevice=this; currentViewedPlaylist=0; if (currentViewedDevice->config->ReadInt(L"media_numfilters", 2) == 1) return WASABI_API_CREATEDIALOGPARAMW((isCloudDevice ? IDD_VIEW_CLOUD_SIMPLE : IDD_VIEW_PMP_VIDEO), parent, pmp_video_dlgproc, (currentViewedDevice->isCloudDevice ? 1 : 2)); else return WASABI_API_CREATEDIALOGPARAMW((isCloudDevice ? IDD_VIEW_CLOUD_ARTISTALBUM : IDD_VIEW_PMP_ARTISTALBUM), parent, pmp_artistalbum_dlgproc, 0); } BOOL DeviceView::DisplayDeviceContextMenu(HNAVITEM item, HWND hostWindow, POINT pt) { HMENU menu = GetSubMenu(m_context_menus,3); if (NULL == menu) return FALSE; if(dev->extraActions(DEVICE_CAN_RENAME_DEVICE,0,0,0)) AppendMenu(menu,0,ID_TREEPLAYLIST_RENAMEPLAYLIST,WASABI_API_LNGSTRINGW(IDS_RENAME_DEVICE)); int r = Menu_TrackSkinnedPopup(menu, TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_LEFTBUTTON|TPM_NONOTIFY, pt.x, pt.y, plugin.hwndLibraryParent, NULL); if(dev->extraActions(DEVICE_CAN_RENAME_DEVICE,0,0,0)) DeleteMenu(menu,ID_TREEPLAYLIST_RENAMEPLAYLIST,MF_BYCOMMAND); switch(r) { case ID_TREEDEVICE_NEWPLAYLIST: CreatePlaylist(); break; case ID_TREEDEVICE_EJECTDEVICE: Eject(); break; case ID_TREEPLAYLIST_RENAMEPLAYLIST: MLNavItem_EditTitle(plugin.hwndLibraryParent, item); //RenamePlaylist(0); break; } return TRUE; } BOOL DeviceView::DisplayPlaylistContextMenu(int playlistId, HNAVITEM item, HWND hostWindow, POINT pt) { HMENU menu = GetSubMenu(m_context_menus,4); if (NULL == menu) return FALSE; EnableMenuItem(menu,ID_TREEPLAYLIST_COPYPLAYLISTTOLOCALMEDIA, MF_BYCOMMAND | (dev->copyToHardDriveSupported()? MF_ENABLED : (MF_DISABLED | MF_GRAYED))); int r = Menu_TrackSkinnedPopup(menu,TPM_RETURNCMD|TPM_RIGHTBUTTON|TPM_LEFTBUTTON|TPM_NONOTIFY, pt.x, pt.y, plugin.hwndLibraryParent, NULL); switch(r) { case ID_TREEPLAYLIST_RENAMEPLAYLIST: MLNavItem_EditTitle(plugin.hwndLibraryParent, item); break; case ID_TREEPLAYLIST_REMOVEPLAYLISTANDFILES: DeletePlaylist(playlistId, true, true); break; case ID_TREEPLAYLIST_REMOVEPLAYLIST: DeletePlaylist(playlistId, false, true); break; case ID_TREEPLAYLIST_COPYPLAYLISTTOLOCALMEDIA: CopyPlaylistToLibrary(playlistId); break; } return TRUE; } static HNAVITEM Navigation_GetItemFromMessage(INT msg, INT_PTR param) { return (msg < ML_MSG_NAVIGATION_FIRST) ? MLNavCtrl_FindItemById(plugin.hwndLibraryParent, param) : (HNAVITEM)param; } BOOL DeviceView::Navigation_IsPlaylistItem(HNAVITEM item, int *playlistId) { for(size_t i=0; i < playlistTreeItems.size(); i++) { if(item == playlistTreeItems[i]) { if (NULL != playlistId) *playlistId = (i + 1); return TRUE; } } return FALSE; } HWND DeviceView::Navigation_CreateViewCb(HNAVITEM item, HWND parentWindow) { if(item == treeItem) { if (FALSE != navigationItemCreated) { currentViewedDevice = this; currentViewedPlaylist = 0; return WASABI_API_CREATEDIALOGW((currentViewedDevice->isCloudDevice ? IDD_VIEW_CLOUD_ARTISTALBUM : IDD_VIEW_PMP_ARTISTALBUM), parentWindow, pmp_artistalbum_dlgproc); } } else if (item == queueTreeItem) { currentViewedDevice = this; currentViewedPlaylist = 0; return WASABI_API_CREATEDIALOGPARAMW((currentViewedDevice->isCloudDevice ? IDD_VIEW_CLOUD_QUEUE : IDD_VIEW_PMP_QUEUE), parentWindow, pmp_queue_dlgproc, (LPARAM)this); } else if(item == videoTreeItem) { currentViewedDevice = this; currentViewedPlaylist = 0; return WASABI_API_CREATEDIALOGPARAMW(IDD_VIEW_PMP_VIDEO, parentWindow, pmp_video_dlgproc, 0); } else { if (FALSE != Navigation_IsPlaylistItem(item, ¤tViewedPlaylist)) { currentViewedDevice = this; return WASABI_API_CREATEDIALOGW(IDD_VIEW_PMP_PLAYLIST, parentWindow, pmp_playlist_dlgproc); } } return NULL; } BOOL DeviceView::Navigation_ShowContextMenuCb(HNAVITEM item, HWND hostWindow, POINT pt) { if (item == treeItem) { if (FALSE != navigationItemCreated) return DisplayDeviceContextMenu(item, hostWindow, pt); } else { int playlistId; if (FALSE != Navigation_IsPlaylistItem(item, &playlistId)) return DisplayPlaylistContextMenu(playlistId, item, hostWindow, pt); } return FALSE; } BOOL DeviceView::Navigation_ClickCb(HNAVITEM item, int actionType, HWND hostWindow) { int playlistId; switch(actionType) { case ML_ACTION_DBLCLICK: case ML_ACTION_ENTER: if (FALSE != Navigation_IsPlaylistItem(item, &playlistId)) { PlayPlaylist(playlistId, false, true, hostWindow); return TRUE; } break; } return FALSE; } int DeviceView::Navigation_DropTargetCb(HNAVITEM item, unsigned int dataType, void *data) { if (item == treeItem) { if (FALSE != navigationItemCreated) { if(NULL == data) return (false != GetTransferFromMlSupported(dataType)) ? 1 : -1; return TransferFromML(dataType, data, -1, 1); } } else { int playlistId; if (FALSE != Navigation_IsPlaylistItem(item, &playlistId)) { if(NULL == data) return (false != GetTransferFromMlSupported(dataType)) ? 1 : -1; return TransferFromML(dataType, data, -1, 1, playlistId); } } return FALSE; } BOOL DeviceView::Navigation_TitleEditBeginCb(HNAVITEM item) { if (item == treeItem) { if (FALSE != navigationItemCreated && FALSE == dev->extraActions(DEVICE_CAN_RENAME_DEVICE,0,0,0)) { return TRUE; } } return FALSE; } BOOL DeviceView::Navigation_TitleEditEndCb(HNAVITEM item, const wchar_t *title) { int playlistId = 0; wchar_t buffer[512] = {0}; if (item == treeItem) { if (FALSE == navigationItemCreated) return FALSE; playlistId = 0; } else { if (FALSE == Navigation_IsPlaylistItem(item, &playlistId)) return FALSE; } if (NULL == title) return TRUE; buffer[0] = L'\0'; dev->getPlaylistName(playlistId, buffer, ARRAYSIZE(buffer)); if (CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT, 0, buffer, -1, title, -1)) return TRUE; dev->setPlaylistName(playlistId, title); DevicePropertiesChanges(); buffer[0] = L'\0'; dev->getPlaylistName(playlistId, buffer, ARRAYSIZE(buffer)); if (0 == playlistId) { free(devPrefsPage.name); devPrefsPage.name = _wcsdup(buffer); UpdateDevicesListView(false); OnNameChanged(buffer); } if (CSTR_EQUAL == CompareString(LOCALE_USER_DEFAULT, 0, buffer, -1, title, -1)) return TRUE; NAVITEM itemInfo; itemInfo.cbSize = sizeof(itemInfo); itemInfo.pszText = buffer; itemInfo.hItem = item; itemInfo.mask = NIMF_TEXT; MLNavItem_SetInfo(plugin.hwndLibraryParent, &itemInfo); return FALSE; } BOOL DeviceView::Navigation_KeyDownCb(HNAVITEM item, NMTVKEYDOWN *keyData, HWND hwnd) { int playlistId; if (item == treeItem) { if (FALSE == navigationItemCreated) return FALSE; switch(keyData->wVKey) { case VK_F2: MLNavItem_EditTitle(plugin.hwndLibraryParent, item); break; } return TRUE; } if (FALSE != Navigation_IsPlaylistItem(item, &playlistId)) { switch(keyData->wVKey) { case VK_F2: MLNavItem_EditTitle(plugin.hwndLibraryParent, item); break; case VK_DELETE: DeletePlaylist(playlistId, false, true); break; } return TRUE; } return FALSE; } intptr_t DeviceView::MessageProc(int message_type, intptr_t param1, intptr_t param2, intptr_t param3) { if(message_type >= ML_MSG_TREE_BEGIN && message_type <= ML_MSG_TREE_END) { HNAVITEM item; switch(message_type) { case ML_MSG_TREE_ONCREATEVIEW: item = Navigation_GetItemFromMessage(message_type, param1); return (INT_PTR)Navigation_CreateViewCb(item, (HWND)param2); case ML_MSG_NAVIGATION_CONTEXTMENU: { POINT pt; POINTSTOPOINT(pt, MAKEPOINTS(param3)); item = Navigation_GetItemFromMessage(message_type, param1); return (INT_PTR)Navigation_ShowContextMenuCb(item, (HWND)param2, pt); } case ML_MSG_TREE_ONCLICK: item = Navigation_GetItemFromMessage(message_type, param1); return (INT_PTR)Navigation_ClickCb(item, (int)param2, (HWND)param3); case ML_MSG_TREE_ONDROPTARGET: item = Navigation_GetItemFromMessage(message_type, param1); return (INT_PTR)Navigation_DropTargetCb(item, (unsigned int)param2, (void*)param3); case ML_MSG_NAVIGATION_ONBEGINTITLEEDIT: item = Navigation_GetItemFromMessage(message_type, param1); return (INT_PTR)Navigation_TitleEditBeginCb(item); case ML_MSG_NAVIGATION_ONENDTITLEEDIT: item = Navigation_GetItemFromMessage(message_type, param1); return (INT_PTR)Navigation_TitleEditEndCb(item, (const wchar_t*)param2); case ML_MSG_TREE_ONKEYDOWN: item = Navigation_GetItemFromMessage(message_type, param1); return (INT_PTR)Navigation_KeyDownCb(item, (NMTVKEYDOWN*)param2, (HWND)param3); } } else if(message_type == ML_MSG_ONSENDTOBUILD) { if (!gen_mlconfig->ReadInt(L"pmp_send_to", DEFAULT_PMP_SEND_TO)) { if (param1 == ML_TYPE_ITEMRECORDLIST || param1 == ML_TYPE_ITEMRECORDLISTW || param1 == ML_TYPE_FILENAMES || param1 == ML_TYPE_FILENAMESW || param1 == ML_TYPE_PLAYLIST || param1 == ML_TYPE_PLAYLISTS) { if (dev->extraActions(DEVICE_SENDTO_UNSUPPORTED, 0, 0, 0) == 0) { wchar_t buffer[128] = {0}; dev->getPlaylistName(0, buffer, 128); mediaLibrary.AddToSendTo(buffer, param2, reinterpret_cast(this)); } } } } else if(message_type == ML_MSG_ONSENDTOSELECT && param2 && param3 == (intptr_t)this) { // TODO!!! // if we get a playlist or playlist list and we can match it to a cloud device then // we check for 'hss' and if so then process as a cloud playlist else do as before if (this->isCloudDevice && (param1 == ML_TYPE_PLAYLIST || param1 == ML_TYPE_PLAYLISTS)) { char name[128] = {0}; if (dev->extraActions(DEVICE_GET_UNIQUE_ID, (intptr_t)name, sizeof(name), 0)) { if (!strcmp(name, "hss"/*HSS_CLIENT*/)) { if(param1 == ML_TYPE_PLAYLIST) { mlPlaylist * pl = (mlPlaylist *)param2; TransferAddCloudPlaylist((wchar_t*)pl->filename, (wchar_t*)pl->title); } else if(param1 == ML_TYPE_PLAYLISTS) { mlPlaylist **playlists = (mlPlaylist **)param2; while(playlists && *playlists) { mlPlaylist *pl = *playlists; TransferAddCloudPlaylist((wchar_t*)pl->filename, (wchar_t*)pl->title); playlists++; } } return 1; } } } UpdateActivityState(); return TransferFromML(param1,(void*)param2,0,1); } return 0; } void DeviceView::DevicePropertiesChanges() { commitNeeded=true; SetEvent(transferContext.notifier); } void DeviceView::Eject() { LinkedQueue * txQueue = getTransferQueue(this); if(txQueue && txQueue->GetSize() == 0) { dev->Eject(); } else { wchar_t titleStr[32] = {0}; MessageBox(plugin.hwndLibraryParent,WASABI_API_LNGSTRINGW(IDS_SYNC_IS_IN_PROGRESS), WASABI_API_LNGSTRINGW_BUF(IDS_CANNOT_EJECT,titleStr,32),0); } } Device * deleteTrackDev; C_ItemList * deleteTracks; extern HWND hwndMediaView; static INT_PTR CALLBACK pmp_delete_progress_dlgproc(HWND hwndDlg, UINT uMsg, WPARAM wParam,LPARAM lParam) { static int i; static songid_t s; switch(uMsg) { case WM_INITDIALOG: i=0; s=0; SetWindowText(hwndDlg,WASABI_API_LNGSTRINGW((!currentViewedDevice || currentViewedDevice && !currentViewedDevice->isCloudDevice ? IDS_DELETING_TRACKS : IDS_REMOVING_TRACKS))); SendDlgItemMessage(hwndDlg,IDC_PROGRESS,PBM_SETRANGE,0,MAKELPARAM(0, (!deleteTracks ? 0 : deleteTracks->GetSize()))); SetTimer(hwndDlg,0,5,NULL); if (FALSE != CenterWindow(hwndDlg, (HWND)lParam)) SendMessage(hwndDlg, DM_REPOSITION, 0, 0L); break; case WM_TIMER: if(wParam == 1) { KillTimer(hwndDlg,wParam); SendDlgItemMessage(hwndDlg,IDC_PROGRESS,PBM_SETPOS,i,0); if(i < deleteTracks->GetSize()) { songid_t s2 = (songid_t)deleteTracks->Get(i++); if(s != s2) { if(hwndMediaView) SendMessage(hwndMediaView,WM_USER+1,(WPARAM)s2,0); deleteTrackDev->deleteTrack(s2); s=s2; } SetTimer(hwndDlg,1,5,NULL); } else EndDialog(hwndDlg, IDOK); } else if(wParam == 0) { KillTimer(hwndDlg,0); int s = deleteTracks->GetSize(); for(int i=0; iGet(i); for(int j=i+1; jGet(j)) { deleteTracks->Del(j--); s--; } } } SetTimer(hwndDlg,1,5,NULL); } break; case WM_COMMAND: if(LOWORD(wParam) == IDC_ABORT) EndDialog(hwndDlg, IDABORT); break; } return 0; } int DeviceView::DeleteTracks(C_ItemList * tracks, HWND centerWindow) { LinkedQueue * txQueue = getTransferQueue(this); if(txQueue && txQueue->GetSize() > 0) { wchar_t sorry[32] = {0}; MessageBox(plugin.hwndLibraryParent,WASABI_API_LNGSTRINGW(IDS_CANNOT_REMOVE_TRACKS_WHILE_TRANSFERING), WASABI_API_LNGSTRINGW_BUF(IDS_SORRY,sorry,32),0); return -1; } if (dev && tracks) { deleteTrackDev = dev; deleteTracks = tracks; return WASABI_API_DIALOGBOXPARAMW(IDD_PROGRESS, plugin.hwndLibraryParent, pmp_delete_progress_dlgproc, (LPARAM)centerWindow); } return IDABORT; } int DeviceView::AddFileListToTransferQueue(char ** files, int num, int playlist) { wchar_t ** filesW = (wchar_t**)calloc(num, sizeof(wchar_t*)); for(int i=0; iGetSize(); i++) { itemRecordW * it = (itemRecordW *)irs->Get(i); freeRecord(it); free(it); } delete irs; return r; } void TransfersListPushPopItem(CopyInst * item, DeviceView *view); int DeviceView::AddItemListToTransferQueue(C_ItemList * items, int playlist) { if(playlist == 0) { if (!isCloudDevice) { int r=0; C_ItemList toSend, haveSent; ProcessDatabaseDifferences(dev,items,&haveSent,&toSend,NULL,NULL); LinkedQueue * txQueue = getTransferQueue(this); if (txQueue) { txQueue->lock(); for(int i = 0; i < toSend.GetSize(); i++) { if((r = this->AddTrackToTransferQueue(this, (itemRecordW*)toSend.Get(i), true)) == -1) break; } txQueue->unlock(); } return r; } else { int r=0; LinkedQueue * txQueue = getTransferQueue(this); if (txQueue) { txQueue->lock(); for(int i = 0; i < items->GetSize(); i++) { if((r = this->AddTrackToTransferQueue(this, (itemRecordW*)items->Get(i), true)) == -1) break; } txQueue->unlock(); } return r; } } else { return TransferTracksToPlaylist(items,playlist); } } int DeviceView::AddItemListToTransferQueue(itemRecordListW * items, int playlist) { if(playlist == 0) { if (!isCloudDevice) { int r=0; C_ItemList toSend; ProcessDatabaseDifferences(dev,items,NULL,&toSend,NULL,NULL); LinkedQueue * txQueue = getTransferQueue(this); if (txQueue) { txQueue->lock(); for (int i = 0; i < toSend.GetSize(); i++) if((r = this->AddTrackToTransferQueue(this, (itemRecordW*)toSend.Get(i), true)) == -1) break; txQueue->unlock(); } return r; } else { int r=0; LinkedQueue * txQueue = getTransferQueue(this); if (txQueue) { txQueue->lock(); for (int i = 0; i < items->Size; i++) if((r = this->AddTrackToTransferQueue(this, &items->Items[i], true)) == -1) break; txQueue->unlock(); } return r; } } else { C_ItemList itemRecords; for (int i = 0; i < items->Size; i++) itemRecords.Add(&items->Items[i]); return TransferTracksToPlaylist(&itemRecords,playlist); } } class ItemListLoader : public ifc_playlistloadercallback { public: void OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info) { fileList.Add(_wcsdup(filename)); } void FreeAll() { fileList.for_all(free); } C_ItemList fileList; protected: RECVS_DISPATCH; }; #define CBCLASS ItemListLoader START_DISPATCH; VCB( IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile ) END_DISPATCH; #undef CBCLASS void DeviceView::TransferPlaylist(wchar_t * file, wchar_t * name0) { // first sort the name out if(!file) return; wchar_t name[256] = {0}; if(!name0) { wchar_t * s = wcsrchr(file,L'\\'); if(!s) s = wcsrchr(file,L'/'); if(!s) s = file; else s++; if(wcslen(s) >= 255) s[255]=0; StringCchCopy(name,256, s); wchar_t * e = wcsrchr(name,L'.'); if(e) *e=0; } else lstrcpyn(name,name0,255); name[255]=0; // name sorted, parse m3u ItemListLoader fileList; playlistManager->Load(file, &fileList); C_ItemList *itemRecords = fileListToItemRecords(&fileList.fileList, CENTER_OVER_ML_VIEW); fileList.FreeAll(); // now we have a list of itemRecords, lets try and add this playlist to the device! int plid = CreatePlaylist(name,true); if(plid != -1) TransferTracksToPlaylist(itemRecords,plid); delete itemRecords; } int DeviceView::TransferTracksToPlaylist(C_ItemList *itemRecords, int plid) { wchar_t name[256] = {0}; dev->getPlaylistName(plid,name,256); int i; for(i=0; iGetSize(); i++) { itemRecordW * ice = (itemRecordW *)itemRecords->Get(i); wchar_t num[12] = {0}; StringCchPrintf(num, 12, L"%x",i); setRecordExtendedItem(ice,L"PLN",num); } C_ItemList irAlreadyOn, siAlreadyOn; ProcessDatabaseDifferences(dev,itemRecords,&irAlreadyOn,NULL,&siAlreadyOn,NULL); // itemRecords_sort, irAlreadyOn, irTransfer and siAlreadyOn will NOT be in playlist order // we must get them into playlist order. In O(n) :/ int l = itemRecords->GetSize(); PlaylistAddItem * pl = (PlaylistAddItem*)calloc(l,sizeof(PlaylistAddItem)); int on=0; for(i=0; i < l; i++) { itemRecordW * ice = (itemRecordW *)itemRecords->Get(i); int n; swscanf(getRecordExtendedItem(ice,L"PLN"),L"%x",&n); if(n >= l) { continue; } pl[n].item = ice; if(on < irAlreadyOn.GetSize()) if((itemRecordW*)irAlreadyOn.Get(on) == ice) // this track is on the device! { pl[n].songid = (songid_t)siAlreadyOn.Get(on); on++; } } // awesome! pl now contains our playlist in proper order with the "songid" fields set if the track is on the device. C_ItemList * directAdd = new C_ItemList; int m = 0; LinkedQueue * txQueue = getTransferQueue(this); if (txQueue) { PlaylistCopyInst * inst = NULL; txQueue->lock(); for(i=0; i < l; i++) { if(pl[i].songid) { directAdd->Add((void*)pl[i].songid); } else { int r = dev->trackAddedToTransferQueue(pl[i].item); if(r) { m |= (-r); freeRecord(pl[i].item); continue; } if(!inst) { if(plid != -1) for(int i=0; iGetSize(); i++) dev->addTrackToPlaylist(plid,(songid_t)directAdd->Get(i)); delete directAdd; } else { inst->plAddSongs = directAdd; AddTrackToTransferQueue(inst); } directAdd = new C_ItemList; inst = new PlaylistCopyInst(this,pl[i].item,name,plid); } freeRecord(pl[i].item); } if(inst) { inst->plAddSongs = directAdd; AddTrackToTransferQueue(inst); } else // NULL inst means no transfers! { if(plid != -1) for(int i=0; iGetSize(); i++) dev->addTrackToPlaylist(plid,(songid_t)directAdd->Get(i)); delete directAdd; } txQueue->unlock(); } if (pl) free(pl); wchar_t warnStr[32] = {0}; WASABI_API_LNGSTRINGW_BUF(IDS_WARNING,warnStr,32); if(m == 1) MessageBox(plugin.hwndLibraryParent,WASABI_API_LNGSTRINGW(IDS_NATS_DEVICE_MAYBE_FULL),warnStr,0); else if(m == 2) MessageBox(plugin.hwndLibraryParent,WASABI_API_LNGSTRINGW(IDS_NATS_SOME_OF_INCOMPATABLE_FORMAT),warnStr,0); else if(m == 3) MessageBox(plugin.hwndLibraryParent,WASABI_API_LNGSTRINGW(IDS_NATS_MAYBE_FULL_AND_INCOMPATABLE_FORMAT),warnStr,0); return 0; } void DeviceView::TransferAddCloudPlaylist(wchar_t * file, wchar_t * name0) { if (!file) return; AGAVE_API_PLAYLISTS->Lock(); for (size_t index = 0; index < AGAVE_API_PLAYLISTS->GetCount(); index++) { const wchar_t* filename = AGAVE_API_PLAYLISTS->GetFilename(index); if (!lstrcmpiW(filename, file)) { int cloud = 1; if (AGAVE_API_PLAYLISTS->GetInfo(index, api_playlists_cloud, &cloud, sizeof(cloud)) == API_PLAYLISTS_SUCCESS) { // not set as a cloud playlist so we need to set and then announce if (!cloud) { cloud = 1; AGAVE_API_PLAYLISTS->SetInfo(index, api_playlists_cloud, &cloud, sizeof(cloud)); AGAVE_API_PLAYLISTS->Flush(); } if (!cloud_hinst) cloud_hinst = (HINSTANCE)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_HINST); if (cloud_hinst && cloud_hinst != (HINSTANCE)1) { winampMediaLibraryPlugin *(*gp)(); gp = (winampMediaLibraryPlugin * (__cdecl *)(void))GetProcAddress(cloud_hinst, "winampGetMediaLibraryPlugin"); if (gp) { winampMediaLibraryPlugin *mlplugin = gp(); if (mlplugin && (mlplugin->version >= MLHDR_VER_OLD && mlplugin->version <= MLHDR_VER)) { mlplugin->MessageProc(0x406, index, 0, 0); } } } PostMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_LIBRARY_PLAYLISTS_REFRESH); } else { } break; } } AGAVE_API_PLAYLISTS->Unlock(); } extern MLTREEITEMW mainTreeItem; HWND hwndToolTips=NULL; int DeviceView::AddTrackToTransferQueue(CopyInst * inst) { LinkedQueue * txQueue = getTransferQueue(this); if (txQueue) { txQueue->Offer(inst); if(txQueue->GetSize() == 1) SetEvent(transferContext.notifier); device_update_map[0] = true; device_update_map[inst->dev] = true; } return 0; } int DeviceView::AddTrackToTransferQueue(DeviceView * device, itemRecordW * item, bool dupeCheck, bool forceDupe) { SongCopyInst * inst = new SongCopyInst(device, item); if (dupeCheck) { LinkedQueue * txQueue = getTransferQueue(this); if (txQueue) { txQueue->lock(); // current queue dupe check for(int i = 0; i < txQueue->GetSize(); i++) { if(((CopyInst *)txQueue->Get(i))->Equals(inst)) { delete inst; txQueue->unlock(); return 0; } } txQueue->unlock(); } } if (!forceDupe) { int r = dev->trackAddedToTransferQueue(&inst->song); if (r) { if (r == 2) { inst->status = STATUS_DONE; inst->res = 2; AddTrackToTransferQueue(inst); return 0; } else { delete inst; } } else AddTrackToTransferQueue(inst); return r; } else { inst->res = 2; AddTrackToTransferQueue(inst); return 0; } } class SyncItemListLoader : public ifc_playlistloadercallback { public: void OnFile(const wchar_t *filename, const wchar_t *title, int lengthInMS, ifc_plentryinfo *info) { if(pos < len) { songs[pos].filename = _wcsdup(filename); metaToGet->Add(&songs[pos].map); songMaps->Add(&songs[pos].pladd); } pos++; } int pos,len; songMapping * songs; C_ItemList * metaToGet, * songMaps; protected: RECVS_DISPATCH; }; #define CBCLASS SyncItemListLoader START_DISPATCH; VCB(IFC_PLAYLISTLOADERCALLBACK_ONFILE, OnFile) END_DISPATCH; #undef CBCLASS typedef struct { mlPlaylistInfo info; songMapping * songs; } SyncPlaylist; void TransfersListUpdateItem(CopyInst * item); void TransfersListUpdateItem(CopyInst * item, DeviceView *view); /* static int setPlaylistTrack(Device * dev, int pl, int n, int len, songid_t song) { if(n >= len) { while(n >= len) { dev->addTrackToPlaylist(pl,song); len++; } return len; } else { dev->addTrackToPlaylist(pl,song); dev->playlistSwapItems(pl,len,n); dev->removeTrackFromPlaylist(pl,len); } return len; } */ class PlaylistSyncCopyInst : public CopyInst { public: bool memFreed; C_ItemList *songMaps; C_ItemList * playlists; PlaylistSyncCopyInst(DeviceView *dev, C_ItemList *songMaps, C_ItemList * playlists) : songMaps(songMaps), playlists(playlists) { usesPreCopy = false; usesPostCopy = true; this->dev = dev; equalsType = -1; status=STATUS_WAITING; // status caption WASABI_API_LNGSTRINGW_BUF(IDS_WAITING,statusCaption,sizeof(statusCaption)/sizeof(wchar_t)); // track caption WASABI_API_LNGSTRINGW_BUF(IDS_PLAYLIST_SYNCRONIZATION,trackCaption,sizeof(trackCaption)/sizeof(wchar_t)); // type caption WASABI_API_LNGSTRINGW_BUF(IDS_OTHER,typeCaption,sizeof(typeCaption)/sizeof(wchar_t)); memFreed=false; } virtual ~PlaylistSyncCopyInst() { freeMemory(); } virtual bool CopyAction() { return false; } virtual void PostCopyAction() { SyncPlaylists(); freeMemory(); } virtual void Cancelled() { freeMemory(); } virtual bool Equals(CopyInst * b) { return false; } void freeMemory() { if(memFreed) return; memFreed=true; if(songMaps) delete songMaps; songMaps = NULL; if(playlists) { int l = playlists->GetSize(); for(int i=0; iGet(i); if(playlist) { if(playlist->songs) { for(int j=0; jinfo.numItems; j++) { if(playlist->songs[j].ice) { freeRecord(playlist->songs[j].ice); free(playlist->songs[j].ice); } if(playlist->songs[j].filename) free(playlist->songs[j].filename); } free(playlist->songs); } free(playlist); } } delete playlists; playlists = NULL; } } void SyncPlaylists() { if(memFreed) return; WASABI_API_LNGSTRINGW_BUF(IDS_WORKING,statusCaption,sizeof(statusCaption)/sizeof(wchar_t)); TransfersListUpdateItem(this); TransfersListUpdateItem(this, dev); MapItemRecordsToSongs(dev->dev,(PlaylistAddItem **)songMaps->GetAll(),songMaps->GetSize()); int numPlaylists = playlists->GetSize(); for(int i=0; iGet(i); int plnum = -1; bool done = false; int l = dev->dev->getPlaylistCount(); int j; for(j=0; j < l; j++) { wchar_t buf[128] = {0}; dev->dev->getPlaylistName(j,buf,128); if(wcscmp(buf,playlist->info.playlistName)) continue; int plen = dev->dev->getPlaylistLength(j); if(plen != playlist->info.numItems) { plnum = j; break; } for(int k=0; ksongs[k].song != dev->dev->getPlaylistTrack(j,k)) { plnum = j; break; } } if(plnum == -1) { done = true; break; } } if(done) continue; if(plnum == -1) { plnum = dev->CreatePlaylist(playlist->info.playlistName,true); if(plnum == -1) continue; } int plen = dev->dev->getPlaylistLength(plnum); while(plen && ((plen % 4) != 1)) dev->dev->removeTrackFromPlaylist(plnum,--plen); // avoid granulation boundarys int n=0; for(j=0; jinfo.numItems; j++) { songid_t s = playlist->songs[j].song; if(s && (n>=plen || s != dev->dev->getPlaylistTrack(plnum,n))) { // begin set item code... if(n >= plen) while(n >= plen) { dev->dev->addTrackToPlaylist(plnum,s); plen++; } else { dev->dev->addTrackToPlaylist(plnum,s); dev->dev->playlistSwapItems(plnum,plen,n); dev->dev->removeTrackFromPlaylist(plnum,plen); } // end set item code } if(s) n++; } plen = dev->dev->getPlaylistLength(plnum); while(plen > n) dev->dev->removeTrackFromPlaylist(plnum,--plen); if(_wcsicmp(playlist->info.playlistName,L"Podcasts")==0) { wchar_t *name=NULL; for(int j=playlist->info.numItems-1; j>=0; j--) { wchar_t *n = getRecordExtendedItem(playlist->songs[j].ice,L"podcastchannel"); if(!name) name=n; if(name && n) { if(_wcsicmp(name,n)) { dev->dev->extraActions(DEVICE_ADDPODCASTGROUP,plnum,j+1,(intptr_t)name); name=n; } if(j==0) dev->dev->extraActions(DEVICE_ADDPODCASTGROUP,plnum,0,(intptr_t)name); } } dev->dev->extraActions(DEVICE_ADDPODCASTGROUP_FINISH,plnum,0,0); } } dev->DevicePropertiesChanges(); freeMemory(); WASABI_API_LNGSTRINGW_BUF(IDS_DONE,statusCaption,sizeof(statusCaption)/sizeof(wchar_t)); TransfersListUpdateItem(this); TransfersListUpdateItem(this, dev); } }; static bool shouldSyncPlaylist(wchar_t * name, C_Config * config) { wchar_t buf[150] = {0}; StringCchPrintf(buf,150, L"sync-%s",name); return config->ReadInt(buf,0) == config->ReadInt(L"plsyncwhitelist",1); } static int sortfunc_podcastpubdate(const void *elem1, const void *elem2) { itemRecordW *ar = (itemRecordW *)elem1; itemRecordW *br = (itemRecordW *)elem2; wchar_t *a = getRecordExtendedItem(ar,L"podcastpubdate"); wchar_t *b = getRecordExtendedItem(br,L"podcastpubdate"); if(!a) a = L"0"; if(!b) b = L"0"; return _wtoi(b) - _wtoi(a); } void DeviceView::OnActivityStarted() { for ( ifc_deviceevent *l_event_handler : event_handlers ) l_event_handler->ActivityStarted( this, this ); } void DeviceView::OnActivityChanged() { for ( ifc_deviceevent *l_event_handler : event_handlers ) l_event_handler->ActivityChanged( this, this ); } void DeviceView::OnActivityFinished() { for ( ifc_deviceevent *l_event_handler : event_handlers ) l_event_handler->ActivityFinished( this, this ); } void DeviceView::UpdateActivityState() { LinkedQueue * txQueue = getTransferQueue(this); if (txQueue && FALSE == activityRunning) { if (0 != txQueue->GetSize()) { activityRunning = TRUE; if (FAILED(GetProgress(¤tProgress))) currentProgress = 0; OnActivityStarted(); } } else { if (txQueue && 0 == txQueue->GetSize()) { activityRunning = FALSE; OnActivityFinished(); } else { unsigned int percent; if (FAILED(GetProgress(&percent)) || percent != currentProgress) { currentProgress = percent; OnActivityChanged(); } } } } void DeviceView::UpdateSpaceInfo(BOOL updateUsedSpace, BOOL notifyChanges) { uint64_t total, used; unsigned int changes; changes = 0; total = dev->getDeviceCapacityTotal(); if (total != totalSpace) { totalSpace = total; changes |= (1 << 0); } if (FALSE != updateUsedSpace) { used = dev->getDeviceCapacityAvailable(); if (used > total) used = total; used = total - used; if (used != usedSpace) { usedSpace = used; changes |= (1 << 1); } } if (0 != changes && FALSE != notifyChanges) { for ( ifc_deviceevent *l_event_handler : event_handlers ) { if (0 != ((1 << 0) & changes)) l_event_handler->TotalSpaceChanged(this, totalSpace); if (0 != ((1 << 1) & changes)) l_event_handler->TotalSpaceChanged(this, usedSpace); } } } void DeviceView::OnNameChanged(const wchar_t *new_name) { for ( ifc_deviceevent *l_event_handler : event_handlers ) l_event_handler->DisplayNameChanged( this, new_name ); } void DeviceView::Sync(bool silent) { // sync configuration settings.... bool syncAllLibrary = config->ReadInt(L"syncAllLibrary",1)!=0; if (AGAVE_API_STATS) { wchar_t device_name[128] = {0}; device_name[0] = 0; if(dev->extraActions(DEVICE_GET_MODEL, (intptr_t)device_name, 128, 0) == 1 && device_name[0]) { AGAVE_API_STATS->SetString("pmp", device_name); } } HWND centerWindow = CENTER_OVER_ML_VIEW; UpdateActivityState(); C_ItemList mllist; wchar_t * querystring=0; itemRecordListW *results = 0; if(syncAllLibrary) { querystring = _wcsdup(config->ReadString(L"SyncQuery",L"type=0")); results = (AGAVE_API_MLDB ? AGAVE_API_MLDB->Query(querystring) : NULL); if (results) for(int i = 0; i < results->Size; i++) mllist.Add(&results->Items[i]); } // read playlists/views and find out what else needs to be added PlaylistSyncCopyInst * sync = NULL; C_ItemList filenameMaps; C_ItemList *songMaps = new C_ItemList; C_ItemList * playlists = new C_ItemList; // first collect playlists without metadata SyncItemListLoader list; list.metaToGet = &filenameMaps; list.songMaps = songMaps; int playlistsnum = SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, 0, ML_IPC_PLAYLIST_COUNT); for(int i=0; iinfo.size = sizeof(mlPlaylistInfo); playlist->info.playlistNum = i; SendMessage(plugin.hwndLibraryParent, WM_ML_IPC, (WPARAM)&playlist->info, ML_IPC_PLAYLIST_INFO); if(shouldSyncPlaylist(playlist->info.playlistName, config)) { playlists->Add(playlist); } else { free(playlist); playlist = 0; continue; } //if(playlist->info.numItems <= 1) { list.pos = list.len = 0; playlistManager->Load(playlist->info.filename, &list); playlist->info.numItems = list.pos; } list.pos = 0; list.len=playlist->info.numItems; list.songs = playlist->songs = (songMapping*)calloc(sizeof(songMapping), list.len); playlistManager->Load(playlist->info.filename, &list); } mapFilesToItemRecords((filenameMap **)filenameMaps.GetAll(), filenameMaps.GetSize(), centerWindow); // get metadata // now sync podcasts... if (dev->extraActions(DEVICE_SUPPORTS_PODCASTS, 0, 0, 0) == 0) { int podcasteps = config->ReadInt(L"podcast-sync_episodes",0); int podcastsnum = AGAVE_API_PODCASTS ? AGAVE_API_PODCASTS->GetNumPodcasts() : 0; if(podcasteps && podcastsnum > 0) { // if we want to sync podcasts and we have podcasts to sync bool all = !!config->ReadInt(L"podcast-sync_all", 1); SyncPlaylist * s = (SyncPlaylist *)calloc(sizeof(SyncPlaylist),1); lstrcpyn(s->info.playlistName, L"Podcasts", 128); //set the name of the playlist containing our podcasts int n = 0, alloc = 512; s->songs = (songMapping*)calloc(alloc, sizeof(songMapping)); for(int i = 0; i < podcastsnum; i++) { ifc_podcast *podcast = AGAVE_API_PODCASTS->EnumPodcast(i); if(podcast) { wchar_t podcast_name[256] = {0}; if(podcast->GetTitle(podcast_name, 256) == 0) { wchar_t buf[300] = {0}; StringCchPrintf(buf, 300, L"podcast-sync-%s", podcast_name); if(podcast_name[0] && (all || config->ReadInt(buf,0))) // if we have a podcast and we want to sync it { wchar_t query[300] = {0}; StringCchPrintf(query, 300, L"podcastchannel = \"%s\"", podcast_name); itemRecordListW *podcasts = AGAVE_API_MLDB->Query(query); if(podcasts) { qsort(podcasts->Items,podcasts->Size,sizeof(itemRecordW),sortfunc_podcastpubdate); // sort the podcasts into publish date order for(int j=0; jSize && (podcasteps == -1 || j < podcasteps); j++) { // add podcast to playlist if(n >= alloc) { size_t old_alloc = alloc; alloc += 512; songMapping* new_songs = (songMapping*)realloc(s->songs,sizeof(songMapping) * alloc); if (new_songs) { s->songs = new_songs; } else { new_songs = (songMapping*)malloc(sizeof(songMapping) * alloc); if (new_songs) { memcpy(new_songs, s->songs, sizeof(songMapping) * old_alloc); free(s->songs); s->songs = new_songs; } else { alloc = old_alloc; continue; } } } ZeroMemory(&s->songs[n],sizeof(songMapping)); s->songs[n].ice = (itemRecordW*)calloc(sizeof(itemRecordW), 1); copyRecord(s->songs[n].ice,&podcasts->Items[j]); mllist.Add(s->songs[n].ice); songMaps->Add(&s->songs[n].pladd); n++; } if(podcasts) AGAVE_API_MLDB->FreeRecordList(podcasts); } } } } } s->info.numItems = n; if(n) playlists->Add(s); else { free(s->songs); free(s); } } } // now collect playlists with metadata (i.e, smart views) // except the new ml_local isn't ready. // calloc a new SyncPlaylist, fill in playlist->info.numItems, playlist->info.playlistName and playlist->songs[].ice then add to playlists. // add tracks to be sync'd for(int i=0; iice) mllist.Add(f->ice); } // prepare sync if(playlists->GetSize()) sync = new PlaylistSyncCopyInst(this, songMaps, playlists); else { delete playlists; delete songMaps; } // work out the tracks to be sent and deleted... C_ItemList synclist,dellist; ProcessDatabaseDifferences(dev, &mllist, NULL, &synclist, NULL, &dellist); if(!synclist.GetSize() && !dellist.GetSize()) { // nothing to do if(sync) { sync->SyncPlaylists(); delete sync; } if(!silent) { wchar_t titleStr[32] = {0}; MessageBox(plugin.hwndLibraryParent, WASABI_API_LNGSTRINGW(IDS_NOTHING_TO_SYNC_UP_TO_DATE), WASABI_API_LNGSTRINGW_BUF(IDS_SYNC, titleStr, 32),0); } } else { // need to sync some tracks if(IDOK == SyncDialog_Show(centerWindow, this, &synclist, &dellist, FALSE)) { config->WriteInt(L"syncOnConnect_time",(int)time(NULL)); if(dellist.GetSize()) { switch(config->ReadInt(L"TrueSync",0)) { case 1: this->DeleteTracks(&dellist, centerWindow); break; case 2: this->CopyTracksToHardDrive(&dellist); break; } } int i = 0, l = 0; LinkedQueue * txQueue = getTransferQueue(this); if (txQueue) { l = synclist.GetSize(); txQueue->lock(); for(i = 0; i < l; i++) if(AddTrackToTransferQueue(this, (itemRecordW*)synclist.Get(i), false) == -1) break; if(sync) AddTrackToTransferQueue(sync); txQueue->unlock(); } if(i != l) { wchar_t titleStr[128] = {0}; MessageBox(plugin.hwndLibraryParent, WASABI_API_LNGSTRINGW(IDS_THERE_IS_NOT_ENOUGH_SPACE_ON_THE_DEVICE), WASABI_API_LNGSTRINGW_BUF(IDS_NOT_ENOUGH_SPACE, titleStr, ARRAYSIZE(titleStr)), MB_OK | MB_ICONWARNING); } } else { if(sync) delete sync; } } if(syncAllLibrary) { if(results) AGAVE_API_MLDB->FreeRecordList(results); free(querystring); } } void DeviceView::CloudSync(bool silent) { if (AGAVE_API_STATS) { wchar_t device_name[128] = {0}; if(dev->extraActions(DEVICE_GET_MODEL, (intptr_t)device_name, 128, 0) == 1 && device_name[0]) { AGAVE_API_STATS->SetString("pmp", device_name); } } UpdateActivityState(); // work out the tracks to be sent... C_ItemList *filenameMaps2 = new C_ItemList, synclist; DeviceView * hss = 0, * local = 0; if (!cloud_hinst) cloud_hinst = (HINSTANCE)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GET_CLOUD_HINST); if (cloud_hinst && cloud_hinst != (HINSTANCE)1) { winampMediaLibraryPlugin *(*gp)(); gp = (winampMediaLibraryPlugin * (__cdecl *)(void))GetProcAddress(cloud_hinst, "winampGetMediaLibraryPlugin"); if (gp) { winampMediaLibraryPlugin *mlplugin = gp(); if (mlplugin && (mlplugin->version >= MLHDR_VER_OLD && mlplugin->version <= MLHDR_VER)) { // determine the cloud device and alter the device // to be checked with as needed by the action done for(int i = 0; i < devices.GetSize(); i++) { DeviceView * d = (DeviceView *)devices.Get(i); if (d->isCloudDevice) { char name[128] = {0}; if (d->dev->extraActions(DEVICE_GET_UNIQUE_ID, (intptr_t)name, sizeof(name), 0)) { if (!strcmp(name, "hss"/*HSS_CLIENT*/)) hss = d; else if (!strcmp(name, "local_desktop")) local = d; } } } if (local && hss && local->dev == dev) { // just use the local library as the source to compare against mlplugin->MessageProc(0x403, /*source*/-1, /*dest*/hss->dev->extraActions(DEVICE_GET_CLOUD_DEVICE_ID,0,0,0), (INT_PTR)filenameMaps2); } else { // just use the local library as the source to compare against mlplugin->MessageProc(0x403, /*source*/-1, /*dest*/dev->extraActions(DEVICE_GET_CLOUD_DEVICE_ID,0,0,0), (INT_PTR)filenameMaps2); } } } } synclist = *fileListToItemRecords(filenameMaps2, CENTER_OVER_ML_VIEW); nu::qsort(synclist.GetAll(), synclist.GetSize(), sizeof(void*), dev, compareSongs); if(!synclist.GetSize()) { if(!silent) { wchar_t titleStr[32] = {0}; MessageBox(plugin.hwndLibraryParent, WASABI_API_LNGSTRINGW(IDS_NOTHING_TO_SYNC_UP_TO_DATE), WASABI_API_LNGSTRINGW_BUF(IDS_SYNC,titleStr,32),0); } } else { DeviceView * destDevice = (local && hss && local->dev == dev ? hss : this); // need to sync some tracks if(IDOK == SyncCloudDialog_Show(CENTER_OVER_ML_VIEW, destDevice, &synclist)) { int l = synclist.GetSize(); cloudTransferQueue.lock(); int i = 0; for (; i < l; i++) if (AddTrackToTransferQueue(destDevice, (itemRecordW*)synclist.Get(i), false) == -1) break; cloudTransferQueue.unlock(); if(i != l) { wchar_t titleStr[128] = {0}; MessageBox(plugin.hwndLibraryParent, WASABI_API_LNGSTRINGW(IDS_THERE_IS_NOT_ENOUGH_SPACE_ON_THE_DEVICE), WASABI_API_LNGSTRINGW_BUF(IDS_NOT_ENOUGH_SPACE, titleStr, ARRAYSIZE(titleStr)), MB_OK | MB_ICONWARNING); } } } } extern itemRecordListW * generateAutoFillList(DeviceView * dev, C_Config * config); // from autofill.cpp void DeviceView::Autofill() { HWND centerWindow; centerWindow = CENTER_OVER_ML_VIEW; if (AGAVE_API_STATS) { wchar_t device_name[128] = {0}; if(dev->extraActions(DEVICE_GET_MODEL, (intptr_t)device_name, 128, 0) == 1 && device_name[0]) { AGAVE_API_STATS->SetString("pmp", device_name); } } UpdateActivityState(); C_ItemList delList,sendList; itemRecordListW * autofillList = generateAutoFillList(this,config); ProcessDatabaseDifferences(dev,autofillList,NULL,&sendList,NULL,&delList); if(IDOK == SyncDialog_Show(centerWindow, this, &sendList, &delList, TRUE)) { config->WriteInt(L"syncOnConnect_time", (int)time(NULL)); // delete all tracks in delList if(IDOK == DeleteTracks(&delList, centerWindow)) { // not aborted // send all tracks in sendList LinkedQueue * txQueue = getTransferQueue(this); if (txQueue) { txQueue->lock(); for(int i = 0; i < sendList.GetSize(); i++) AddTrackToTransferQueue(this, (itemRecordW*)sendList.Get(i), false); txQueue->unlock(); } } } if(autofillList) freeRecordList(autofillList); } extern int serverPort; bool DeviceView::PlayTracks(C_ItemList * tracks, int startPlaybackAt, bool enqueue, bool msgIfImpossible, HWND parent) { if(tracks->GetSize() == 0) return true; // direct playback? if(dev->playTracks((songid_t*)tracks->GetAll(),tracks->GetSize(),startPlaybackAt,enqueue)) return true; if(serverPort>0 && dev->copyToHardDriveSupported()) { // indirect playback? if(!enqueue) { //clear playlist SendMessage(plugin.hwndWinampParent,WM_WA_IPC,0,IPC_DELETE); } wchar_t buf[2048] = {0}; dev->getPlaylistName(0,buf,128); AutoUrl device(buf); for(int i=0; iGetSize(); i++) { songid_t s = (songid_t)tracks->Get(i); //encode fields to url format wchar_t metadata[2048] = {0}; dev->getTrackArtist(s,metadata,2048); AutoUrl artist(metadata); dev->getTrackAlbum(s,metadata,2048); AutoUrl album(metadata); dev->getTrackTitle(s,metadata,2048); AutoUrl title(metadata); // construct URL wchar_t ext[10]=L""; dev->getTrackExtraInfo(s,L"ext",ext,10); char buf[8192] = {0}; StringCchPrintfA(buf,8192, "http://127.0.0.1:%d/?a=%s&l=%s&t=%s&d=%s%s%s",serverPort,artist,album,title,device,*ext?";.":"",(char*)AutoChar(ext)); // get title AutoWide wideUrl(buf); wchar_t buf2[4096] = {0}; getTitle(dev,s,wideUrl,buf2,4096); // enqueue file enqueueFileWithMetaStructW ef = { wideUrl, buf2, NULL, dev->getTrackLength( s ) / 1000 }; SendMessage(plugin.hwndWinampParent, WM_WA_IPC, (WPARAM)&ef, IPC_PLAYFILEW); } if(!enqueue) //play item startPlaybackAt { SendMessage(plugin.hwndWinampParent,WM_WA_IPC,startPlaybackAt,IPC_SETPLAYLISTPOS); SendMessage(plugin.hwndWinampParent,WM_COMMAND,40047,0); //stop SendMessage(plugin.hwndWinampParent,WM_COMMAND,40045,0); //play } return true; } if(msgIfImpossible) { wchar_t titleStr[32] = {0}; MessageBox(parent,WASABI_API_LNGSTRINGW(IDS_DOES_NOT_SUPPORT_DIRECT_PLAYBACK), WASABI_API_LNGSTRINGW_BUF(IDS_UNSUPPORTED,titleStr,32),0); } return false; } bool DeviceView::PlayPlaylist(int playlistId, bool enqueue, bool msgIfImpossible, HWND parent) { int l = dev->getPlaylistLength(playlistId); C_ItemList tracks; for(int j=0; jgetPlaylistTrack(playlistId,j)); return PlayTracks(&tracks, 0, enqueue, msgIfImpossible, parent); } void DeviceView::CopyTracksToHardDrive(C_ItemList * tracks) { CopyTracksToHardDrive((songid_t*)tracks->GetAll(),tracks->GetSize()); } static void getReverseCopyFilenameFormat(wchar_t* filepath, wchar_t* format, int len, BOOL * uppercaseext) { wchar_t m_def_extract_path[MAX_PATH] = L"C:\\My Music"; wchar_t m_def_filename_fmt[MAX_PATH] = L" - \\## - - "; GetDefaultSaveToFolder(m_def_extract_path); bool cdrip = !!global_config->ReadInt(L"extractusecdrip", 1); const wchar_t *mlinifile = (const wchar_t*)SendMessage(plugin.hwndWinampParent, WM_WA_IPC, 0, IPC_GETMLINIFILEW); wchar_t buf[2048] = {0}; if(cdrip) GetPrivateProfileString(L"gen_ml_config",L"extractpath",m_def_extract_path,buf,2048,mlinifile); else lstrcpyn(buf,global_config->ReadString(L"extractpath",m_def_extract_path),2048); lstrcpyn(filepath,buf,len); int l = wcslen(filepath); if(*(filepath+l-1) != L'\\') { *(filepath+l) = L'\\'; *(filepath+l+1)=0; l++; } if(cdrip) GetPrivateProfileString(L"gen_ml_config",L"extractfmt2",m_def_filename_fmt,buf,2048,mlinifile); else lstrcpyn(buf,global_config->ReadString(L"extractfmt2",m_def_filename_fmt),2048); if(l < len) lstrcpyn(format/*+l*/,buf,len - l); if(cdrip) *uppercaseext = GetPrivateProfileInt(L"gen_ml_config",L"extractucext",0,mlinifile); else *uppercaseext = global_config->ReadInt(L"extractucext",0); } void DeviceView::CopyTracksToHardDrive(songid_t * tracks, int numTracks) { if(!dev->copyToHardDriveSupported()) return; BOOL uppercaseext=FALSE; wchar_t filepath[MAX_PATH] = {0}, format[2048] = {0}; getReverseCopyFilenameFormat(filepath,format,2048,&uppercaseext); LinkedQueue * txQueue = getTransferQueue(this); if (txQueue) { txQueue->lock(); for(int i=0; i<numTracks; i++) { AddTrackToTransferQueue(new ReverseCopyInst(this,filepath,format,tracks[i],true,!!uppercaseext)); } txQueue->unlock(); } } void DeviceView::CopyPlaylistToLibrary(int plnum) { if(plnum==0) return; wchar_t name[128] = {0}; dev->getPlaylistName(plnum,name,128); wchar_t filename[MAX_PATH] = {0}; wchar_t dir[MAX_PATH] = {0}; GetTempPath(MAX_PATH,dir); GetTempFileName(dir,L"pmppl",0,filename); _wunlink(filename); { wchar_t * ext = wcsrchr(filename,L'.'); if(ext) *ext=0; StringCchCat(filename,MAX_PATH,L".m3u"); } FILE * f = _wfopen(filename,L"wt"); if(f) { fputws(L"#EXTM3U\n",f); fclose(f); } /* mlMakePlaylistV2 a = {sizeof(mlMakePlaylistV2),name,ML_TYPE_FILENAMES,"\0\0",PL_FLAG_SHOW | PL_FLAG_FILL_FILENAME}; SendMessage(plugin.hwndLibraryParent,WM_ML_IPC,(WPARAM)&a,ML_IPC_PLAYLIST_MAKE); */ wchar_t filepath[MAX_PATH] = {0}, format[2048] = {0}; BOOL uppercaseext=FALSE; getReverseCopyFilenameFormat(filepath,format,2048,&uppercaseext); int l = dev->getPlaylistLength(plnum); LinkedQueue * txQueue = getTransferQueue(this); if (txQueue) { txQueue->lock(); for(int i=0; i<l; i++) AddTrackToTransferQueue(new ReversePlaylistCopyInst(this,filepath,format,dev->getPlaylistTrack(plnum,i),filename,name,i==l-1,true)); txQueue->unlock(); } } void DeviceView::Unregister() { for(size_t i=0; i < playlistTreeItems.size(); i++) { HNAVITEM item = playlistTreeItems[i]; // TODO: free memory associated with the text for item MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, item); } playlistTreeItems.clear(); if (videoTreeItem) MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, videoTreeItem); videoTreeItem=0; if (AGAVE_API_DEVICEMANAGER) AGAVE_API_DEVICEMANAGER->DeviceUnregister(name); if (treeItem) MLNavCtrl_DeleteItem(plugin.hwndLibraryParent, treeItem); treeItem=0; }