diff foosdk/sdk/foobar2000/SDK/metadb_handle_list.cpp @ 1:20d02a178406 default tip

*: check in everything else yay
author Paper <paper@tflc.us>
date Mon, 05 Jan 2026 02:15:46 -0500
parents
children
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/foosdk/sdk/foobar2000/SDK/metadb_handle_list.cpp	Mon Jan 05 02:15:46 2026 -0500
@@ -0,0 +1,569 @@
+#include "foobar2000-sdk-pch.h"
+#include "foosort.h"
+#include "threadPool.h"
+#include "foosortstring.h"
+#include <vector>
+#include "titleformat.h"
+#include "library_manager.h"
+#include "genrand.h"
+
+namespace {
+
+	struct custom_sort_data_multi {
+		static constexpr unsigned numLocal = 4;
+		void setup(size_t count) {
+			if (count > numLocal) texts2 = std::make_unique< fb2k::sortString_t[] >( count - numLocal );
+		}
+		fb2k::sortString_t& operator[] (size_t which) {
+			return which < numLocal ? texts1[which] : texts2[which - numLocal];
+		}
+		const fb2k::sortString_t& operator[] (size_t which) const {
+			return which < numLocal ? texts1[which] : texts2[which - numLocal];
+		}
+
+		fb2k::sortString_t texts1[numLocal];
+		std::unique_ptr< fb2k::sortString_t[] > texts2;
+		size_t index;
+	};
+	struct custom_sort_data {
+		fb2k::sortString_t text;
+		size_t index;
+	};
+	template<int direction>
+	static int custom_sort_compare(const custom_sort_data& elem1, const custom_sort_data& elem2) {
+		int ret = direction * fb2k::sortStringCompare(elem1.text, elem2.text);
+		if (ret == 0) ret = pfc::sgn_t((t_ssize)elem1.index - (t_ssize)elem2.index);
+		return ret;
+	}
+
+}
+
+void metadb_handle_list_helper::sort_by_format(metadb_handle_list_ref p_list,const char * spec,titleformat_hook * p_hook)
+{
+	service_ptr_t<titleformat_object> script;
+	if (titleformat_compiler::get()->compile(script,spec))
+		sort_by_format(p_list,script,p_hook);
+}
+
+void metadb_handle_list_helper::sort_by_format_get_order(metadb_handle_list_cref p_list,t_size* order,const char * spec,titleformat_hook * p_hook)
+{
+	service_ptr_t<titleformat_object> script;
+	if (titleformat_compiler::get()->compile(script,spec))
+		sort_by_format_get_order(p_list,order,script,p_hook);
+}
+
+void metadb_handle_list_helper::sort_by_format(metadb_handle_list_ref p_list,const service_ptr_t<titleformat_object> & p_script,titleformat_hook * p_hook, int direction)
+{
+	const t_size count = p_list.get_count();
+	pfc::array_t<t_size> order; order.set_size(count);
+	sort_by_format_get_order(p_list,order.get_ptr(),p_script,p_hook,direction);
+	p_list.reorder(order.get_ptr());
+}
+
+namespace {
+
+	class tfhook_sort : public titleformat_hook {
+	public:
+		tfhook_sort() {
+			m_API->seed();
+		}
+		bool process_field(titleformat_text_out *,const char *,t_size,bool &) override {
+			return false;
+		}
+		bool process_function(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,titleformat_hook_function_params * p_params,bool & p_found_flag) override {
+			if (stricmp_utf8_ex(p_name, p_name_length, "rand", SIZE_MAX) == 0) {
+				t_size param_count = p_params->get_param_count();
+				t_uint32 val;
+				if (param_count == 1) {
+					t_uint32 mod = (t_uint32)p_params->get_param_uint(0);
+					if (mod > 0) {
+						val = m_API->genrand(mod);
+					} else {
+						val = 0;
+					}
+				} else {
+					val = m_API->genrand(0xFFFFFFFF);
+				}
+				p_out->write_int(titleformat_inputtypes::unknown, val);
+				p_found_flag = true;
+				return true;
+			} else {
+				return false;
+			}
+		}
+	private:
+		genrand_service::ptr m_API = genrand_service::get();
+	};
+}
+
+void metadb_handle_list_helper::sort_by_format_get_order(metadb_handle_list_cref p_list,t_size* order,const service_ptr_t<titleformat_object> & p_script,titleformat_hook * p_hook,int p_direction)
+{
+	sort_by_format_get_order_v2(p_list, order, p_script, p_hook, p_direction, fb2k::noAbort );
+}
+
+void metadb_handle_list_helper::sort_by_relative_path(metadb_handle_list_ref p_list)
+{
+	const t_size count = p_list.get_count();
+	pfc::array_t<t_size> order; order.set_size(count);
+	sort_by_relative_path_get_order(p_list,order.get_ptr());
+	p_list.reorder(order.get_ptr());
+}
+
+void metadb_handle_list_helper::sort_by_relative_path_get_order(metadb_handle_list_cref p_list,t_size* order)
+{
+	const t_size count = p_list.get_count();
+	t_size n;
+	std::vector<custom_sort_data> data;
+	data.resize(count);
+	auto api = library_manager::get();
+	
+	pfc::string8_fastalloc temp;
+	temp.prealloc(512);
+	for(n=0;n<count;n++)
+	{
+		metadb_handle_ptr item;
+		p_list.get_item_ex(item,n);
+		if (!api->get_relative_path(item,temp)) temp = "";
+		data[n].index = n;
+		data[n].text = fb2k::makeSortString(temp);
+		//data[n].subsong = item->get_subsong_index();
+	}
+
+	pfc::sort_t(data,custom_sort_compare<1>,count);
+	//qsort(data.get_ptr(),count,sizeof(custom_sort_data),(int (__cdecl *)(const void *elem1, const void *elem2 ))custom_sort_compare);
+
+	for(n=0;n<count;n++)
+	{
+		order[n]=data[n].index;
+	}
+}
+
+void metadb_handle_list_helper::remove_duplicates(metadb_handle_list_ref p_list)
+{
+	t_size count = p_list.get_count();
+	if (count>0)
+	{
+		pfc::bit_array_bittable mask(count);
+		pfc::array_t<t_size> order; order.set_size(count);
+		order_helper::g_fill(order);
+
+		p_list.sort_get_permutation_t(pfc::compare_t<metadb_handle_ptr,metadb_handle_ptr>,order.get_ptr());
+		
+		t_size n;
+		bool found = false;
+		for(n=0;n<count-1;n++)
+		{
+			if (p_list.get_item(order[n])==p_list.get_item(order[n+1]))
+			{
+				found = true;
+				mask.set(order[n+1],true);
+			}
+		}
+		
+		if (found) p_list.remove_mask(mask);
+	}
+}
+
+void metadb_handle_list_helper::sort_by_pointer_remove_duplicates(metadb_handle_list_ref p_list)
+{
+	const t_size count = p_list.get_count();
+	if (count>0)
+	{
+		sort_by_pointer(p_list);
+		bool b_found = false;
+		for(size_t n=0;n<count-1;n++)
+		{
+			if (p_list.get_item(n)==p_list.get_item(n+1))
+			{
+				b_found = true;
+				break;
+			}
+		}
+
+		if (b_found)
+		{
+			pfc::bit_array_bittable mask(count);
+			for(size_t n=0;n<count-1;n++)
+			{
+				if (p_list.get_item(n)==p_list.get_item(n+1))
+					mask.set(n+1,true);
+			}
+			p_list.remove_mask(mask);
+		}
+	}
+}
+
+void metadb_handle_list_helper::sort_by_path_quick(metadb_handle_list_ref p_list)
+{
+	p_list.sort_t(metadb::path_compare_metadb_handle);
+}
+
+
+void metadb_handle_list_helper::sort_by_pointer(metadb_handle_list_ref p_list)
+{
+	p_list.sort();
+}
+
+t_size metadb_handle_list_helper::bsearch_by_pointer(metadb_handle_list_cref p_list,const metadb_handle_ptr & val)
+{
+	t_size blah;
+	if (p_list.bsearch_t(pfc::compare_t<metadb_handle_ptr,metadb_handle_ptr>,val,blah)) return blah;
+	else return SIZE_MAX;
+}
+
+
+void metadb_handle_list_helper::sorted_by_pointer_extract_difference(metadb_handle_list const & p_list_1,metadb_handle_list const & p_list_2,metadb_handle_list & p_list_1_specific,metadb_handle_list & p_list_2_specific)
+{
+	t_size found_1, found_2;
+	const t_size count_1 = p_list_1.get_count(), count_2 = p_list_2.get_count();
+	t_size ptr_1, ptr_2;
+
+	found_1 = found_2 = 0;
+	ptr_1 = ptr_2 = 0;
+	while(ptr_1 < count_1 || ptr_2 < count_2)
+	{
+		while(ptr_1 < count_1 && (ptr_2 == count_2 || p_list_1[ptr_1] < p_list_2[ptr_2]))
+		{
+			found_1++;
+			t_size ptr_1_new = ptr_1 + 1;
+			while(ptr_1_new < count_1 && p_list_1[ptr_1_new] == p_list_1[ptr_1]) ptr_1_new++;
+			ptr_1 = ptr_1_new;
+		}
+		while(ptr_2 < count_2 && (ptr_1 == count_1 || p_list_2[ptr_2] < p_list_1[ptr_1]))
+		{
+			found_2++;
+			t_size ptr_2_new = ptr_2 + 1;
+			while(ptr_2_new < count_2 && p_list_2[ptr_2_new] == p_list_2[ptr_2]) ptr_2_new++;
+			ptr_2 = ptr_2_new;
+		}
+		while(ptr_1 < count_1 && ptr_2 < count_2 && p_list_1[ptr_1] == p_list_2[ptr_2]) {ptr_1++; ptr_2++;}
+	}
+
+	
+
+	p_list_1_specific.set_count(found_1);
+	p_list_2_specific.set_count(found_2);
+	if (found_1 > 0 || found_2 > 0)
+	{
+		found_1 = found_2 = 0;
+		ptr_1 = ptr_2 = 0;
+
+		while(ptr_1 < count_1 || ptr_2 < count_2)
+		{
+			while(ptr_1 < count_1 && (ptr_2 == count_2 || p_list_1[ptr_1] < p_list_2[ptr_2]))
+			{
+				p_list_1_specific[found_1++] = p_list_1[ptr_1];
+				t_size ptr_1_new = ptr_1 + 1;
+				while(ptr_1_new < count_1 && p_list_1[ptr_1_new] == p_list_1[ptr_1]) ptr_1_new++;
+				ptr_1 = ptr_1_new;
+			}
+			while(ptr_2 < count_2 && (ptr_1 == count_1 || p_list_2[ptr_2] < p_list_1[ptr_1]))
+			{
+				p_list_2_specific[found_2++] = p_list_2[ptr_2];
+				t_size ptr_2_new = ptr_2 + 1;
+				while(ptr_2_new < count_2 && p_list_2[ptr_2_new] == p_list_2[ptr_2]) ptr_2_new++;
+				ptr_2 = ptr_2_new;
+			}
+			while(ptr_1 < count_1 && ptr_2 < count_2 && p_list_1[ptr_1] == p_list_2[ptr_2]) {ptr_1++; ptr_2++;}
+		}
+
+	}
+}
+
+double metadb_handle_list_helper::calc_total_duration_v2(metadb_handle_list_cref p_list, unsigned maxThreads, abort_callback & aborter) {
+	const size_t count = p_list.get_count();
+	size_t numThreads = pfc::getOptimalWorkerThreadCountEx( pfc::min_t<size_t>(maxThreads, count / 2000 ));
+	if (numThreads == 1) {
+		double ret = 0;
+		for (size_t n = 0; n < count; n++)
+		{
+			double temp = p_list.get_item(n)->get_length();
+			if (temp > 0) ret += temp;
+		}
+		return ret;
+	}
+
+	pfc::array_t<double> sums; sums.resize(numThreads); sums.fill_null();
+
+	{
+		pfc::refcounter walk = 0, walkSums = 0;
+
+		auto worker = [&] {
+			double ret = 0;
+			for (;;) {
+				size_t idx = walk++;
+				if (idx >= count || aborter.is_set()) break;
+
+				double temp = p_list.get_item(idx)->get_length();
+				if (temp > 0) ret += temp;
+			}
+			sums[walkSums++] = ret;
+		};
+
+		fb2k::cpuThreadPool::runMultiHelper(worker, numThreads);
+	}
+	aborter.check();
+	double ret = 0;
+	for (size_t walk = 0; walk < numThreads; ++walk) ret += sums[walk];
+	return ret;	
+}
+
+pfc::string8 metadb_handle_list_helper::format_total_size(metadb_handle_list_cref p_list) {
+    pfc::string8 temp;
+    bool unknown = false;
+    t_filesize val = metadb_handle_list_helper::calc_total_size_ex(p_list,unknown);
+    if (unknown) temp << "> ";
+    temp << pfc::format_file_size_short(val);
+    return temp;
+}
+
+double metadb_handle_list_helper::calc_total_duration(metadb_handle_list_cref p_list)
+{
+	double ret = 0;
+	for (auto handle : p_list) {
+		double temp = handle->get_length();
+		if (temp > 0) ret += temp;
+	}
+	return ret;
+}
+
+void metadb_handle_list_helper::sort_by_path(metadb_handle_list_ref p_list)
+{
+	sort_by_format(p_list,"%path_sort%",NULL);
+}
+
+void metadb_handle_list_helper::sort_by_format_v2(metadb_handle_list_ref p_list, const service_ptr_t<titleformat_object> & script, titleformat_hook * hook, int direction, abort_callback & aborter) {
+	pfc::array_t<size_t> order; order.set_size( p_list.get_count() );
+	sort_by_format_get_order_v2( p_list, order.get_ptr(), script, hook, direction, aborter );
+	p_list.reorder( order.get_ptr() );
+}
+
+void metadb_handle_list_helper::sort_by_format_get_order_v2(metadb_handle_list_cref p_list, size_t * order, const service_ptr_t<titleformat_object> & p_script, titleformat_hook * p_hook, int p_direction, abort_callback & aborter) {
+	sorter_t s = { p_script, p_direction, p_hook };
+	size_t total = p_list.get_count();
+	for (size_t walk = 0; walk < total; ++walk) order[walk] = walk;
+	sort_by_format_get_order_v3(p_list, order, &s, 1, aborter);
+}
+
+void metadb_handle_list_helper::sort_by_format_get_order_v3(metadb_handle_list_cref p_list, size_t* order,sorter_t const* sorters, size_t nSorters, abort_callback& aborter) {
+	// pfc::hires_timer timer; timer.start();
+
+	typedef custom_sort_data_multi data_t;
+
+	const t_size count = p_list.get_count();
+	if (count == 0) return;
+
+	PFC_ASSERT(pfc::permutation_is_valid(order, count));
+
+	auto data = std::make_unique< data_t[] >(count);
+
+#if FOOBAR2000_TARGET_VERSION >= 81
+	bool need_info = false;
+	for (size_t iSorter = 0; iSorter < nSorters; ++iSorter) {
+		auto& s = sorters[iSorter];
+		PFC_ASSERT(s.direction == -1 || s.direction == 1);
+		if (s.obj->requires_metadb_info_()) {
+			need_info = true; break;
+		}
+	}
+	if (need_info) {
+		// FB2K_console_formatter() << "sorting with queryMultiParallelEx_<>";
+		struct qmpc_context {
+			qmpc_context() {
+				temp.prealloc(512);
+			}
+			tfhook_sort myHook;
+			pfc::string8 temp;
+		};
+		metadb_v2::get()->queryMultiParallelEx_< qmpc_context >(p_list, [&](size_t idx, metadb_v2::rec_t const& rec, qmpc_context& ctx) {
+			aborter.check();
+			auto& out = data[idx];
+			out.setup(nSorters);
+			out.index = order[idx];
+
+			auto h = p_list[idx];
+			
+			for (size_t iSorter = 0; iSorter < nSorters; ++iSorter) {
+				auto& s = sorters[iSorter];
+				if (s.hook) {
+					titleformat_hook_impl_splitter hookSplitter(&ctx.myHook, s.hook);
+					h->formatTitle_v2_(rec, &hookSplitter, ctx.temp, s.obj, nullptr);
+				} else {
+					h->formatTitle_v2_(rec, &ctx.myHook, ctx.temp, s.obj, nullptr);
+				}
+				out[iSorter] = fb2k::makeSortString(ctx.temp);
+			}
+		});
+	} else {
+		// FB2K_console_formatter() << "sorting with blank metadb info";
+		auto api = fb2k::cpuThreadPool::get();
+		pfc::counter walk = 0;
+		api->runMulti_([&] {
+			pfc::string8 temp;
+			const metadb_v2_rec_t rec = {};
+			tfhook_sort myHook;
+			for (;;) {
+				aborter.check();
+				size_t idx = walk++;
+				if (idx >= count) return;
+
+				auto& out = data[idx];
+				out.setup(nSorters);
+				out.index = order[idx];
+
+				for (size_t iSorter = 0; iSorter < nSorters; ++iSorter) {
+					auto& s = sorters[iSorter];
+					if (s.hook) {
+						titleformat_hook_impl_splitter hookSplitter(&myHook, s.hook);
+						p_list[idx]->formatTitle_v2_(rec, &hookSplitter, temp, s.obj, nullptr);
+					} else {
+						p_list[idx]->formatTitle_v2_(rec, &myHook, temp, s.obj, nullptr);
+					}
+					
+					out[iSorter] = fb2k::makeSortString(temp);
+				}
+			}
+			}, api->numRunsSanity((count + 1999) / 2000));
+
+	}
+#else
+	{
+		pfc::counter counter(0);
+
+		auto work = [&] {
+			tfhook_sort myHook;
+
+			pfc::string8_fastalloc temp; temp.prealloc(512);
+			for (;; ) {
+				const t_size index = (counter)++;
+				if (index >= count || aborter.is_set()) break;
+
+				auto& out = data[index];
+				out.setup(nSorters);
+				out.index = order[index];
+
+				for (size_t iSorter = 0; iSorter < nSorters; ++iSorter) {
+					auto& s = sorters[iSorter];
+					if (s.hook) {
+						titleformat_hook_impl_splitter hookSplitter(&myHook, s.hook);
+						p_list[index]->format_title(&hookSplitter, temp, s.obj, 0);
+					} else {
+						p_list[index]->format_title(&myHook, temp, s.obj, 0);
+					}
+					
+					out[iSorter] = fb2k::makeSortString(temp);
+				}
+			}
+		};
+
+		size_t nThreads = pfc::getOptimalWorkerThreadCountEx(count / 128);
+		if (nThreads == 1) {
+			work();
+		} else {
+			fb2k::cpuThreadPool::runMultiHelper(work, nThreads);
+		}
+	}
+#endif
+	aborter.check();
+	//	console::formatter() << "metadb_handle sort: prepared in " << pfc::format_time_ex(timer.query(),6);
+
+
+	{
+		auto compare = [&](data_t const& elem1, data_t const& elem2) -> int {
+			for (size_t iSorter = 0; iSorter < nSorters; ++iSorter) {
+				int v = fb2k::sortStringCompare(elem1[iSorter], elem2[iSorter]);
+				if (v) return v * sorters[iSorter].direction;
+			}
+			
+			return pfc::sgn_t((t_ssize)elem1.index - (t_ssize)elem2.index);
+		};
+
+		typedef decltype(data) container_t;
+		typedef decltype(compare) compare_t;
+		pfc::sort_callback_impl_simple_wrap_t<container_t, compare_t> cb(data, compare);
+
+		size_t concurrency = pfc::getOptimalWorkerThreadCountEx(count / 4096);
+		fb2k::sort(cb, count, concurrency, aborter);
+	}
+
+	//qsort(data.get_ptr(),count,sizeof(custom_sort_data),p_direction > 0 ? _custom_sort_compare<1> : _custom_sort_compare<-1>);
+
+
+	//	console::formatter() << "metadb_handle sort: sorted in " << pfc::format_time_ex(timer.query(),6);
+
+	for (t_size n = 0; n < count; n++)
+	{
+		order[n] = data[n].index;
+	}
+
+	// FB2K_console_formatter() << "metadb_handle sort: finished in " << pfc::format_time_ex(timer.query(),6);
+
+}
+
+t_filesize metadb_handle_list_helper::calc_total_size(metadb_handle_list_cref p_list, bool skipUnknown) {
+	pfc::avltree_t< const char*, metadb::path_comparator > beenHere;
+//	metadb_handle_list list(p_list);
+//	list.sort_t(metadb::path_compare_metadb_handle);
+
+	t_filesize ret = 0;
+	t_size n, m = p_list.get_count();
+	for(n=0;n<m;n++) {
+		bool isNew;
+		metadb_handle_ptr h; p_list.get_item_ex(h, n);
+		beenHere.add_ex( h->get_path(), isNew);
+		if (isNew) {
+			t_filesize t = h->get_filesize();
+			if (t == filesize_invalid) {
+				if (!skipUnknown) return filesize_invalid;
+			} else {
+				ret += t;
+			}
+		}
+	}
+	return ret;
+}
+
+t_filesize metadb_handle_list_helper::calc_total_size_ex(metadb_handle_list_cref p_list, bool & foundUnknown) {
+	foundUnknown = false;
+	metadb_handle_list list(p_list);
+	list.sort_t(metadb::path_compare_metadb_handle);
+
+	t_filesize ret = 0;
+	t_size n, m = list.get_count();
+	for(n=0;n<m;n++) {
+		if (n==0 || metadb::path_compare(list[n-1]->get_path(),list[n]->get_path())) {
+			t_filesize t = list[n]->get_filesize();
+			if (t == filesize_invalid) {
+				foundUnknown = true;
+			} else {
+				ret += t;
+			}
+		}
+	}
+	return ret;
+}
+
+bool metadb_handle_list_helper::extract_folder_path(metadb_handle_list_cref list, pfc::string_base & folderOut) {
+	const t_size total = list.get_count();
+	if (total == 0) return false;
+	pfc::string_formatter temp, folder;
+	folder = list[0]->get_path();
+	folder.truncate_to_parent_path();
+	for(size_t walk = 1; walk < total; ++walk) {
+		temp = list[walk]->get_path();
+		temp.truncate_to_parent_path();
+		if (metadb::path_compare(folder, temp) != 0) return false;
+	}
+	folderOut = folder;
+	return true;
+}
+bool metadb_handle_list_helper::extract_single_path(metadb_handle_list_cref list, const char * &pathOut) {
+	const t_size total = list.get_count();
+	if (total == 0) return false;
+	const char * path = list[0]->get_path();
+	for(t_size walk = 1; walk < total; ++walk) {
+		if (metadb::path_compare(path, list[walk]->get_path()) != 0) return false;
+	}
+	pathOut	= path;
+	return true;
+}