2714 lines
72 KiB
C++
2714 lines
72 KiB
C++
//#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;
|
|
} |