aboutsummaryrefslogtreecommitdiff
path: root/Src/Plugins/Library/ml_pmp/DeviceView.cpp
diff options
context:
space:
mode:
authorJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
committerJef <jef@targetspot.com>2024-09-24 08:54:57 -0400
commit20d28e80a5c861a9d5f449ea911ab75b4f37ad0d (patch)
tree12f17f78986871dd2cfb0a56e5e93b545c1ae0d0 /Src/Plugins/Library/ml_pmp/DeviceView.cpp
parent537bcbc86291b32fc04ae4133ce4d7cac8ebe9a7 (diff)
downloadwinamp-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.cpp2714
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(&registered_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, &currentViewedPlaylist))
+ {
+ 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(&currentProgress)))
+ 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