diff options
author | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
---|---|---|
committer | Jef <jef@targetspot.com> | 2024-09-24 08:54:57 -0400 |
commit | 20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch) | |
tree | 12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/Plugins/Library/ml_pmp/DeviceView.cpp | |
parent | 537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff) | |
download | winamp-20d28e80a5c861a9d5f449ea911ab75b4f37ad0d.tar.gz |
Initial community commit
Diffstat (limited to 'Src/Plugins/Library/ml_pmp/DeviceView.cpp')
-rw-r--r-- | Src/Plugins/Library/ml_pmp/DeviceView.cpp | 2714 |
1 files changed, 2714 insertions, 0 deletions
diff --git a/Src/Plugins/Library/ml_pmp/DeviceView.cpp b/Src/Plugins/Library/ml_pmp/DeviceView.cpp new file mode 100644 index 00000000..6f2b8644 --- /dev/null +++ b/Src/Plugins/Library/ml_pmp/DeviceView.cpp @@ -0,0 +1,2714 @@ +//#define _WIN32_WINNT 0x0400 +#include "../Winamp/buildType.h" +#include "main.h" +#include "DeviceView.h" + +//#include <commctrl.h> +#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 <shlwapi.h> +#include <time.h> +#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 <strsafe.h> +#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; i<l; i++) + { + AddPlaylistNode(i); + } +} + +DeviceView::~DeviceView() +{ + if(configDevice == this) SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(intptr_t)&prefsPage,IPC_OPENPREFSTOPAGE); + + // remove it when we're removed what we added + int lastPrefsPageLoaded = prefsPageLoaded; + prefsPageLoaded-=1; + if(lastPrefsPageLoaded == 1) + { + SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(intptr_t)&prefsPage,IPC_REMOVE_PREFS_DLG); + } + + //OutputDebugString(L"device unloading started"); + // get rid of the transfer thread + threadKillswitch=1; + transferContext.WaitForKill(); + if(threadKillswitch != 100) + { + /*OutputDebugString(L"FUCKO");*/ + } + + SendMessage(plugin.hwndWinampParent,WM_WA_IPC,(intptr_t)&devPrefsPage,IPC_REMOVE_PREFS_DLG); + free(devPrefsPage.name); + //OutputDebugString(L"device unloading finished"); + delete config; +} + +void DeviceView::SetVideoView(BOOL enabled) +{ + videoView=enabled; + config->WriteInt(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<INT_PTR>(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; i<s; i++) + { + void * p = deleteTracks->Get(i); + for(int j=i+1; j<s; j++) + { + if(p == deleteTracks->Get(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; i<num; i++) + { + filesW[i] = AutoWideDup(files[i]); + } + int r = AddFileListToTransferQueue(filesW,num,playlist); + for(int i=0; i<num; i++) + { + free(filesW[i]); + } + free(filesW); + return r; +} + +int DeviceView::AddFileListToTransferQueue(wchar_t ** files, int num, int playlist) +{ + C_ItemList * irs = fileListToItemRecords(files,num, CENTER_OVER_ML_VIEW); + int r = AddItemListToTransferQueue(irs,playlist); + for(int i=0; i < irs->GetSize(); 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; i<itemRecords->GetSize(); 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; i<directAdd->GetSize(); 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; i<directAdd->GetSize(); 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; i<l; i++) + { + SyncPlaylist * playlist = (SyncPlaylist *)playlists->Get(i); + if(playlist) + { + if(playlist->songs) + { + for(int j=0; j<playlist->info.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; i<numPlaylists; i++) + { + SyncPlaylist * playlist = (SyncPlaylist *)playlists->Get(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; k<plen; k++) + { + if(playlist->songs[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; j<playlist->info.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; i<playlistsnum; i++) + { + SyncPlaylist* playlist = (SyncPlaylist*)calloc(sizeof(SyncPlaylist),1); + playlist->info.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; j<podcasts->Size && (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; i<filenameMaps.GetSize(); i++) + { + filenameMap* f = (filenameMap*)filenameMaps.Get(i); + if(f->ice) + 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; i<tracks->GetSize(); 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; j<l; j++) + tracks.Add((void*)dev->getPlaylistTrack(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"<Artist> - <Album>\\## - <Trackartist> - <Title>"; + 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; +}
\ No newline at end of file |