406 lines
13 KiB
C++
406 lines
13 KiB
C++
|
#include "main.h"
|
||
|
#include "ml_local.h"
|
||
|
#include <shlwapi.h>
|
||
|
#include <assert.h>
|
||
|
#include "../nu/sort.h"
|
||
|
|
||
|
/*
|
||
|
#ifdef _M_IX86
|
||
|
#undef min
|
||
|
static inline int min(int x, int y)
|
||
|
{
|
||
|
return y+((x-y)>>31)&(x-y);
|
||
|
}
|
||
|
#undef max
|
||
|
static inline int max(int x, int y)
|
||
|
{
|
||
|
return x-(((x-y)>>(31))&(x-y));
|
||
|
}
|
||
|
|
||
|
#elif defined(_WIN64)
|
||
|
#undef min
|
||
|
static inline int min(int x, int y)
|
||
|
{
|
||
|
return y+((x-y)>>63)&(x-y);
|
||
|
}
|
||
|
#undef max
|
||
|
static inline int max(int x, int y)
|
||
|
{
|
||
|
return x-(((x-y)>>(63))&(x-y));
|
||
|
}
|
||
|
#endif
|
||
|
*/
|
||
|
|
||
|
#ifdef _M_IX86
|
||
|
const size_t convert_max_characters = 16; // it's actually 11, but this probably causes less memory fragmentation
|
||
|
#elif defined(_M_X64)
|
||
|
const size_t convert_max_characters = 20;
|
||
|
#endif
|
||
|
|
||
|
static inline int Compare_Int_NegativeIsNull(int v1, int v2)
|
||
|
{
|
||
|
v1 = max(v1,0);
|
||
|
v2 = max(v2,0);
|
||
|
return(v1 - v2);
|
||
|
}
|
||
|
|
||
|
typedef int (__fastcall *SortFunction)(const itemRecordW *a, const itemRecordW *b);
|
||
|
#define SORT(field) SortBy ## field
|
||
|
#define STRING_SORT(field) static int __fastcall SORT(field)(const itemRecordW *a, const itemRecordW *b) { return WCSCMP_NULLOK(a->field, b->field); }
|
||
|
#define EXT_STRING_SORT(field) static int __fastcall SORT(field)(const itemRecordW *a, const itemRecordW *b) { wchar_t *a_field = getRecordExtendedItem_fast(a, extended_fields.field); wchar_t *b_field = getRecordExtendedItem_fast(b, extended_fields.field); return WCSCMP_NULLOK(a_field, b_field);}
|
||
|
#define TIME_SORT(field) static int __fastcall SORT(field)(const itemRecordW *a, const itemRecordW *b) { time_t v1 = (time_t)a->field; time_t v2 = (time_t)b->field; if (v1 == -1) v1 = 0; if (v2 == -1) v2 = 0; return (int)(v1 - v2);}
|
||
|
#define EXT_TIME_SORT(field) static int __fastcall SORT(field)(const itemRecordW *a, const itemRecordW *b) { wchar_t *a_field = getRecordExtendedItem_fast(a, extended_fields.dateadded); wchar_t *b_field = getRecordExtendedItem_fast(b, extended_fields.dateadded); time_t v1 = a_field?_wtoi(a_field):0; time_t v2 = b_field?_wtoi(b_field):0; if (v1 == -1) v1 = 0; if (v2 == -1) v2 = 0; return (int)(v1 - v2);}
|
||
|
#define INT_SORT(field) static int __fastcall SORT(field)(const itemRecordW *a, const itemRecordW *b) { return Compare_Int_NegativeIsNull(a->field, b->field); }
|
||
|
#define EXT_INT_SORT(field) static int __fastcall SORT(field)(const itemRecordW *a, const itemRecordW *b) { wchar_t *a_field = getRecordExtendedItem_fast(a, extended_fields.field); wchar_t *b_field = getRecordExtendedItem_fast(b, extended_fields.field); int v1 = a_field?_wtoi(a_field):0; int v2 = b_field?_wtoi(b_field):0; return (v1-v2);}
|
||
|
#define FLOAT_SORT(field) static int __fastcall SORT(field)(const itemRecordW *a, const itemRecordW *b) { return FLOATWCMP_NULLOK(a->field, b->field); }
|
||
|
STRING_SORT(artist);
|
||
|
STRING_SORT(title);
|
||
|
STRING_SORT(album);
|
||
|
INT_SORT(length);
|
||
|
INT_SORT(track);
|
||
|
STRING_SORT(genre);
|
||
|
INT_SORT(year);
|
||
|
static int __fastcall SORT(filespec)(const itemRecordW *a, const itemRecordW *b)
|
||
|
{
|
||
|
// remove path before compare...
|
||
|
wchar_t * af = L"";
|
||
|
if (a->filename)
|
||
|
af = PathFindFileNameW(a->filename);
|
||
|
|
||
|
wchar_t * bf = L"";
|
||
|
if (b->filename)
|
||
|
bf = PathFindFileNameW(b->filename);
|
||
|
|
||
|
return _wcsicmp(af, bf);
|
||
|
}
|
||
|
|
||
|
INT_SORT(rating);
|
||
|
INT_SORT(playcount);
|
||
|
TIME_SORT(lastplay);
|
||
|
TIME_SORT(lastupd);
|
||
|
TIME_SORT(filetime);
|
||
|
STRING_SORT(comment);
|
||
|
INT_SORT(filesize);
|
||
|
INT_SORT(bitrate);
|
||
|
INT_SORT(type);
|
||
|
INT_SORT(disc);
|
||
|
STRING_SORT(albumartist);
|
||
|
STRING_SORT(filename);
|
||
|
FLOAT_SORT(replaygain_album_gain);
|
||
|
FLOAT_SORT(replaygain_track_gain);
|
||
|
STRING_SORT(publisher);
|
||
|
STRING_SORT(composer);
|
||
|
static int __fastcall SORT(extension)(const itemRecordW *a, const itemRecordW *b)
|
||
|
{
|
||
|
wchar_t *extA = PathFindExtensionW(a->filename);
|
||
|
wchar_t *extB = PathFindExtensionW(b->filename);
|
||
|
if (extA && *extA)
|
||
|
extA++;
|
||
|
if (extB && *extB)
|
||
|
extB++;
|
||
|
return WCSCMP_NULLOK(extA, extB);
|
||
|
}
|
||
|
EXT_INT_SORT(ispodcast);
|
||
|
EXT_STRING_SORT(podcastchannel);
|
||
|
EXT_STRING_SORT(podcastpubdate);
|
||
|
INT_SORT(bpm);
|
||
|
STRING_SORT(category);
|
||
|
EXT_STRING_SORT(director);
|
||
|
EXT_STRING_SORT(producer);
|
||
|
EXT_TIME_SORT(dateadded);
|
||
|
EXT_STRING_SORT(cloud);
|
||
|
|
||
|
static int __fastcall SORT(dimension)(const itemRecordW *a, const itemRecordW *b)
|
||
|
{
|
||
|
wchar_t *a_width = getRecordExtendedItem_fast(a, extended_fields.width);
|
||
|
wchar_t *b_width = getRecordExtendedItem_fast(b, extended_fields.width);
|
||
|
wchar_t *a_height = getRecordExtendedItem_fast(a, extended_fields.height);
|
||
|
wchar_t *b_height = getRecordExtendedItem_fast(b, extended_fields.height);
|
||
|
int w1 = a_width?_wtoi(a_width):0;
|
||
|
int w2 = b_width?_wtoi(b_width):0;
|
||
|
int h1 = a_height?_wtoi(a_height):0;
|
||
|
int h2 = b_height?_wtoi(b_height):0;
|
||
|
if (w1 != w2)
|
||
|
return (w1-w2);
|
||
|
else
|
||
|
return (h1-h2);
|
||
|
}
|
||
|
// DISABLED FOR 5.62 RELEASE - DRO
|
||
|
//EXT_STRING_SORT(codec);
|
||
|
|
||
|
#define FORCE_ASCENDING ((SortFunction)-2)
|
||
|
// this is where we define sort orders!
|
||
|
static const SortFunction sortOrder[MEDIAVIEW_COL_NUMS][MEDIAVIEW_COL_NUMS+2] =
|
||
|
{
|
||
|
{SORT(artist), FORCE_ASCENDING, SORT(album), SORT(disc), SORT(track), SORT(title), 0}, // Artist
|
||
|
{SORT(title), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
|
||
|
{SORT(album), FORCE_ASCENDING, SORT(albumartist), SORT(disc), SORT(track), SORT(title), 0},
|
||
|
{SORT(length), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
|
||
|
{SORT(track), FORCE_ASCENDING, SORT(title), SORT(artist), SORT(album), SORT(disc), 0},
|
||
|
{SORT(genre), FORCE_ASCENDING, SORT(albumartist), SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
|
||
|
{SORT(year), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
|
||
|
{SORT(filespec), SORT(filename),0},
|
||
|
{SORT(rating), SORT(playcount), SORT(lastplay), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
|
||
|
{SORT(playcount), SORT(lastplay), FORCE_ASCENDING,SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
|
||
|
{SORT(lastplay), FORCE_ASCENDING,SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
|
||
|
{SORT(lastupd), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
|
||
|
{SORT(filetime), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
|
||
|
{SORT(comment), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
|
||
|
{SORT(filesize),FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
|
||
|
{SORT(bitrate), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
|
||
|
{SORT(type), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
|
||
|
{SORT(disc), FORCE_ASCENDING, SORT(track), SORT(title), SORT(artist), SORT(album), SORT(disc), 0},
|
||
|
{SORT(albumartist), FORCE_ASCENDING, SORT(album), SORT(disc), SORT(track), 0},
|
||
|
{SORT(filename), 0},
|
||
|
{SORT(replaygain_album_gain), FORCE_ASCENDING, SORT(album), SORT(disc), SORT(track), SORT(title), 0},
|
||
|
{SORT(replaygain_track_gain), FORCE_ASCENDING, SORT(title), 0},
|
||
|
{SORT(publisher),FORCE_ASCENDING,SORT(artist), SORT(album), SORT(disc), SORT(track), SORT(title), 0},
|
||
|
{SORT(composer), FORCE_ASCENDING,SORT(album), SORT(disc), SORT(track), 0},
|
||
|
{SORT(extension), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
|
||
|
{SORT(ispodcast), FORCE_ASCENDING, SORT(podcastchannel), SORT(title), 0},
|
||
|
{SORT(podcastchannel), SORT(title), 0},
|
||
|
{SORT(podcastpubdate), 0},
|
||
|
{SORT(bpm), 0}, // TODO
|
||
|
{SORT(category),FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), SORT(title), 0},
|
||
|
{SORT(director),FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), SORT(title), 0},
|
||
|
{SORT(producer),FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), SORT(title), 0},
|
||
|
{SORT(dimension), 0},
|
||
|
// DISABLED FOR 5.62 RELEASE - DRO
|
||
|
//{SORT(codec), 0},
|
||
|
{SORT(dateadded), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
|
||
|
{SORT(cloud), FORCE_ASCENDING, SORT(artist), SORT(album), SORT(disc), SORT(track), 0},
|
||
|
};
|
||
|
|
||
|
/* ---- */
|
||
|
struct SortRules
|
||
|
{
|
||
|
int by;
|
||
|
int dir;
|
||
|
};
|
||
|
static int __fastcall sortFuncW(const void *elem1, const void *elem2, const void *context)
|
||
|
{
|
||
|
assert(sizeof(sortOrder) / sizeof(*sortOrder) == MEDIAVIEW_COL_NUMS);
|
||
|
|
||
|
const itemRecordW *a = (const itemRecordW*)elem1;
|
||
|
const itemRecordW *b = (const itemRecordW*)elem2;
|
||
|
|
||
|
const SortRules *rules = (SortRules *)context;
|
||
|
int use_by = rules->by;
|
||
|
int use_dir = !!rules->dir;
|
||
|
|
||
|
int dir_values[] = {-1, 1};
|
||
|
|
||
|
#define RETIFNZ(v) if ((v)<0) return dir_values[use_dir]; if ((v)>0) return -dir_values[use_dir];
|
||
|
|
||
|
const SortFunction *order =sortOrder[use_by];
|
||
|
int i=0;
|
||
|
while (order[i])
|
||
|
{
|
||
|
if (order[i]==FORCE_ASCENDING)
|
||
|
{
|
||
|
use_dir=0;
|
||
|
i++;
|
||
|
continue;
|
||
|
}
|
||
|
int v = order[i](a, b);
|
||
|
RETIFNZ(v);
|
||
|
i++;
|
||
|
}
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
void sortResults(int column, int dir, itemRecordListW *obj) // sorts the results based on the current sort mode
|
||
|
{
|
||
|
if (obj->Size > 1)
|
||
|
{
|
||
|
SortRules rules = {column, dir};
|
||
|
qsort_itemRecord(obj->Items, obj->Size, &rules, sortFuncW);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void sortResults(C_Config *viewconf, itemRecordListW *obj) // sorts the results based on the current sort mode
|
||
|
{
|
||
|
if (viewconf)
|
||
|
{
|
||
|
SortRules rules = {viewconf->ReadInt(L"mv_sort_by", MEDIAVIEW_COL_ARTIST), viewconf->ReadInt(L"mv_sort_dir", 0)};
|
||
|
if (obj->Size > 1)
|
||
|
qsort_itemRecord(obj->Items, obj->Size, &rules, sortFuncW);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void setCloudValue(itemRecordW *item, const wchar_t* value)
|
||
|
{
|
||
|
wchar_t *temp = ndestring_malloc(convert_max_characters*sizeof(wchar_t));
|
||
|
lstrcpynW(temp, value, convert_max_characters);
|
||
|
setRecordExtendedItem(item, extended_fields.cloud, temp);
|
||
|
ndestring_release(temp);
|
||
|
}
|
||
|
|
||
|
int saveQueryToListW(C_Config *viewconf, nde_scanner_t s, itemRecordListW *obj,
|
||
|
CloudFiles *uploaded, CloudFiles *uploading,
|
||
|
resultsniff_funcW cb, int user32, int *killswitch, __int64 *total_bytes)
|
||
|
{
|
||
|
__int64 total_kb = 0;
|
||
|
|
||
|
emptyRecordList(obj);
|
||
|
|
||
|
EnterCriticalSection(&g_db_cs);
|
||
|
NDE_Scanner_First(s, killswitch);
|
||
|
if (killswitch && *killswitch)
|
||
|
{
|
||
|
LeaveCriticalSection(&g_db_cs);
|
||
|
return 0;
|
||
|
}
|
||
|
|
||
|
int r = 0;
|
||
|
unsigned int total_length_s = 0;
|
||
|
unsigned int uncert_cnt = 0;
|
||
|
unsigned int cert_cnt = 0;
|
||
|
int recordCount = NDE_Scanner_GetRecordsCount(s);
|
||
|
allocRecordList(obj, recordCount);
|
||
|
do
|
||
|
{
|
||
|
nde_field_t f = NDE_Scanner_GetFieldByID(s, MAINTABLE_ID_FILENAME);
|
||
|
if (f)
|
||
|
{
|
||
|
allocRecordList(obj, obj->Size + 1);
|
||
|
if (!obj->Alloc) break;
|
||
|
|
||
|
obj->Items[obj->Size].filename = NDE_StringField_GetString(f);
|
||
|
ndestring_retain(obj->Items[obj->Size].filename);
|
||
|
total_kb += ScannerRefToObjCacheNFNW(s, obj, (cb == (resultsniff_funcW)-1 ? true : false));
|
||
|
|
||
|
// look for any files known to the cloud so the icon can be set accordingly
|
||
|
// TODO possibly need to do more here to better cope with transient states??
|
||
|
if (uploaded && uploaded->size() > 0)
|
||
|
{
|
||
|
bool found = false;
|
||
|
for(CloudFiles::iterator iter = uploaded->begin(); iter != uploaded->end(); iter++)
|
||
|
{
|
||
|
if (nde_wcsicmp_fn(obj->Items[obj->Size - 1].filename,(*iter)) == 0)
|
||
|
{
|
||
|
bool pending = false;
|
||
|
found = true;
|
||
|
|
||
|
// catches files being uploaded but not fully known to be in the cloud, etc
|
||
|
if (uploading && uploading->size() > 0)
|
||
|
{
|
||
|
for(CloudFiles::iterator iter2 = uploading->begin(); iter2 != uploading->end(); iter2++)
|
||
|
{
|
||
|
if (nde_wcsicmp_fn(obj->Items[obj->Size - 1].filename,(*iter2)) == 0)
|
||
|
{
|
||
|
pending = true;
|
||
|
setCloudValue(&obj->Items[obj->Size - 1], L"5");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!pending)
|
||
|
{
|
||
|
setCloudValue(&obj->Items[obj->Size - 1], L"0");
|
||
|
}
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!found)
|
||
|
{
|
||
|
// catches files being uploaded but not fully known to be in the cloud, etc
|
||
|
if (uploading && uploading->size() > 0)
|
||
|
{
|
||
|
for(CloudFiles::iterator iter = uploading->begin(); iter != uploading->end(); iter++)
|
||
|
{
|
||
|
if (nde_wcsicmp_fn(obj->Items[obj->Size - 1].filename,(*iter)) == 0)
|
||
|
{
|
||
|
found = true;
|
||
|
setCloudValue(&obj->Items[obj->Size - 1], L"5");
|
||
|
break;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (!found)
|
||
|
{
|
||
|
setCloudValue(&obj->Items[obj->Size - 1], L"4");
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
int thisl = obj->Items[obj->Size - 1].length;
|
||
|
|
||
|
if (thisl > 0)
|
||
|
{
|
||
|
total_length_s += thisl;
|
||
|
cert_cnt++;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
uncert_cnt++;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
r = NDE_Scanner_Next(s, killswitch);
|
||
|
if (killswitch && *killswitch)
|
||
|
{
|
||
|
LeaveCriticalSection(&g_db_cs);
|
||
|
return 0;
|
||
|
}
|
||
|
}
|
||
|
while (r && !NDE_Scanner_EOF(s));
|
||
|
|
||
|
if (((Table *)g_table)->HasErrors()) // TODO: don't use C++ NDE API
|
||
|
{
|
||
|
wchar_t *last_query = NULL;
|
||
|
if (m_media_scanner)
|
||
|
{
|
||
|
const wchar_t *lq = NDE_Scanner_GetLastQuery(m_media_scanner);
|
||
|
if (lq) last_query = _wcsdup(lq);
|
||
|
NDE_Table_DestroyScanner(g_table, m_media_scanner);
|
||
|
}
|
||
|
NDE_Table_Compact(g_table);
|
||
|
if (m_media_scanner)
|
||
|
{
|
||
|
m_media_scanner = NDE_Table_CreateScanner(g_table);
|
||
|
if (last_query != NULL)
|
||
|
{
|
||
|
NDE_Scanner_Query(m_media_scanner, last_query);
|
||
|
free(last_query);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
LeaveCriticalSection(&g_db_cs);
|
||
|
|
||
|
if (total_bytes)
|
||
|
{
|
||
|
// maintain compatibility as needed
|
||
|
if (cb == (resultsniff_funcW)-1)
|
||
|
*total_bytes = total_kb * 1024;
|
||
|
else
|
||
|
*total_bytes = total_kb;
|
||
|
}
|
||
|
compactRecordList(obj);
|
||
|
|
||
|
if (cb && cb != (resultsniff_funcW)-1)
|
||
|
{
|
||
|
cb(obj->Items, obj->Size, user32, killswitch);
|
||
|
}
|
||
|
|
||
|
if (killswitch && *killswitch) return 0;
|
||
|
|
||
|
sortResults(viewconf, obj);
|
||
|
|
||
|
if (killswitch && *killswitch) return 0;
|
||
|
|
||
|
if (uncert_cnt)
|
||
|
{
|
||
|
if (cert_cnt)
|
||
|
{
|
||
|
__int64 avg_len = (__int64)total_length_s / cert_cnt;
|
||
|
total_length_s += (DWORD)(avg_len * uncert_cnt);
|
||
|
}
|
||
|
total_length_s |= (1 << 31);
|
||
|
}
|
||
|
|
||
|
return total_length_s;
|
||
|
}
|