diff foosdk/sdk/foobar2000/helpers/track_property_callback_impl.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/helpers/track_property_callback_impl.cpp	Mon Jan 05 02:15:46 2026 -0500
@@ -0,0 +1,202 @@
+#include "StdAfx.h"
+
+#include <list>
+#include <memory>
+
+#include "track_property_callback_impl.h"
+#include <SDK/threadPool.h>
+#include <SDK/track_property.h>
+
+void track_property_callback_impl::set_property(const char * p_group, double p_sortpriority, const char * p_name, const char * p_value) {
+	propertyname_container temp;
+	temp.m_name = p_name;
+	temp.m_priority = p_sortpriority;
+
+	pfc::string8 fixEOL;
+	if (m_cutMultiLine && strchr(p_value, '\n') != nullptr) {
+		fixEOL = p_value; fixEOL.fix_eol(); p_value = fixEOL;
+	}
+
+	m_entries.find_or_add(p_group).set(temp, p_value);
+}
+
+bool track_property_callback_impl::is_group_wanted(const char * p_group) {
+	if (m_groupFilter) return m_groupFilter(p_group);
+	return true;
+}
+
+void track_property_callback_impl::merge(track_property_callback_impl const & other) {
+	for (auto iterGroup = other.m_entries.first(); iterGroup.is_valid(); ++iterGroup) {
+		auto & in = iterGroup->m_value;
+		auto & out = m_entries[iterGroup->m_key];
+		for (auto iterEntry = in.first(); iterEntry.is_valid(); ++iterEntry) {
+			out.set(iterEntry->m_key, iterEntry->m_value);
+		}
+	}
+}
+
+static bool is_filtered_info_field(const char * p_name) {
+	for (auto ptr : track_property_provider::enumerate()) {
+		if (ptr->is_our_tech_info(p_name)) return true;
+	}
+	return false;
+}
+
+static const char strGroupOther[] = "Other";
+
+static pfc::string8 encloseInfoName(const char* name) {
+	pfc::string8 temp;
+	temp << "<";
+	uAddStringUpper(temp, name);
+	temp << ">";
+	return temp;
+}
+
+static void enumOtherHere(track_property_callback_impl & callback, metadb_info_container::ptr info_) {
+	if (info_.is_empty()) return;
+	const file_info * infoptr = &info_->info();
+	for (t_size n = 0, m = infoptr->info_get_count(); n < m; n++) {
+		const char * name = infoptr->info_enum_name(n);
+		if (!is_filtered_info_field(name)) {
+			callback.set_property(strGroupOther, 0, encloseInfoName(name), infoptr->info_enum_value(n));
+		}
+	}
+}
+
+static trackInfoContainer::ptr getInfoHelper(track_property_provider_v3_info_source* source, size_t idx) {
+	return source->get_info(idx);
+}
+
+static trackInfoContainer::ptr getInfoHelper(track_property_provider_v5_info_source* source, size_t idx) {
+	return source->get_info(idx).info;
+}
+
+template<typename infoSource_t>
+static void enumOther( track_property_callback_impl & callback, metadb_handle_list_cref items, infoSource_t* infoSource ) {
+
+	const size_t itemCount = items.get_count();
+	if (itemCount == 1 ) {
+		enumOtherHere(callback, getInfoHelper(infoSource,0) );
+		return;
+	}
+
+	typedef file_info::field_name_comparator field_name_comparator_t;
+	typedef pfc::comparator_stricmp_ascii value_comparator_t;
+
+	typedef pfc::avltree_t< pfc::string8, field_name_comparator_t > field_mask_t;
+
+	struct stats_t {
+		size_t count = 0;
+		double totalDuration = 0;
+	};
+	typedef pfc::map_t<pfc::string8,stats_t,value_comparator_t > field_results_t;
+	typedef pfc::map_t<pfc::string8, field_results_t, field_name_comparator_t> results_t;
+	results_t results;
+
+	field_mask_t fieldsIgnore, fieldsUse;
+	bool useDuration = true;
+	double totalDuration = 0;
+	
+	for (size_t itemWalk = 0; itemWalk < itemCount; ++itemWalk) {
+		auto info_ = getInfoHelper(infoSource, itemWalk);
+		if (info_.is_empty()) continue;
+		const file_info * infoptr = &info_->info();
+		const size_t numInfo = infoptr->info_get_count();
+		const double duration = infoptr->get_length();
+		if (duration > 0) totalDuration += duration;
+		else useDuration = false;
+		for (size_t infoWalk = 0; infoWalk < numInfo; ++infoWalk) {
+			const char * name = infoptr->info_enum_name(infoWalk);
+			if ( fieldsIgnore.contains( name )) continue;
+			if (!fieldsUse.contains(name)) {
+				const bool bUse = !is_filtered_info_field( name );
+				if ( bUse ) fieldsUse += name;
+				else { fieldsIgnore += name; continue; }
+			}
+
+			const char * value = infoptr->info_enum_value(infoWalk);
+			auto & stats = results[name][value];
+			++ stats.count;
+			if ( duration > 0 ) {
+				stats.totalDuration += duration;
+			}
+		}
+	}
+
+	for (auto iter = results.first(); iter.is_valid(); ++iter) {
+		const auto key = encloseInfoName(iter->m_key);
+		pfc::string8 out;
+		if (iter->m_value.get_count() == 1 && iter->m_value.first()->m_value.count == itemCount) {
+			out = iter->m_value.first()->m_key;
+		} else {
+			for (auto iterValue = iter->m_value.first(); iterValue.is_valid(); ++iterValue) {
+				double percentage;
+				if ( useDuration ) percentage = iterValue->m_value.totalDuration / totalDuration;
+				else percentage = (double) iterValue->m_value.count / (double) itemCount;
+				if (!out.is_empty()) out << "; ";
+				out << iterValue->m_key << " (" << pfc::format_fixedpoint( pfc::rint64( percentage * 1000.0), 1) << "%)";
+			}
+		}
+		callback.set_property(strGroupOther, 0, key, out);
+	}
+}
+
+template<typename source_t> static void enumerateTrackProperties_(track_property_callback_impl& callback, std::function< metadb_handle_list_cref() > itemsSource, source_t infoSource, std::function<abort_callback& () > abortSource) {
+	if (core_api::is_main_thread()) {
+		// should not get here like this
+		// but that does make our job easier
+		auto& items = itemsSource();
+		auto info = infoSource();
+		for (auto ptr : track_property_provider::enumerate()) {
+			ptr->enumerate_properties_helper(items, info, callback, abortSource());
+		}
+		if (callback.is_group_wanted(strGroupOther)) {
+			enumOther(callback, items, info);
+		}
+		return;
+	}
+
+	std::list<std::shared_ptr<pfc::event> > lstWaitFor;
+	std::list<std::shared_ptr< track_property_callback_impl > > lstMerge;
+	for (auto ptr : track_property_provider::enumerate()) {
+		auto evt = std::make_shared<pfc::event>();
+		auto cb = std::make_shared< track_property_callback_impl >(callback); // clone watched group info
+		auto work = [ptr, itemsSource, evt, cb, infoSource, abortSource] {
+			try {
+				ptr->enumerate_properties_helper(itemsSource(), infoSource(), *cb, abortSource());
+			} catch (...) {}
+			evt->set_state(true);
+		};
+
+		track_property_provider_v4::ptr v4;
+		if (v4 &= ptr) {
+			// Supports v4 = split a worker thread, work in parallel
+			fb2k::inCpuWorkerThread(work);
+		} else {
+			// No v4 = delegate to main thread. Ugly but gets the job done.
+			fb2k::inMainThread(work);
+		}
+
+		lstWaitFor.push_back(std::move(evt));
+		lstMerge.push_back(std::move(cb));
+	}
+
+	if (callback.is_group_wanted(strGroupOther)) {
+		enumOther(callback, itemsSource(), infoSource());
+	}
+
+	for (auto& i : lstWaitFor) {
+		abortSource().waitForEvent(*i, -1);
+	}
+	for (auto& i : lstMerge) {
+		callback.merge(*i);
+	}
+}
+
+void enumerateTrackProperties(track_property_callback_impl& callback, std::function< metadb_handle_list_cref() > itemsSource, std::function<track_property_provider_v3_info_source* ()> infoSource, std::function<abort_callback& () > abortSource) {
+	enumerateTrackProperties_(callback, itemsSource, infoSource, abortSource);
+}
+
+void enumerateTrackProperties_v5(track_property_callback_impl& callback, std::function< metadb_handle_list_cref() > itemsSource, std::function<track_property_provider_v5_info_source* ()> infoSource, std::function<abort_callback& () > abortSource) {
+	enumerateTrackProperties_(callback, itemsSource, infoSource, abortSource);
+}
\ No newline at end of file