#include "ArtistAlbumLists.h" #include "Filters.h" #include "api__ml_pmp.h" #include "resource1.h" #include "metadata_utils.h" extern winampMediaLibraryPlugin plugin; #define SKIP_THE_AND_WHITESPACE(x) { while (!iswalnum(*x) && *x) x++; if (!_wcsnicmp(x,L"the ",4)) x+=4; while (*x == L' ') x++; } int STRCMP_NULLOK(const wchar_t *pa, const wchar_t *pb) { if (!pa) pa=L""; else SKIP_THE_AND_WHITESPACE(pa) if (!pb) pb=L""; else SKIP_THE_AND_WHITESPACE(pb) return lstrcmpi(pa,pb); } #undef SKIP_THE_AND_WHITESPACE Filter * getFilter(wchar_t *name); #define RETIFNZ(v) if ((v)<0) return use_dir?1:-1; if ((v)>0) return use_dir?-1:1; typedef struct { songid_t songid; Device * dev; } SortSongItem; int thread_killed = 0; static int useby, usedir, usecloud; static Device * currentDev; static int sortFunc(const void *elem1, const void *elem2) { int use_by=useby; int use_dir=usedir; songid_t a=(songid_t)*(void **)elem1; songid_t b=(songid_t)*(void **)elem2; wchar_t bufa[2048] = {0}, bufb[2048] = {0}; // this might be too slow, but it'd be nice for (int x = 0; x < 5; x ++) { if (thread_killed) break; bufa[0]=bufb[0]=0; if (use_by == (7+usecloud)) // year -> artist -> album -> track { int v1=currentDev->getTrackYear(a); int v2=currentDev->getTrackYear(b); if (v1<0)v1=0; if (v2<0)v2=0; RETIFNZ(v1-v2) use_by=0; } else if (use_by == 4 && usecloud) // cloud -> artist -> album -> track { currentDev->getTrackExtraInfo(a,L"cloud",bufa,ARRAYSIZE(bufa)); currentDev->getTrackExtraInfo(b,L"cloud",bufb,ARRAYSIZE(bufb)); int v=STRCMP_NULLOK(bufa,bufb); RETIFNZ(v) use_by=0; } else if (use_by == 1) // title -> artist -> album -> disc -> track { currentDev->getTrackTitle(a,bufa,2048); currentDev->getTrackTitle(b,bufb,2048); int v=STRCMP_NULLOK(bufa,bufb); RETIFNZ(v) use_by=0; } else if (use_by == 0) // artist -> album -> disc -> track -> title { currentDev->getTrackArtist(a,bufa,2048); currentDev->getTrackArtist(b,bufb,2048); int v=STRCMP_NULLOK(bufa,bufb); RETIFNZ(v) use_by=2; } else if (use_by == 2) // album -> disc -> track -> title -> artist { currentDev->getTrackAlbum(a,bufa,2048); currentDev->getTrackAlbum(b,bufb,2048); int v=STRCMP_NULLOK(bufa,bufb); RETIFNZ(v) use_dir=0; use_by=5; } else if (use_by == 5+usecloud) // disc -> track -> title -> artist -> album { int v1=currentDev->getTrackDiscNum(a); int v2=currentDev->getTrackDiscNum(b); if (v1<0)v1=0; if (v2<0)v2=0; RETIFNZ(v1-v2) use_by=4; } else if (use_by == 4+usecloud) // track -> title -> artist -> album -> disc { int v1=currentDev->getTrackTrackNum(a); int v2=currentDev->getTrackTrackNum(b); if (v1<0)v1=0; if (v2<0)v2=0; RETIFNZ(v1-v2) use_by=1; } else if (use_by == 6+usecloud) // genre -> artist -> album -> disc -> track { currentDev->getTrackGenre(a,bufa,2048); currentDev->getTrackGenre(b,bufb,2048); int v=STRCMP_NULLOK(bufa,bufb); RETIFNZ(v) use_by=0; } else if (use_by == 3) // length -> artist -> album -> disc -> track { int v1=currentDev->getTrackLength(a); int v2=currentDev->getTrackLength(b); if (v1<0)v1=0; if (v2<0)v2=0; RETIFNZ(v1-v2) use_by=0; } else if (use_by == (8+usecloud)) // bitrate -> artist -> album -> disc -> track { int v1=currentDev->getTrackBitrate(a); int v2=currentDev->getTrackBitrate(b); if (v1<0)v1=0; if (v2<0)v2=0; RETIFNZ(v1-v2) use_by=0; } else if (use_by == (9+usecloud)) // size -> artist -> album -> disc -> track { __int64 v1=currentDev->getTrackSize(a); __int64 v2=currentDev->getTrackSize(b); if (v1<0)v1=0; if (v2<0)v2=0; RETIFNZ(v1-v2) use_by=0; } else if (use_by == (10+usecloud)) // playcount -> artist -> album -> disc -> track { int v1=currentDev->getTrackPlayCount(a); int v2=currentDev->getTrackPlayCount(b); if (v1<0)v1=0; if (v2<0)v2=0; RETIFNZ(v1-v2) use_by=0; } else if (use_by == (11+usecloud)) // rating -> artist -> album -> disc -> track { int v1=currentDev->getTrackRating(a); int v2=currentDev->getTrackRating(b); if (v1<0)v1=0; if (v2<0)v2=0; RETIFNZ(v1-v2) use_by=0; } else if (use_by == (12+usecloud)) { double v = difftime((time_t)currentDev->getTrackLastPlayed(a),(time_t)currentDev->getTrackLastPlayed(b)); RETIFNZ(v); use_by=0; } else if (use_by == (13+usecloud)) // album artist -> album { currentDev->getTrackAlbumArtist(a,bufa,ARRAYSIZE(bufa)); currentDev->getTrackAlbumArtist(b,bufb,ARRAYSIZE(bufb)); int v=STRCMP_NULLOK(bufa,bufb); RETIFNZ(v) use_by=2; } else if (use_by == (14+usecloud)) // publisher -> album { currentDev->getTrackPublisher(a,bufa,ARRAYSIZE(bufa)); currentDev->getTrackPublisher(b,bufb,ARRAYSIZE(bufb)); int v=STRCMP_NULLOK(bufa,bufb); RETIFNZ(v) use_by=2; } else if (use_by == (15+usecloud)) // composer -> album { currentDev->getTrackComposer(a,bufa,ARRAYSIZE(bufa)); currentDev->getTrackComposer(b,bufb,ARRAYSIZE(bufb)); int v=STRCMP_NULLOK(bufa,bufb); RETIFNZ(v) use_by=2; } else if (use_by == (16+usecloud)) // mime -> artist -> album -> disc -> track { currentDev->getTrackMimeType(a,bufa,ARRAYSIZE(bufa)); currentDev->getTrackMimeType(b,bufb,ARRAYSIZE(bufb)); int v=STRCMP_NULLOK(bufa,bufb); RETIFNZ(v) use_by=2; } else if (use_by == (17+usecloud)) // date added -> artist -> album -> disc -> track { double v = difftime((time_t)currentDev->getTrackDateAdded(a),(time_t)currentDev->getTrackDateAdded(b)); RETIFNZ(v); use_by=0; } else break; // no sort order? if (thread_killed) break; } return 0; } static int sortFunc_filteritems(const void *elem1, const void *elem2) { FilterItem *a=(FilterItem *)*(void **)elem1; FilterItem *b=(FilterItem *)*(void **)elem2; return a->compareTo2(b,useby,usedir); } class FilterList : public ListContents { public: Filter * filter; C_ItemList * items; ArtistAlbumLists * aaList; wchar_t topString[128]; int nextFilterNum; int tracks; int sortcol; int sortdir; int id; FilterList(int id, Device * dev0, C_Config * config, Filter * filter, ArtistAlbumLists * aaList, bool cloud) : id(id), filter(filter), sortcol(0), sortdir(0), aaList(aaList), tracks(0), nextFilterNum(0), items(0) { this->topString[0] = 0; this->config = config; this->dev = dev0; this->cloud = cloud; this->cloudcol = -1; filter->AddColumns(dev, &fields, config, !!this->cloud); for(int i = 0; i < fields.GetSize(); i++) { if (!lstrcmpi(((ListField *)fields.Get(i))->name, L"cloud")) { this->cloudcol = ((ListField *)fields.Get(i))->pos; break; } } wchar_t temp[16] = {0}; wsprintf(temp, L"filter%d_sortcol", id); this->sortcol = config->ReadInt(temp, 0); wsprintf(temp, L"filter%d_sortdir", id); this->sortdir = config->ReadInt(temp, 0); this->SortColumns(); } virtual ~FilterList() { wchar_t temp[16] = {0}; wsprintf(temp, L"filter%d_sortcol", id); config->WriteInt(temp, sortcol); wsprintf(temp, L"filter%d_sortdir", id); config->WriteInt(temp, sortdir); } virtual int GetNumColumns() { return fields.GetSize(); } virtual int GetNumRows() { return items->GetSize() + (filter->HaveTopItem()?1:0); } virtual wchar_t * GetColumnTitle(int num) { if(num >=0 && num < fields.GetSize()) return ((ListField *)fields.Get(num))->name; return L""; } virtual int GetColumnWidth(int num) { if(num >=0 && num < fields.GetSize()) return ((ListField *)fields.Get(num))->width; return 0; } virtual void GetCellText(int row, int col, wchar_t * buf, int buflen) { buf[0]=0; if(col >= fields.GetSize() || aaList->bgThread_Handle) return; int colid = ((ListField*)fields.Get(col))->field; if(filter->HaveTopItem()) { if(row) ((FilterItem*)items->Get(row-1))->GetCellText(colid,buf,buflen); else { if(colid%100 == 0) lstrcpyn(buf,topString,buflen); else if(colid%100 == 41) wsprintf(buf,L"%d",tracks); else if(colid%100 == 40 && filter->nextFilter) wsprintf(buf,L"%d",nextFilterNum); } } else ((FilterItem*)items->Get(row))->GetCellText(colid,buf,buflen); } virtual void SortList() { if (sortcol > fields.GetSize()) return; useby=((ListField*)fields.Get(sortcol))->field; usedir=sortdir; qsort(items->GetAll(),items->GetSize(),sizeof(void*),sortFunc_filteritems); } virtual int GetSortDirection() { return sortdir; } virtual int GetSortColumn() { return sortcol; } virtual void ColumnClicked(int col) { if(col == sortcol) sortdir = sortdir?0:1; else { sortdir=0; sortcol=col; } SortList(); } virtual void ColumnResize(int col, int newWidth) { if(col >=0 && col < fields.GetSize()) { ListField * lf = (ListField *)fields.Get(col); lf->width = newWidth; wchar_t buf[100] = {0}; wsprintf(buf,L"colWidth_%d",lf->field); config->WriteInt(buf,newWidth); } } virtual pmpart_t GetArt(int row) { return ((FilterItem*)items->Get(row))->GetArt(); } virtual void SetMode(int mode) { filter->SetMode(mode); int i=fields.GetSize(); while(i>=0) { i--; delete (ListField*)fields.Get(i); fields.Del(i); } filter->AddColumns(dev, &fields, config, !!this->cloud); SortColumns(); } virtual songid_t GetTrack(int pos) { return 0; } }; static void getStars(int stars, wchar_t * buf, int buflen) { wchar_t * r=L""; switch(stars) { case 1: r=L"\u2605"; break; case 2: r=L"\u2605\u2605"; break; case 3: r=L"\u2605\u2605\u2605"; break; case 4: r=L"\u2605\u2605\u2605\u2605"; break; case 5: r=L"\u2605\u2605\u2605\u2605\u2605"; break; } lstrcpyn(buf,r,buflen); } extern void timeToString(__time64_t time, wchar_t * buf, int buflen); static void timeValue(int totalsecs, wchar_t *dest) { int secs=totalsecs%60; int mins=(totalsecs/60)%60; int hours=(totalsecs/3600)%24; int days=(totalsecs/86400); if(days==0) { wsprintf(dest,L"%d:%02d:%02d",hours,mins,secs); } else if(days==1) { wsprintf(dest,L"%d day+%d:%02d:%02d",days,hours,mins,secs); } else { wsprintf(dest,L"%d days+%d:%02d:%02d",days,hours,mins,secs); } } void GetInfoString(wchar_t * buf, Device * dev, int numTracks, __int64 totalSize, int totalPlayLength, int cloud) { wchar_t lengthStr[100]=L""; wchar_t sizeStr[100]=L""; wchar_t availStr[100]=L""; wchar_t devCapacityStr[100]=L""; int usedPercent; int fieldsBits = (int)dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0); if(!fieldsBits || (fieldsBits & SUPPORTS_LENGTH)) { lengthStr[0] = L'['; timeValue(totalPlayLength,&lengthStr[1]); wcscat_s(lengthStr,100,L"] "); } __int64 available = dev->getDeviceCapacityAvailable(); WASABI_API_LNG->FormattedSizeString(sizeStr, ARRAYSIZE(sizeStr), totalSize); WASABI_API_LNG->FormattedSizeString(availStr, ARRAYSIZE(availStr), available); __int64 capacity = dev->getDeviceCapacityTotal(); WASABI_API_LNG->FormattedSizeString(devCapacityStr, ARRAYSIZE(devCapacityStr), capacity); if(capacity > 0) usedPercent = (int)((((__int64)100)*available) / capacity); else usedPercent = 0; if (!cloud || cloud && available > 0) wsprintf(buf, WASABI_API_LNGSTRINGW(IDS_X_ITEMS_X_AVAILABLE), numTracks, lengthStr, sizeStr, availStr, usedPercent, devCapacityStr); else wsprintf(buf, WASABI_API_LNGSTRINGW(IDS_X_ITEMS_X_AVAILABLE_SLIM), numTracks, lengthStr, sizeStr); } class TracksList : public PrimaryListContents { public: __int64 totalSize; int totalPlayLength; int sortcol; int sortdir; C_ItemList * tracks; Device * dev; ArtistAlbumLists * aaList; TracksList(Device * dev,C_Config * config, ArtistAlbumLists * aaList, bool cloud) : dev(dev), aaList(aaList), totalSize(0), totalPlayLength(0), tracks(0) { this->config = config; this->cloud = cloud; this->cloudcol = -1; this->sortcol = config->ReadInt(L"sortcol", 0); this->sortdir = config->ReadInt(L"sortdir", 0); int fieldsBits = (int)dev->extraActions(DEVICE_SUPPORTED_METADATA,0,0,0); if (!fieldsBits) fieldsBits = -1; if (fieldsBits & SUPPORTS_ARTIST) fields.Add(new ListField(0, 200, WASABI_API_LNGSTRINGW(IDS_ARTIST), config)); if (fieldsBits & SUPPORTS_TITLE) fields.Add(new ListField(1, 200, WASABI_API_LNGSTRINGW(IDS_TITLE), config)); if (fieldsBits & SUPPORTS_ALBUM) fields.Add(new ListField(2, 200, WASABI_API_LNGSTRINGW(IDS_ALBUM), config)); if (fieldsBits & SUPPORTS_LENGTH) fields.Add(new ListField(3, 64, WASABI_API_LNGSTRINGW(IDS_LENGTH), config)); if (cloud) fields.Add(new ListField(3+cloud, 27, WASABI_API_LNGSTRINGW(IDS_CLOUD), config)); if (fieldsBits & SUPPORTS_TRACKNUM) fields.Add(new ListField(4+cloud, 50, WASABI_API_LNGSTRINGW(IDS_TRACK_NUMBER), config)); if (fieldsBits & SUPPORTS_DISCNUM) fields.Add(new ListField(5+cloud, 38, WASABI_API_LNGSTRINGW(IDS_DISC), config)); if (fieldsBits & SUPPORTS_GENRE) fields.Add(new ListField(6+cloud, 100, WASABI_API_LNGSTRINGW(IDS_GENRE), config)); if (fieldsBits & SUPPORTS_YEAR) fields.Add(new ListField(7+cloud, 38, WASABI_API_LNGSTRINGW(IDS_YEAR), config)); if (fieldsBits & SUPPORTS_BITRATE) fields.Add(new ListField(8+cloud, 45, WASABI_API_LNGSTRINGW(IDS_BITRATE), config)); if (fieldsBits & SUPPORTS_SIZE) fields.Add(new ListField(9+cloud, 90, WASABI_API_LNGSTRINGW(IDS_SIZE), config)); if (fieldsBits & SUPPORTS_PLAYCOUNT) fields.Add(new ListField(10+cloud, 64, WASABI_API_LNGSTRINGW(IDS_PLAY_COUNT), config)); if (fieldsBits & SUPPORTS_RATING) fields.Add(new ListField(11+cloud, 64, WASABI_API_LNGSTRINGW(IDS_RATING), config)); if (fieldsBits & SUPPORTS_LASTPLAYED) fields.Add(new ListField(12+cloud, 120, WASABI_API_LNGSTRINGW(IDS_LAST_PLAYED), config)); if (fieldsBits & SUPPORTS_ALBUMARTIST) fields.Add(new ListField(13+cloud, 200, WASABI_API_LNGSTRINGW(IDS_ALBUM_ARTIST), config, true)); if (fieldsBits & SUPPORTS_PUBLISHER) fields.Add(new ListField(14+cloud, 200, WASABI_API_LNGSTRINGW(IDS_PUBLISHER), config, true)); if (fieldsBits & SUPPORTS_COMPOSER) fields.Add(new ListField(15+cloud, 200, WASABI_API_LNGSTRINGW(IDS_COMPOSER), config, true)); if (fieldsBits & SUPPORTS_MIMETYPE) fields.Add(new ListField(16+cloud, 100, WASABI_API_LNGSTRINGW(IDS_MIME_TYPE), config, true)); if (fieldsBits & SUPPORTS_DATEADDED) fields.Add(new ListField(17+cloud, 120, WASABI_API_LNGSTRINGW(IDS_DATE_ADDED), config, true)); this->SortColumns(); if (cloud) { // not pretty but it'll allow us to know the current // position of the cloud column for drawing purposes for(int i = 0; i < fields.GetSize(); i++) { if (!lstrcmpi(((ListField *)fields.Get(i))->name, L"cloud")) { this->cloudcol = ((ListField *)fields.Get(i))->pos; break; } } } } virtual ~TracksList() { config->WriteInt(L"sortcol", sortcol); config->WriteInt(L"sortdir", sortdir); } virtual int GetNumColumns() { return fields.GetSize(); } virtual int GetNumRows() { return (tracks ? tracks->GetSize() : 0); } virtual int GetColumnWidth(int num) { if(num >=0 && num < fields.GetSize()) return ((ListField *)fields.Get(num))->width; return 0; } virtual wchar_t * GetColumnTitle(int num) { if(num >=0 && num < fields.GetSize()) return ((ListField *)fields.Get(num))->name; return L""; } virtual void GetCellText(int row, int col, wchar_t * buf, int buflen) { buf[0]=0; if(row >= tracks->GetSize() || aaList->bgThread_Handle) return; songid_t s = (songid_t)tracks->Get(row); if(col >=0 && col < fields.GetSize()) { if (cloud) { switch(((ListField *)fields.Get(col))->field) { case 0: dev->getTrackArtist(s,buf,buflen); return; case 1: dev->getTrackTitle(s,buf,buflen); return; case 2: dev->getTrackAlbum(s,buf,buflen); return; case 3: { int l=dev->getTrackLength(s); if (l>=0) wsprintf(buf,L"%d:%02d",l/1000/60,(l/1000)%60); return; } case 4: { dev->getTrackExtraInfo(s,L"cloud",buf,buflen); return; } case 5: { int t=dev->getTrackTrackNum(s); if (t>0) wsprintf(buf,L"%d",t); return; } case 6: { int d = dev->getTrackDiscNum(s); if(d>0) wsprintf(buf,L"%d",d); return; } case 7: dev->getTrackGenre(s,buf,buflen); return; case 8: { int d = dev->getTrackYear(s); if(d>0) wsprintf(buf,L"%d",d); return; } case 9: { int d = dev->getTrackBitrate(s); if(d>0) wsprintf(buf,WASABI_API_LNGSTRINGW(IDS_KBPS),d); return; } case 10: WASABI_API_LNG->FormattedSizeString(buf, buflen, dev->getTrackSize(s)); return; case 11: { int d = dev->getTrackPlayCount(s); if(d>=0) wsprintf(buf,L"%d",d); return; } case 12: getStars(dev->getTrackRating(s),buf,buflen); return; case 13: timeToString(dev->getTrackLastPlayed(s),buf,buflen); return; case 14: dev->getTrackAlbumArtist(s,buf,buflen); return; case 15: dev->getTrackPublisher(s,buf,buflen); return; case 16: dev->getTrackComposer(s,buf,buflen); return; case 17: dev->getTrackMimeType(s,buf,buflen); return; case 18: timeToString(dev->getTrackDateAdded(s),buf,buflen); return; } } else { switch(((ListField *)fields.Get(col))->field) { case 0: dev->getTrackArtist(s,buf,buflen); return; case 1: dev->getTrackTitle(s,buf,buflen); return; case 2: dev->getTrackAlbum(s,buf,buflen); return; case 3: { int l=dev->getTrackLength(s); wsprintf(buf,L"%d:%02d",l/1000/60,(l/1000)%60); return; } case 4: { int t=dev->getTrackTrackNum(s); if (t>0) wsprintf(buf,L"%d",t); return; } case 5: { int d = dev->getTrackDiscNum(s); if(d>0) wsprintf(buf,L"%d",d); return; } case 6: dev->getTrackGenre(s,buf,buflen); return; case 7: { int d = dev->getTrackYear(s); if(d>0) wsprintf(buf,L"%d",d); return; } case 8: { int d = dev->getTrackBitrate(s); if(d>0) wsprintf(buf,WASABI_API_LNGSTRINGW(IDS_KBPS),d); return; } case 9: WASABI_API_LNG->FormattedSizeString(buf, buflen, dev->getTrackSize(s)); return; case 10: { int d = dev->getTrackPlayCount(s); if(d>=0) wsprintf(buf,L"%d",d); return; } case 11: getStars(dev->getTrackRating(s),buf,buflen); return; case 12: timeToString(dev->getTrackLastPlayed(s),buf,buflen); return; case 13: dev->getTrackAlbumArtist(s,buf,buflen); return; case 14: dev->getTrackPublisher(s,buf,buflen); return; case 15: dev->getTrackComposer(s,buf,buflen); return; case 16: dev->getTrackMimeType(s,buf,buflen); return; case 17: timeToString(dev->getTrackDateAdded(s),buf,buflen); return; } } } } virtual void SortList() { useby = ((ListField*)fields.Get(sortcol))->field; usedir = sortdir; // if a cloud item then adjust things as needed // since we're inserting between genre and year usecloud = cloud; thread_killed = 0; currentDev = dev; qsort(tracks->GetAll(),tracks->GetSize(),sizeof(void*),sortFunc); } virtual void ColumnResize(int col, int newWidth) { if(col >=0 && col < fields.GetSize()) { ListField * lf = (ListField *)fields.Get(col); lf->width = newWidth; wchar_t buf[100] = {0}; wsprintf(buf,L"colWidth_%d",lf->field); config->WriteInt(buf,newWidth); } } virtual int GetSortColumn() { return sortcol; } virtual int GetSortDirection() { return sortdir; } virtual void ColumnClicked(int col) { if(col == sortcol) sortdir = sortdir?0:1; else { sortdir=0; sortcol=col; } SortList(); } virtual void GetInfoString(wchar_t * buf) { ::GetInfoString(buf, dev, tracks->GetSize(), totalSize, totalPlayLength, cloud); } virtual songid_t GetTrack(int pos) { return (songid_t)tracks->Get(pos); } virtual void RemoveTrack(songid_t song) { for(int i=0; iGetSize(); i++) { if((songid_t)tracks->Get(i) == song) tracks->Del(i--); } } }; static void FreeFilterItemList(C_ItemList * list) { if(!list) return; for(int i=0; i < list->GetSize(); i++) { FilterItem * a = (FilterItem*)list->Get(i); if(a->nextFilter) FreeFilterItemList(a->nextFilter); a->nextFilter=0; delete a; } delete list; } static DWORD WINAPI bgThreadSearchProc(void *tmp) { ArtistAlbumLists *aacList = (ArtistAlbumLists *)tmp; return (aacList ? aacList->bgSearchThreadProc(tmp) : 0); } static DWORD WINAPI bgThreadLoadProc(void *tmp) { ArtistAlbumLists *aacList = (ArtistAlbumLists *)tmp; return (aacList ? aacList->bgLoadThreadProc(tmp) : 0); } static DWORD WINAPI bgThreadRefineProc(void *tmp) { ArtistAlbumLists *aacList = (ArtistAlbumLists *)tmp; return (aacList ? aacList->bgRefineThreadProc(tmp) : 0); } extern HWND hwndMediaView; DWORD WINAPI ArtistAlbumLists::bgLoadThreadProc(void *tmp) { int l = dev->getPlaylistLength(playlistId); for(int i=0; igetPlaylistTrack(playlistId,i); int t = dev->getTrackType(x); if(type != -1 && t != type) continue; searchedTracks->Add((void*)x); trackList->Add((void*)x); unrefinedTracks->Add((void*)x); } for(int i=0; iitems); filters[0]->items = CompilePrimaryList(searchedTracks); } else { delete filters[i]->items; filters[i]->items = CompileSecondaryList(filters[i-1]->items,i,true); } filters[i]->SortList(); } SetRefine(L""); if (!bgThread_Kill) PostMessage(hwndMediaView, WM_APP + 3, 0x69, 0); return 0; } DWORD WINAPI ArtistAlbumLists::bgSearchThreadProc(void *tmp) { int l = dev->getPlaylistLength(playlistId); C_ItemList allTracks; for(int i=0; igetPlaylistTrack(playlistId,i); int t = dev->getTrackType(x); if(type != -1 && t != type) continue; allTracks.Add((void*)x); } searchedTracks = FilterSongs(lastSearch, &allTracks); delete unrefinedTracks; unrefinedTracks = new C_ItemList; l=searchedTracks->GetSize(); for(int i=0; iAdd(searchedTracks->Get(i)); for(int i=0; iitems); filters[0]->items = CompilePrimaryList(searchedTracks); } else { delete filters[i]->items; filters[i]->items = CompileSecondaryList(filters[i-1]->items,i,true); } } SetRefine(L""); for(int i=0; iSortList(); if (!bgThread_Kill) PostMessage(hwndMediaView, WM_APP + 3, 0x69, 0); return 0; } DWORD WINAPI ArtistAlbumLists::bgRefineThreadProc(void *tmp) { SetRefine(lastRefine); if (!bgThread_Kill) PostMessage(hwndMediaView, WM_APP + 3, 0x69, 0); return 0; } void ArtistAlbumLists::bgQuery_Stop() // exported for other people to call since it is useful (eventually { KillTimer(hwndMediaView, 123); if (bgThread_Handle) { thread_killed = bgThread_Kill = 1; WaitForSingleObject(bgThread_Handle, INFINITE); CloseHandle(bgThread_Handle); bgThread_Handle = 0; } } void ArtistAlbumLists::bgQuery(int mode) // only internal used { bgQuery_Stop(); // TODO cache the HWND to avoid confusion SetTimer(hwndMediaView, 123, 200, NULL); DWORD id; bgThread_Kill = 0; bgThread_Handle = CreateThread(NULL, 0, (!mode ? bgThreadLoadProc : (mode == 1 ? bgThreadSearchProc : bgThreadRefineProc)), (LPVOID)this, 0, &id); } ArtistAlbumLists::ArtistAlbumLists(Device * dev, int playlistId, C_Config * config, wchar_t ** filterNames, int numFilters0, int type, bool async) { this->type = type; this->dev = dev; this->playlistId = playlistId; this->numFilters = numFilters0; this->bgThread_Handle = 0; this->async = async; this->lastSearch = 0; this->lastRefine = 0; ZeroMemory(&filters, sizeof(filters)); if (config->ReadInt(L"savefilter", 1)) { lastSearch = wcsdup(config->ReadString(L"savedfilter", L"")); lastRefine = wcsdup(config->ReadString(L"savedrefinefilter", L"")); } Filter * f = NULL; for(int i = 0; i < numFilters; i++) { if(!i) { f = firstFilter = getFilter(filterNames[i]); } else { f->nextFilter = getFilter(filterNames[i]); f = f->nextFilter; } } f = firstFilter; for(int i=0; inextFilter; } tracksLC = new TracksList(dev,config,this,async); searchedTracks = new C_ItemList; trackList = new C_ItemList; unrefinedTracks = new C_ItemList; if (!async && (!lastSearch || lastSearch && !*lastSearch)) { int l = dev->getPlaylistLength(playlistId); for(int i=0; igetPlaylistTrack(playlistId,i); int t = dev->getTrackType(x); if(type != -1 && t != type) continue; searchedTracks->Add((void*)x); trackList->Add((void*)x); unrefinedTracks->Add((void*)x); } } for(int i=0; iitems = CompilePrimaryList(searchedTracks); else filters[i]->items = CompileSecondaryList(filters[i-1]->items,i,true); filters[i]->SortList(); } tracksLC->tracks = trackList; tracksLC->dev=dev; if (async) bgQuery(); else SetRefine((lastRefine ? lastRefine : L"")); } ArtistAlbumLists::~ArtistAlbumLists() { if(numFilters && filters[0]) FreeFilterItemList(filters[0]->items); for(int i=0; ifilter; if(i!=0) delete filters[i]->items; delete filters[i]; } delete trackList; delete searchedTracks; delete unrefinedTracks; delete tracksLC; if (lastSearch) free(lastSearch); if (lastRefine) free(lastRefine); } static void parsequicksearch(wchar_t *out, wchar_t *in) // parses a list into a list of terms that we are searching for { int inquotes=0, neednull=0; while (in && *in) { wchar_t c=*in++; if (c != L' ' && c != L'\t' && c != L'\"') { neednull=1; *out++=c; } else if (c == L'\"') { inquotes=!inquotes; if (!inquotes) { *out++=0; neednull=0; } } else { if (inquotes) *out++=c; else if (neednull) { *out++=0; neednull=0; } } } *out++=0; *out++=0; } static int in_string(wchar_t *string, wchar_t *substring) { if (!string) return 0; if (!*substring) return 1; int l=lstrlen(substring); while (string[0]) if (!_wcsnicmp(string++,substring,l)) return 1; return 0; } C_ItemList * FilterSongs(const wchar_t * filter, const C_ItemList * songs, Device * dev, bool cloud) { wchar_t filterstr[256] = {0}, filteritems[300] = {0}; lstrcpyn(filterstr,filter,256); parsequicksearch(filteritems,filterstr); C_ItemList * filtered = new C_ItemList; int l = songs->GetSize(); for(int i=0; iGet(i); wchar_t *p=filteritems; if(p && *p) { while(p && *p) { bool in=false; for(int j=0; j<15; j++) { wchar_t buf[2048] = {0}; int buflen=2048; if (cloud) { switch(j) { case 0: dev->getTrackArtist(s,buf,buflen); break; case 1: dev->getTrackTitle(s,buf,buflen); break; case 2: dev->getTrackAlbum(s,buf,buflen); break; case 3: { int l=dev->getTrackLength(s); wsprintf(buf,L"%d:%02d",l/1000/60,(l/1000)%60); break; } case 4: { dev->getTrackExtraInfo(s,L"cloud",buf,buflen); break; } case 5: wsprintf(buf,L"%d",dev->getTrackTrackNum(s)); break; case 6: wsprintf(buf,L"%d",dev->getTrackDiscNum(s)); break; case 7: dev->getTrackGenre(s,buf,buflen); break; case 8: wsprintf(buf,L"%d",dev->getTrackYear(s)); break; case 9: wsprintf(buf,WASABI_API_LNGSTRINGW(IDS_KBPS),dev->getTrackBitrate(s)); break; case 10: WASABI_API_LNG->FormattedSizeString(buf, buflen, dev->getTrackSize(s)); break; case 11: wsprintf(buf,L"%d",dev->getTrackPlayCount(s)); break; case 12: getStars(dev->getTrackRating(s),buf,buflen); break; case 13: timeToString(dev->getTrackLastPlayed(s),buf,buflen); break; case 14: dev->getTrackAlbumArtist(s,buf,buflen); break; case 15: dev->getTrackPublisher(s,buf,buflen); break; case 16: dev->getTrackComposer(s,buf,buflen); break; case 17: dev->getTrackMimeType(s,buf,buflen); break; case 18: timeToString(dev->getTrackDateAdded(s),buf,buflen); break; default: lstrcpyn(buf,L"",buflen); break; } } else { switch(j) { case 0: dev->getTrackArtist(s,buf,buflen); break; case 1: dev->getTrackTitle(s,buf,buflen); break; case 2: dev->getTrackAlbum(s,buf,buflen); break; case 3: { int l=dev->getTrackLength(s); wsprintf(buf,L"%d:%02d",l/1000/60,(l/1000)%60); break; } case 4: wsprintf(buf,L"%d",dev->getTrackTrackNum(s)); break; case 5: wsprintf(buf,L"%d",dev->getTrackDiscNum(s)); break; case 6: dev->getTrackGenre(s,buf,buflen); break; case 7: wsprintf(buf,L"%d",dev->getTrackYear(s)); break; case 8: wsprintf(buf,WASABI_API_LNGSTRINGW(IDS_KBPS),dev->getTrackBitrate(s)); break; case 9: WASABI_API_LNG->FormattedSizeString(buf, buflen, dev->getTrackSize(s)); break; case 10: wsprintf(buf,L"%d",dev->getTrackPlayCount(s)); break; case 11: getStars(dev->getTrackRating(s),buf,buflen); break; case 12: timeToString(dev->getTrackLastPlayed(s),buf,buflen); break; case 13: dev->getTrackAlbumArtist(s,buf,buflen); break; case 14: dev->getTrackPublisher(s,buf,buflen); break; case 15: dev->getTrackComposer(s,buf,buflen); break; case 16: dev->getTrackMimeType(s,buf,buflen); break; case 17: timeToString(dev->getTrackDateAdded(s),buf,buflen); break; default: lstrcpyn(buf,L"",buflen); break; } } if(in_string(buf,p)) { in=true; break;} } if(in) p+=lstrlen(p)+1; else break; } } if(p && *p) continue; filtered->Add((void*)s); } return filtered; } C_ItemList * ArtistAlbumLists::FilterSongs(const wchar_t * filter, const C_ItemList * songs) { return ::FilterSongs(filter,songs,dev,this->async); } static Filter * firstFil; static int sortFunc_ssi(const void *elem1, const void *elem2) { SortSongItem * a = (SortSongItem *)elem1; SortSongItem * b = (SortSongItem *)elem2; Filter * f = firstFil; while(f) { int r = f->sortFunc(a->dev,a->songid,b->songid); if(r) return r; f = f->nextFilter; } return 0; } C_ItemList * ArtistAlbumLists::CompilePrimaryList(const C_ItemList * songs) { C_ItemList * list = new C_ItemList; SortSongItem *songList = (SortSongItem*)calloc(songs->GetSize(), sizeof(SortSongItem)); int l=songs->GetSize(); for(int i=0; iGet(i); songList[i].dev = dev; } firstFil = firstFilter; qsort(songList,l,sizeof(SortSongItem),sortFunc_ssi); //sort it FilterItem * items[MAX_FILTERS]={0}; Filter * filters[MAX_FILTERS]={0}; Filter * f = firstFilter; int numFilters=0; while(f) {filters[numFilters++] = f; f=f->nextFilter;} for(int i=0; iisInGroup(dev,s,items[j])) filters[j]->addToGroup(dev,s,items[j]); else { for(int k=j; knewGroup(dev,s); items[k]->nextFilter = new C_ItemList; if(k==0) list->Add(items[k]); else { items[k-1]->nextFilter->Add(items[k]); items[k-1]->numNextFilter++; } } break; } } } if (songList) { free(songList); songList = 0; } if(list->GetSize() && ((FilterItem*)list->Get(0))->isWithoutGroup()) { wsprintf(this->filters[0]->topString,WASABI_API_LNGSTRINGW(IDS_ALL_X_WITHOUT_X), list->GetSize()-1,(list->GetSize()==2)?this->filters[0]->filter->name:this->filters[0]->filter->namePlural,((FilterItem*)list->Get(0))->numTracks,this->filters[0]->filter->name); } else wsprintf(this->filters[0]->topString,WASABI_API_LNGSTRINGW(IDS_ALL_X), list->GetSize(),(list->GetSize()==1)?this->filters[0]->filter->name:this->filters[0]->filter->namePlural); CharLower(this->filters[0]->topString+3); this->filters[0]->tracks = songs->GetSize(); return list; } static int sortFunc_filters(const void *elem1, const void *elem2) { FilterItem *a=(FilterItem *)*(void **)elem1; FilterItem *b=(FilterItem *)*(void **)elem2; return a->compareTo(b); } C_ItemList * ArtistAlbumLists::CompileSecondaryList(const C_ItemList * selectedItems, int level, bool updateTopArtist) { int totalTracks=0; C_ItemList * list = new C_ItemList; C_ItemList * collatedlist = new C_ItemList; for(int i=0; i < selectedItems->GetSize(); i++) { FilterItem * item = (FilterItem*)selectedItems->Get(i); if (item) { C_ItemList *nf = item->independentNextFilter?item->independentNextFilter:item->nextFilter; for(int j=0; j < nf->GetSize(); j++) list->Add(nf->Get(j)); } } qsort(list->GetAll(),list->GetSize(),sizeof(void*),sortFunc_filters); FilterItem * curItem=0; for(int i=0; i < list->GetSize(); i++) { FilterItem * item = (FilterItem*)list->Get(i); if(curItem && !curItem->compareTo(item)) { curItem->independentTracks += item->numTracks; curItem->independentSize += item->size; curItem->independentLength += item->length; curItem->independentCloudState += item->cloudState; } else { curItem = item; collatedlist->Add(item); item->independentTracks = item->numTracks; curItem->independentSize = item->size; curItem->independentLength = item->length; curItem->independentCloudState = item->cloudState; if(item->independentNextFilter) delete item->independentNextFilter; item->independentNextFilter = new C_ItemList; } totalTracks+=item->numTracks; for(int k=0; knextFilter->GetSize(); k++) curItem->independentNextFilter->Add(item->nextFilter->Get(k)); } delete list; if(updateTopArtist) this->filters[level-1]->nextFilterNum = collatedlist->GetSize(); if(collatedlist->GetSize() && ((FilterItem*)collatedlist->Get(0))->isWithoutGroup()) { wsprintf(this->filters[level]->topString,WASABI_API_LNGSTRINGW(IDS_ALL_X_WITHOUT_X), collatedlist->GetSize(),(collatedlist->GetSize()==1)?this->filters[level]->filter->name:this->filters[level]->filter->namePlural,((FilterItem*)collatedlist->Get(0))->numTracks,this->filters[level]->filter->name); } else { wsprintf(this->filters[level]->topString,WASABI_API_LNGSTRINGW(IDS_ALL_X), collatedlist->GetSize(),(collatedlist->GetSize()==1)?this->filters[level]->filter->name:this->filters[level]->filter->namePlural); } CharLower(this->filters[level]->topString+3); this->filters[level]->tracks=totalTracks; return collatedlist; } // removes song from all relevant lists void ArtistAlbumLists::RemoveTrack(songid_t song) { if (searchedTracks) { for (int i=0;iGetSize();i++) { if (searchedTracks->Get(i) == (void *) song) { searchedTracks->Del(i--); } } } } void ArtistAlbumLists::SelectionChanged(int filterNum, SkinnedListView **listview) { for(int i=filterNum; ilistview.GetCount(); bool all = (i != filterNum || (ListView_GetSelectedCount(listview[i]->listview.getwnd())==0)); if(listview[i]->listview.GetSelected(0) && filters[i]->filter->HaveTopItem()) all=true; C_ItemList selectedItems; int j=0,f=0; if(filters[i]->filter->HaveTopItem()) { j=1; f=1; } for(; jlistview.GetSelected(j)) selectedItems.Add(filters[i]->items->Get(j-f)); } delete filters[i+1]->items; filters[i+1]->items = CompileSecondaryList(&selectedItems,i+1,false); filters[i+1]->SortList(); listview[i+1]->UpdateList(); } C_ItemList * tracks = new C_ItemList; C_ItemList * selectedItems[MAX_FILTERS]={0}; for(int i=0; i<=filterNum; i++) { bool all = (ListView_GetSelectedCount(listview[i]->listview.getwnd())==0); if(listview[i]->listview.GetSelected(0) && filters[i]->filter->HaveTopItem()) all=true; if(all) selectedItems[i] = NULL; else { selectedItems[i] = new C_ItemList; int m = filters[i]->items->GetSize(); int offset = filters[i]->filter->HaveTopItem()?1:0; for(int k=0; klistview.GetSelected(k+offset)) selectedItems[i]->Add(filters[i]->items->Get(k)); } } int l=searchedTracks->GetSize(); for(int j=0; jGet(j); bool matches=true; for(int i=0; i<=filterNum; i++) { matches=false; if(selectedItems[i]) { for(int k=0; kGetSize(); k++) if(filters[i]->filter->isInGroup(dev,track,(FilterItem*)selectedItems[i]->Get(k))) { matches=true; break; } } else matches=true; if(!matches) break; } if(matches) //woo hoo, its in! tracks->Add((void*)track); } delete unrefinedTracks; unrefinedTracks = tracks; SetRefine(L""); for(int i=0; i<=filterNum; i++) { delete selectedItems[i]; } } void ArtistAlbumLists::SetRefine(const wchar_t * str, bool async) { if (!async) { C_ItemList * refinedTracks = FilterSongs(str,unrefinedTracks); C_ItemList * oldTrackList = trackList; trackList = refinedTracks; tracksLC->tracks = trackList; delete oldTrackList; tracksLC->SortList(); // get stats __int64 fileSize=0; int playLength=0; int millis=0; for(int i=0; i < trackList->GetSize(); i++) { songid_t s = (songid_t)trackList->Get(i); fileSize += (__int64)dev->getTrackSize(s); playLength += dev->getTrackLength(s)/1000; millis += dev->getTrackLength(s)%1000; } playLength += millis/1000; tracksLC->totalPlayLength=playLength; tracksLC->totalSize=fileSize; } else { if (lastRefine) { free(lastRefine); lastRefine = 0; } lastRefine = wcsdup(str); bgQuery(2); } } void ArtistAlbumLists::SetSearch(const wchar_t * str, bool async) { bgQuery_Stop(); if (!async) { delete searchedTracks; searchedTracks = NULL; C_ItemList allTracks; int l = dev->getPlaylistLength(playlistId); for(int i=0; igetPlaylistTrack(playlistId,i); if(type != -1 && dev->getTrackType(x) != type) continue; allTracks.Add((void*)x); } searchedTracks = FilterSongs(str,&allTracks); delete unrefinedTracks; unrefinedTracks = new C_ItemList; l=searchedTracks->GetSize(); for(int i=0; iAdd(searchedTracks->Get(i)); for(int i=0; iitems); filters[0]->items = CompilePrimaryList(searchedTracks); } else { delete filters[i]->items; filters[i]->items = CompileSecondaryList(filters[i-1]->items,i,true); } } SetRefine(L""); for(int i=0; iSortList(); } else { if (lastSearch) { free(lastSearch); lastSearch = 0; } if (!(str && *str)) { if (unrefinedTracks) delete unrefinedTracks; if (searchedTracks) delete searchedTracks; if (trackList) delete trackList; searchedTracks = new C_ItemList; trackList = new C_ItemList; unrefinedTracks = new C_ItemList; } else { lastSearch = wcsdup(str); } bgQuery((str && *str)); } } ListContents * ArtistAlbumLists::GetFilterList(int i) { return filters[i]; } PrimaryListContents * ArtistAlbumLists::GetTracksList() { return this->tracksLC; }