latest SDK

This commit is contained in:
2021-12-14 00:28:25 -07:00
commit 68b10d413b
492 changed files with 80542 additions and 0 deletions

View File

@@ -0,0 +1,42 @@
#include "foobar2000.h"
void abort_callback::check() const {
if (is_aborting()) throw exception_aborted();
}
void abort_callback::sleep(double p_timeout_seconds) const {
if (!sleep_ex(p_timeout_seconds)) throw exception_aborted();
}
bool abort_callback::sleep_ex(double p_timeout_seconds) const {
// return true IF NOT SET (timeout), false if set
return !pfc::event::g_wait_for(get_abort_event(),p_timeout_seconds);
}
bool abort_callback::waitForEvent( pfc::eventHandle_t evtHandle, double timeOut ) {
int status = pfc::event::g_twoEventWait( this->get_abort_event(), evtHandle, timeOut );
switch(status) {
case 1: throw exception_aborted();
case 2: return true;
case 0: return false;
default: uBugCheck();
}
}
bool abort_callback::waitForEvent(pfc::event& evt, double timeOut) {
return waitForEvent(evt.get_handle(), timeOut);
}
void abort_callback::waitForEvent(pfc::eventHandle_t evtHandle) {
bool status = waitForEvent(evtHandle, -1); (void)status;
PFC_ASSERT(status); // should never return false
}
void abort_callback::waitForEvent(pfc::event& evt) {
bool status = waitForEvent(evt, -1); (void)status;
PFC_ASSERT(status); // should never return false
}
namespace fb2k {
abort_callback_dummy noAbort;
}

View File

@@ -0,0 +1,119 @@
#ifndef _foobar2000_sdk_abort_callback_h_
#define _foobar2000_sdk_abort_callback_h_
namespace foobar2000_io {
PFC_DECLARE_EXCEPTION(exception_aborted,pfc::exception,"User abort");
typedef pfc::eventHandle_t abort_callback_event;
#ifdef check
#undef check
#endif
//! This class is used to signal underlying worker code whether user has decided to abort a potentially time-consuming operation. \n
//! It is commonly required by all filesystem related or decoding-related operations. \n
//! Code that receives an abort_callback object should periodically check it and abort any operations being performed if it is signaled, typically throwing exception_aborted. \n
//! See abort_callback_impl for an implementation.
class NOVTABLE abort_callback
{
public:
//! Returns whether user has requested the operation to be aborted.
virtual bool is_aborting() const = 0;
inline bool is_set() const {return is_aborting();}
//! Retrieves event object that can be used with some OS calls. The even object becomes signaled when abort is triggered. On win32, this is equivalent to win32 event handle (see: CreateEvent). \n
//! You must not close this handle or call any methods that change this handle's state (SetEvent() or ResetEvent()), you can only wait for it.
virtual abort_callback_event get_abort_event() const = 0;
inline abort_callback_event get_handle() const {return get_abort_event();}
//! Checks if user has requested the operation to be aborted, and throws exception_aborted if so.
void check() const;
//! For compatibility with old code. Do not call.
inline void check_e() const {check();}
//! Sleeps p_timeout_seconds or less when aborted, throws exception_aborted on abort.
void sleep(double p_timeout_seconds) const;
//! Sleeps p_timeout_seconds or less when aborted, returns true when execution should continue, false when not.
bool sleep_ex(double p_timeout_seconds) const;
//! Waits for an event. Returns true if event is now signaled, false if the specified period has elapsed and the event did not become signaled. \n
//! Throws exception_aborted if aborted.
bool waitForEvent( pfc::eventHandle_t evtHandle, double timeOut );
//! Waits for an event. Returns true if event is now signaled, false if the specified period has elapsed and the event did not become signaled. \n
//! Throws exception_aborted if aborted.
bool waitForEvent(pfc::event& evt, double timeOut);
//! Waits for an event. Returns once the event became signaled; throw exception_aborted if abort occurred first.
void waitForEvent(pfc::eventHandle_t evtHandle);
//! Waits for an event. Returns once the event became signaled; throw exception_aborted if abort occurred first.
void waitForEvent(pfc::event& evt);
protected:
abort_callback() {}
~abort_callback() {}
};
//! Implementation of abort_callback interface.
class abort_callback_impl : public abort_callback {
public:
abort_callback_impl() : m_aborting(false) {}
inline void abort() {set_state(true);}
inline void set() {set_state(true);}
inline void reset() {set_state(false);}
void set_state(bool p_state) {m_aborting = p_state; m_event.set_state(p_state);}
bool is_aborting() const {return m_aborting;}
abort_callback_event get_abort_event() const {return m_event.get_handle();}
private:
abort_callback_impl(const abort_callback_impl &) = delete;
const abort_callback_impl & operator=(const abort_callback_impl&) = delete;
volatile bool m_aborting;
pfc::event m_event;
};
#ifdef _WIN32
//! Dummy abort_callback that never gets aborted. \n
//! Slightly more efficient than the regular one especially when you need to regularly create temporary instances of it.
class abort_callback_dummy : public abort_callback {
public:
bool is_aborting() const { return false; }
abort_callback_event get_abort_event() const { return GetInfiniteWaitEvent();}
};
#else
// FIX ME come up with a scheme to produce a persistent infinite wait filedescriptor on non Windows
// Could use /dev/null but still need to open it on upon object creation which defeats the purpose
typedef abort_callback_impl abort_callback_dummy;
#endif
}
typedef foobar2000_io::abort_callback_event fb2k_event_handle;
typedef foobar2000_io::abort_callback fb2k_event;
typedef foobar2000_io::abort_callback_impl fb2k_event_impl;
using namespace foobar2000_io;
#define FB2K_PFCv2_ABORTER_SCOPE( abortObj ) \
(abortObj).check(); \
PP::waitableReadRef_t aborterRef = {(abortObj).get_abort_event()}; \
PP::aborter aborter_pfcv2( aborterRef ); \
PP::aborterScope l_aborterScope( aborter_pfcv2 );
namespace fb2k {
// A shared abort_callback_dummy instance
extern abort_callback_dummy noAbort;
}
#endif //_foobar2000_sdk_abort_callback_h_

View File

@@ -0,0 +1,47 @@
#include "foobar2000.h"
bool advconfig_entry::is_branch() {
advconfig_branch::ptr branch;
return branch &= this;
}
bool advconfig_entry::g_find(service_ptr_t<advconfig_entry>& out, const GUID & id) {
service_enum_t<advconfig_entry> e; service_ptr_t<advconfig_entry> ptr; while(e.next(ptr)) { if (ptr->get_guid() == id) {out = ptr; return true;} } return false;
}
t_uint32 advconfig_entry::get_preferences_flags_() {
{
advconfig_entry_string_v2::ptr ex;
if (service_query_t(ex)) return ex->get_preferences_flags();
}
{
advconfig_entry_checkbox_v2::ptr ex;
if (service_query_t(ex)) return ex->get_preferences_flags();
}
return 0;
}
bool advconfig_entry_checkbox::get_default_state_() {
{
advconfig_entry_checkbox_v2::ptr ex;
if (service_query_t(ex)) return ex->get_default_state();
}
bool backup = get_state();
reset();
bool rv = get_state();
set_state(backup);
return rv;
}
void advconfig_entry_string::get_default_state_(pfc::string_base & out) {
{
advconfig_entry_string_v2::ptr ex;
if (service_query_t(ex)) {ex->get_default_state(out); return;}
}
pfc::string8 backup;
get_state(backup);
reset();
get_state(out);
set_state(backup);
}

333
foobar2000/SDK/advconfig.h Normal file
View File

@@ -0,0 +1,333 @@
#pragma once
//! Entrypoint class for adding items to Advanced Preferences page. \n
//! Implementations must derive from one of subclasses: advconfig_branch, advconfig_entry_checkbox, advconfig_entry_string. \n
//! Implementations are typically registered using static service_factory_single_t<myclass>, or using provided helper classes in case of standard implementations declared in this header.
class NOVTABLE advconfig_entry : public service_base {
public:
virtual void get_name(pfc::string_base & p_out) = 0;
virtual GUID get_guid() = 0;
virtual GUID get_parent() = 0;
virtual void reset() = 0;
virtual double get_sort_priority() = 0;
bool is_branch();
t_uint32 get_preferences_flags_();
static bool g_find(service_ptr_t<advconfig_entry>& out, const GUID & id);
template<typename outptr> static bool g_find_t(outptr & out, const GUID & id) {
service_ptr_t<advconfig_entry> temp;
if (!g_find(temp, id)) return false;
return temp->service_query_t(out);
}
static const GUID guid_root;
static const GUID guid_branch_tagging,guid_branch_decoding,guid_branch_tools,guid_branch_playback,guid_branch_display,guid_branch_debug, guid_branch_tagging_general;
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(advconfig_entry);
};
//! Creates a new branch in Advanced Preferences. \n
//! Implementation: see advconfig_branch_impl / advconfig_branch_factory.
class NOVTABLE advconfig_branch : public advconfig_entry {
public:
FB2K_MAKE_SERVICE_INTERFACE(advconfig_branch,advconfig_entry);
};
//! Creates a checkbox/radiocheckbox entry in Advanced Preferences. \n
//! The difference between checkboxes and radiocheckboxes is different icon (obviously) and that checking a radiocheckbox unchecks all other radiocheckboxes in the same branch. \n
//! Implementation: see advconfig_entry_checkbox_impl / advconfig_checkbox_factory_t.
class NOVTABLE advconfig_entry_checkbox : public advconfig_entry {
public:
virtual bool get_state() = 0;
virtual void set_state(bool p_state) = 0;
virtual bool is_radio() = 0;
bool get_default_state_();
FB2K_MAKE_SERVICE_INTERFACE(advconfig_entry_checkbox,advconfig_entry);
};
class NOVTABLE advconfig_entry_checkbox_v2 : public advconfig_entry_checkbox {
FB2K_MAKE_SERVICE_INTERFACE(advconfig_entry_checkbox_v2, advconfig_entry_checkbox)
public:
virtual bool get_default_state() = 0;
virtual t_uint32 get_preferences_flags() {return 0;} //signals whether changing this setting should trigger playback restart or app restart; see: preferences_state::* constants
};
//! Creates a string/integer editbox entry in Advanced Preferences.\n
//! Implementation: see advconfig_entry_string_impl / advconfig_string_factory.
class NOVTABLE advconfig_entry_string : public advconfig_entry {
public:
virtual void get_state(pfc::string_base & p_out) = 0;
virtual void set_state(const char * p_string,t_size p_length = ~0) = 0;
virtual t_uint32 get_flags() = 0;
void get_default_state_(pfc::string_base & out);
enum {
flag_is_integer = 1 << 0,
flag_is_signed = 1 << 1,
};
FB2K_MAKE_SERVICE_INTERFACE(advconfig_entry_string,advconfig_entry);
};
class NOVTABLE advconfig_entry_string_v2 : public advconfig_entry_string {
FB2K_MAKE_SERVICE_INTERFACE(advconfig_entry_string_v2, advconfig_entry_string)
public:
virtual void get_default_state(pfc::string_base & out) = 0;
virtual void validate(pfc::string_base & val) {}
virtual t_uint32 get_preferences_flags() {return 0;} //signals whether changing this setting should trigger playback restart or app restart; see: preferences_state::* constants
};
//! Standard implementation of advconfig_branch. \n
//! Usage: no need to use this class directly - use advconfig_branch_factory instead.
class advconfig_branch_impl : public advconfig_branch {
public:
advconfig_branch_impl(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority) : m_name(p_name), m_guid(p_guid), m_parent(p_parent), m_priority(p_priority) {}
void get_name(pfc::string_base & p_out) {p_out = m_name;}
GUID get_guid() {return m_guid;}
GUID get_parent() {return m_parent;}
void reset() {}
double get_sort_priority() {return m_priority;}
private:
pfc::string8 m_name;
GUID m_guid,m_parent;
const double m_priority;
};
//! Standard implementation of advconfig_entry_checkbox. \n
//! p_is_radio parameter controls whether we're implementing a checkbox or a radiocheckbox (see advconfig_entry_checkbox description for more details).
template<bool p_is_radio = false, uint32_t prefFlags = 0>
class advconfig_entry_checkbox_impl : public advconfig_entry_checkbox_v2 {
public:
advconfig_entry_checkbox_impl(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,bool p_initialstate)
: m_name(p_name), m_initialstate(p_initialstate), m_state(p_guid,p_initialstate), m_parent(p_parent), m_priority(p_priority) {}
void get_name(pfc::string_base & p_out) {p_out = m_name;}
GUID get_guid() {return m_state.get_guid();}
GUID get_parent() {return m_parent;}
void reset() {m_state = m_initialstate;}
bool get_state() {return m_state;}
void set_state(bool p_state) {m_state = p_state;}
bool is_radio() {return p_is_radio;}
double get_sort_priority() {return m_priority;}
bool get_state_() const {return m_state;}
bool get_default_state() {return m_initialstate;}
bool get_default_state_() const {return m_initialstate;}
t_uint32 get_preferences_flags() {return prefFlags;}
private:
pfc::string8 m_name;
const bool m_initialstate;
cfg_bool m_state;
GUID m_parent;
const double m_priority;
};
//! Service factory helper around standard advconfig_branch implementation. Use this class to register your own Advanced Preferences branches. \n
//! Usage: static advconfig_branch_factory mybranch(name, branchID, parentBranchID, priority);
class advconfig_branch_factory : public service_factory_single_t<advconfig_branch_impl> {
public:
advconfig_branch_factory(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority)
: service_factory_single_t<advconfig_branch_impl>(p_name,p_guid,p_parent,p_priority) {}
};
//! Service factory helper around standard advconfig_entry_checkbox implementation. Use this class to register your own Advanced Preferences checkbox/radiocheckbox entries. \n
//! Usage: static advconfig_entry_checkbox<isRadioBox> mybox(name, itemID, parentID, priority, initialstate);
template<bool p_is_radio, uint32_t prefFlags = 0>
class advconfig_checkbox_factory_t : public service_factory_single_t<advconfig_entry_checkbox_impl<p_is_radio, prefFlags> > {
public:
advconfig_checkbox_factory_t(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,bool p_initialstate)
: service_factory_single_t<advconfig_entry_checkbox_impl<p_is_radio, prefFlags> >(p_name,p_guid,p_parent,p_priority,p_initialstate) {}
bool get() const {return this->get_static_instance().get_state_();}
void set(bool val) {this->get_static_instance().set_state(val);}
operator bool() const {return get();}
bool operator=(bool val) {set(val); return val;}
};
//! Service factory helper around standard advconfig_entry_checkbox implementation, specialized for checkboxes (rather than radiocheckboxes). See advconfig_checkbox_factory_t<> for more details.
typedef advconfig_checkbox_factory_t<false> advconfig_checkbox_factory;
//! Service factory helper around standard advconfig_entry_checkbox implementation, specialized for radiocheckboxes (rather than standard checkboxes). See advconfig_checkbox_factory_t<> for more details.
typedef advconfig_checkbox_factory_t<true> advconfig_radio_factory;
//! Standard advconfig_entry_string implementation. Use advconfig_string_factory to register your own string entries in Advanced Preferences instead of using this class directly.
class advconfig_entry_string_impl : public advconfig_entry_string_v2 {
public:
advconfig_entry_string_impl(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,const char * p_initialstate, t_uint32 p_prefFlags)
: m_name(p_name), m_parent(p_parent), m_priority(p_priority), m_initialstate(p_initialstate), m_state(p_guid,p_initialstate), m_prefFlags(p_prefFlags) {}
void get_name(pfc::string_base & p_out) {p_out = m_name;}
GUID get_guid() {return m_state.get_guid();}
GUID get_parent() {return m_parent;}
void reset() {core_api::ensure_main_thread();m_state = m_initialstate;}
double get_sort_priority() {return m_priority;}
void get_state(pfc::string_base & p_out) {core_api::ensure_main_thread();p_out = m_state;}
void set_state(const char * p_string,t_size p_length = ~0) {core_api::ensure_main_thread();m_state.set_string(p_string,p_length);}
t_uint32 get_flags() {return 0;}
void get_default_state(pfc::string_base & out) {out = m_initialstate;}
t_uint32 get_preferences_flags() {return m_prefFlags;}
private:
const pfc::string8 m_initialstate, m_name;
cfg_string m_state;
const double m_priority;
const GUID m_parent;
const t_uint32 m_prefFlags;
};
//! Service factory helper around standard advconfig_entry_string implementation. Use this class to register your own string entries in Advanced Preferences. \n
//! Usage: static advconfig_string_factory mystring(name, itemID, branchID, priority, initialValue);
class advconfig_string_factory : public service_factory_single_t<advconfig_entry_string_impl> {
public:
advconfig_string_factory(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,const char * p_initialstate, t_uint32 p_prefFlags = 0)
: service_factory_single_t<advconfig_entry_string_impl>(p_name,p_guid,p_parent,p_priority,p_initialstate, p_prefFlags) {}
void get(pfc::string_base & out) {get_static_instance().get_state(out);}
void set(const char * in) {get_static_instance().set_state(in);}
};
//! Special advconfig_entry_string implementation - implements integer entries. Use advconfig_integer_factory to register your own integer entries in Advanced Preferences instead of using this class directly.
template<typename int_t_>
class advconfig_entry_integer_impl_ : public advconfig_entry_string_v2 {
public:
typedef int_t_ int_t;
advconfig_entry_integer_impl_(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,int_t p_initialstate,int_t p_min,int_t p_max, t_uint32 p_prefFlags)
: m_name(p_name), m_parent(p_parent), m_priority(p_priority), m_initval(p_initialstate), m_min(p_min), m_max(p_max), m_state(p_guid,p_initialstate), m_prefFlags(p_prefFlags) {
PFC_ASSERT( p_min < p_max );
}
void get_name(pfc::string_base & p_out) {p_out = m_name;}
GUID get_guid() {return m_state.get_guid();}
GUID get_parent() {return m_parent;}
void reset() {m_state = m_initval;}
double get_sort_priority() {return m_priority;}
void get_state(pfc::string_base & p_out) {format(p_out, m_state.get_value());}
void set_state(const char * p_string,t_size p_length) {set_state_int(myATOI(p_string,p_length));}
t_uint32 get_flags() {return advconfig_entry_string::flag_is_integer | (is_signed() ? flag_is_signed : 0);}
int_t get_state_int() const {return m_state;}
void set_state_int(int_t val) {m_state = pfc::clip_t<int_t>(val,m_min,m_max);}
void get_default_state(pfc::string_base & out) {
format(out, m_initval);
}
void validate(pfc::string_base & val) {
format(val, pfc::clip_t<int_t>(myATOI(val,~0), m_min, m_max) );
}
t_uint32 get_preferences_flags() {return m_prefFlags;}
private:
static void format( pfc::string_base & out, int_t v ) {
if (is_signed()) out = pfc::format_int( v ).get_ptr();
else out = pfc::format_uint( v ).get_ptr();
}
static int_t myATOI( const char * s, size_t l ) {
if (is_signed()) return pfc::atoi64_ex(s,l);
else return pfc::atoui64_ex(s,l);
}
static bool is_signed() {
return ((int_t)-1) < ((int_t)0);
}
cfg_int_t<int_t> m_state;
const double m_priority;
const int_t m_initval, m_min, m_max;
const GUID m_parent;
const pfc::string8 m_name;
const t_uint32 m_prefFlags;
};
typedef advconfig_entry_integer_impl_<uint64_t> advconfig_entry_integer_impl;
//! Service factory helper around integer-specialized advconfig_entry_string implementation. Use this class to register your own integer entries in Advanced Preferences. \n
//! Usage: static advconfig_integer_factory myint(name, itemID, parentID, priority, initialValue, minValue, maxValue);
template<typename int_t_>
class advconfig_integer_factory_ : public service_factory_single_t<advconfig_entry_integer_impl_<int_t_> > {
public:
typedef int_t_ int_t;
advconfig_integer_factory_(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,t_uint64 p_initialstate,t_uint64 p_min,t_uint64 p_max, t_uint32 p_prefFlags = 0)
: service_factory_single_t<advconfig_entry_integer_impl_<int_t_> >(p_name,p_guid,p_parent,p_priority,p_initialstate,p_min,p_max,p_prefFlags) {}
int_t get() const {return this->get_static_instance().get_state_int();}
void set(int_t val) {this->get_static_instance().set_state_int(val);}
operator int_t() const {return get();}
int_t operator=(int_t val) {set(val); return val;}
};
typedef advconfig_integer_factory_<uint64_t> advconfig_integer_factory;
typedef advconfig_integer_factory_<int64_t> advconfig_signed_integer_factory;
//! Not currently used, reserved for future use.
class NOVTABLE advconfig_entry_enum : public advconfig_entry {
public:
virtual t_size get_value_count() = 0;
virtual void enum_value(pfc::string_base & p_out,t_size p_index) = 0;
virtual t_size get_state() = 0;
virtual void set_state(t_size p_value) = 0;
FB2K_MAKE_SERVICE_INTERFACE(advconfig_entry_enum,advconfig_entry);
};
//! Special version if advconfig_entry_string_impl that allows the value to be retrieved from worker threads.
class advconfig_entry_string_impl_MT : public advconfig_entry_string_v2 {
public:
advconfig_entry_string_impl_MT(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,const char * p_initialstate, t_uint32 p_prefFlags)
: m_name(p_name), m_parent(p_parent), m_priority(p_priority), m_initialstate(p_initialstate), m_state(p_guid,p_initialstate), m_prefFlags(p_prefFlags) {}
void get_name(pfc::string_base & p_out) {p_out = m_name;}
GUID get_guid() {return m_state.get_guid();}
GUID get_parent() {return m_parent;}
void reset() {
inWriteSync(m_sync);
m_state = m_initialstate;
}
double get_sort_priority() {return m_priority;}
void get_state(pfc::string_base & p_out) {
inReadSync(m_sync);
p_out = m_state;
}
void set_state(const char * p_string,t_size p_length = ~0) {
inWriteSync(m_sync);
m_state.set_string(p_string,p_length);
}
t_uint32 get_flags() {return 0;}
void get_default_state(pfc::string_base & out) {out = m_initialstate;}
t_uint32 get_preferences_flags() {return m_prefFlags;}
private:
const pfc::string8 m_initialstate, m_name;
cfg_string m_state;
pfc::readWriteLock m_sync;
const double m_priority;
const GUID m_parent;
const t_uint32 m_prefFlags;
};
//! Special version if advconfig_string_factory that allows the value to be retrieved from worker threads.
class advconfig_string_factory_MT : public service_factory_single_t<advconfig_entry_string_impl_MT> {
public:
advconfig_string_factory_MT(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,const char * p_initialstate, t_uint32 p_prefFlags = 0)
: service_factory_single_t<advconfig_entry_string_impl_MT>(p_name,p_guid,p_parent,p_priority,p_initialstate, p_prefFlags) {}
void get(pfc::string_base & out) {get_static_instance().get_state(out);}
void set(const char * in) {get_static_instance().set_state(in);}
};
/*
Advanced Preferences variable declaration examples
static advconfig_string_factory mystring("name goes here",myguid,parentguid,0,"asdf");
to retrieve state: pfc::string8 val; mystring.get(val);
static advconfig_checkbox_factory mycheckbox("name goes here",myguid,parentguid,0,false);
to retrieve state: mycheckbox.get();
static advconfig_integer_factory myint("name goes here",myguid,parentguid,0,initialValue,minimumValue,maximumValue);
to retrieve state: myint.get();
*/

View File

@@ -0,0 +1,185 @@
#include "foobar2000.h"
GUID album_art_extractor::get_guid() {
album_art_extractor_v2::ptr v2;
if ( v2 &= this ) return v2->get_guid();
return pfc::guid_null;
}
GUID album_art_editor::get_guid() {
album_art_editor_v2::ptr v2;
if ( v2 &= this ) return v2->get_guid();
return pfc::guid_null;
}
bool album_art_extractor_instance::query(const GUID & what, album_art_data::ptr & out, abort_callback & abort) {
try { out = query(what, abort); return true; } catch (exception_album_art_not_found) { return false; }
}
bool album_art_extractor_instance::have_entry(const GUID & what, abort_callback & abort) {
try { query(what, abort); return true; } catch(exception_album_art_not_found) { return false; }
}
void album_art_editor_instance::remove_all_() {
album_art_editor_instance_v2::ptr v2;
if ( v2 &= this ) {
v2->remove_all();
} else {
for( size_t walk = 0; walk < album_art_ids::num_types(); ++ walk ) {
try {
this->remove( album_art_ids::query_type( walk ) );
} catch(exception_io_data) {}
catch(exception_album_art_not_found) {}
}
}
}
bool album_art_editor::g_get_interface(service_ptr_t<album_art_editor> & out,const char * path) {
service_enum_t<album_art_editor> e; ptr ptr;
auto ext = pfc::string_extension(path);
while(e.next(ptr)) {
if (ptr->is_our_path(path,ext)) { out = ptr; return true; }
}
return false;
}
bool album_art_editor::g_is_supported_path(const char * path) {
ptr ptr; return g_get_interface(ptr,path);
}
album_art_editor_instance_ptr album_art_editor::g_open(file_ptr p_filehint,const char * p_path,abort_callback & p_abort) {
#ifdef FOOBAR2000_DESKTOP
{
input_manager_v2::ptr m;
if (fb2k::std_api_try_get(m)) {
album_art_editor_instance::ptr ret;
ret ^= m->open_v2(album_art_editor_instance::class_guid, p_filehint, p_path, false, nullptr, p_abort);
return ret;
}
}
#endif
album_art_editor::ptr obj;
if (!g_get_interface(obj, p_path)) throw exception_album_art_unsupported_format();
return obj->open(p_filehint, p_path, p_abort);
}
bool album_art_extractor::g_get_interface(service_ptr_t<album_art_extractor> & out,const char * path) {
service_enum_t<album_art_extractor> e; ptr ptr;
auto ext = pfc::string_extension(path);
while(e.next(ptr)) {
if (ptr->is_our_path(path,ext)) { out = ptr; return true; }
}
return false;
}
bool album_art_extractor::g_is_supported_path(const char * path) {
ptr ptr; return g_get_interface(ptr,path);
}
album_art_extractor_instance_ptr album_art_extractor::g_open(file_ptr p_filehint,const char * p_path,abort_callback & p_abort) {
#ifdef FOOBAR2000_DESKTOP
{
input_manager_v2::ptr m;
if (fb2k::std_api_try_get(m)) {
album_art_extractor_instance::ptr ret;
ret ^= m->open_v2(album_art_extractor_instance::class_guid, p_filehint, p_path, false, nullptr, p_abort);
return ret;
}
}
#endif
album_art_extractor::ptr obj;
if (!g_get_interface(obj, p_path)) throw exception_album_art_unsupported_format();
return obj->open(p_filehint, p_path, p_abort);
}
album_art_extractor_instance_ptr album_art_extractor::g_open_allowempty(file_ptr p_filehint,const char * p_path,abort_callback & p_abort) {
try {
return g_open(p_filehint, p_path, p_abort);
} catch(exception_album_art_not_found) {
return new service_impl_t<album_art_extractor_instance_simple>();
}
}
namespace {
class now_playing_album_art_notify_lambda : public now_playing_album_art_notify {
public:
void on_album_art(album_art_data::ptr data) {
f(data);
}
std::function<void(album_art_data::ptr) > f;
};
}
now_playing_album_art_notify * now_playing_album_art_notify_manager::add(std::function<void (album_art_data::ptr) > f ) {
PFC_ASSERT ( f != nullptr );
auto obj = new now_playing_album_art_notify_lambda;
obj->f = f;
add(obj);
return obj;
}
namespace {
struct aa_t {
GUID type; const char * name;
};
static const GUID guids[] = {
album_art_ids::cover_front,
album_art_ids::cover_back,
album_art_ids::artist,
album_art_ids::disc,
album_art_ids::icon,
};
static const char * const names[] = {
"front cover",
"back cover",
"artist",
"disc",
"icon"
};
static const char * const names2[] = {
"Front Cover",
"Back Cover",
"Artist",
"Disc",
"Icon"
};
}
size_t album_art_ids::num_types() {
PFC_STATIC_ASSERT( PFC_TABSIZE( guids ) == PFC_TABSIZE( names ) );
PFC_STATIC_ASSERT( PFC_TABSIZE( guids ) == PFC_TABSIZE( names2 ) );
return PFC_TABSIZE( guids );
}
GUID album_art_ids::query_type(size_t idx) {
PFC_ASSERT( idx < PFC_TABSIZE( guids ) );
return guids[idx];
}
const char * album_art_ids::query_name(size_t idx) {
PFC_ASSERT( idx < PFC_TABSIZE( names ) );
return names[idx];
}
const char * album_art_ids::name_of(const GUID & id) {
for( size_t w = 0; w < num_types(); ++w ) {
if ( query_type(w) == id ) return query_name(w);
}
return nullptr;
}
const char * album_art_ids::query_capitalized_name( size_t idx ) {
PFC_ASSERT( idx < PFC_TABSIZE( names2 ) );
return names2[idx];
}
const char * album_art_ids::capitalized_name_of( const GUID & id) {
for( size_t w = 0; w < num_types(); ++w ) {
if ( query_type(w) == id ) return query_capitalized_name(w);
}
return nullptr;
}

260
foobar2000/SDK/album_art.h Normal file
View File

@@ -0,0 +1,260 @@
#pragma once
#include <functional>
//! Common class for handling picture data. \n
//! Type of contained picture data is unknown and to be determined according to memory block contents by code parsing/rendering the picture. Commonly encountered types are: BMP, PNG, JPEG and GIF. \n
//! Implementation: use album_art_data_impl.
class NOVTABLE album_art_data : public service_base {
public:
//! Retrieves a pointer to a memory block containing the picture.
virtual const void * get_ptr() const = 0;
//! Retrieves size of the memory block containing the picture.
virtual t_size get_size() const = 0;
//! Determine whether two album_art_data objects store the same picture data.
static bool equals(album_art_data const & v1, album_art_data const & v2) {
const t_size s = v1.get_size();
if (s != v2.get_size()) return false;
return memcmp(v1.get_ptr(), v2.get_ptr(),s) == 0;
}
bool operator==(const album_art_data & other) const {return equals(*this,other);}
bool operator!=(const album_art_data & other) const {return !equals(*this,other);}
FB2K_MAKE_SERVICE_INTERFACE(album_art_data,service_base);
};
typedef service_ptr_t<album_art_data> album_art_data_ptr;
namespace fb2k {
typedef album_art_data_ptr memBlockRef;
}
//! Namespace containing identifiers of album art types.
namespace album_art_ids {
//! Front cover.
static const GUID cover_front = { 0xf1e66f4e, 0xfe09, 0x4b94, { 0x91, 0xa3, 0x67, 0xc2, 0x3e, 0xd1, 0x44, 0x5e } };
//! Back cover.
static const GUID cover_back = { 0xcb552d19, 0x86d5, 0x434c, { 0xac, 0x77, 0xbb, 0x24, 0xed, 0x56, 0x7e, 0xe4 } };
//! Picture of a disc or other storage media.
static const GUID disc = { 0x3dba9f36, 0xf928, 0x4fa4, { 0x87, 0x9c, 0xd3, 0x40, 0x47, 0x59, 0x58, 0x7e } };
//! Album-specific icon (NOT a file type icon).
static const GUID icon = { 0x74cdf5b4, 0x7053, 0x4b3d, { 0x9a, 0x3c, 0x54, 0x69, 0xf5, 0x82, 0x6e, 0xec } };
//! Artist picture.
static const GUID artist = { 0x9a654042, 0xacd1, 0x43f7, { 0xbf, 0xcf, 0xd3, 0xec, 0xf, 0xfe, 0x40, 0xfa } };
size_t num_types();
GUID query_type( size_t );
// returns lowercase name
const char * query_name( size_t );
const char * name_of( const GUID & );
// returns Capitalized name
const char * query_capitalized_name( size_t );
const char * capitalized_name_of( const GUID & );
};
PFC_DECLARE_EXCEPTION(exception_album_art_not_found,exception_io_not_found,"Attached picture not found");
PFC_DECLARE_EXCEPTION(exception_album_art_unsupported_entry,exception_io_data,"Unsupported attached picture entry");
PFC_DECLARE_EXCEPTION(exception_album_art_unsupported_format,exception_io_data,"Attached picture operations not supported for this file format");
//! Class encapsulating access to album art stored in a media file. Use album_art_extractor class obtain album_art_extractor_instance referring to specified media file.
class NOVTABLE album_art_extractor_instance : public service_base {
FB2K_MAKE_SERVICE_INTERFACE(album_art_extractor_instance,service_base);
public:
//! Throws exception_album_art_not_found when the requested album art entry could not be found in the referenced media file.
virtual album_art_data_ptr query(const GUID & p_what,abort_callback & p_abort) = 0;
bool have_entry( const GUID & what, abort_callback & abort );
bool query(const GUID & what, album_art_data::ptr & out, abort_callback & abort);
};
//! Class encapsulating access to album art stored in a media file. Use album_art_editor class to obtain album_art_editor_instance referring to specified media file.
class NOVTABLE album_art_editor_instance : public album_art_extractor_instance {
FB2K_MAKE_SERVICE_INTERFACE(album_art_editor_instance,album_art_extractor_instance);
public:
//! Throws exception_album_art_unsupported_entry when the file format we're dealing with does not support specific entry.
virtual void set(const GUID & p_what,album_art_data_ptr p_data,abort_callback & p_abort) = 0;
//! Removes the requested entry. Fails silently when the entry doesn't exist.
virtual void remove(const GUID & p_what) = 0;
//! Finalizes file tag update operation.
virtual void commit(abort_callback & p_abort) = 0;
//! Helper; see album_art_editor_instance_v2::remove_all();
void remove_all_();
};
class NOVTABLE album_art_editor_instance_v2 : public album_art_editor_instance {
FB2K_MAKE_SERVICE_INTERFACE(album_art_editor_instance_v2, album_art_editor_instance);
public:
//! Tells the editor to remove all entries, including unsupported picture types that do not translate to fb2k ids.
virtual void remove_all() = 0;
};
typedef service_ptr_t<album_art_extractor_instance> album_art_extractor_instance_ptr;
typedef service_ptr_t<album_art_editor_instance> album_art_editor_instance_ptr;
//! Entrypoint class for accessing album art extraction functionality. Register your own implementation to allow album art extraction from your media file format. \n
//! If you want to extract album art from a media file, it's recommended that you use album_art_manager API instead of calling album_art_extractor directly.
class NOVTABLE album_art_extractor : public service_base {
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(album_art_extractor);
public:
//! Returns whether the specified file is one of formats supported by our album_art_extractor implementation.
//! @param p_path Path to file being queried.
//! @param p_extension Extension of file being queried (also present in p_path parameter) - provided as a separate parameter for performance reasons.
virtual bool is_our_path(const char * p_path,const char * p_extension) = 0;
//! Instantiates album_art_extractor_instance providing access to album art stored in a specified media file. \n
//! Throws one of I/O exceptions on failure; exception_album_art_not_found when the file has no album art record at all.
//! @param p_filehint Optional; specifies a file interface to use for accessing the specified file; can be null - in that case, the implementation will open and close the file internally.
virtual album_art_extractor_instance_ptr open(file_ptr p_filehint,const char * p_path,abort_callback & p_abort) = 0;
static bool g_get_interface(service_ptr_t<album_art_extractor> & out,const char * path);
static bool g_is_supported_path(const char * path);
static album_art_extractor_instance_ptr g_open(file_ptr p_filehint,const char * p_path,abort_callback & p_abort);
static album_art_extractor_instance_ptr g_open_allowempty(file_ptr p_filehint,const char * p_path,abort_callback & p_abort);
//! Returns GUID of the corresponding input class. Null GUID if none.
GUID get_guid();
};
//! \since 1.5
class NOVTABLE album_art_extractor_v2 : public album_art_extractor {
FB2K_MAKE_SERVICE_INTERFACE(album_art_extractor_v2 , album_art_extractor);
public:
//! Returns GUID of the corresponding input class. Null GUID if none.
virtual GUID get_guid() = 0;
};
//! Entrypoint class for accessing album art editing functionality. Register your own implementation to allow album art editing on your media file format.
class NOVTABLE album_art_editor : public service_base {
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(album_art_editor);
public:
//! Returns whether the specified file is one of formats supported by our album_art_editor implementation.
//! @param p_path Path to file being queried.
//! @param p_extension Extension of file being queried (also present in p_path parameter) - provided as a separate parameter for performance reasons.
virtual bool is_our_path(const char * p_path,const char * p_extension) = 0;
//! Instantiates album_art_editor_instance providing access to album art stored in a specified media file. \n
//! @param p_filehint Optional; specifies a file interface to use for accessing the specified file; can be null - in that case, the implementation will open and close the file internally.
virtual album_art_editor_instance_ptr open(file_ptr p_filehint,const char * p_path,abort_callback & p_abort) = 0;
//! Helper; attempts to retrieve an album_art_editor service pointer that supports the specified file.
//! @returns True on success, false on failure (no registered album_art_editor supports this file type).
static bool g_get_interface(service_ptr_t<album_art_editor> & out,const char * path);
//! Helper; returns whether one of registered album_art_editor implementations is capable of opening the specified file.
static bool g_is_supported_path(const char * path);
static album_art_editor_instance_ptr g_open(file_ptr p_filehint,const char * p_path,abort_callback & p_abort);
//! Returns GUID of the corresponding input class. Null GUID if none.
GUID get_guid();
};
//! \since 1.5
class NOVTABLE album_art_editor_v2 : public album_art_editor {
FB2K_MAKE_SERVICE_INTERFACE( album_art_editor_v2, album_art_editor )
public:
//! Returns GUID of the corresponding input class. Null GUID if none.
virtual GUID get_guid() = 0;
};
//! \since 0.9.5
//! Helper API for extracting album art from APEv2 tags.
class NOVTABLE tag_processor_album_art_utils : public service_base {
FB2K_MAKE_SERVICE_COREAPI(tag_processor_album_art_utils)
public:
//! Throws one of I/O exceptions on failure; exception_album_art_not_found when the file has no album art record at all.
virtual album_art_extractor_instance_ptr open(file_ptr p_file,abort_callback & p_abort) = 0;
//! \since 1.1.6
//! Throws exception_not_implemented on earlier than 1.1.6.
virtual album_art_editor_instance_ptr edit(file_ptr p_file,abort_callback & p_abort) = 0;
};
//! Album art path list - see album_art_extractor_instance_v2
class NOVTABLE album_art_path_list : public service_base {
FB2K_MAKE_SERVICE_INTERFACE(album_art_path_list, service_base)
public:
virtual const char * get_path(t_size index) const = 0;
virtual t_size get_count() const = 0;
};
//! album_art_extractor_instance extension; lets the frontend query referenced file paths (eg. when using external album art).
class NOVTABLE album_art_extractor_instance_v2 : public album_art_extractor_instance {
FB2K_MAKE_SERVICE_INTERFACE(album_art_extractor_instance_v2, album_art_extractor_instance)
public:
virtual album_art_path_list::ptr query_paths(const GUID & p_what, abort_callback & p_abort) = 0;
};
//! \since 1.0
//! Provides methods for interfacing with the foobar2000 core album art loader. \n
//! Use this when you need to load album art for a specific group of tracks.
class NOVTABLE album_art_manager_v2 : public service_base {
FB2K_MAKE_SERVICE_COREAPI(album_art_manager_v2)
public:
//! Instantiates an album art extractor object for the specified group of items.
virtual album_art_extractor_instance_v2::ptr open(metadb_handle_list_cref items, pfc::list_base_const_t<GUID> const & ids, abort_callback & abort) = 0;
//! Instantiates an album art extractor object that retrieves stub images.
virtual album_art_extractor_instance_v2::ptr open_stub(abort_callback & abort) = 0;
};
//! \since 1.0
//! Called when no other album art source (internal, external, other registered fallbacks) returns relevant data for the specified items. \n
//! Can be used to implement online lookup and such.
class NOVTABLE album_art_fallback : public service_base {
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(album_art_fallback)
public:
virtual album_art_extractor_instance_v2::ptr open(metadb_handle_list_cref items, pfc::list_base_const_t<GUID> const & ids, abort_callback & abort) = 0;
};
//! \since 1.1.7
class NOVTABLE album_art_manager_config : public service_base {
FB2K_MAKE_SERVICE_INTERFACE(album_art_manager_config, service_base)
public:
virtual bool get_external_pattern(pfc::string_base & out, const GUID & type) = 0;
virtual bool use_embedded_pictures() = 0;
virtual bool use_fallbacks() = 0;
};
//! \since 1.1.7
class NOVTABLE album_art_manager_v3 : public album_art_manager_v2 {
FB2K_MAKE_SERVICE_COREAPI_EXTENSION(album_art_manager_v3, album_art_manager_v2)
public:
//! @param config An optional album_art_manager_config object to override global settings. Pass null to use global settings.
virtual album_art_extractor_instance_v2::ptr open_v3(metadb_handle_list_cref items, pfc::list_base_const_t<GUID> const & ids, album_art_manager_config::ptr config, abort_callback & abort) = 0;
};
//! \since 1.4
//! A notification about a newly loaded album art being ready to display. \n
//! See: now_playing_album_art_notify_manager.
class NOVTABLE now_playing_album_art_notify {
public:
//! Called when album art has finished loading for the now playing track.
//! @param data The newly loaded album art. Never a null object - the callbacks are simply not called when there is nothing to show.
virtual void on_album_art( album_art_data::ptr data ) = 0;
};
//! \since 1.4
//! Since various components require the album art of the now-playing track, a centralized loader has been provided, so the file isn't hammered independently by different components. \n
//! Use this in conjunction with play_callback notifications to render now-playing track information.
class NOVTABLE now_playing_album_art_notify_manager : public service_base {
FB2K_MAKE_SERVICE_COREAPI(now_playing_album_art_notify_manager)
public:
//! Register a notification to be told when the album art has been loaded.
virtual void add(now_playing_album_art_notify*) = 0;
//! Unregister a previously registered notification.
virtual void remove(now_playing_album_art_notify*) = 0;
//! Retrieves the album art for the currently playing track.
//! @returns The current album art (front cover), or null if there is no art or the art is being loaded and is not yet available.
virtual album_art_data::ptr current() = 0;
//! Helper; register a lambda notification. Pass the returned obejct to remove() to unregister.
now_playing_album_art_notify* add( std::function<void (album_art_data::ptr) > );
};

View File

@@ -0,0 +1,157 @@
#pragma once
//! Implements album_art_data.
class album_art_data_impl : public album_art_data {
public:
const void * get_ptr() const {return m_content.get_ptr();}
t_size get_size() const {return m_content.get_size();}
void * get_ptr() {return m_content.get_ptr();}
void set_size(t_size p_size) {m_content.set_size(p_size);}
//! Reads picture data from the specified stream object.
void from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort) {
set_size(p_bytes); p_stream->read_object(get_ptr(),p_bytes,p_abort);
}
//! Creates an album_art_data object from picture data contained in a memory buffer.
static album_art_data_ptr g_create(const void * p_buffer,t_size p_bytes) {
service_ptr_t<album_art_data_impl> instance = new service_impl_t<album_art_data_impl>();
instance->set_size(p_bytes);
memcpy(instance->get_ptr(),p_buffer,p_bytes);
return instance;
}
//! Creates an album_art_data object from picture data contained in a stream.
static album_art_data_ptr g_create(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort) {
service_ptr_t<album_art_data_impl> instance = new service_impl_t<album_art_data_impl>();
instance->from_stream(p_stream,p_bytes,p_abort);
return instance;
}
private:
pfc::array_t<t_uint8> m_content;
};
//! Helper - simple implementation of album_art_extractor_instance.
class album_art_extractor_instance_simple : public album_art_extractor_instance {
public:
void set(const GUID & p_what,album_art_data_ptr p_content) {m_content.set(p_what,p_content);}
bool have_item(const GUID & p_what) {return m_content.have_item(p_what);}
album_art_data_ptr query(const GUID & p_what,abort_callback & p_abort) {
album_art_data_ptr temp;
if (!m_content.query(p_what,temp)) throw exception_album_art_not_found();
return temp;
}
bool is_empty() const {return m_content.get_count() == 0;}
bool remove(const GUID & p_what) {
return m_content.remove(p_what);
}
private:
pfc::map_t<GUID,album_art_data_ptr> m_content;
};
//! Helper implementation of album_art_extractor - reads album art from arbitrary file formats that comply with APEv2 tagging specification.
class album_art_extractor_impl_stdtags : public album_art_extractor_v2 {
public:
//! @param exts Semicolon-separated list of file format extensions to support.
album_art_extractor_impl_stdtags(const char * exts, const GUID & guid) : m_guid(guid) {
pfc::splitStringSimple_toList(m_extensions,';',exts);
}
bool is_our_path(const char * p_path,const char * p_extension) override {
return m_extensions.have_item(p_extension);
}
album_art_extractor_instance_ptr open(file_ptr p_filehint,const char * p_path,abort_callback & p_abort) override {
PFC_ASSERT( is_our_path(p_path, pfc::string_extension(p_path) ) );
file_ptr l_file ( p_filehint );
if (l_file.is_empty()) filesystem::g_open_read(l_file, p_path, p_abort);
return tag_processor_album_art_utils::get()->open( l_file, p_abort );
}
GUID get_guid() override {
return m_guid;
}
private:
pfc::avltree_t<pfc::string,pfc::string::comparatorCaseInsensitiveASCII> m_extensions;
const GUID m_guid;
};
//! Helper implementation of album_art_editor - edits album art from arbitrary file formats that comply with APEv2 tagging specification.
class album_art_editor_impl_stdtags : public album_art_editor_v2 {
public:
//! @param exts Semicolon-separated list of file format extensions to support.
album_art_editor_impl_stdtags(const char * exts, const GUID & guid) : m_guid(guid) {
pfc::splitStringSimple_toList(m_extensions,';',exts);
}
bool is_our_path(const char * p_path,const char * p_extension) {
return m_extensions.have_item(p_extension);
}
album_art_editor_instance_ptr open(file_ptr p_filehint,const char * p_path,abort_callback & p_abort) {
PFC_ASSERT( is_our_path(p_path, pfc::string_extension(p_path) ) );
file_ptr l_file ( p_filehint );
if (l_file.is_empty()) filesystem::g_open(l_file, p_path, filesystem::open_mode_write_existing, p_abort);
return tag_processor_album_art_utils::get()->edit( l_file, p_abort );
}
GUID get_guid() override {
return m_guid;
}
private:
pfc::avltree_t<pfc::string,pfc::string::comparatorCaseInsensitiveASCII> m_extensions;
const GUID m_guid;
};
//! Helper - a more advanced implementation of album_art_extractor_instance.
class album_art_extractor_instance_fileref : public album_art_extractor_instance {
public:
album_art_extractor_instance_fileref(file::ptr f) : m_file(f) {}
void set(const GUID & p_what,t_filesize p_offset, t_filesize p_size) {
const t_fileref ref = {p_offset, p_size};
m_data.set(p_what, ref);
m_cache.remove(p_what);
}
bool have_item(const GUID & p_what) {
return m_data.have_item(p_what);
}
album_art_data_ptr query(const GUID & p_what,abort_callback & p_abort) {
album_art_data_ptr item;
if (m_cache.query(p_what,item)) return item;
t_fileref ref;
if (!m_data.query(p_what, ref)) throw exception_album_art_not_found();
m_file->seek(ref.m_offset, p_abort);
item = album_art_data_impl::g_create(m_file.get_ptr(), pfc::downcast_guarded<t_size>(ref.m_size), p_abort);
m_cache.set(p_what, item);
return item;
}
bool is_empty() const {return m_data.get_count() == 0;}
private:
struct t_fileref {
t_filesize m_offset, m_size;
};
const file::ptr m_file;
pfc::map_t<GUID, t_fileref> m_data;
pfc::map_t<GUID, album_art_data::ptr> m_cache;
};
//! album_art_path_list implementation helper
class album_art_path_list_impl : public album_art_path_list {
public:
template<typename t_in> album_art_path_list_impl(const t_in & in) {pfc::list_to_array(m_data, in);}
const char * get_path(t_size index) const {return m_data[index];}
t_size get_count() const {return m_data.get_size();}
private:
pfc::array_t<pfc::string8> m_data;
};
//! album_art_path_list implementation helper
class album_art_path_list_dummy : public album_art_path_list {
public:
const char * get_path(t_size index) const {uBugCheck();}
t_size get_count() const {return 0;}
};

View File

@@ -0,0 +1,30 @@
#include "foobar2000.h"
bool app_close_blocker::g_query()
{
service_ptr_t<app_close_blocker> ptr;
service_enum_t<app_close_blocker> e;
while(e.next(ptr))
{
if (!ptr->query()) return false;
}
return true;
}
service_ptr async_task_manager::g_acquire() {
#if FOOBAR2000_TARGET_VERSION >= 80
return get()->acquire();
#else
ptr obj;
if ( tryGet(obj) ) return obj->acquire();
return nullptr;
#endif
}
void fb2k::splitTask( std::function<void ()> f) {
auto taskref = async_task_manager::g_acquire();
pfc::splitThread( [f,taskref] {
f();
(void)taskref; // retain until here
} );
}

View File

@@ -0,0 +1,88 @@
#pragma once
#include <functional>
//! (DEPRECATED) This service is used to signal whether something is currently preventing main window from being closed and app from being shut down.
class NOVTABLE app_close_blocker : public service_base
{
public:
//! Checks whether this service is currently preventing main window from being closed and app from being shut down.
virtual bool query() = 0;
//! Static helper function, checks whether any of registered app_close_blocker services is currently preventing main window from being closed and app from being shut down.
static bool g_query();
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(app_close_blocker);
};
//! An interface encapsulating a task preventing the foobar2000 application from being closed. Instances of this class need to be registered using app_close_blocking_task_manager methods. \n
//! Implementation: it's recommended that you derive from app_close_blocking_task_impl class instead of deriving from app_close_blocking_task directly, it manages registration/unregistration behind-the-scenes.
class NOVTABLE app_close_blocking_task {
public:
virtual void query_task_name(pfc::string_base & out) = 0;
protected:
app_close_blocking_task() {}
~app_close_blocking_task() {}
PFC_CLASS_NOT_COPYABLE_EX(app_close_blocking_task);
};
//! Entrypoint class for registering app_close_blocking_task instances. Introduced in 0.9.5.1. \n
//! Usage: static_api_ptr_t<app_close_blocking_task_manager>(). May fail if user runs pre-0.9.5.1. It's recommended that you use app_close_blocking_task_impl class instead of calling app_close_blocking_task_manager directly.
class NOVTABLE app_close_blocking_task_manager : public service_base {
FB2K_MAKE_SERVICE_COREAPI(app_close_blocking_task_manager);
public:
virtual void register_task(app_close_blocking_task * task) = 0;
virtual void unregister_task(app_close_blocking_task * task) = 0;
};
//! Helper; implements standard functionality required by app_close_blocking_task implementations - registers/unregisters the task on construction/destruction.
class app_close_blocking_task_impl : public app_close_blocking_task {
public:
app_close_blocking_task_impl() { app_close_blocking_task_manager::get()->register_task(this);}
~app_close_blocking_task_impl() { app_close_blocking_task_manager::get()->unregister_task(this);}
void query_task_name(pfc::string_base & out) { out = "<unnamed task>"; }
};
class app_close_blocking_task_impl_dynamic : public app_close_blocking_task {
public:
app_close_blocking_task_impl_dynamic() : m_taskActive() {}
~app_close_blocking_task_impl_dynamic() { toggle_blocking(false); }
void query_task_name(pfc::string_base & out) { out = "<unnamed task>"; }
protected:
void toggle_blocking(bool state) {
if (state != m_taskActive) {
auto api = app_close_blocking_task_manager::get();
if (state) api->register_task(this);
else api->unregister_task(this);
m_taskActive = state;
}
}
private:
bool m_taskActive;
};
//! \since 1.4.5
//! Provides means for async tasks - running detached from any UI - to reliably finish before the app terminates. \n
//! During a late phase of app shutdown, past initquit ops, when no worker code should be still running - a core-managed instance of abort_callback is signaled, \n
//! then main thread stalls until all objects created with acquire() have been released. \n
//! As this API was introduced out-of-band with 1.4.5, you should use tryGet() to obtain it with FOOBAR2000_TARGET_VERSION < 80, but can safely use ::get() with FOOBAR2000_TARGET_VERSION >= 80.
class async_task_manager : public service_base {
FB2K_MAKE_SERVICE_COREAPI( async_task_manager );
public:
virtual abort_callback & get_aborter() = 0;
virtual service_ptr acquire() = 0;
//! acquire() helper; returns nullptr if the API isn't available due to old fb2k
static service_ptr g_acquire();
};
namespace fb2k {
//! pfc::splitThread() + async_task_manager::acquire
void splitTask( std::function<void ()> );
}

85
foobar2000/SDK/archive.h Normal file
View File

@@ -0,0 +1,85 @@
#pragma once
namespace foobar2000_io {
class archive;
class NOVTABLE archive_callback : public abort_callback {
public:
virtual bool on_entry(archive * owner,const char * url,const t_filestats & p_stats,const service_ptr_t<file> & p_reader) = 0;
};
//! Interface for archive reader services. When implementing, derive from archive_impl rather than from deriving from archive directly.
class NOVTABLE archive : public filesystem {
FB2K_MAKE_SERVICE_INTERFACE(archive,filesystem);
public:
//! Lists archive contents. \n
//! May be called with any path, not only path accepted by is_our_archive.
virtual void archive_list(const char * p_path,const service_ptr_t<file> & p_reader,archive_callback & p_callback,bool p_want_readers) = 0;
//! Optional method to weed out unsupported formats prior to calling archive_list. \n
//! Use this to suppress calls to archive_list() to avoid spurious exceptions being thrown. \n
//! Implemented via archive_v2.
bool is_our_archive( const char * path );
};
//! \since 1.5
//! New 1.5 series API, though allowed to implement/call in earlier versions. \n
//! Suppresses spurious C++ exceptions on all files not recognized as archives by this instance.
class NOVTABLE archive_v2 : public archive {
FB2K_MAKE_SERVICE_INTERFACE(archive_v2, archive)
public:
//! Optional method to weed out unsupported formats prior to calling archive_list. \n
//! Use this to suppress calls to archive_list() to avoid spurious exceptions being thrown.
virtual bool is_our_archive( const char * path ) = 0;
};
//! \since 1.6
//! New 1.6 series API, though allowed to implement/call in earlier versions.
class NOVTABLE archive_v3 : public archive_v2 {
FB2K_MAKE_SERVICE_INTERFACE(archive_v3, archive_v2)
public:
//! Determine supported archive file types. \n
//! Returns a list of extensions, colon delimited, e.g.: "zip,rar,7z"
virtual void list_extensions(pfc::string_base & out) = 0;
};
//! Root class for archive implementations. Derive from this instead of from archive directly.
class NOVTABLE archive_impl : public archive_v3 {
private:
//do not override these
bool get_canonical_path(const char * path,pfc::string_base & out);
bool is_our_path(const char * path);
bool get_display_path(const char * path,pfc::string_base & out);
void remove(const char * path,abort_callback & p_abort);
void move(const char * src,const char * dst,abort_callback & p_abort);
bool is_remote(const char * src);
bool relative_path_create(const char * file_path,const char * playlist_path,pfc::string_base & out);
bool relative_path_parse(const char * relative_path,const char * playlist_path,pfc::string_base & out);
void open(service_ptr_t<file> & p_out,const char * path, t_open_mode mode,abort_callback & p_abort);
void create_directory(const char * path,abort_callback &);
void list_directory(const char * p_path,directory_callback & p_out,abort_callback & p_abort);
void get_stats(const char * p_path,t_filestats & p_stats,bool & p_is_writeable,abort_callback & p_abort);
void list_extensions(pfc::string_base & out) override { out = get_archive_type(); }
protected:
//override these
virtual const char * get_archive_type()=0;//eg. "zip", must be lowercase
virtual t_filestats get_stats_in_archive(const char * p_archive,const char * p_file,abort_callback & p_abort) = 0;
virtual void open_archive(service_ptr_t<file> & p_out,const char * archive,const char * file, abort_callback & p_abort) = 0;//opens for reading
public:
//override these
virtual void archive_list(const char * path,const service_ptr_t<file> & p_reader,archive_callback & p_out,bool p_want_readers)= 0 ;
virtual bool is_our_archive( const char * path ) = 0;
static bool g_is_unpack_path(const char * path);
static bool g_parse_unpack_path(const char * path,pfc::string_base & archive,pfc::string_base & file);
static bool g_parse_unpack_path_ex(const char * path,pfc::string_base & archive,pfc::string_base & file, pfc::string_base & type);
static void g_make_unpack_path(pfc::string_base & path,const char * archive,const char * file,const char * type);
void make_unpack_path(pfc::string_base & path,const char * archive,const char * file);
};
template<typename T>
class archive_factory_t : public service_factory_single_t<T> {};
}

View File

@@ -0,0 +1,702 @@
#include "foobar2000.h"
void audio_chunk::set_data(const audio_sample * src,t_size samples,unsigned nch,unsigned srate,unsigned channel_config)
{
t_size size = samples * nch;
set_data_size(size);
if (src)
pfc::memcpy_t(get_data(),src,size);
else
pfc::memset_t(get_data(),(audio_sample)0,size);
set_sample_count(samples);
set_channels(nch,channel_config);
set_srate(srate);
}
inline bool check_exclusive(unsigned val, unsigned mask)
{
return (val&mask)!=0 && (val&mask)!=mask;
}
static void _import8u(uint8_t const * in, audio_sample * out, size_t count) {
for(size_t walk = 0; walk < count; ++walk) {
uint32_t i = *(in++);
i -= 0x80; // to signed
*(out++) = (float) (int32_t) i / (float) 0x80;
}
}
static void _import8s(uint8_t const * in, audio_sample * out, size_t count) {
for(size_t walk = 0; walk < count; ++walk) {
int32_t i = (int8_t) *(in++);
*(out++) = (float) i / (float) 0x80;
}
}
static audio_sample _import24s(uint32_t i) {
i ^= 0x800000; // to unsigned
i -= 0x800000; // and back to signed / fill MSBs proper
return (float) (int32_t) i / (float) 0x800000;
}
static void _import24(const void * in_, audio_sample * out, size_t count) {
const uint8_t * in = (const uint8_t*) in_;
#if 1
while(count > 0 && !pfc::is_ptr_aligned_t<4>(in)) {
uint32_t i = *(in++);
i |= (uint32_t) *(in++) << 8;
i |= (uint32_t) *(in++) << 16;
*(out++) = _import24s(i);
--count;
}
{
for(size_t loop = count >> 2; loop; --loop) {
uint32_t i1 = * (uint32_t*) in; in += 4;
uint32_t i2 = * (uint32_t*) in; in += 4;
uint32_t i3 = * (uint32_t*) in; in += 4;
*out++ = _import24s( i1 & 0xFFFFFF );
*out++ = _import24s( (i1 >> 24) | ((i2 & 0xFFFF) << 8) );
*out++ = _import24s( (i2 >> 16) | ((i3 & 0xFF) << 16) );
*out++ = _import24s( i3 >> 8 );
}
count &= 3;
}
for( ; count ; --count) {
uint32_t i = *(in++);
i |= (uint32_t) *(in++) << 8;
i |= (uint32_t) *(in++) << 16;
*(out++) = _import24s(i);
}
#else
if (count > 0) {
int32_t i = *(in++);
i |= (int32_t) *(in++) << 8;
i |= (int32_t) (int8_t) *in << 16;
*out++ = (audio_sample) i / (audio_sample) 0x800000;
--count;
// Now we have in ptr at offset_of_next - 1 and we can read as int32 then discard the LSBs
for(;count;--count) {
int32_t i = *( int32_t*) in; in += 3;
*out++ = (audio_sample) (i >> 8) / (audio_sample) 0x800000;
}
}
#endif
}
template<bool byteSwap, bool isSigned> static void _import16any(const void * in, audio_sample * out, size_t count) {
uint16_t const * inPtr = (uint16_t const*) in;
const audio_sample factor = 1.0f / (audio_sample) 0x8000;
for(size_t walk = 0; walk < count; ++walk) {
uint16_t v = *inPtr++;
if (byteSwap) v = pfc::byteswap_t(v);
if (!isSigned) v ^= 0x8000; // to signed
*out++ = (audio_sample) (int16_t) v * factor;
}
}
template<bool byteSwap, bool isSigned> static void _import32any(const void * in, audio_sample * out, size_t count) {
uint32_t const * inPtr = (uint32_t const*) in;
const audio_sample factor = 1.0f / (audio_sample) 0x80000000ul;
for(size_t walk = 0; walk < count; ++walk) {
uint32_t v = *inPtr++;
if (byteSwap) v = pfc::byteswap_t(v);
if (!isSigned) v ^= 0x80000000u; // to signed
*out++ = (audio_sample) (int32_t) v * factor;
}
}
template<bool byteSwap, bool isSigned> static void _import24any(const void * in, audio_sample * out, size_t count) {
uint8_t const * inPtr = (uint8_t const*) in;
const audio_sample factor = 1.0f / (audio_sample) 0x800000;
for(size_t walk = 0; walk < count; ++walk) {
uint32_t v;
if (byteSwap) v = (uint32_t) inPtr[2] | ( (uint32_t) inPtr[1] << 8 ) | ( (uint32_t) inPtr[0] << 16 );
else v = (uint32_t) inPtr[0] | ( (uint32_t) inPtr[1] << 8 ) | ( (uint32_t) inPtr[2] << 16 );
inPtr += 3;
if (isSigned) v ^= 0x800000; // to unsigned
v -= 0x800000; // then subtract to get proper MSBs
*out++ = (audio_sample) (int32_t) v * factor;
}
}
void audio_chunk::set_data_fixedpoint_ex(const void * source,t_size size,unsigned srate,unsigned nch,unsigned bps,unsigned flags,unsigned p_channel_config)
{
PFC_ASSERT( check_exclusive(flags,FLAG_SIGNED|FLAG_UNSIGNED) );
PFC_ASSERT( check_exclusive(flags,FLAG_LITTLE_ENDIAN|FLAG_BIG_ENDIAN) );
bool byteSwap = !!(flags & FLAG_BIG_ENDIAN);
if (pfc::byte_order_is_big_endian) byteSwap = !byteSwap;
t_size count = size / (bps/8);
set_data_size(count);
audio_sample * buffer = get_data();
bool isSigned = !!(flags & FLAG_SIGNED);
switch(bps)
{
case 8:
// byte order irrelevant
if (isSigned) _import8s( (const uint8_t*) source , buffer, count);
else _import8u( (const uint8_t*) source , buffer, count);
break;
case 16:
if (byteSwap) {
if (isSigned) {
_import16any<true, true>( source, buffer, count );
} else {
_import16any<true, false>( source, buffer, count );
}
} else {
if (isSigned) {
//_import16any<false, true>( source, buffer, count );
audio_math::convert_from_int16((const int16_t*)source,count,buffer,1.0);
} else {
_import16any<false, false>( source, buffer, count);
}
}
break;
case 24:
if (byteSwap) {
if (isSigned) {
_import24any<true, true>( source, buffer, count );
} else {
_import24any<true, false>( source, buffer, count );
}
} else {
if (isSigned) {
//_import24any<false, true>( source, buffer, count);
_import24( source, buffer, count);
} else {
_import24any<false, false>( source, buffer, count);
}
}
break;
case 32:
if (byteSwap) {
if (isSigned) {
_import32any<true, true>( source, buffer, count );
} else {
_import32any<true, false>( source, buffer, count );
}
} else {
if (isSigned) {
audio_math::convert_from_int32((const int32_t*)source,count,buffer,1.0);
} else {
_import32any<false, false>( source, buffer, count);
}
}
break;
default:
//unknown size, cant convert
pfc::memset_t(buffer,(audio_sample)0,count);
break;
}
set_sample_count(count/nch);
set_srate(srate);
set_channels(nch,p_channel_config);
}
void audio_chunk::set_data_fixedpoint_ms(const void * ptr, size_t bytes, unsigned sampleRate, unsigned channels, unsigned bps, unsigned channelConfig) {
//set_data_fixedpoint_ex(ptr,bytes,sampleRate,channels,bps,(bps==8 ? FLAG_UNSIGNED : FLAG_SIGNED) | flags_autoendian(), channelConfig);
PFC_ASSERT( bps != 0 );
size_t count = bytes / (bps/8);
this->set_data_size( count );
audio_sample * buffer = this->get_data();
switch(bps) {
case 8:
_import8u((const uint8_t*)ptr, buffer, count);
break;
case 16:
audio_math::convert_from_int16((const int16_t*) ptr, count, buffer, 1.0);
break;
case 24:
_import24( ptr, buffer, count);
break;
case 32:
audio_math::convert_from_int32((const int32_t*) ptr, count, buffer, 1.0);
break;
default:
PFC_ASSERT(!"Unknown bit depth!");
memset(buffer, 0, sizeof(audio_sample) * count);
break;
}
set_sample_count(count/channels);
set_srate(sampleRate);
set_channels(channels,channelConfig);
}
void audio_chunk::set_data_fixedpoint_signed(const void * ptr,t_size bytes,unsigned sampleRate,unsigned channels,unsigned bps,unsigned channelConfig) {
PFC_ASSERT( bps != 0 );
size_t count = bytes / (bps/8);
this->set_data_size( count );
audio_sample * buffer = this->get_data();
switch(bps) {
case 8:
_import8s((const uint8_t*)ptr, buffer, count);
break;
case 16:
audio_math::convert_from_int16((const int16_t*) ptr, count, buffer, 1.0);
break;
case 24:
_import24( ptr, buffer, count);
break;
case 32:
audio_math::convert_from_int32((const int32_t*) ptr, count, buffer, 1.0);
break;
default:
PFC_ASSERT(!"Unknown bit depth!");
memset(buffer, 0, sizeof(audio_sample) * count);
break;
}
set_sample_count(count/channels);
set_srate(sampleRate);
set_channels(channels,channelConfig);
}
void audio_chunk::set_data_int16(const int16_t * src,t_size samples,unsigned nch,unsigned srate,unsigned channel_config) {
const size_t count = samples * nch;
this->set_data_size( count );
audio_sample * buffer = this->get_data();
audio_math::convert_from_int16(src, count, buffer, 1.0);
set_sample_count(samples);
set_srate(srate);
set_channels(nch,channel_config);
}
template<class t_float>
static void process_float_multi(audio_sample * p_out,const t_float * p_in,const t_size p_count)
{
for(size_t n=0;n<p_count;n++) p_out[n] = (audio_sample)p_in[n];
}
template<class t_float>
static void process_float_multi_swap(audio_sample * p_out,const t_float * p_in,const t_size p_count)
{
for(size_t n=0;n<p_count;n++) {
p_out[n] = (audio_sample) pfc::byteswap_t(p_in[n]);
}
}
void audio_chunk::set_data_floatingpoint_ex(const void * ptr,t_size size,unsigned srate,unsigned nch,unsigned bps,unsigned flags,unsigned p_channel_config)
{
PFC_ASSERT(bps==32 || bps==64 || bps == 16 || bps == 24);
PFC_ASSERT( check_exclusive(flags,FLAG_LITTLE_ENDIAN|FLAG_BIG_ENDIAN) );
PFC_ASSERT( ! (flags & (FLAG_SIGNED|FLAG_UNSIGNED) ) );
bool use_swap = pfc::byte_order_is_big_endian ? !!(flags & FLAG_LITTLE_ENDIAN) : !!(flags & FLAG_BIG_ENDIAN);
const t_size count = size / (bps/8);
set_data_size(count);
audio_sample * out = get_data();
if (bps == 32)
{
if (use_swap)
process_float_multi_swap(out,reinterpret_cast<const float*>(ptr),count);
else
process_float_multi(out,reinterpret_cast<const float*>(ptr),count);
}
else if (bps == 64)
{
if (use_swap)
process_float_multi_swap(out,reinterpret_cast<const double*>(ptr),count);
else
process_float_multi(out,reinterpret_cast<const double*>(ptr),count);
} else if (bps == 16) {
const uint16_t * in = reinterpret_cast<const uint16_t*>(ptr);
if (use_swap) {
for(size_t walk = 0; walk < count; ++walk) out[walk] = audio_math::decodeFloat16(pfc::byteswap_t(in[walk]));
} else {
for(size_t walk = 0; walk < count; ++walk) out[walk] = audio_math::decodeFloat16(in[walk]);
}
} else if (bps == 24) {
const uint8_t * in = reinterpret_cast<const uint8_t*>(ptr);
if (use_swap) {
for(size_t walk = 0; walk < count; ++walk) out[walk] = audio_math::decodeFloat24ptrbs(&in[walk*3]);
} else {
for(size_t walk = 0; walk < count; ++walk) out[walk] = audio_math::decodeFloat24ptr(&in[walk*3]);
}
} else pfc::throw_exception_with_message< exception_io_data >("invalid bit depth");
set_sample_count(count/nch);
set_srate(srate);
set_channels(nch,p_channel_config);
}
pfc::string8 audio_chunk::formatChunkSpec() const {
pfc::string8 msg;
msg << get_sample_rate() << " Hz, " << get_channels() << ":0x" << pfc::format_hex(get_channel_config(), 2) << " channels, " << get_sample_count() << " samples";
return msg;
}
void audio_chunk::debugChunkSpec() const {
FB2K_DebugLog() << "Chunk: " << this->formatChunkSpec();
}
#if PFC_DEBUG
void audio_chunk::assert_valid(const char * ctx) const {
if (!is_valid()) {
FB2K_DebugLog() << "audio_chunk::assert_valid failure in " << ctx;
debugChunkSpec();
uBugCheck();
}
}
#endif
bool audio_chunk::is_valid() const
{
unsigned nch = get_channels();
if (nch == 0 || nch > 32) return false;
if (!g_is_valid_sample_rate(get_srate())) return false;
t_size samples = get_sample_count();
if (samples==0 || samples >= 0x80000000ul / (sizeof(audio_sample) * nch) ) return false;
t_size size = get_data_size();
if (samples * nch > size) return false;
if (!get_data()) return false;
return true;
}
bool audio_chunk::is_spec_valid() const {
return this->get_spec().is_valid();
}
void audio_chunk::pad_with_silence_ex(t_size samples,unsigned hint_nch,unsigned hint_srate) {
if (is_empty())
{
if (hint_srate && hint_nch) {
return set_data(0,samples,hint_nch,hint_srate);
} else throw exception_io_data();
}
else
{
if (hint_srate && hint_srate != get_srate()) samples = MulDiv_Size(samples,get_srate(),hint_srate);
if (samples > get_sample_count())
{
t_size old_size = get_sample_count() * get_channels();
t_size new_size = samples * get_channels();
set_data_size(new_size);
pfc::memset_t(get_data() + old_size,(audio_sample)0,new_size - old_size);
set_sample_count(samples);
}
}
}
void audio_chunk::pad_with_silence(t_size samples) {
if (samples > get_sample_count())
{
t_size old_size = get_sample_count() * get_channels();
t_size new_size = pfc::multiply_guarded(samples,(size_t)get_channels());
set_data_size(new_size);
pfc::memset_t(get_data() + old_size,(audio_sample)0,new_size - old_size);
set_sample_count(samples);
}
}
void audio_chunk::set_silence(t_size samples) {
t_size items = samples * get_channels();
set_data_size(items);
pfc::memset_null_t(get_data(), items);
set_sample_count(samples);
}
void audio_chunk::set_silence_seconds( double seconds ) {
set_silence( (size_t) audio_math::time_to_samples( seconds, this->get_sample_rate() ) );
}
void audio_chunk::insert_silence_fromstart(t_size samples) {
t_size old_size = get_sample_count() * get_channels();
t_size delta = samples * get_channels();
t_size new_size = old_size + delta;
set_data_size(new_size);
audio_sample * ptr = get_data();
pfc::memmove_t(ptr+delta,ptr,old_size);
pfc::memset_t(ptr,(audio_sample)0,delta);
set_sample_count(get_sample_count() + samples);
}
bool audio_chunk::process_skip(double & skipDuration) {
t_uint64 skipSamples = audio_math::time_to_samples(skipDuration, get_sample_rate());
if (skipSamples == 0) {skipDuration = 0; return true;}
const t_size mySamples = get_sample_count();
if (skipSamples < mySamples) {
skip_first_samples((t_size)skipSamples);
skipDuration = 0;
return true;
}
if (skipSamples == mySamples) {
skipDuration = 0;
return false;
}
skipDuration -= audio_math::samples_to_time(mySamples, get_sample_rate());
return false;
}
t_size audio_chunk::skip_first_samples(t_size samples_delta)
{
t_size samples_old = get_sample_count();
if (samples_delta >= samples_old)
{
set_sample_count(0);
set_data_size(0);
return samples_old;
}
else
{
t_size samples_new = samples_old - samples_delta;
unsigned nch = get_channels();
audio_sample * ptr = get_data();
pfc::memmove_t(ptr,ptr+nch*samples_delta,nch*samples_new);
set_sample_count(samples_new);
set_data_size(nch*samples_new);
return samples_delta;
}
}
audio_sample audio_chunk::get_peak(audio_sample p_peak) const {
return pfc::max_t(p_peak, get_peak());
}
audio_sample audio_chunk::get_peak() const {
return audio_math::calculate_peak(get_data(),get_sample_count() * get_channels());
}
void audio_chunk::scale(audio_sample p_value)
{
audio_sample * ptr = get_data();
audio_math::scale(ptr,get_sample_count() * get_channels(),ptr,p_value);
}
namespace {
struct sampleToIntDesc {
unsigned bps, bpsValid;
bool useUpperBits;
float scale;
};
template<typename int_t> class sampleToInt {
public:
sampleToInt(sampleToIntDesc const & d) {
clipLo = - ( (int_t) 1 << (d.bpsValid-1));
clipHi = ( (int_t) 1 << (d.bpsValid-1)) - 1;
scale = (float) ( (int64_t) 1 << (d.bpsValid - 1) ) * d.scale;
if (d.useUpperBits) {
shift = d.bps - d.bpsValid;
} else {
shift = 0;
}
}
inline int_t operator() (audio_sample s) const {
int_t v;
if (sizeof(int_t) > 4) v = (int_t) audio_math::rint64( s * scale );
else v = (int_t)audio_math::rint32( s * scale );
return pfc::clip_t<int_t>( v, clipLo, clipHi) << shift;
}
private:
int_t clipLo, clipHi;
int8_t shift;
float scale;
};
}
static void render_24bit(const audio_sample * in, t_size inLen, void * out, sampleToIntDesc const & d) {
t_uint8 * outWalk = reinterpret_cast<t_uint8*>(out);
sampleToInt<int32_t> gen(d);
for(t_size walk = 0; walk < inLen; ++walk) {
int32_t v = gen(in[walk]);
*(outWalk ++) = (t_uint8) (v & 0xFF);
*(outWalk ++) = (t_uint8) ((v >> 8) & 0xFF);
*(outWalk ++) = (t_uint8) ((v >> 16) & 0xFF);
}
}
static void render_8bit(const audio_sample * in, t_size inLen, void * out, sampleToIntDesc const & d) {
sampleToInt<int32_t> gen(d);
t_int8 * outWalk = reinterpret_cast<t_int8*>(out);
for(t_size walk = 0; walk < inLen; ++walk) {
*outWalk++ = (t_int8)gen(in[walk]);
}
}
static void render_16bit(const audio_sample * in, t_size inLen, void * out, sampleToIntDesc const & d) {
sampleToInt<int32_t> gen(d);
int16_t * outWalk = reinterpret_cast<int16_t*>(out);
for(t_size walk = 0; walk < inLen; ++walk) {
*outWalk++ = (int16_t)gen(in[walk]);
}
}
template<typename internal_t>
static void render_32bit_(const audio_sample * in, t_size inLen, void * out, sampleToIntDesc const & d) {
sampleToInt<internal_t> gen(d); // must use int64 for clipping
int32_t * outWalk = reinterpret_cast<int32_t*>(out);
for(t_size walk = 0; walk < inLen; ++walk) {
*outWalk++ = (int32_t)gen(in[walk]);
}
}
bool audio_chunk::g_toFixedPoint(const audio_sample * in, void * out, size_t count, uint32_t bps, uint32_t bpsValid, bool useUpperBits, float scale) {
const sampleToIntDesc d = {bps, bpsValid, useUpperBits, scale};
if (bps == 0) {
PFC_ASSERT(!"How did we get here?");
return false;
} else if (bps <= 8) {
render_8bit(in, count, out, d);
} else if (bps <= 16) {
render_16bit(in, count, out, d);
} else if (bps <= 24) {
render_24bit(in, count, out, d);
} else if (bps <= 32) {
if (bpsValid <= 28) { // for speed
render_32bit_<int32_t>(in, count, out, d);
} else {
render_32bit_<int64_t>(in, count, out, d);
}
} else {
PFC_ASSERT(!"How did we get here?");
return false;
}
return true;
}
bool audio_chunk::toFixedPoint(class mem_block_container & out, uint32_t bps, uint32_t bpsValid, bool useUpperBits, float scale) const {
bps = (bps + 7) & ~7;
if (bps < bpsValid) return false;
const size_t count = get_sample_count() * get_channel_count();
out.set_size( count * (bps/8) );
return g_toFixedPoint(get_data(), out.get_ptr(), count, bps, bpsValid, useUpperBits, scale);
}
bool audio_chunk::to_raw_data(mem_block_container & out, t_uint32 bps, bool useUpperBits, float scale) const {
uint32_t bpsValid = bps;
bps = (bps + 7) & ~7;
const size_t count = get_sample_count() * get_channel_count();
out.set_size( count * (bps/8) );
void * outPtr = out.get_ptr();
audio_sample const * inPtr = get_data();
if (bps == 32) {
float * f = (float*) outPtr;
for(size_t w = 0; w < count; ++w) f[w] = inPtr[w] * scale;
return true;
} else {
return g_toFixedPoint(inPtr, outPtr, count, bps, bpsValid, useUpperBits, scale);
}
}
audio_chunk::spec_t audio_chunk::makeSpec(uint32_t rate, uint32_t channels) {
return makeSpec( rate, channels, g_guess_channel_config(channels) );
}
audio_chunk::spec_t audio_chunk::makeSpec(uint32_t rate, uint32_t channels, uint32_t mask) {
spec_t spec = {};
spec.sampleRate = rate; spec.chanCount = channels; spec.chanMask = mask;
return spec;
}
bool audio_chunk::spec_t::equals( const spec_t & v1, const spec_t & v2 ) {
return v1.sampleRate == v2.sampleRate && v1.chanCount == v2.chanCount && v1.chanMask == v2.chanMask;
}
pfc::string8 audio_chunk::spec_t::toString(const char * delim) const {
pfc::string_formatter temp;
if ( sampleRate > 0 ) temp << sampleRate << "Hz";
if (chanCount > 0) {
if ( temp.length() > 0 ) temp << delim;
temp << chanCount << "ch";
}
if ( chanMask != audio_chunk::channel_config_mono && chanMask != audio_chunk::channel_config_stereo ) {
pfc::string8 strMask;
audio_chunk::g_formatChannelMaskDesc( chanMask, strMask );
if ( temp.length() > 0) temp << delim;
temp << strMask;
}
return temp;
}
audio_chunk::spec_t audio_chunk::get_spec() const {
spec_t spec = {};
spec.sampleRate = this->get_sample_rate();
spec.chanCount = this->get_channel_count();
spec.chanMask = this->get_channel_config();
return spec;
}
void audio_chunk::set_spec(const spec_t & spec) {
set_sample_rate(spec.sampleRate);
set_channels( spec.chanCount, spec.chanMask );
}
bool audio_chunk::spec_t::is_valid() const {
if (this->chanCount==0 || this->chanCount>256) return false;
if (!audio_chunk::g_is_valid_sample_rate(this->sampleRate)) return false;
return true;
}
#ifdef _WIN32
WAVEFORMATEX audio_chunk::spec_t::toWFX() const {
const uint32_t sampleWidth = sizeof(audio_sample);
WAVEFORMATEX wfx = {};
wfx.wFormatTag = WAVE_FORMAT_IEEE_FLOAT;
wfx.nChannels = chanCount;
wfx.nSamplesPerSec = sampleRate;
wfx.nAvgBytesPerSec = sampleRate * chanCount * sampleWidth;
wfx.nBlockAlign = chanCount * sampleWidth;
wfx.wBitsPerSample = sampleWidth * 8;
return wfx;
}
WAVEFORMATEXTENSIBLE audio_chunk::spec_t::toWFXEX() const {
const uint32_t sampleWidth = sizeof(audio_sample);
const bool isFloat = true;
WAVEFORMATEXTENSIBLE wfxe;
wfxe.Format = toWFX();
wfxe.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
wfxe.Format.cbSize = sizeof(wfxe) - sizeof(wfxe.Format);
wfxe.Samples.wValidBitsPerSample = sampleWidth * 8;
wfxe.dwChannelMask = audio_chunk::g_channel_config_to_wfx(this->chanMask);
wfxe.SubFormat = isFloat ? KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : KSDATAFORMAT_SUBTYPE_PCM;
return wfxe;
}
WAVEFORMATEX audio_chunk::spec_t::toWFXWithBPS(uint32_t bps) const {
const uint32_t sampleWidth = (bps+7)/8;
WAVEFORMATEX wfx = {};
wfx.wFormatTag = WAVE_FORMAT_PCM;
wfx.nChannels = chanCount;
wfx.nSamplesPerSec = sampleRate;
wfx.nAvgBytesPerSec = sampleRate * chanCount * sampleWidth;
wfx.nBlockAlign = chanCount * sampleWidth;
wfx.wBitsPerSample = sampleWidth * 8;
return wfx;
}
WAVEFORMATEXTENSIBLE audio_chunk::spec_t::toWFXEXWithBPS(uint32_t bps) const {
const uint32_t sampleWidth = (bps + 7) / 8;
const bool isFloat = false;
WAVEFORMATEXTENSIBLE wfxe;
wfxe.Format = toWFXWithBPS(bps);
wfxe.Format.wFormatTag = WAVE_FORMAT_EXTENSIBLE;
wfxe.Format.cbSize = sizeof(wfxe) - sizeof(wfxe.Format);
wfxe.Samples.wValidBitsPerSample = sampleWidth * 8;
wfxe.dwChannelMask = audio_chunk::g_channel_config_to_wfx(this->chanMask);
wfxe.SubFormat = isFloat ? KSDATAFORMAT_SUBTYPE_IEEE_FLOAT : KSDATAFORMAT_SUBTYPE_PCM;
return wfxe;
}
#endif // _WIN32
void audio_chunk::append(const audio_chunk& other) {
if (other.get_spec() != this->get_spec()) {
throw pfc::exception_invalid_params();
}
this->grow_data_size(get_used_size() + other.get_used_size());
audio_sample* p = this->get_data() + get_used_size();
memcpy(p, other.get_data(), other.get_used_size() * sizeof(audio_sample));
set_sample_count(get_sample_count() + other.get_sample_count());
}

View File

@@ -0,0 +1,383 @@
#pragma once
#ifdef _WIN32
#include <MMReg.h>
#endif
//! Thrown when audio_chunk sample rate or channel mapping changes in mid-stream and the code receiving audio_chunks can't deal with that scenario.
PFC_DECLARE_EXCEPTION(exception_unexpected_audio_format_change, exception_io_data, "Unexpected audio format change" );
//! Interface to container of a chunk of audio data. See audio_chunk_impl for an implementation.
class NOVTABLE audio_chunk {
public:
enum {
sample_rate_min = 1000, sample_rate_max = 20000000
};
static bool g_is_valid_sample_rate(t_uint32 p_val) {return p_val >= sample_rate_min && p_val <= sample_rate_max;}
//! Channel map flag declarations. Note that order of interleaved channel data in the stream is same as order of these flags.
enum
{
channel_front_left = 1<<0,
channel_front_right = 1<<1,
channel_front_center = 1<<2,
channel_lfe = 1<<3,
channel_back_left = 1<<4,
channel_back_right = 1<<5,
channel_front_center_left = 1<<6,
channel_front_center_right = 1<<7,
channel_back_center = 1<<8,
channel_side_left = 1<<9,
channel_side_right = 1<<10,
channel_top_center = 1<<11,
channel_top_front_left = 1<<12,
channel_top_front_center = 1<<13,
channel_top_front_right = 1<<14,
channel_top_back_left = 1<<15,
channel_top_back_center = 1<<16,
channel_top_back_right = 1<<17,
channel_config_mono = channel_front_center,
channel_config_stereo = channel_front_left | channel_front_right,
channel_config_4point0 = channel_front_left | channel_front_right | channel_back_left | channel_back_right,
channel_config_5point0 = channel_front_left | channel_front_right | channel_front_center | channel_back_left | channel_back_right,
channel_config_5point1 = channel_front_left | channel_front_right | channel_front_center | channel_lfe | channel_back_left | channel_back_right,
channel_config_5point1_side = channel_front_left | channel_front_right | channel_front_center | channel_lfe | channel_side_left | channel_side_right,
channel_config_7point1 = channel_config_5point1 | channel_side_left | channel_side_right,
channels_back_left_right = channel_back_left | channel_back_right,
channels_side_left_right = channel_side_left | channel_side_right,
defined_channel_count = 18,
};
//! Helper function; guesses default channel map for the specified channel count. Returns 0 on failure.
static unsigned g_guess_channel_config(unsigned count);
//! Helper function; determines channel map for the specified channel count according to Xiph specs. Throws exception_io_data on failure.
static unsigned g_guess_channel_config_xiph(unsigned count);
//! Helper function; translates audio_chunk channel map to WAVEFORMATEXTENSIBLE channel map.
static uint32_t g_channel_config_to_wfx(unsigned p_config);
//! Helper function; translates WAVEFORMATEXTENSIBLE channel map to audio_chunk channel map.
static unsigned g_channel_config_from_wfx(uint32_t p_wfx);
//! Extracts flag describing Nth channel from specified map. Usable to figure what specific channel in a stream means.
static unsigned g_extract_channel_flag(unsigned p_config,unsigned p_index);
//! Counts channels specified by channel map.
static unsigned g_count_channels(unsigned p_config);
//! Calculates index of a channel specified by p_flag in a stream where channel map is described by p_config.
static unsigned g_channel_index_from_flag(unsigned p_config,unsigned p_flag);
static const char * g_channel_name(unsigned p_flag);
static const char * g_channel_name_byidx(unsigned p_index);
static unsigned g_find_channel_idx(unsigned p_flag);
static void g_formatChannelMaskDesc(unsigned flags, pfc::string_base & out);
static pfc::string8 g_formatChannelMaskDesc(unsigned flags);
//! Retrieves audio data buffer pointer (non-const version). Returned pointer is for temporary use only; it is valid until next set_data_size call, or until the object is destroyed. \n
//! Size of returned buffer is equal to get_data_size() return value (in audio_samples). Amount of actual data may be smaller, depending on sample count and channel count. Conditions where sample count * channel count are greater than data size should not be possible.
virtual audio_sample * get_data() = 0;
//! Retrieves audio data buffer pointer (const version). Returned pointer is for temporary use only; it is valid until next set_data_size call, or until the object is destroyed. \n
//! Size of returned buffer is equal to get_data_size() return value (in audio_samples). Amount of actual data may be smaller, depending on sample count and channel count. Conditions where sample count * channel count are greater than data size should not be possible.
virtual const audio_sample * get_data() const = 0;
//! Retrieves size of allocated buffer space, in audio_samples.
virtual t_size get_data_size() const = 0;
//! Resizes audio data buffer to specified size. Throws std::bad_alloc on failure.
virtual void set_data_size(t_size p_new_size) = 0;
//! Sanity helper, same as set_data_size.
void allocate(size_t size) { set_data_size( size ); }
//! Retrieves sample rate of contained audio data.
virtual unsigned get_srate() const = 0;
//! Sets sample rate of contained audio data.
virtual void set_srate(unsigned val) = 0;
//! Retrieves channel count of contained audio data.
virtual unsigned get_channels() const = 0;
//! Helper - for consistency - same as get_channels().
inline unsigned get_channel_count() const {return get_channels();}
//! Retrieves channel map of contained audio data. Conditions where number of channels specified by channel map don't match get_channels() return value should not be possible.
virtual unsigned get_channel_config() const = 0;
//! Sets channel count / channel map.
virtual void set_channels(unsigned p_count,unsigned p_config) = 0;
//! Retrieves number of valid samples in the buffer. \n
//! Note that a "sample" means a unit of interleaved PCM data representing states of each channel at given point of time, not a single PCM value. \n
//! For an example, duration of contained audio data is equal to sample count / sample rate, while actual size of contained data is equal to sample count * channel count.
virtual t_size get_sample_count() const = 0;
//! Sets number of valid samples in the buffer. WARNING: sample count * channel count should never be above allocated buffer size.
virtual void set_sample_count(t_size val) = 0;
//! Helper, same as get_srate().
inline unsigned get_sample_rate() const {return get_srate();}
//! Helper, same as set_srate().
inline void set_sample_rate(unsigned val) {set_srate(val);}
//! Helper; sets channel count to specified value and uses default channel map for this channel count.
void set_channels(unsigned val) {set_channels(val,g_guess_channel_config(val));}
//! Helper; resizes audio data buffer when its current size is smaller than requested.
inline void grow_data_size(t_size p_requested) {if (p_requested > get_data_size()) set_data_size(p_requested);}
//! Retrieves duration of contained audio data, in seconds.
inline double get_duration() const
{
double rv = 0;
t_size srate = get_srate (), samples = get_sample_count();
if (srate>0 && samples>0) rv = (double)samples/(double)srate;
return rv;
}
//! Returns whether the chunk is empty (contains no audio data).
inline bool is_empty() const {return get_channels()==0 || get_srate()==0 || get_sample_count()==0;}
//! Returns whether the chunk contents are valid (for bug check purposes).
bool is_valid() const;
void debugChunkSpec() const;
pfc::string8 formatChunkSpec() const;
#if PFC_DEBUG
void assert_valid(const char * ctx) const;
#else
void assert_valid(const char * ctx) const {}
#endif
//! Returns whether the chunk contains valid sample rate & channel info (but allows an empty chunk).
bool is_spec_valid() const;
//! Returns actual amount of audio data contained in the buffer (sample count * channel count). Must not be greater than data size (see get_data_size()).
size_t get_used_size() const {return get_sample_count() * get_channels();}
//! Same as get_used_size(); old confusingly named version.
size_t get_data_length() const {return get_sample_count() * get_channels();}
#ifdef _MSC_VER
#pragma deprecated( get_data_length )
#endif
//! Resets all audio_chunk data.
inline void reset() {
set_sample_count(0);
set_srate(0);
set_channels(0,0);
set_data_size(0);
}
//! Helper, sets chunk data to contents of specified buffer, with specified number of channels / sample rate / channel map.
void set_data(const audio_sample * src,t_size samples,unsigned nch,unsigned srate,unsigned channel_config);
//! Helper, sets chunk data to contents of specified buffer, with specified number of channels / sample rate, using default channel map for specified channel count.
inline void set_data(const audio_sample * src,t_size samples,unsigned nch,unsigned srate) {set_data(src,samples,nch,srate,g_guess_channel_config(nch));}
void set_data_int16(const int16_t * src,t_size samples,unsigned nch,unsigned srate,unsigned channel_config);
//! Helper, sets chunk data to contents of specified buffer, using default win32/wav conventions for signed/unsigned switch.
inline void set_data_fixedpoint(const void * ptr,t_size bytes,unsigned srate,unsigned nch,unsigned bps,unsigned channel_config) {
this->set_data_fixedpoint_ms(ptr, bytes, srate, nch, bps, channel_config);
}
void set_data_fixedpoint_signed(const void * ptr,t_size bytes,unsigned srate,unsigned nch,unsigned bps,unsigned channel_config);
enum
{
FLAG_LITTLE_ENDIAN = 1,
FLAG_BIG_ENDIAN = 2,
FLAG_SIGNED = 4,
FLAG_UNSIGNED = 8,
};
inline static unsigned flags_autoendian() {
return pfc::byte_order_is_big_endian ? FLAG_BIG_ENDIAN : FLAG_LITTLE_ENDIAN;
}
void set_data_fixedpoint_ex(const void * ptr,t_size bytes,unsigned p_sample_rate,unsigned p_channels,unsigned p_bits_per_sample,unsigned p_flags,unsigned p_channel_config);//p_flags - see FLAG_* above
void set_data_fixedpoint_ms(const void * ptr, size_t bytes, unsigned sampleRate, unsigned channels, unsigned bps, unsigned channelConfig);
void set_data_floatingpoint_ex(const void * ptr,t_size bytes,unsigned p_sample_rate,unsigned p_channels,unsigned p_bits_per_sample,unsigned p_flags,unsigned p_channel_config);//signed/unsigned flags dont apply
inline void set_data_32(const float * src,t_size samples,unsigned nch,unsigned srate) {return set_data(src,samples,nch,srate);}
//! Appends silent samples at the end of the chunk. \n
//! The chunk may be empty prior to this call, its sample rate & channel count will be set to the specified values then. \n
//! The chunk may have different sample rate than requested; silent sample count will be recalculated to the used sample rate retaining actual duration.
//! @param samples Number of silent samples to append.
//! @param hint_nch If no channel count is set on this chunk, it will be set to this value.
//! @param hint_srate The sample rate of silent samples being inserted. If no sampler ate is set on this chunk, it will be set to this value.\n
//! Otherwise if chunk's sample rate doesn't match hint_srate, sample count will be recalculated to chunk's actual sample rate.
void pad_with_silence_ex(t_size samples,unsigned hint_nch,unsigned hint_srate);
//! Appends silent samples at the end of the chunk. \n
//! The chunk must have valid sample rate & channel count prior to this call.
//! @param Number of silent samples to append.s
void pad_with_silence(t_size samples);
//! Inserts silence at the beginning of the audio chunk.
//! @param Number of silent samples to insert.
void insert_silence_fromstart(t_size samples);
//! Helper; removes N first samples from the chunk. \n
//! If the chunk contains fewer samples than requested, it becomes empty.
//! @returns Number of samples actually removed.
t_size skip_first_samples(t_size samples);
//! Produces a chunk of silence, with the specified duration. \n
//! Any existing audio sdata will be discarded. \n
//! Expects sample rate and channel count to be set first. \n
//! Also allocates memory for the requested amount of data see: set_data_size().
//! @param samples Desired number of samples.
void set_silence(t_size samples);
//! Produces a chunk of silence, with the specified duration. \n
//! Any existing audio sdata will be discarded. \n
//! Expects sample rate and channel count to be set first. \n
//! Also allocates memory for the requested amount of data see: set_data_size().
//! @param samples Desired duration in seconds.
void set_silence_seconds( double seconds );
//! Helper; skips first samples of the chunk updating a remaining to-skip counter.
//! @param skipDuration Reference to the duration of audio remining to be skipped, in seconds. Updated by each call.
//! @returns False if the chunk became empty, true otherwise.
bool process_skip(double & skipDuration);
//! Simple function to get original PCM stream back. Assumes host's endianness, integers are signed - including the 8bit mode; 32bit mode assumed to be float.
//! @returns false when the conversion could not be performed because of unsupported bit depth etc.
bool to_raw_data(class mem_block_container & out, t_uint32 bps, bool useUpperBits = true, float scale = 1.0) const;
//! Convert audio_chunk contents to fixed-point PCM format.
//! @param useUpperBits relevant if bps != bpsValid, signals whether upper or lower bits of each sample should be used.
bool toFixedPoint(class mem_block_container & out, uint32_t bps, uint32_t bpsValid, bool useUpperBits = true, float scale = 1.0) const;
//! Convert a buffer of audio_samples to fixed-point PCM format.
//! @param useUpperBits relevant if bps != bpsValid, signals whether upper or lower bits of each sample should be used.
static bool g_toFixedPoint(const audio_sample * in, void * out, size_t count, uint32_t bps, uint32_t bpsValid, bool useUpperBits = true, float scale = 1.0);
//! Helper, calculates peak value of data in the chunk. The optional parameter specifies initial peak value, to simplify calling code.
audio_sample get_peak(audio_sample p_peak) const;
audio_sample get_peak() const;
//! Helper function; scales entire chunk content by specified value.
void scale(audio_sample p_value);
//! Helper; copies content of another audio chunk to this chunk.
void copy(const audio_chunk & p_source) {
set_data(p_source.get_data(),p_source.get_sample_count(),p_source.get_channels(),p_source.get_srate(),p_source.get_channel_config());
}
const audio_chunk & operator=(const audio_chunk & p_source) {
copy(p_source);
return *this;
}
struct spec_t {
uint32_t sampleRate;
uint32_t chanCount, chanMask;
static bool equals( const spec_t & v1, const spec_t & v2 );
bool operator==(const spec_t & other) const { return equals(*this, other);}
bool operator!=(const spec_t & other) const { return !equals(*this, other);}
bool is_valid() const;
void clear() { sampleRate = 0; chanCount = 0; chanMask = 0; }
#ifdef _WIN32
//! Creates WAVE_FORMAT_IEEE_FLOAT WAVEFORMATEX structure
WAVEFORMATEX toWFX() const;
//! Creates WAVE_FORMAT_IEEE_FLOAT WAVEFORMATEXTENSIBLE structure
WAVEFORMATEXTENSIBLE toWFXEX() const;
//! Creates WAVE_FORMAT_PCM WAVEFORMATEX structure
WAVEFORMATEX toWFXWithBPS(uint32_t bps) const;
//! Creates WAVE_FORMAT_PCM WAVEFORMATEXTENSIBLE structure
WAVEFORMATEXTENSIBLE toWFXEXWithBPS(uint32_t bps) const;
#endif
pfc::string8 toString( const char * delim = " " ) const;
};
static spec_t makeSpec(uint32_t rate, uint32_t channels);
static spec_t makeSpec(uint32_t rate, uint32_t channels, uint32_t chanMask);
static spec_t emptySpec() { return makeSpec(0, 0, 0); }
spec_t get_spec() const;
void set_spec(const spec_t &);
void append(const audio_chunk& other);
protected:
audio_chunk() {}
~audio_chunk() {}
};
//! Implementation of audio_chunk. Takes pfc allocator template as template parameter.
template<typename container_t = pfc::mem_block_aligned_t<audio_sample, 16> >
class audio_chunk_impl_t : public audio_chunk {
typedef audio_chunk_impl_t<container_t> t_self;
container_t m_data;
unsigned m_srate = 0, m_nch = 0, m_setup = 0;
t_size m_samples = 0;
public:
audio_chunk_impl_t() {}
audio_chunk_impl_t(const audio_sample * src,unsigned samples,unsigned nch,unsigned srate) {set_data(src,samples,nch,srate);}
audio_chunk_impl_t(const audio_chunk & p_source) {copy(p_source);}
virtual audio_sample * get_data() {return m_data.get_ptr();}
virtual const audio_sample * get_data() const {return m_data.get_ptr();}
virtual t_size get_data_size() const {return m_data.get_size();}
virtual void set_data_size(t_size new_size) {m_data.set_size(new_size);}
virtual unsigned get_srate() const {return m_srate;}
virtual void set_srate(unsigned val) {m_srate=val;}
virtual unsigned get_channels() const {return m_nch;}
virtual unsigned get_channel_config() const {return m_setup;}
virtual void set_channels(unsigned val,unsigned setup) {m_nch = val;m_setup = setup;}
void set_channels(unsigned val) {set_channels(val,g_guess_channel_config(val));}
virtual t_size get_sample_count() const {return m_samples;}
virtual void set_sample_count(t_size val) {m_samples = val;}
const t_self & operator=(const audio_chunk & p_source) {copy(p_source);return *this;}
};
typedef audio_chunk_impl_t<> audio_chunk_impl;
typedef audio_chunk_impl_t<pfc::mem_block_aligned_incremental_t<audio_sample, 16> > audio_chunk_fast_impl;
//! Implements const methods of audio_chunk only, referring to an external buffer. For temporary use only (does not maintain own storage), e.g.: somefunc( audio_chunk_temp_impl(mybuffer,....) );
class audio_chunk_memref_impl : public audio_chunk {
public:
audio_chunk_memref_impl(const audio_sample * p_data,t_size p_samples,t_uint32 p_sample_rate,t_uint32 p_channels,t_uint32 p_channel_config) :
m_samples(p_samples), m_sample_rate(p_sample_rate), m_channels(p_channels), m_channel_config(p_channel_config), m_data(p_data)
{
#if PFC_DEBUG
assert_valid(__FUNCTION__);
#endif
}
audio_sample * get_data() {throw pfc::exception_not_implemented();}
const audio_sample * get_data() const {return m_data;}
t_size get_data_size() const {return m_samples * m_channels;}
void set_data_size(t_size) {throw pfc::exception_not_implemented();}
unsigned get_srate() const {return m_sample_rate;}
void set_srate(unsigned) {throw pfc::exception_not_implemented();}
unsigned get_channels() const {return m_channels;}
unsigned get_channel_config() const {return m_channel_config;}
void set_channels(unsigned,unsigned) {throw pfc::exception_not_implemented();}
t_size get_sample_count() const {return m_samples;}
void set_sample_count(t_size) {throw pfc::exception_not_implemented();}
private:
t_size m_samples;
t_uint32 m_sample_rate,m_channels,m_channel_config;
const audio_sample * m_data;
};
// Compatibility typedefs.
typedef audio_chunk_fast_impl audio_chunk_impl_temporary;
typedef audio_chunk_impl audio_chunk_i;
typedef audio_chunk_memref_impl audio_chunk_temp_impl;
class audio_chunk_partial_ref : public audio_chunk_temp_impl {
public:
audio_chunk_partial_ref(const audio_chunk & chunk, t_size base, t_size count) : audio_chunk_temp_impl(chunk.get_data() + base * chunk.get_channels(), count, chunk.get_sample_rate(), chunk.get_channels(), chunk.get_channel_config()) {}
};

View File

@@ -0,0 +1,210 @@
#include "foobar2000.h"
#ifdef _WIN32
#include <ks.h>
#include <ksmedia.h>
#if 0
#define SPEAKER_FRONT_LEFT 0x1
#define SPEAKER_FRONT_RIGHT 0x2
#define SPEAKER_FRONT_CENTER 0x4
#define SPEAKER_LOW_FREQUENCY 0x8
#define SPEAKER_BACK_LEFT 0x10
#define SPEAKER_BACK_RIGHT 0x20
#define SPEAKER_FRONT_LEFT_OF_CENTER 0x40
#define SPEAKER_FRONT_RIGHT_OF_CENTER 0x80
#define SPEAKER_BACK_CENTER 0x100
#define SPEAKER_SIDE_LEFT 0x200
#define SPEAKER_SIDE_RIGHT 0x400
#define SPEAKER_TOP_CENTER 0x800
#define SPEAKER_TOP_FRONT_LEFT 0x1000
#define SPEAKER_TOP_FRONT_CENTER 0x2000
#define SPEAKER_TOP_FRONT_RIGHT 0x4000
#define SPEAKER_TOP_BACK_LEFT 0x8000
#define SPEAKER_TOP_BACK_CENTER 0x10000
#define SPEAKER_TOP_BACK_RIGHT 0x20000
static struct {DWORD m_wfx; unsigned m_native; } const g_translation_table[] =
{
{SPEAKER_FRONT_LEFT, audio_chunk::channel_front_left},
{SPEAKER_FRONT_RIGHT, audio_chunk::channel_front_right},
{SPEAKER_FRONT_CENTER, audio_chunk::channel_front_center},
{SPEAKER_LOW_FREQUENCY, audio_chunk::channel_lfe},
{SPEAKER_BACK_LEFT, audio_chunk::channel_back_left},
{SPEAKER_BACK_RIGHT, audio_chunk::channel_back_right},
{SPEAKER_FRONT_LEFT_OF_CENTER, audio_chunk::channel_front_center_left},
{SPEAKER_FRONT_RIGHT_OF_CENTER, audio_chunk::channel_front_center_right},
{SPEAKER_BACK_CENTER, audio_chunk::channel_back_center},
{SPEAKER_SIDE_LEFT, audio_chunk::channel_side_left},
{SPEAKER_SIDE_RIGHT, audio_chunk::channel_side_right},
{SPEAKER_TOP_CENTER, audio_chunk::channel_top_center},
{SPEAKER_TOP_FRONT_LEFT, audio_chunk::channel_top_front_left},
{SPEAKER_TOP_FRONT_CENTER, audio_chunk::channel_top_front_center},
{SPEAKER_TOP_FRONT_RIGHT, audio_chunk::channel_top_front_right},
{SPEAKER_TOP_BACK_LEFT, audio_chunk::channel_top_back_left},
{SPEAKER_TOP_BACK_CENTER, audio_chunk::channel_top_back_center},
{SPEAKER_TOP_BACK_RIGHT, audio_chunk::channel_top_back_right},
};
#endif
#endif
// foobar2000 channel flags are 1:1 identical to Windows WFX ones.
uint32_t audio_chunk::g_channel_config_to_wfx(unsigned p_config)
{
return p_config;
#if 0
DWORD ret = 0;
unsigned n;
for(n=0;n<PFC_TABSIZE(g_translation_table);n++)
{
if (p_config & g_translation_table[n].m_native) ret |= g_translation_table[n].m_wfx;
}
return ret;
#endif
}
unsigned audio_chunk::g_channel_config_from_wfx(uint32_t p_wfx)
{
return p_wfx;
#if 0
unsigned ret = 0;
unsigned n;
for(n=0;n<PFC_TABSIZE(g_translation_table);n++)
{
if (p_wfx & g_translation_table[n].m_wfx) ret |= g_translation_table[n].m_native;
}
return ret;
#endif
}
static const unsigned g_audio_channel_config_table[] =
{
0,
audio_chunk::channel_config_mono,
audio_chunk::channel_config_stereo,
audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_lfe,
audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_back_left | audio_chunk::channel_back_right,
audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_back_left | audio_chunk::channel_back_right | audio_chunk::channel_lfe,
audio_chunk::channel_config_5point1,
0,
audio_chunk::channel_config_7point1,
0,
audio_chunk::channel_config_7point1 | audio_chunk::channel_front_center_right | audio_chunk::channel_front_center_left,
};
unsigned audio_chunk::g_guess_channel_config(unsigned count)
{
if (count == 0) return 0;
if (count > 32) throw exception_io_data();
unsigned ret = 0;
if (count < PFC_TABSIZE(g_audio_channel_config_table)) ret = g_audio_channel_config_table[count];
if (ret == 0) {
ret = (1 << count) - 1;
}
PFC_ASSERT(g_count_channels(ret) == count);
return ret;
}
unsigned audio_chunk::g_guess_channel_config_xiph(unsigned count) {
return g_guess_channel_config(count);
}
unsigned audio_chunk::g_channel_index_from_flag(unsigned p_config,unsigned p_flag) {
unsigned index = 0;
for(unsigned walk = 0; walk < 32; walk++) {
unsigned query = 1 << walk;
if (p_flag & query) return index;
if (p_config & query) index++;
}
return ~0;
}
unsigned audio_chunk::g_extract_channel_flag(unsigned p_config,unsigned p_index)
{
unsigned toskip = p_index;
unsigned flag = 1;
while(flag)
{
if (p_config & flag)
{
if (toskip == 0) break;
toskip--;
}
flag <<= 1;
}
return flag;
}
unsigned audio_chunk::g_count_channels(unsigned p_config)
{
return pfc::countBits32(p_config);
}
static const char * const chanNames[] = {
"FL", //channel_front_left = 1<<0,
"FR", //channel_front_right = 1<<1,
"FC", //channel_front_center = 1<<2,
"LFE", //channel_lfe = 1<<3,
"BL", //channel_back_left = 1<<4,
"BR", //channel_back_right = 1<<5,
"FCL", //channel_front_center_left = 1<<6,
"FCR", //channel_front_center_right = 1<<7,
"BC", //channel_back_center = 1<<8,
"SL", //channel_side_left = 1<<9,
"SR", //channel_side_right = 1<<10,
"TC", //channel_top_center = 1<<11,
"TFL", //channel_top_front_left = 1<<12,
"TFC", //channel_top_front_center = 1<<13,
"TFR", //channel_top_front_right = 1<<14,
"TBL", //channel_top_back_left = 1<<15,
"TBC", //channel_top_back_center = 1<<16,
"TBR", //channel_top_back_right = 1<<17,
};
unsigned audio_chunk::g_find_channel_idx(unsigned p_flag) {
unsigned rv = 0;
if ((p_flag & 0xFFFF) == 0) {
rv += 16; p_flag >>= 16;
}
if ((p_flag & 0xFF) == 0) {
rv += 8; p_flag >>= 8;
}
if ((p_flag & 0xF) == 0) {
rv += 4; p_flag >>= 4;
}
if ((p_flag & 0x3) == 0) {
rv += 2; p_flag >>= 2;
}
if ((p_flag & 0x1) == 0) {
rv += 1; p_flag >>= 1;
}
PFC_ASSERT( p_flag & 1 );
return rv;
}
const char * audio_chunk::g_channel_name(unsigned p_flag) {
return g_channel_name_byidx(g_find_channel_idx(p_flag));
}
const char * audio_chunk::g_channel_name_byidx(unsigned p_index) {
if (p_index < PFC_TABSIZE(chanNames)) return chanNames[p_index];
else return "?";
}
pfc::string8 audio_chunk::g_formatChannelMaskDesc(unsigned flags) {
pfc::string8 temp; g_formatChannelMaskDesc(flags, temp); return temp;
}
void audio_chunk::g_formatChannelMaskDesc(unsigned flags, pfc::string_base & out) {
out.reset();
unsigned idx = 0;
while(flags) {
if (flags & 1) {
if (!out.is_empty()) out << " ";
out << g_channel_name_byidx(idx);
}
flags >>= 1;
++idx;
}
}

View File

@@ -0,0 +1,3 @@
#pragma once
// header added for fb2k mobile compatibility

View File

@@ -0,0 +1,27 @@
#pragma once
//! This class handles conversion of audio data (audio_chunk) to various linear PCM types, with optional dithering.
class NOVTABLE audio_postprocessor : public service_base
{
public:
//! Processes one chunk of audio data.
//! @param p_chunk Chunk of audio data to process.
//! @param p_output Receives output linear signed PCM data.
//! @param p_out_bps Desired bit depth of output.
//! @param p_out_bps_physical Desired physical word width of output. Must be either 8, 16, 24 or 32, greater or equal to p_out_bps. This is typically set to same value as p_out_bps.
//! @param p_dither Indicates whether dithering should be used. Note that dithering is CPU-heavy.
//! @param p_prescale Value to scale all audio samples by when converting. Set to 1.0 to do nothing.
virtual void run(const audio_chunk & p_chunk,
mem_block_container & p_output,
t_uint32 p_out_bps,
t_uint32 p_out_bps_physical,
bool p_dither,
audio_sample p_prescale
) = 0;
FB2K_MAKE_SERVICE_COREAPI(audio_postprocessor);
};

View File

@@ -0,0 +1,104 @@
/*
Autoplaylist APIs
These APIs were introduced in foobar2000 0.9.5, to reduce amount of code required to create your own autoplaylists. Creation of autoplaylists is was also possible before through playlist lock APIs.
In most cases, you'll want to turn regular playlists into autoplaylists using the following code:
autoplaylist_manager::get()->add_client_simple(querypattern, sortpattern, playlistindex, forceSort ? autoplaylist_flag_sort : 0);
If you require more advanced functionality, such as using your own code to filter which part of user's Media Library should be placed in specific autoplaylist, you must implement autoplaylist_client (to let autoplaylist manager invoke your handlers when needed) / autoplaylist_client_factory (to re-instantiate your autoplaylist_client after a foobar2000 restart cycle).
*/
enum {
//! When set, core will keep the autoplaylist sorted and prevent user from reordering it.
autoplaylist_flag_sort = 1 << 0,
};
//! Main class controlling autoplaylist behaviors. Implemented by autoplaylist client in scenarios where simple query/sort strings are not enough (core provides a standard implementation for simple queries).
class NOVTABLE autoplaylist_client : public service_base {
FB2K_MAKE_SERVICE_INTERFACE(autoplaylist_client,service_base)
public:
virtual GUID get_guid() = 0;
//! Provides a boolean mask of which items from the specified list should appear in this autoplaylist.
virtual void filter(metadb_handle_list_cref data, bool * out) = 0;
//! Return true when you have filled p_orderbuffer with a permutation to apply to p_items, false when you don't support sorting (core's own sort scheme will be applied).
virtual bool sort(metadb_handle_list_cref p_items,t_size * p_orderbuffer) = 0;
//! Retrieves your configuration data to be used later when re-instantiating your autoplaylist_client after a restart.
virtual void get_configuration(stream_writer * p_stream,abort_callback & p_abort) = 0;
virtual void show_ui(t_size p_source_playlist) = 0;
//! Helper.
template<typename t_array> void get_configuration(t_array & p_out) {
PFC_STATIC_ASSERT( sizeof(p_out[0]) == 1 );
typedef pfc::array_t<t_uint8,pfc::alloc_fast_aggressive> t_temp; t_temp temp;
{
stream_writer_buffer_append_ref_t<t_temp> writer(temp);
get_configuration(&writer,fb2k::noAbort);
}
p_out = temp;
}
};
typedef service_ptr_t<autoplaylist_client> autoplaylist_client_ptr;
//! \since 0.9.5.3
class NOVTABLE autoplaylist_client_v2 : public autoplaylist_client {
FB2K_MAKE_SERVICE_INTERFACE(autoplaylist_client_v2, autoplaylist_client);
public:
//! Sets a completion_notify object that the autoplaylist_client implementation should call when its filtering behaviors have changed so the whole playlist needs to be rebuilt. \n
//! completion_notify::on_completion() status parameter meaning: \n
//! 0.9.5.3 : ignored. \n
//! 0.9.5.4 and newer: set to 1 to indicate that your configuration has changed as well (for an example as a result of user edits) to get a get_configuration() call as well as cause the playlist to be rebuilt; set to zero otherwise - when the configuration hasn't changed but the playlist needs to be rebuilt as a result of some other event.
virtual void set_full_refresh_notify(completion_notify::ptr notify) = 0;
//! Returns whether the show_ui() method is available / does anything useful with our implementation (not everyone implements show_ui).
virtual bool show_ui_available() = 0;
//! Returns a human-readable autoplaylist implementer's label to display in playlist's context menu / description / etc.
virtual void get_display_name(pfc::string_base & out) = 0;
};
//! Class needed to re-instantiate autoplaylist_client after a restart. Not directly needed to set up an autoplaylist_client, but without it, your autoplaylist will be lost after a restart.
class NOVTABLE autoplaylist_client_factory : public service_base {
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(autoplaylist_client_factory)
public:
//! Must return same GUID as your autoplaylist_client::get_guid()
virtual GUID get_guid() = 0;
//! Instantiates your autoplaylist_client with specified configuration.
virtual autoplaylist_client_ptr instantiate(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) = 0;
};
PFC_DECLARE_EXCEPTION(exception_autoplaylist,pfc::exception,"Autoplaylist error")
PFC_DECLARE_EXCEPTION(exception_autoplaylist_already_owned,exception_autoplaylist,"This playlist is already an autoplaylist")
PFC_DECLARE_EXCEPTION(exception_autoplaylist_not_owned,exception_autoplaylist,"This playlist is not an autoplaylist")
PFC_DECLARE_EXCEPTION(exception_autoplaylist_lock_failure,exception_autoplaylist,"Playlist could not be locked")
//! Primary class for managing autoplaylists. Implemented by core, do not reimplement; instantiate using autoplaylist_manager::get().
class NOVTABLE autoplaylist_manager : public service_base {
FB2K_MAKE_SERVICE_COREAPI(autoplaylist_manager)
public:
//! Throws exception_autoplaylist or one of its subclasses on failure.
//! @param p_flags See autoplaylist_flag_* constants.
virtual void add_client(autoplaylist_client_ptr p_client,t_size p_playlist,t_uint32 p_flags) = 0;
virtual bool is_client_present(t_size p_playlist) = 0;
//! Throws exception_autoplaylist or one of its subclasses on failure (eg. not an autoplaylist).
virtual autoplaylist_client_ptr query_client(t_size p_playlist) = 0;
virtual void remove_client(t_size p_playlist) = 0;
//! Helper; sets up an autoplaylist using standard autoplaylist_client implementation based on simple query/sort strings. When using this, you don't need to maintain own autoplaylist_client/autoplaylist_client_factory implementations, and autoplaylists that you create will not be lost when your DLL is removed, as opposed to using add_client() directly.
//! Throws exception_autoplaylist or one of its subclasses on failure.
//! @param p_flags See autoplaylist_flag_* constants.
virtual void add_client_simple(const char * p_query,const char * p_sort,t_size p_playlist,t_uint32 p_flags) = 0;
};
//! \since 0.9.5.4
//! Extended version of autoplaylist_manager, available from 0.9.5.4 up, with methods allowing modification of autoplaylist flags.
class NOVTABLE autoplaylist_manager_v2 : public autoplaylist_manager {
FB2K_MAKE_SERVICE_COREAPI_EXTENSION(autoplaylist_manager_v2, autoplaylist_manager)
public:
virtual t_uint32 get_client_flags(t_size playlist) = 0;
virtual void set_client_flags(t_size playlist, t_uint32 newFlags) = 0;
//! For use with autoplaylist client configuration dialogs. It's recommended not to call this from anything else.
virtual t_uint32 get_client_flags(autoplaylist_client::ptr client) = 0;
//! For use with autoplaylist client configuration dialogs. It's recommended not to call this from anything else.
virtual void set_client_flags(autoplaylist_client::ptr client, t_uint32 newFlags) = 0;
};

View File

@@ -0,0 +1,60 @@
#include "foobar2000.h"
cfg_var_reader * cfg_var_reader::g_list = NULL;
cfg_var_writer * cfg_var_writer::g_list = NULL;
void cfg_var_reader::config_read_file(stream_reader * p_stream,abort_callback & p_abort)
{
pfc::map_t<GUID,cfg_var_reader*> vars;
for(cfg_var_reader * walk = g_list; walk != NULL; walk = walk->m_next) {
vars.set(walk->m_guid,walk);
}
for(;;) {
GUID guid;
t_uint32 size;
if (p_stream->read(&guid,sizeof(guid),p_abort) != sizeof(guid)) break;
guid = pfc::byteswap_if_be_t(guid);
p_stream->read_lendian_t(size,p_abort);
cfg_var_reader * var;
if (vars.query(guid,var)) {
stream_reader_limited_ref wrapper(p_stream,size);
try {
var->set_data_raw(&wrapper,size,p_abort);
} catch(exception_io_data) {}
wrapper.flush_remaining(p_abort);
} else {
p_stream->skip_object(size,p_abort);
}
}
}
void cfg_var_writer::config_write_file(stream_writer * p_stream,abort_callback & p_abort) {
cfg_var_writer * ptr;
pfc::array_t<t_uint8,pfc::alloc_fast_aggressive> temp;
for(ptr = g_list; ptr; ptr = ptr->m_next) {
temp.set_size(0);
{
stream_writer_buffer_append_ref_t<pfc::array_t<t_uint8,pfc::alloc_fast_aggressive> > stream(temp);
ptr->get_data_raw(&stream,p_abort);
}
p_stream->write_lendian_t(ptr->m_guid,p_abort);
p_stream->write_lendian_t(pfc::downcast_guarded<t_uint32>(temp.get_size()),p_abort);
if (temp.get_size() > 0) {
p_stream->write_object(temp.get_ptr(),temp.get_size(),p_abort);
}
}
}
void cfg_string::get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {
p_stream->write_object(get_ptr(),length(),p_abort);
}
void cfg_string::set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {
pfc::string8_fastalloc temp;
p_stream->read_string_raw(temp,p_abort);
set_string(temp);
}

292
foobar2000/SDK/cfg_var.h Normal file
View File

@@ -0,0 +1,292 @@
#ifndef _FOOBAR2000_SDK_CFG_VAR_H_
#define _FOOBAR2000_SDK_CFG_VAR_H_
#define CFG_VAR_ASSERT_SAFEINIT PFC_ASSERT(!core_api::are_services_available());/*imperfect check for nonstatic instantiation*/
//! Reader part of cfg_var object. In most cases, you should use cfg_var instead of using cfg_var_reader directly.
class NOVTABLE cfg_var_reader {
public:
//! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var.
cfg_var_reader(const GUID & guid) : m_guid(guid) { CFG_VAR_ASSERT_SAFEINIT; m_next = g_list; g_list = this; }
~cfg_var_reader() { CFG_VAR_ASSERT_SAFEINIT; }
//! Sets state of the variable. Called only from main thread, when reading configuration file.
//! @param p_stream Stream containing new state of the variable.
//! @param p_sizehint Number of bytes contained in the stream; reading past p_sizehint bytes will fail (EOF).
virtual void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) = 0;
//! For internal use only, do not call.
static void config_read_file(stream_reader * p_stream,abort_callback & p_abort);
const GUID m_guid;
private:
static cfg_var_reader * g_list;
cfg_var_reader * m_next;
PFC_CLASS_NOT_COPYABLE_EX(cfg_var_reader)
};
//! Writer part of cfg_var object. In most cases, you should use cfg_var instead of using cfg_var_writer directly.
class NOVTABLE cfg_var_writer {
public:
//! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var.
cfg_var_writer(const GUID & guid) : m_guid(guid) { CFG_VAR_ASSERT_SAFEINIT; m_next = g_list; g_list = this;}
~cfg_var_writer() { CFG_VAR_ASSERT_SAFEINIT; }
//! Retrieves state of the variable. Called only from main thread, when writing configuration file.
//! @param p_stream Stream receiving state of the variable.
virtual void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) = 0;
//! For internal use only, do not call.
static void config_write_file(stream_writer * p_stream,abort_callback & p_abort);
const GUID m_guid;
private:
static cfg_var_writer * g_list;
cfg_var_writer * m_next;
PFC_CLASS_NOT_COPYABLE_EX(cfg_var_writer)
};
//! Base class for configuration variable classes; provides self-registration mechaisms and methods to set/retrieve configuration data; those methods are automatically called for all registered instances by backend when configuration file is being read or written.\n
//! Note that cfg_var class and its derivatives may be only instantiated statically (as static objects or members of other static objects), NEVER dynamically (operator new, local variables, members of objects instantiated as such).
class NOVTABLE cfg_var : public cfg_var_reader, public cfg_var_writer {
protected:
//! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var.
cfg_var(const GUID & p_guid) : cfg_var_reader(p_guid), cfg_var_writer(p_guid) {}
public:
GUID get_guid() const {return cfg_var_reader::m_guid;}
};
//! Generic integer config variable class. Template parameter can be used to specify integer type to use.\n
//! Note that cfg_var class and its derivatives may be only instantiated statically (as static objects or members of other static objects), NEVER dynamically (operator new, local variables, members of objects instantiated as such).
template<typename t_inttype>
class cfg_int_t : public cfg_var {
private:
t_inttype m_val;
protected:
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {p_stream->write_lendian_t(m_val,p_abort);}
void set_data_raw(stream_reader * p_stream,t_size,abort_callback & p_abort) {
t_inttype temp;
p_stream->read_lendian_t(temp,p_abort);//alter member data only on success, this will throw an exception when something isn't right
m_val = temp;
}
public:
//! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var.
//! @param p_default Default value of the variable.
explicit inline cfg_int_t(const GUID & p_guid,t_inttype p_default) : cfg_var(p_guid), m_val(p_default) {}
inline const cfg_int_t<t_inttype> & operator=(const cfg_int_t<t_inttype> & p_val) {m_val=p_val.m_val;return *this;}
inline t_inttype operator=(t_inttype p_val) {m_val=p_val;return m_val;}
inline operator t_inttype() const {return m_val;}
inline t_inttype get_value() const {return m_val;}
};
typedef cfg_int_t<t_int32> cfg_int;
typedef cfg_int_t<t_uint32> cfg_uint;
//! Since relevant byteswapping functions also understand GUIDs, this can be used to declare a cfg_guid.
typedef cfg_int_t<GUID> cfg_guid;
typedef cfg_int_t<bool> cfg_bool;
typedef cfg_int_t<float> cfg_float;
typedef cfg_int_t<double> cfg_double;
//! String config variable. Stored in the stream with int32 header containing size in bytes, followed by non-null-terminated UTF-8 data.\n
//! Note that cfg_var class and its derivatives may be only instantiated statically (as static objects or members of other static objects), NEVER dynamically (operator new, local variables, members of objects instantiated as such).
class cfg_string : public cfg_var, public pfc::string8 {
protected:
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort);
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort);
public:
//! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var.
//! @param p_defaultval Default/initial value of the variable.
explicit inline cfg_string(const GUID & p_guid,const char * p_defaultval) : cfg_var(p_guid), pfc::string8(p_defaultval) {}
inline const cfg_string& operator=(const cfg_string & p_val) {set_string(p_val);return *this;}
inline const cfg_string& operator=(const char* p_val) {set_string(p_val);return *this;}
inline operator const char * () const {return get_ptr();}
};
class cfg_string_mt : public cfg_var {
protected:
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {
pfc::string8 temp;
get(temp);
p_stream->write_object(temp.get_ptr(), temp.length(),p_abort);
}
void set_data_raw(stream_reader * p_stream,t_size,abort_callback & p_abort) {
pfc::string8_fastalloc temp;
p_stream->read_string_raw(temp,p_abort);
set(temp);
}
public:
cfg_string_mt(const GUID & id, const char * defVal) : cfg_var(id), m_val(defVal) {}
void get(pfc::string_base & out) const {
inReadSync( m_sync );
out = m_val;
}
void set(const char * val, t_size valLen = ~0) {
inWriteSync( m_sync );
m_val.set_string(val, valLen);
}
private:
mutable pfc::readWriteLock m_sync;
pfc::string8 m_val;
};
//! Struct config variable template. Warning: not endian safe, should be used only for nonportable code.\n
//! Note that cfg_var class and its derivatives may be only instantiated statically (as static objects or members of other static objects), NEVER dynamically (operator new, local variables, members of objects instantiated as such).
template<typename t_struct>
class cfg_struct_t : public cfg_var {
private:
t_struct m_val;
protected:
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {p_stream->write_object(&m_val,sizeof(m_val),p_abort);}
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {
t_struct temp;
p_stream->read_object(&temp,sizeof(temp),p_abort);
m_val = temp;
}
public:
//! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var.
inline cfg_struct_t(const GUID & p_guid,const t_struct & p_val) : cfg_var(p_guid), m_val(p_val) {}
//! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var.
inline cfg_struct_t(const GUID & p_guid,int filler) : cfg_var(p_guid) {memset(&m_val,filler,sizeof(t_struct));}
inline const cfg_struct_t<t_struct> & operator=(const cfg_struct_t<t_struct> & p_val) {m_val = p_val.get_value();return *this;}
inline const cfg_struct_t<t_struct> & operator=(const t_struct & p_val) {m_val = p_val;return *this;}
inline const t_struct& get_value() const {return m_val;}
inline t_struct& get_value() {return m_val;}
inline operator t_struct() const {return m_val;}
};
template<typename TObj>
class cfg_objList : public cfg_var, public pfc::list_t<TObj> {
public:
typedef cfg_objList<TObj> t_self;
cfg_objList(const GUID& guid) : cfg_var(guid) {}
template<typename TSource, unsigned Count> cfg_objList(const GUID& guid, const TSource (& source)[Count]) : cfg_var(guid) {
reset(source);
}
template<typename TSource, unsigned Count> void reset(const TSource (& source)[Count]) {
this->set_size(Count); for(t_size walk = 0; walk < Count; ++walk) (*this)[walk] = source[walk];
}
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {
stream_writer_formatter<> out(*p_stream,p_abort);
out << pfc::downcast_guarded<t_uint32>(this->get_size());
for(t_size walk = 0; walk < this->get_size(); ++walk) out << (*this)[walk];
}
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {
try {
stream_reader_formatter<> in(*p_stream,p_abort);
t_uint32 count; in >> count;
this->set_count(count);
for(t_uint32 walk = 0; walk < count; ++walk) in >> (*this)[walk];
} catch(...) {
this->remove_all();
throw;
}
}
template<typename t_in> t_self & operator=(t_in const & source) {this->remove_all(); this->add_items(source); return *this;}
template<typename t_in> t_self & operator+=(t_in const & p_source) {this->add_item(p_source); return *this;}
template<typename t_in> t_self & operator|=(t_in const & p_source) {this->add_items(p_source); return *this;}
};
template<typename TList>
class cfg_objListEx : public cfg_var, public TList {
public:
typedef cfg_objListEx<TList> t_self;
cfg_objListEx(const GUID & guid) : cfg_var(guid) {}
void get_data_raw(stream_writer * p_stream, abort_callback & p_abort) {
stream_writer_formatter<> out(*p_stream,p_abort);
out << pfc::downcast_guarded<t_uint32>(this->get_count());
for(typename TList::const_iterator walk = this->first(); walk.is_valid(); ++walk) out << *walk;
}
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {
this->remove_all();
stream_reader_formatter<> in(*p_stream,p_abort);
t_uint32 count; in >> count;
for(t_uint32 walk = 0; walk < count; ++walk) {
typename TList::t_item item; in >> item; this->add_item(item);
}
}
template<typename t_in> t_self & operator=(t_in const & source) {this->remove_all(); this->add_items(source); return *this;}
template<typename t_in> t_self & operator+=(t_in const & p_source) {this->add_item(p_source); return *this;}
template<typename t_in> t_self & operator|=(t_in const & p_source) {this->add_items(p_source); return *this;}
};
template<typename TObj>
class cfg_obj : public cfg_var, public TObj {
public:
cfg_obj(const GUID& guid) : cfg_var(guid), TObj() {}
template<typename TInitData> cfg_obj(const GUID& guid,const TInitData& initData) : cfg_var(guid), TObj(initData) {}
TObj & val() {return *this;}
TObj const & val() const {return *this;}
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {
stream_writer_formatter<> out(*p_stream,p_abort);
const TObj * ptr = this;
out << *ptr;
}
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {
stream_reader_formatter<> in(*p_stream,p_abort);
TObj * ptr = this;
in >> *ptr;
}
};
template<typename TObj, typename TImport> class cfg_objListImporter : private cfg_var_reader {
public:
typedef cfg_objList<TObj> TMasterVar;
cfg_objListImporter(TMasterVar & var, const GUID & guid) : m_var(var), cfg_var_reader(guid) {}
private:
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {
TImport temp;
try {
stream_reader_formatter<> in(*p_stream,p_abort);
t_uint32 count; in >> count;
m_var.set_count(count);
for(t_uint32 walk = 0; walk < count; ++walk) {
in >> temp;
m_var[walk] = temp;
}
} catch(...) {
m_var.remove_all();
throw;
}
}
TMasterVar & m_var;
};
template<typename TMap> class cfg_objMap : private cfg_var, public TMap {
public:
cfg_objMap(const GUID & id) : cfg_var(id) {}
private:
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {
stream_writer_formatter<> out(*p_stream, p_abort);
out << pfc::downcast_guarded<t_uint32>(this->get_count());
for(typename TMap::const_iterator walk = this->first(); walk.is_valid(); ++walk) {
out << walk->m_key << walk->m_value;
}
}
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {
this->remove_all();
stream_reader_formatter<> in(*p_stream, p_abort);
t_uint32 count; in >> count;
for(t_uint32 walk = 0; walk < count; ++walk) {
typename TMap::t_key key; in >> key; PFC_ASSERT( !this->have_item(key) );
try { in >> this->find_or_add( key ); } catch(...) { this->remove(key); throw; }
}
}
};
#endif

View File

@@ -0,0 +1,43 @@
#include "foobar2000.h"
#include "chapterizer.h"
void chapter_list::copy(const chapter_list & p_source)
{
t_size n, count = p_source.get_chapter_count();
set_chapter_count(count);
for(n=0;n<count;n++) set_info(n,p_source.get_info(n));
set_pregap(p_source.get_pregap());
}
#ifdef FOOBAR2000_HAVE_CHAPTERIZER
// {3F489088-6179-434e-A9DB-3A14A1B081AC}
FOOGUIDDECL const GUID chapterizer::class_guid=
{ 0x3f489088, 0x6179, 0x434e, { 0xa9, 0xdb, 0x3a, 0x14, 0xa1, 0xb0, 0x81, 0xac } };
bool chapterizer::g_find(service_ptr_t<chapterizer> & p_out,const char * p_path)
{
service_ptr_t<chapterizer> ptr;
service_enum_t<chapterizer> e;
while(e.next(ptr)) {
if (ptr->is_our_path(p_path)) {
p_out = ptr;
return true;
}
}
return false;
}
bool chapterizer::g_is_pregap_capable(const char * p_path) {
service_ptr_t<chapterizer> ptr;
service_enum_t<chapterizer> e;
while(e.next(ptr)) {
if (ptr->supports_pregaps() && ptr->is_our_path(p_path)) {
return true;
}
}
return false;
}
#endif

View File

@@ -0,0 +1,91 @@
#pragma once
// Not everything is on #ifdef FOOBAR2000_HAVE_CHAPTERIZER
// Some things use chapter_list internally even if chapterizer is disabled
//! Interface for object storing list of chapters.
class NOVTABLE chapter_list {
public:
//! Returns number of chapters.
virtual t_size get_chapter_count() const = 0;
//! Queries description of specified chapter.
//! @param p_chapter Index of chapter to query, greater or equal zero and less than get_chapter_count() value. If p_chapter value is out of valid range, results are undefined (e.g. crash).
//! @returns reference to file_info object describing specified chapter (length part of file_info indicates distance between beginning of this chapter and next chapter mark). Returned reference value for temporary use only, becomes invalid after any non-const operation on the chapter_list object.
virtual const file_info & get_info(t_size p_chapter) const = 0;
//! Sets number of chapters.
virtual void set_chapter_count(t_size p_count) = 0;
//! Modifies description of specified chapter.
//! @param p_chapter_index Index of chapter to modify, greater or equal zero and less than get_chapter_count() value. If p_chapter value is out of valid range, results are undefined (e.g. crash).
//! @param p_info New chapter description. Note that length part of file_info is used to calculate chapter marks.
virtual void set_info(t_size p_chapter,const file_info & p_info) = 0;
virtual double get_pregap() const = 0;
virtual void set_pregap(double val) = 0;
//! Copies contents of specified chapter_list object to this object.
void copy(const chapter_list & p_source);
inline const chapter_list & operator=(const chapter_list & p_source) {copy(p_source); return *this;}
protected:
chapter_list() {}
~chapter_list() {}
};
//! Implements chapter_list.
template<typename file_info_ = file_info_impl>
class chapter_list_impl_t : public chapter_list {
public:
chapter_list_impl_t() : m_pregap() {}
typedef chapter_list_impl_t<file_info_> t_self;
chapter_list_impl_t(const chapter_list & p_source) : m_pregap() {copy(p_source);}
const t_self & operator=(const chapter_list & p_source) {copy(p_source); return *this;}
t_size get_chapter_count() const {return m_infos.get_size();}
const file_info & get_info(t_size p_chapter) const {return m_infos[p_chapter];}
void set_chapter_count(t_size p_count) {m_infos.set_size(p_count);}
void set_info(t_size p_chapter,const file_info & p_info) {m_infos[p_chapter] = p_info;}
file_info_ & get_info_(t_size p_chapter) {return m_infos[p_chapter];}
double get_pregap() const {return m_pregap;}
void set_pregap(double val) {PFC_ASSERT(val >= 0); m_pregap = val;}
private:
pfc::array_t<file_info_> m_infos;
double m_pregap;
};
typedef chapter_list_impl_t<> chapter_list_impl;
#ifdef FOOBAR2000_HAVE_CHAPTERIZER
//! This service implements chapter list editing operations for various file formats, e.g. for MP4 chapters or CD images with embedded cuesheets. Used by converter "encode single file with chapters" feature.
class NOVTABLE chapterizer : public service_base {
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(chapterizer);
public:
//! Tests whether specified path is supported by this implementation.
//! @param p_ext Extension of the file being processed.
virtual bool is_our_path(const char * p_path) = 0;
//! Writes new chapter list to specified file.
//! @param p_path Path of file to modify.
//! @param p_list New chapter list to write.
//! @param p_abort abort_callback object signaling user aborting the operation.
virtual void set_chapters(const char * p_path,chapter_list const & p_list,abort_callback & p_abort) = 0;
//! Retrieves chapter list from specified file.
//! @param p_path Path of file to examine.
//! @param p_list Object receiving chapter list.
//! @param p_abort abort_callback object signaling user aborting the operation.
virtual void get_chapters(const char * p_path,chapter_list & p_list,abort_callback & p_abort) = 0;
virtual bool supports_pregaps() = 0;
//! Static helper, tries to find chapterizer interface that supports specified file.
static bool g_find(service_ptr_t<chapterizer> & p_out,const char * p_path);
static bool g_is_pregap_capable(const char * p_path);
};
#endif

View File

@@ -0,0 +1,12 @@
#include "foobar2000.h"
void commandline_handler_metadb_handle::on_file(const char * url) {
metadb_handle_list handles;
try {
metadb_io::get()->path_to_handles_simple(url, handles);
} catch(std::exception const & e) {
console::complain("Path evaluation failure", e);
return;
}
for(t_size walk = 0; walk < handles.get_size(); ++walk) on_file(handles[walk]);
}

View File

@@ -0,0 +1,41 @@
class NOVTABLE commandline_handler : public service_base
{
public:
enum result
{
RESULT_NOT_OURS,//not our command
RESULT_PROCESSED,//command processed
RESULT_PROCESSED_EXPECT_FILES,//command processed, we want to takeover file urls after this command
};
virtual result on_token(const char * token)=0;
virtual void on_file(const char * url) {};//optional
virtual void on_files_done() {};//optional
virtual bool want_directories() {return false;}
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(commandline_handler);
};
class commandline_handler_metadb_handle : public commandline_handler//helper
{
protected:
virtual void on_file(const char * url);
virtual bool want_directories() {return true;}
public:
virtual result on_token(const char * token)=0;
virtual void on_files_done() {};
virtual void on_file(const metadb_handle_ptr & ptr)=0;
};
/*
how commandline_handler is used:
scenario #1:
creation => on_token() => deletion
scenario #2:
creation => on_token() returning RESULT_PROCESSED_EXPECT_FILES => on_file(), on_file().... => on_files_done() => deletion
*/
template<typename T>
class commandline_handler_factory_t : public service_factory_t<T> {};

View File

@@ -0,0 +1,10 @@
#pragma once
#include <functional>
namespace fb2k {
typedef service_ptr objRef;
objRef callOnRelease( std::function< void () > );
objRef callOnReleaseInMainThread( std::function< void () > );
}

View File

@@ -0,0 +1,87 @@
#include "foobar2000.h"
namespace {
class main_thread_callback_myimpl : public main_thread_callback {
public:
void callback_run() {
m_notify->on_completion(m_code);
}
main_thread_callback_myimpl(completion_notify_ptr p_notify,unsigned p_code) : m_notify(p_notify), m_code(p_code) {}
private:
completion_notify_ptr m_notify;
unsigned m_code;
};
}
void completion_notify::g_signal_completion_async(completion_notify_ptr p_notify,unsigned p_code) {
if (p_notify.is_valid()) {
main_thread_callback_manager::get()->add_callback(new service_impl_t<main_thread_callback_myimpl>(p_notify,p_code));
}
}
void completion_notify::on_completion_async(unsigned p_code) {
main_thread_callback_manager::get()->add_callback(new service_impl_t<main_thread_callback_myimpl>(this,p_code));
}
completion_notify::ptr completion_notify_receiver::create_or_get_task(unsigned p_id) {
completion_notify_orphanable_ptr ptr;
if (!m_tasks.query(p_id,ptr)) {
ptr = completion_notify_create(this,p_id);
m_tasks.set(p_id,ptr);
}
return ptr;
}
completion_notify_ptr completion_notify_receiver::create_task(unsigned p_id) {
completion_notify_orphanable_ptr ptr;
if (m_tasks.query(p_id,ptr)) ptr->orphan();
ptr = completion_notify_create(this,p_id);
m_tasks.set(p_id,ptr);
return ptr;
}
bool completion_notify_receiver::have_task(unsigned p_id) const {
return m_tasks.have_item(p_id);
}
void completion_notify_receiver::orphan_task(unsigned p_id) {
completion_notify_orphanable_ptr ptr;
if (m_tasks.query(p_id,ptr)) {
ptr->orphan();
m_tasks.remove(p_id);
}
}
completion_notify_receiver::~completion_notify_receiver() {
orphan_all_tasks();
}
void completion_notify_receiver::orphan_all_tasks() {
m_tasks.enumerate(orphanfunc);
m_tasks.remove_all();
}
namespace {
using namespace fb2k;
class completion_notify_func : public completion_notify {
public:
void on_completion(unsigned p_code) {
m_func(p_code);
}
completionNotifyFunc_t m_func;
};
}
namespace fb2k {
completion_notify::ptr makeCompletionNotify( completionNotifyFunc_t func ) {
service_ptr_t<completion_notify_func> n = new service_impl_t< completion_notify_func >;
n->m_func = func;
return n;
}
}

View File

@@ -0,0 +1,85 @@
#pragma once
#include <functional>
//! Generic service for receiving notifications about async operation completion. Used by various other services.
class completion_notify : public service_base {
public:
//! Called when an async operation has been completed. Note that on_completion is always called from main thread. You can use on_completion_async() helper if you need to signal completion while your context is in another thread.\n
//! IMPLEMENTATION WARNING: If process being completed creates a window taking caller's window as parent, you must not destroy the parent window inside on_completion(). If you need to do so, use PostMessage() or main_thread_callback to delay the deletion.
//! @param p_code Context-specific status code. Possible values depend on the operation being performed.
virtual void on_completion(unsigned p_code) = 0;
//! Helper. Queues a notification, using main_thread_callback.
void on_completion_async(unsigned p_code);
//! Helper. Checks for null ptr and calls on_completion_async when the ptr is not null.
static void g_signal_completion_async(service_ptr_t<completion_notify> p_notify,unsigned p_code);
FB2K_MAKE_SERVICE_INTERFACE(completion_notify,service_base);
};
//! Implementation helper.
class completion_notify_dummy : public completion_notify {
public:
void on_completion(unsigned) {}
};
//! Implementation helper.
class completion_notify_orphanable : public completion_notify {
public:
virtual void orphan() = 0;
};
//! Helper implementation.
//! IMPLEMENTATION WARNING: If process being completed creates a window taking caller's window as parent, you must not destroy the parent window inside on_task_completion(). If you need to do so, use PostMessage() or main_thread_callback to delay the deletion.
template<typename t_receiver>
class completion_notify_impl : public completion_notify_orphanable {
public:
void on_completion(unsigned p_code) {
if (m_receiver != NULL) {
m_receiver->on_task_completion(m_taskid,p_code);
}
}
void setup(t_receiver * p_receiver, unsigned p_task_id) {m_receiver = p_receiver; m_taskid = p_task_id;}
void orphan() {m_receiver = NULL; m_taskid = 0;}
private:
t_receiver * m_receiver;
unsigned m_taskid;
};
template<typename t_receiver>
service_nnptr_t<completion_notify_orphanable> completion_notify_create(t_receiver * p_receiver,unsigned p_taskid) {
service_nnptr_t<completion_notify_impl<t_receiver> > instance = new service_impl_t<completion_notify_impl<t_receiver> >();
instance->setup(p_receiver,p_taskid);
return instance;
}
typedef service_ptr_t<completion_notify> completion_notify_ptr;
typedef service_ptr_t<completion_notify_orphanable> completion_notify_orphanable_ptr;
typedef service_nnptr_t<completion_notify> completion_notify_nnptr;
typedef service_nnptr_t<completion_notify_orphanable> completion_notify_orphanable_nnptr;
//! Helper base class for classes that manage nonblocking tasks and get notified back thru completion_notify interface.
class completion_notify_receiver {
public:
completion_notify::ptr create_or_get_task(unsigned p_id);
completion_notify_ptr create_task(unsigned p_id);
bool have_task(unsigned p_id) const;
void orphan_task(unsigned p_id);
~completion_notify_receiver();
void orphan_all_tasks();
virtual void on_task_completion(unsigned p_id,unsigned p_status) {(void)p_id;(void)p_status;}
private:
static void orphanfunc(unsigned,completion_notify_orphanable_nnptr p_item) {p_item->orphan();}
pfc::map_t<unsigned,completion_notify_orphanable_nnptr> m_tasks;
};
namespace fb2k {
typedef std::function<void (unsigned)> completionNotifyFunc_t;
//! Modern completion_notify helper
completion_notify::ptr makeCompletionNotify( completionNotifyFunc_t );
}

View File

@@ -0,0 +1,56 @@
#include "foobar2000.h"
// This is a helper class that gets reliably static-instantiated in all components so any special code that must be executed on startup can be shoved into its constructor.
class foobar2000_component_globals {
public:
foobar2000_component_globals() {
#if defined(_MSC_VER) && !defined(_DEBUG) && !defined(_DLL)
// only with MSVC, non release build, static runtime
::OverrideCrtAbort();
#endif
}
};
class NOVTABLE foobar2000_client
{
public:
typedef service_factory_base* pservice_factory_base;
enum {
FOOBAR2000_CLIENT_VERSION_COMPATIBLE = 72,
FOOBAR2000_CLIENT_VERSION = FOOBAR2000_TARGET_VERSION,
};
virtual t_uint32 get_version() = 0;
virtual pservice_factory_base get_service_list() = 0;
virtual void get_config(stream_writer * p_stream,abort_callback & p_abort) = 0;
virtual void set_config(stream_reader * p_stream,abort_callback & p_abort) = 0;
virtual void set_library_path(const char * path,const char * name) = 0;
virtual void services_init(bool val) = 0;
virtual bool is_debug() = 0;
protected:
foobar2000_client() {}
~foobar2000_client() {}
};
class NOVTABLE foobar2000_api {
public:
virtual service_class_ref service_enum_find_class(const GUID & p_guid) = 0;
virtual bool service_enum_create(service_ptr_t<service_base> & p_out,service_class_ref p_class,t_size p_index) = 0;
virtual t_size service_enum_get_count(service_class_ref p_class) = 0;
virtual HWND get_main_window()=0;
virtual bool assert_main_thread()=0;
virtual bool is_main_thread()=0;
virtual bool is_shutting_down()=0;
virtual const char * get_profile_path()=0;
virtual bool is_initializing() = 0;
//New in 0.9.6
virtual bool is_portable_mode_enabled() = 0;
virtual bool is_quiet_mode_enabled() = 0;
protected:
foobar2000_api() {}
~foobar2000_api() {}
};
extern foobar2000_api * g_foobar2000_api;

View File

@@ -0,0 +1,6 @@
#ifndef _COMPONENT_CLIENT_H_
#define _COMPONENT_CLIENT_H_
#endif //_COMPONENT_CLIENT_H_

View File

@@ -0,0 +1,7 @@
#ifndef _COMPONENTS_MENU_H_
#define _COMPONENTS_MENU_H_
#error deprecated, see menu_item.h
#endif

View File

@@ -0,0 +1,36 @@
#include "foobar2000.h"
bool component_installation_validator::test_my_name(const char * fn) {
const char * path = core_api::get_my_full_path();
path += pfc::scan_filename(path);
bool retVal = ( strcmp(path, fn) == 0 );
PFC_ASSERT( retVal );
return retVal;
}
bool component_installation_validator::have_other_file(const char * fn) {
for(int retry = 0;;) {
pfc::string_formatter path = core_api::get_my_full_path();
path.truncate(path.scan_filename());
path << fn;
try {
try {
bool v = filesystem::g_exists(path, fb2k::noAbort);
PFC_ASSERT( v );
return v;
} catch(std::exception const & e) {
FB2K_console_formatter() << "Component integrity check error: " << e << " (on: " << fn << ")";
throw;
}
} catch(exception_io_denied) {
} catch(exception_io_sharing_violation) {
} catch(exception_io_file_corrupted) { // happens
return false;
} catch(...) {
uBugCheck();
}
if (++retry == 10) uBugCheck();
Sleep(100);
}
}

View File

@@ -0,0 +1,93 @@
//! Entrypoint interface for declaring component's version information. Instead of implementing this directly, use DECLARE_COMPONENT_VERSION().
class NOVTABLE componentversion : public service_base {
public:
virtual void get_file_name(pfc::string_base & out)=0;
virtual void get_component_name(pfc::string_base & out)=0;
virtual void get_component_version(pfc::string_base & out)=0;
virtual void get_about_message(pfc::string_base & out)=0;//about message uses "\n" for line separators
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(componentversion);
};
//! Implementation helper. You typically want to use DECLARE_COMPONENT_VERSION() instead.
class componentversion_impl_simple : public componentversion {
const char * name,*version,*about;
public:
//do not derive/override
virtual void get_file_name(pfc::string_base & out) {out.set_string(core_api::get_my_file_name());}
virtual void get_component_name(pfc::string_base & out) {out.set_string(name?name:"");}
virtual void get_component_version(pfc::string_base & out) {out.set_string(version?version:"");}
virtual void get_about_message(pfc::string_base & out) {out.set_string(about?about:"");}
explicit componentversion_impl_simple(const char * p_name,const char * p_version,const char * p_about) : name(p_name), version(p_version), about(p_about ? p_about : "") {}
};
//! Implementation helper. You typically want to use DECLARE_COMPONENT_VERSION() instead.
class componentversion_impl_copy : public componentversion {
pfc::string8 name,version,about;
public:
//do not derive/override
virtual void get_file_name(pfc::string_base & out) {out.set_string(core_api::get_my_file_name());}
virtual void get_component_name(pfc::string_base & out) {out.set_string(name);}
virtual void get_component_version(pfc::string_base & out) {out.set_string(version);}
virtual void get_about_message(pfc::string_base & out) {out.set_string(about);}
explicit componentversion_impl_copy(const char * p_name,const char * p_version,const char * p_about) : name(p_name), version(p_version), about(p_about ? p_about : "") {}
};
typedef service_factory_single_transparent_t<componentversion_impl_simple> __componentversion_impl_simple_factory;
typedef service_factory_single_transparent_t<componentversion_impl_copy> __componentversion_impl_copy_factory;
class componentversion_impl_simple_factory : public __componentversion_impl_simple_factory {
public:
componentversion_impl_simple_factory(const char * p_name,const char * p_version,const char * p_about) : __componentversion_impl_simple_factory(p_name,p_version,p_about) {}
};
class componentversion_impl_copy_factory : public __componentversion_impl_copy_factory {
public:
componentversion_impl_copy_factory(const char * p_name,const char * p_version,const char * p_about) : __componentversion_impl_copy_factory(p_name,p_version,p_about) {}
};
//! Use this to declare your component's version information. Parameters must ba plain const char * string constants. \n
//! You should have only one DECLARE_COMPONENT_VERSION() per component DLL. Having more than one will confuse component updater's version matching. \n
//! Please keep your version numbers formatted as: N[.N[.N....]][ alpha|beta|RC[ N[.N...]] \n
//! Sample version numbers, in ascending order: 0.9 < 0.10 < 1.0 alpha 1 < 1.0 alpha 2 < 1.0 beta 1 < 1.0 RC < 1.0 RC1 < 1.0 < 1.1 < 1.10 \n
//! For a working sample of how foobar2000 sorts version numbers, see http://www.foobar2000.org/versionator.php \n
//! Example: DECLARE_COMPONENT_VERSION("blah","1.3.3.7","")
#define DECLARE_COMPONENT_VERSION(NAME,VERSION,ABOUT) \
namespace {class componentversion_myimpl : public componentversion { public: componentversion_myimpl() {PFC_ASSERT( ABOUT );} \
void get_file_name(pfc::string_base & out) {out = core_api::get_my_file_name();} \
void get_component_name(pfc::string_base & out) {out = NAME;} \
void get_component_version(pfc::string_base & out) {out = VERSION;} \
void get_about_message(pfc::string_base & out) {out = ABOUT;} \
}; static service_factory_single_t<componentversion_myimpl> g_componentversion_myimpl_factory; }
// static componentversion_impl_simple_factory g_componentversion_service(NAME,VERSION,ABOUT);
//! Same as DECLARE_COMPONENT_VERSION(), but parameters can be dynamically generated strings rather than compile-time constants.
#define DECLARE_COMPONENT_VERSION_COPY(NAME,VERSION,ABOUT) \
static componentversion_impl_copy_factory g_componentversion_service(NAME,VERSION,ABOUT);
//! \since 1.0
//! Allows components to cleanly abort app startup in case the installation appears to have become corrupted.
class component_installation_validator : public service_base {
public:
virtual bool is_installed_correctly() = 0;
static bool test_my_name(const char * fn);
static bool have_other_file(const char * fn);
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(component_installation_validator)
};
//! Simple implementation of component_installation_validator that makes sure that our component DLL has not been renamed around by idiot users.
class component_installation_validator_filename : public component_installation_validator {
public:
component_installation_validator_filename(const char * dllName) : m_dllName(dllName) {}
bool is_installed_correctly() {
return test_my_name(m_dllName);
}
private:
const char * const m_dllName;
};
#define VALIDATE_COMPONENT_FILENAME(FN) \
static service_factory_single_t<component_installation_validator_filename> g_component_installation_validator_filename(FN);

View File

@@ -0,0 +1,14 @@
#include "foobar2000.h"
static filesystem::ptr defaultFS() {
return filesystem::get( core_api::get_profile_path() );
}
void config_io_callback_v3::on_quicksave() {
this->on_quicksave_v3(defaultFS());
}
void config_io_callback_v3::on_write(bool bReset) {
auto fs = defaultFS();
if (bReset) this->on_reset_v3(fs);
else this->on_write_v3(fs);
}

View File

@@ -0,0 +1,43 @@
#pragma once
//! Implementing this interface lets you maintain your own configuration files rather than depending on the cfg_var system. \n
//! Note that you must not make assumptions about what happens first: config_io_callback::on_read(), initialization of cfg_var values or config_io_callback::on_read() in other components. Order of these things is undefined and will change with each run. \n
//! Use service_factory_single_t<myclass> to register your implementations. Do not call other people's implementations, core is responsible for doing that when appropriate.
class NOVTABLE config_io_callback : public service_base {
public:
//! Called on startup. You can read your configuration file from here. \n
//! Hint: use core_api::get_profile_path() to retrieve the path of the folder where foobar2000 configuration files are stored.
virtual void on_read() = 0;
//! Called typically on shutdown but you should expect a call at any point after on_read(). You can write your configuration file from here.
//! Hint: use core_api::get_profile_path() to retrieve the path of the folder where foobar2000 configuration files are stored.
//! @param reset If set to true, our configuration is being reset, so you should wipe your files rather than rewrite them with current configuration.
virtual void on_write(bool reset) = 0;
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(config_io_callback);
};
//! \since 1.0
class NOVTABLE config_io_callback_v2 : public config_io_callback {
FB2K_MAKE_SERVICE_INTERFACE(config_io_callback_v2, config_io_callback)
public:
//! Implement optionally. Called to quickly flush recent configuration changes. If your instance of config_io_callback needs to perform timeconsuming tasks when saving, you should skip implementing this method entirely.
virtual void on_quicksave() = 0;
};
//! \since 1.4
//! New methods take a filesystem object that should be used for the update, so the whole config update can be performed as one transacted filesystem operation. \n
//! The core performs necessary checks to ensure that the volume where our profile resides is supports transacted operations. \n
//! However there are odd cases of people junctioning the profile folder and such. We cannot guarantee that your code won't run into such cases. \n
//! If you get a exception_io_transactions_unsupported, let the caller deal with it - your call will be retried with a regular filesystem instead of a transacted one.
class NOVTABLE config_io_callback_v3 : public config_io_callback_v2 {
FB2K_MAKE_SERVICE_INTERFACE(config_io_callback_v3, config_io_callback_v2);
public:
void on_quicksave();
void on_write(bool bReset);
virtual void on_reset_v3( filesystem::ptr fs ) = 0;
virtual void on_write_v3( filesystem::ptr fs ) = 0;
virtual void on_quicksave_v3( filesystem::ptr fs ) = 0;
};
// For internal use in fb2k core
#define FB2K_PROFILE_CONFIG_READS_WRITES 0

View File

@@ -0,0 +1,210 @@
#include "foobar2000.h"
void config_object_notify_manager::g_on_changed(const service_ptr_t<config_object> & p_object)
{
if (core_api::assert_main_thread())
{
service_enum_t<config_object_notify_manager> e;
service_ptr_t<config_object_notify_manager> ptr;
while(e.next(ptr))
ptr->on_changed(p_object);
}
}
bool config_object::g_find(service_ptr_t<config_object> & p_out,const GUID & p_guid)
{
service_ptr_t<config_object> ptr;
service_enum_t<config_object> e;
while(e.next(ptr))
{
if (ptr->get_guid() == p_guid)
{
p_out = ptr;
return true;
}
}
return false;
}
void config_object::g_get_data_string(const GUID & p_guid,pfc::string_base & p_out)
{
service_ptr_t<config_object> ptr;
if (!g_find(ptr,p_guid)) throw exception_service_not_found();
ptr->get_data_string(p_out);
}
void config_object::g_set_data_string(const GUID & p_guid,const char * p_data,t_size p_length)
{
service_ptr_t<config_object> ptr;
if (!g_find(ptr,p_guid)) throw exception_service_not_found();
ptr->set_data_string(p_data,p_length);
}
void config_object::get_data_int32(t_int32 & p_out)
{
t_int32 temp;
get_data_struct_t<t_int32>(temp);
byte_order::order_le_to_native_t(temp);
p_out = temp;
}
void config_object::set_data_int32(t_int32 p_val)
{
t_int32 temp = p_val;
byte_order::order_native_to_le_t(temp);
set_data_struct_t<t_int32>(temp);
}
bool config_object::get_data_bool_simple(bool p_default) {
try {
bool ret = p_default;
get_data_bool(ret);
return ret;
} catch(...) {return p_default;}
}
t_int32 config_object::get_data_int32_simple(t_int32 p_default) {
try {
t_int32 ret = p_default;
get_data_int32(ret);
return ret;
} catch(...) {return p_default;}
}
void config_object::g_get_data_int32(const GUID & p_guid,t_int32 & p_out) {
service_ptr_t<config_object> ptr;
if (!g_find(ptr,p_guid)) throw exception_service_not_found();
ptr->get_data_int32(p_out);
}
void config_object::g_set_data_int32(const GUID & p_guid,t_int32 p_val) {
service_ptr_t<config_object> ptr;
if (!g_find(ptr,p_guid)) throw exception_service_not_found();
ptr->set_data_int32(p_val);
}
bool config_object::g_get_data_bool_simple(const GUID & p_guid,bool p_default)
{
service_ptr_t<config_object> ptr;
if (!g_find(ptr,p_guid)) throw exception_service_not_found();
return ptr->get_data_bool_simple(p_default);
}
t_int32 config_object::g_get_data_int32_simple(const GUID & p_guid,t_int32 p_default)
{
service_ptr_t<config_object> ptr;
if (!g_find(ptr,p_guid)) throw exception_service_not_found();
return ptr->get_data_int32_simple(p_default);
}
void config_object::get_data_bool(bool & p_out) {get_data_struct_t<bool>(p_out);}
void config_object::set_data_bool(bool p_val) {set_data_struct_t<bool>(p_val);}
void config_object::g_get_data_bool(const GUID & p_guid,bool & p_out) {g_get_data_struct_t<bool>(p_guid,p_out);}
void config_object::g_set_data_bool(const GUID & p_guid,bool p_val) {g_set_data_struct_t<bool>(p_guid,p_val);}
namespace {
class stream_writer_string : public stream_writer {
public:
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
m_out.add_string((const char*)p_buffer,p_bytes);
}
stream_writer_string(pfc::string_base & p_out) : m_out(p_out) {m_out.reset();}
private:
pfc::string_base & m_out;
};
class stream_writer_fixedbuffer : public stream_writer {
public:
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
if (p_bytes > 0) {
if (p_bytes > m_bytes - m_bytes_read) throw pfc::exception_overflow();
memcpy((t_uint8*)m_out,p_buffer,p_bytes);
m_bytes_read += p_bytes;
}
}
stream_writer_fixedbuffer(void * p_out,t_size p_bytes,t_size & p_bytes_read) : m_out(p_out), m_bytes(p_bytes), m_bytes_read(p_bytes_read) {m_bytes_read = 0;}
private:
void * m_out;
t_size m_bytes;
t_size & m_bytes_read;
};
class stream_writer_get_length : public stream_writer {
public:
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
m_length += p_bytes;
}
stream_writer_get_length(t_size & p_length) : m_length(p_length) {m_length = 0;}
private:
t_size & m_length;
};
};
t_size config_object::get_data_raw(void * p_out,t_size p_bytes) {
t_size ret = 0;
stream_writer_fixedbuffer stream(p_out,p_bytes,ret);
get_data(&stream,fb2k::noAbort);
return ret;
}
t_size config_object::get_data_raw_length() {
t_size ret = 0;
stream_writer_get_length stream(ret);
get_data(&stream,fb2k::noAbort);
return ret;
}
void config_object::set_data_raw(const void * p_data,t_size p_bytes, bool p_notify) {
stream_reader_memblock_ref stream(p_data,p_bytes);
set_data(&stream,fb2k::noAbort,p_notify);
}
void config_object::set_data_string(const char * p_data,t_size p_length) {
set_data_raw(p_data,pfc::strlen_max(p_data,p_length));
}
void config_object::get_data_string(pfc::string_base & p_out) {
stream_writer_string stream(p_out);
get_data(&stream,fb2k::noAbort);
}
//config_object_impl stuff
void config_object_impl::get_data(stream_writer * p_stream,abort_callback & p_abort) const {
inReadSync(m_sync);
p_stream->write_object(m_data.get_ptr(),m_data.get_size(),p_abort);
}
void config_object_impl::set_data(stream_reader * p_stream,abort_callback & p_abort,bool p_notify) {
core_api::ensure_main_thread();
{
inWriteSync(m_sync);
m_data.set_size(0);
enum {delta = 1024};
t_uint8 buffer[delta];
for(;;)
{
t_size delta_done = p_stream->read(buffer,delta,p_abort);
if (delta_done > 0)
{
m_data.append_fromptr(buffer,delta_done);
}
if (delta_done != delta) break;
}
}
if (p_notify) config_object_notify_manager::g_on_changed(this);
}
config_object_impl::config_object_impl(const GUID & p_guid,const void * p_data,t_size p_bytes) : cfg_var(p_guid)
{
m_data.set_data_fromptr((const t_uint8*)p_data,p_bytes);
}

View File

@@ -0,0 +1,85 @@
#ifndef _CONFIG_OBJECT_H_
#define _CONFIG_OBJECT_H_
class config_object;
class NOVTABLE config_object_notify_manager : public service_base
{
public:
virtual void on_changed(const service_ptr_t<config_object> & p_object) = 0;
static void g_on_changed(const service_ptr_t<config_object> & p_object);
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(config_object_notify_manager);
};
class NOVTABLE config_object : public service_base
{
public:
//interface
virtual GUID get_guid() const = 0;
virtual void get_data(stream_writer * p_stream,abort_callback & p_abort) const = 0;
virtual void set_data(stream_reader * p_stream,abort_callback & p_abort,bool p_sendnotify = true) = 0;
//helpers
static bool g_find(service_ptr_t<config_object> & p_out,const GUID & p_guid);
void set_data_raw(const void * p_data,t_size p_bytes,bool p_sendnotify = true);
t_size get_data_raw(void * p_out,t_size p_bytes);
t_size get_data_raw_length();
template<class T> void get_data_struct_t(T& p_out);
template<class T> void set_data_struct_t(const T& p_in);
template<class T> static void g_get_data_struct_t(const GUID & p_guid,T & p_out);
template<class T> static void g_set_data_struct_t(const GUID & p_guid,const T & p_in);
void set_data_string(const char * p_data,t_size p_length);
void get_data_string(pfc::string_base & p_out);
void get_data_bool(bool & p_out);
void set_data_bool(bool p_val);
void get_data_int32(t_int32 & p_out);
void set_data_int32(t_int32 p_val);
bool get_data_bool_simple(bool p_default);
t_int32 get_data_int32_simple(t_int32 p_default);
static void g_get_data_string(const GUID & p_guid,pfc::string_base & p_out);
static void g_set_data_string(const GUID & p_guid,const char * p_data,t_size p_length = ~0);
static void g_get_data_bool(const GUID & p_guid,bool & p_out);
static void g_set_data_bool(const GUID & p_guid,bool p_val);
static void g_get_data_int32(const GUID & p_guid,t_int32 & p_out);
static void g_set_data_int32(const GUID & p_guid,t_int32 p_val);
static bool g_get_data_bool_simple(const GUID & p_guid,bool p_default);
static t_int32 g_get_data_int32_simple(const GUID & p_guid,t_int32 p_default);
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(config_object);
};
class standard_config_objects
{
public:
static const GUID bool_remember_window_positions, bool_ui_always_on_top,bool_playlist_stop_after_current;
static const GUID bool_playback_follows_cursor, bool_cursor_follows_playback;
static const GUID bool_show_keyboard_shortcuts_in_menus;
static const GUID string_gui_last_directory_media,string_gui_last_directory_playlists;
static const GUID int32_dynamic_bitrate_display_rate;
inline static bool query_show_keyboard_shortcuts_in_menus() {return config_object::g_get_data_bool_simple(standard_config_objects::bool_show_keyboard_shortcuts_in_menus,true);}
inline static bool query_remember_window_positions() {return config_object::g_get_data_bool_simple(standard_config_objects::bool_remember_window_positions,true);}
};
class config_object_notify : public service_base
{
public:
virtual t_size get_watched_object_count() = 0;
virtual GUID get_watched_object(t_size p_index) = 0;
virtual void on_watched_object_changed(const service_ptr_t<config_object> & p_object) = 0;
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(config_object_notify);
};
#endif // _CONFIG_OBJECT_H_

View File

@@ -0,0 +1,173 @@
#ifndef _CONFIG_OBJECT_IMPL_H_
#define _CONFIG_OBJECT_IMPL_H_
//template function bodies from config_object class
template<class T>
void config_object::get_data_struct_t(T& p_out) {
if (get_data_raw(&p_out,sizeof(T)) != sizeof(T)) throw exception_io_data_truncation();
}
template<class T>
void config_object::set_data_struct_t(const T& p_in) {
return set_data_raw(&p_in,sizeof(T));
}
template<class T>
void config_object::g_get_data_struct_t(const GUID & p_guid,T & p_out) {
service_ptr_t<config_object> ptr;
if (!g_find(ptr,p_guid)) throw exception_service_not_found();
return ptr->get_data_struct_t<T>(p_out);
}
template<class T>
void config_object::g_set_data_struct_t(const GUID & p_guid,const T & p_in) {
service_ptr_t<config_object> ptr;
if (!g_find(ptr,p_guid)) throw exception_service_not_found();
return ptr->set_data_struct_t<T>(p_in);
}
class config_object_impl : public config_object, private cfg_var
{
public:
GUID get_guid() const {return cfg_var::get_guid();}
void get_data(stream_writer * p_stream,abort_callback & p_abort) const;
void set_data(stream_reader * p_stream,abort_callback & p_abort,bool p_notify);
config_object_impl(const GUID & p_guid,const void * p_data,t_size p_bytes);
private:
//cfg_var methods
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {get_data(p_stream,p_abort);}
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {set_data(p_stream,p_abort,false);}
mutable pfc::readWriteLock m_sync;
pfc::array_t<t_uint8> m_data;
};
typedef service_factory_single_transparent_t<config_object_impl> config_object_factory;
template<t_size p_size>
class config_object_fixed_const_impl_t : public config_object {
public:
config_object_fixed_const_impl_t(const GUID & p_guid, const void * p_data) : m_guid(p_guid) {memcpy(m_data,p_data,p_size);}
GUID get_guid() const {return m_guid;}
void get_data(stream_writer * p_stream, abort_callback & p_abort) const { p_stream->write_object(m_data,p_size,p_abort); }
void set_data(stream_reader * p_stream, abort_callback & p_abort, bool p_notify) { PFC_ASSERT(!"Should not get here."); }
private:
t_uint8 m_data[p_size];
const GUID m_guid;
};
template<t_size p_size>
class config_object_fixed_impl_t : public config_object, private cfg_var {
public:
GUID get_guid() const {return cfg_var::get_guid();}
void get_data(stream_writer * p_stream,abort_callback & p_abort) const {
inReadSync(m_sync);
p_stream->write_object(m_data,p_size,p_abort);
}
void set_data(stream_reader * p_stream,abort_callback & p_abort,bool p_notify) {
core_api::ensure_main_thread();
{
t_uint8 temp[p_size];
p_stream->read_object(temp,p_size,p_abort);
inWriteSync(m_sync);
memcpy(m_data,temp,p_size);
}
if (p_notify) config_object_notify_manager::g_on_changed(this);
}
config_object_fixed_impl_t (const GUID & p_guid,const void * p_data)
: cfg_var(p_guid)
{
memcpy(m_data,p_data,p_size);
}
private:
//cfg_var methods
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {get_data(p_stream,p_abort);}
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {set_data(p_stream,p_abort,false);}
mutable pfc::readWriteLock m_sync;
t_uint8 m_data[p_size];
};
template<t_size p_size, bool isConst> class _config_object_fixed_impl_switch;
template<t_size p_size> class _config_object_fixed_impl_switch<p_size,false> { public: typedef config_object_fixed_impl_t<p_size> type; };
template<t_size p_size> class _config_object_fixed_impl_switch<p_size,true> { public: typedef config_object_fixed_const_impl_t<p_size> type; };
template<t_size p_size, bool isConst = false>
class config_object_fixed_factory_t : public service_factory_single_transparent_t< typename _config_object_fixed_impl_switch<p_size,isConst>::type >
{
public:
config_object_fixed_factory_t(const GUID & p_guid,const void * p_initval)
:
service_factory_single_transparent_t< typename _config_object_fixed_impl_switch<p_size,isConst>::type >
(p_guid,p_initval)
{}
};
class config_object_string_factory : public config_object_factory
{
public:
config_object_string_factory(const GUID & p_guid,const char * p_string,t_size p_string_length = ~0)
: config_object_factory(p_guid,p_string,pfc::strlen_max(p_string,~0)) {}
};
template<bool isConst = false>
class config_object_bool_factory_t : public config_object_fixed_factory_t<1,isConst> {
public:
config_object_bool_factory_t(const GUID & p_guid,bool p_initval)
: config_object_fixed_factory_t<1,isConst>(p_guid,&p_initval) {}
};
typedef config_object_bool_factory_t<> config_object_bool_factory;
template<class T,bool isConst = false>
class config_object_int_factory_t : public config_object_fixed_factory_t<sizeof(T),isConst>
{
private:
struct t_initval
{
T m_initval;
t_initval(T p_initval) : m_initval(p_initval) {byte_order::order_native_to_le_t(m_initval);}
T * get_ptr() {return &m_initval;}
};
public:
config_object_int_factory_t(const GUID & p_guid,T p_initval)
: config_object_fixed_factory_t<sizeof(T)>(p_guid,t_initval(p_initval).get_ptr() )
{}
};
typedef config_object_int_factory_t<t_int32> config_object_int32_factory;
class config_object_notify_impl_simple : public config_object_notify
{
public:
t_size get_watched_object_count() {return 1;}
GUID get_watched_object(t_size p_index) {return m_guid;}
void on_watched_object_changed(const service_ptr_t<config_object> & p_object) {m_func(p_object);}
typedef void (*t_func)(const service_ptr_t<config_object> &);
config_object_notify_impl_simple(const GUID & p_guid,t_func p_func) : m_guid(p_guid), m_func(p_func) {}
private:
GUID m_guid;
t_func m_func;
};
typedef service_factory_single_transparent_t<config_object_notify_impl_simple> config_object_notify_simple_factory;
#endif //_CONFIG_OBJECT_IMPL_H_

View File

@@ -0,0 +1,82 @@
#include "foobar2000.h"
void console::info(const char * p_message) {print(p_message);}
void console::error(const char * p_message) {complain("Error", p_message);}
void console::warning(const char * p_message) {complain("Warning", p_message);}
void console::info_location(const playable_location & src) {print_location(src);}
void console::info_location(const metadb_handle_ptr & src) {print_location(src);}
void console::print_location(const metadb_handle_ptr & src)
{
print_location(src->get_location());
}
void console::print_location(const playable_location & src)
{
FB2K_console_formatter() << src;
}
void console::complain(const char * what, const char * msg) {
FB2K_console_formatter() << what << ": " << msg;
}
void console::complain(const char * what, std::exception const & e) {
complain(what, e.what());
}
void console::print(const char* p_message)
{
if (core_api::are_services_available()) {
service_ptr_t<console_receiver> ptr;
service_enum_t<console_receiver> e;
while(e.next(ptr)) ptr->print(p_message,~0);
}
}
void console::printf(const char* p_format,...)
{
va_list list;
va_start(list,p_format);
printfv(p_format,list);
va_end(list);
}
void console::printfv(const char* p_format,va_list p_arglist)
{
pfc::string8_fastalloc temp;
uPrintfV(temp,p_format,p_arglist);
print(temp);
}
namespace {
class event_logger_recorder_impl : public event_logger_recorder {
public:
void playback( event_logger::ptr playTo ) {
for(auto i = m_entries.first(); i.is_valid(); ++i ) {
playTo->log_entry( i->line.get_ptr(), i->severity );
}
}
void log_entry( const char * line, unsigned severity ) {
auto rec = m_entries.insert_last();
rec->line = line;
rec->severity = severity;
}
private:
struct entry_t {
pfc::string_simple line;
unsigned severity;
};
pfc::chain_list_v2_t< entry_t > m_entries;
};
}
event_logger_recorder::ptr event_logger_recorder::create() {
return new service_impl_t<event_logger_recorder_impl>();
}

53
foobar2000/SDK/console.h Normal file
View File

@@ -0,0 +1,53 @@
//! Namespace with functions for sending text to console. All functions are fully multi-thread safe, though they must not be called during dll initialization or deinitialization (e.g. static object constructors or destructors) when service system is not available.
namespace console
{
void info(const char * p_message);
void error(const char * p_message);
void warning(const char * p_message);
void info_location(const playable_location & src);
void info_location(const metadb_handle_ptr & src);
void print_location(const playable_location & src);
void print_location(const metadb_handle_ptr & src);
void print(const char*);
void printf(const char*,...);
void printfv(const char*,va_list p_arglist);
class lineWriter {
public:
const lineWriter & operator<<( const char * msg ) const {print(msg);return *this;}
};
//! Usage: console::formatter() << "blah " << somenumber << " asdf" << somestring;
class formatter : public pfc::string_formatter {
public:
~formatter() {if (!is_empty()) console::print(get_ptr());}
};
#define FB2K_console_formatter() ::console::formatter()._formatter()
#define FB2K_console_formatter1() ::console::lineWriter()
#define FB2K_console_print(X) ::console::print(X)
void complain(const char * what, const char * msg);
void complain(const char * what, std::exception const & e);
class timer_scope {
public:
timer_scope(const char * name) : m_name(name) {m_timer.start();}
~timer_scope() {
try {
FB2K_console_formatter() << m_name << ": " << pfc::format_time_ex(m_timer.query(), 6);
} catch(...) {}
}
private:
pfc::hires_timer m_timer;
const char * const m_name;
};
};
//! Interface receiving console output. Do not call directly; use console namespace functions instead.
class NOVTABLE console_receiver : public service_base {
public:
virtual void print(const char * p_message,t_size p_message_length) = 0;
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(console_receiver);
};

View File

@@ -0,0 +1,341 @@
#pragma once
//! Reserved for future use.
typedef void * t_glyph;
class NOVTABLE contextmenu_item_node {
public:
enum t_flags {
FLAG_CHECKED = 1,
FLAG_DISABLED = 2,
FLAG_GRAYED = 4,
FLAG_DISABLED_GRAYED = FLAG_DISABLED|FLAG_GRAYED,
FLAG_RADIOCHECKED = 8, //new in 0.9.5.2 - overrides FLAG_CHECKED, set together with FLAG_CHECKED for backwards compatibility.
};
enum t_type {
type_group,
type_command,
type_separator,
//for compatibility
TYPE_POPUP = type_group,TYPE_COMMAND = type_command,TYPE_SEPARATOR = type_separator,
};
virtual bool get_display_data(pfc::string_base & p_out,unsigned & p_displayflags,metadb_handle_list_cref p_data,const GUID & p_caller) = 0;
virtual t_type get_type() = 0;
virtual void execute(metadb_handle_list_cref p_data,const GUID & p_caller) = 0;
virtual t_glyph get_glyph(metadb_handle_list_cref p_data,const GUID & p_caller) {return 0;}//RESERVED
virtual t_size get_children_count() = 0;
virtual contextmenu_item_node * get_child(t_size p_index) = 0;
virtual bool get_description(pfc::string_base & p_out) = 0;
virtual GUID get_guid() = 0;
virtual bool is_mappable_shortcut() = 0;
protected:
contextmenu_item_node() {}
~contextmenu_item_node() {}
};
class NOVTABLE contextmenu_item_node_root : public contextmenu_item_node
{
public:
virtual ~contextmenu_item_node_root() {}
};
class NOVTABLE contextmenu_item_node_leaf : public contextmenu_item_node
{
public:
t_type get_type() {return TYPE_COMMAND;}
t_size get_children_count() {return 0;}
contextmenu_item_node * get_child(t_size) {return NULL;}
};
class NOVTABLE contextmenu_item_node_root_leaf : public contextmenu_item_node_root
{
public:
t_type get_type() {return TYPE_COMMAND;}
t_size get_children_count() {return 0;}
contextmenu_item_node * get_child(t_size) {return NULL;}
};
class NOVTABLE contextmenu_item_node_popup : public contextmenu_item_node
{
public:
t_type get_type() {return TYPE_POPUP;}
void execute(metadb_handle_list_cref data,const GUID & caller) {}
bool get_description(pfc::string_base & p_out) {return false;}
};
class NOVTABLE contextmenu_item_node_root_popup : public contextmenu_item_node_root
{
public:
t_type get_type() {return TYPE_POPUP;}
void execute(metadb_handle_list_cref data,const GUID & caller) {}
bool get_description(pfc::string_base & p_out) {return false;}
};
class contextmenu_item_node_separator : public contextmenu_item_node
{
public:
t_type get_type() {return TYPE_SEPARATOR;}
void execute(metadb_handle_list_cref data,const GUID & caller) {}
bool get_description(pfc::string_base & p_out) {return false;}
t_size get_children_count() {return 0;}
bool get_display_data(pfc::string_base & p_out,unsigned & p_displayflags,metadb_handle_list_cref p_data,const GUID & p_caller)
{
p_displayflags = 0;
p_out = "---";
return true;
}
contextmenu_item_node * get_child(t_size) {return NULL;}
GUID get_guid() {return pfc::guid_null;}
bool is_mappable_shortcut() {return false;}
};
/*!
Service class for declaring context menu commands.\n
See contextmenu_item_simple for implementation helper without dynamic menu generation features.\n
All methods are valid from main app thread only.
*/
class NOVTABLE contextmenu_item : public service_base {
public:
enum t_enabled_state {
FORCE_OFF,
DEFAULT_OFF,
DEFAULT_ON,
};
//! Retrieves number of menu items provided by this contextmenu_item implementation.
virtual unsigned get_num_items() = 0;
//! Instantiates a context menu item (including sub-node tree for items that contain dynamically-generated sub-items).
virtual contextmenu_item_node_root * instantiate_item(unsigned p_index,metadb_handle_list_cref p_data,const GUID & p_caller) = 0;
//! Retrieves GUID of the context menu item.
virtual GUID get_item_guid(unsigned p_index) = 0;
//! Retrieves human-readable name of the context menu item.
virtual void get_item_name(unsigned p_index,pfc::string_base & p_out) = 0;
//! Obsolete since v1.0, don't use or override in new components.
virtual void get_item_default_path(unsigned p_index,pfc::string_base & p_out) {p_out = "";}
//! Retrieves item's description to show in the status bar. Set p_out to the string to be displayed and return true if you provide a description, return false otherwise.
virtual bool get_item_description(unsigned p_index,pfc::string_base & p_out) = 0;
//! Controls default state of context menu preferences for this item: \n
//! Return DEFAULT_ON to show this item in the context menu by default - useful for most cases. \n
//! Return DEFAULT_OFF to hide this item in the context menu by default - useful for rarely used utility commands. \n
//! Return FORCE_OFF to hide this item by default and prevent the user from making it visible (very rarely used). \n
//! foobar2000 v1.6 and newer: FORCE_OFF items are meant for being shown only in the keyboard shortcut list, not anywhere else. \n
//! Values returned by this method should be constant for this context menu item and not change later. Do not use this to conditionally hide the item - return false from get_display_data() instead.
virtual t_enabled_state get_enabled_state(unsigned p_index) = 0;
//! Executes the menu item command without going thru the instantiate_item path. For items with dynamically-generated sub-items, p_node is identifies of the sub-item command to execute.
virtual void item_execute_simple(unsigned p_index,const GUID & p_node,metadb_handle_list_cref p_data,const GUID & p_caller) = 0;
bool item_get_display_data_root(pfc::string_base & p_out,unsigned & displayflags,unsigned p_index,metadb_handle_list_cref p_data,const GUID & p_caller);
bool item_get_display_data(pfc::string_base & p_out,unsigned & displayflags,unsigned p_index,const GUID & p_node,metadb_handle_list_cref p_data,const GUID & p_caller);
GUID get_parent_fallback();
GUID get_parent_();
//! Deprecated - use caller_active_playlist_selection instead.
static const GUID caller_playlist;
static const GUID caller_active_playlist_selection, caller_active_playlist, caller_playlist_manager, caller_now_playing, caller_keyboard_shortcut_list, caller_media_library_viewer;
static const GUID caller_undefined;
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(contextmenu_item);
};
//! \since 1.0
class NOVTABLE contextmenu_item_v2 : public contextmenu_item {
FB2K_MAKE_SERVICE_INTERFACE(contextmenu_item_v2, contextmenu_item)
public:
virtual double get_sort_priority() {return 0;}
virtual GUID get_parent() {return get_parent_fallback();}
};
//! contextmenu_item implementation helper for implementing non-dynamically-generated context menu items; derive from this instead of from contextmenu_item directly if your menu items are static.
class NOVTABLE contextmenu_item_simple : public contextmenu_item_v2 {
private:
public:
//! Same as contextmenu_item_node::t_flags.
enum t_flags
{
FLAG_CHECKED = 1,
FLAG_DISABLED = 2,
FLAG_GRAYED = 4,
FLAG_DISABLED_GRAYED = FLAG_DISABLED|FLAG_GRAYED,
FLAG_RADIOCHECKED = 8, //new in 0.9.5.2 - overrides FLAG_CHECKED, set together with FLAG_CHECKED for backwards compatibility.
};
// Functions to be overridden by implementers (some are not mandatory).
virtual t_enabled_state get_enabled_state(unsigned p_index) {return contextmenu_item::DEFAULT_ON;}
virtual unsigned get_num_items() = 0;
virtual void get_item_name(unsigned p_index,pfc::string_base & p_out) = 0;
virtual void context_command(unsigned p_index,metadb_handle_list_cref p_data,const GUID& p_caller) = 0;
virtual bool context_get_display(unsigned p_index,metadb_handle_list_cref p_data,pfc::string_base & p_out,unsigned & p_displayflags,const GUID & p_caller) {
PFC_ASSERT(p_index>=0 && p_index<get_num_items());
get_item_name(p_index,p_out);
return true;
}
virtual GUID get_item_guid(unsigned p_index) = 0;
virtual bool get_item_description(unsigned p_index,pfc::string_base & p_out) = 0;
private:
class contextmenu_item_node_impl : public contextmenu_item_node_root_leaf {
public:
contextmenu_item_node_impl(contextmenu_item_simple * p_owner,unsigned p_index) : m_owner(p_owner), m_index(p_index) {}
bool get_display_data(pfc::string_base & p_out,unsigned & p_displayflags,metadb_handle_list_cref p_data,const GUID & p_caller) {return m_owner->get_display_data(m_index,p_data,p_out,p_displayflags,p_caller);}
void execute(metadb_handle_list_cref p_data,const GUID & p_caller) {m_owner->context_command(m_index,p_data,p_caller);}
bool get_description(pfc::string_base & p_out) {return m_owner->get_item_description(m_index,p_out);}
GUID get_guid() {return pfc::guid_null;}
bool is_mappable_shortcut() {return m_owner->item_is_mappable_shortcut(m_index);}
private:
service_ptr_t<contextmenu_item_simple> m_owner;
unsigned m_index;
};
contextmenu_item_node_root * instantiate_item(unsigned p_index,metadb_handle_list_cref p_data,const GUID & p_caller)
{
return new contextmenu_item_node_impl(this,p_index);
}
void item_execute_simple(unsigned p_index,const GUID & p_node,metadb_handle_list_cref p_data,const GUID & p_caller)
{
if (p_node == pfc::guid_null)
context_command(p_index,p_data,p_caller);
}
virtual bool item_is_mappable_shortcut(unsigned p_index)
{
return true;
}
virtual bool get_display_data(unsigned n,metadb_handle_list_cref data,pfc::string_base & p_out,unsigned & displayflags,const GUID & caller)
{
bool rv = false;
assert(n>=0 && n<get_num_items());
if (data.get_count()>0)
{
rv = context_get_display(n,data,p_out,displayflags,caller);
}
return rv;
}
};
//! Helper.
template<typename T>
class contextmenu_item_factory_t : public service_factory_single_t<T> {};
//! Helper.
#define DECLARE_CONTEXT_MENU_ITEM(P_CLASSNAME,P_NAME,P_DEFAULTPATH,P_FUNC,P_GUID,P_DESCRIPTION) \
namespace { \
class P_CLASSNAME : public contextmenu_item_simple { \
public: \
unsigned get_num_items() {return 1;} \
void get_item_name(unsigned p_index,pfc::string_base & p_out) {p_out = P_NAME;} \
void get_item_default_path(unsigned p_index,pfc::string_base & p_out) {p_out = P_DEFAULTPATH;} \
void context_command(unsigned p_index,metadb_handle_list_cref p_data,const GUID& p_caller) {P_FUNC(p_data);} \
GUID get_item_guid(unsigned p_index) {return P_GUID;} \
bool get_item_description(unsigned p_index,pfc::string_base & p_out) {if (P_DESCRIPTION[0] == 0) return false;p_out = P_DESCRIPTION; return true;} \
}; \
static contextmenu_item_factory_t<P_CLASSNAME> g_##P_CLASSNAME##_factory; \
}
//! New in 0.9.5.1. Static methods safe to use in prior versions as it will use slow fallback mode when the service isn't present. \n
//! Functionality provided by menu_item_resolver methods isn't much different from just walking all registered contextmenu_item / mainmenu_commands implementations to find the command we want, but it uses a hint map to locate the service we're looking for without walking all of them which may be significantly faster in certain scenarios.
class menu_item_resolver : public service_base {
FB2K_MAKE_SERVICE_COREAPI(menu_item_resolver)
public:
virtual bool resolve_context_command(const GUID & id, service_ptr_t<class contextmenu_item> & out, t_uint32 & out_index) = 0;
virtual bool resolve_main_command(const GUID & id, service_ptr_t<class mainmenu_commands> & out, t_uint32 & out_index) = 0;
static bool g_resolve_context_command(const GUID & id, service_ptr_t<class contextmenu_item> & out, t_uint32 & out_index);
static bool g_resolve_main_command(const GUID & id, service_ptr_t<class mainmenu_commands> & out, t_uint32 & out_index);
};
//! \since 1.0
class NOVTABLE contextmenu_group : public service_base {
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(contextmenu_group);
public:
virtual GUID get_guid() = 0;
virtual GUID get_parent() = 0;
virtual double get_sort_priority() = 0;
};
//! \since 1.0
class NOVTABLE contextmenu_group_popup : public contextmenu_group {
FB2K_MAKE_SERVICE_INTERFACE(contextmenu_group_popup, contextmenu_group)
public:
virtual void get_display_string(pfc::string_base & out) = 0;
void get_name(pfc::string_base & out) {get_display_string(out);}
};
class contextmenu_groups {
public:
static const GUID root, utilities, tagging, tagging_pictures, replaygain, fileoperations, playbackstatistics, properties, convert, legacy;
};
class contextmenu_group_impl : public contextmenu_group {
public:
contextmenu_group_impl(const GUID & guid, const GUID & parent, double sortPriority = 0) : m_guid(guid), m_parent(parent), m_sortPriority(sortPriority) {}
GUID get_guid() {return m_guid;}
GUID get_parent() {return m_parent;}
double get_sort_priority() {return m_sortPriority;}
private:
const GUID m_guid, m_parent;
const double m_sortPriority;
};
class contextmenu_group_popup_impl : public contextmenu_group_popup {
public:
contextmenu_group_popup_impl(const GUID & guid, const GUID & parent, const char * name, double sortPriority = 0) : m_guid(guid), m_parent(parent), m_sortPriority(sortPriority), m_name(name) {}
GUID get_guid() {return m_guid;}
GUID get_parent() {return m_parent;}
double get_sort_priority() {return m_sortPriority;}
void get_display_string(pfc::string_base & out) {out = m_name;}
private:
const GUID m_guid, m_parent;
const double m_sortPriority;
const char * const m_name;
};
namespace contextmenu_priorities {
enum {
root_queue = -100,
root_main = -50,
root_tagging,
root_fileoperations,
root_convert,
root_utilities,
root_replaygain,
root_playbackstatistics,
root_legacy = 99,
root_properties = 100,
tagging_pictures = 100,
};
};
class contextmenu_group_factory : public service_factory_single_t<contextmenu_group_impl> {
public:
contextmenu_group_factory(const GUID & guid, const GUID & parent, double sortPriority = 0) : service_factory_single_t<contextmenu_group_impl>(guid, parent, sortPriority) {}
};
class contextmenu_group_popup_factory : public service_factory_single_t<contextmenu_group_popup_impl> {
public:
contextmenu_group_popup_factory(const GUID & guid, const GUID & parent, const char * name, double sortPriority = 0) : service_factory_single_t<contextmenu_group_popup_impl>(guid, parent, name, sortPriority) {}
};

View File

@@ -0,0 +1,130 @@
class NOVTABLE keyboard_shortcut_manager : public service_base
{
public:
static bool g_get(service_ptr_t<keyboard_shortcut_manager> & p_out) {return service_enum_create_t(p_out,0);}
enum shortcut_type
{
TYPE_MAIN,
TYPE_CONTEXT,
TYPE_CONTEXT_PLAYLIST,
TYPE_CONTEXT_NOW_PLAYING,
};
virtual bool process_keydown(shortcut_type type,const pfc::list_base_const_t<metadb_handle_ptr> & data,unsigned keycode)=0;
virtual bool process_keydown_ex(shortcut_type type,const pfc::list_base_const_t<metadb_handle_ptr> & data,unsigned keycode,const GUID & caller)=0;
bool on_keydown(shortcut_type type,WPARAM wp);
bool on_keydown_context(const pfc::list_base_const_t<metadb_handle_ptr> & data,WPARAM wp,const GUID & caller);
bool on_keydown_auto(WPARAM wp);
bool on_keydown_auto_playlist(WPARAM wp);
bool on_keydown_auto_context(const pfc::list_base_const_t<metadb_handle_ptr> & data,WPARAM wp,const GUID & caller);
bool on_keydown_restricted_auto(WPARAM wp);
bool on_keydown_restricted_auto_playlist(WPARAM wp);
bool on_keydown_restricted_auto_context(const pfc::list_base_const_t<metadb_handle_ptr> & data,WPARAM wp,const GUID & caller);
virtual bool get_key_description_for_action(const GUID & p_command,const GUID & p_subcommand, pfc::string_base & out, shortcut_type type, bool is_global)=0;
static bool is_text_key(t_uint32 vkCode);
static bool is_typing_key(t_uint32 vkCode);
static bool is_typing_key_combo(t_uint32 vkCode, t_uint32 modifiers);
static bool is_typing_modifier(t_uint32 flags);
static bool is_typing_message(HWND editbox, const MSG * msg);
static bool is_typing_message(const MSG * msg);
FB2K_MAKE_SERVICE_COREAPI(keyboard_shortcut_manager);
};
//! New in 0.9.5.
class keyboard_shortcut_manager_v2 : public keyboard_shortcut_manager {
public:
//! Deprecates old keyboard_shortcut_manager methods. If the action requires selected items, they're obtained from ui_selection_manager API automatically.
virtual bool process_keydown_simple(t_uint32 keycode) = 0;
//! Helper for use with message filters.
bool pretranslate_message(const MSG * msg, HWND thisPopupWnd);
FB2K_MAKE_SERVICE_COREAPI_EXTENSION(keyboard_shortcut_manager_v2,keyboard_shortcut_manager);
};
class NOVTABLE contextmenu_node {
public:
virtual contextmenu_item_node::t_type get_type()=0;
virtual const char * get_name()=0;
virtual t_size get_num_children()=0;//TYPE_POPUP only
virtual contextmenu_node * get_child(t_size n)=0;//TYPE_POPUP only
virtual unsigned get_display_flags()=0;//TYPE_COMMAND/TYPE_POPUP only, see contextmenu_item::FLAG_*
virtual unsigned get_id()=0;//TYPE_COMMAND only, returns zero-based index (helpful for win32 menu command ids)
virtual void execute()=0;//TYPE_COMMAND only
virtual bool get_description(pfc::string_base & out)=0;//TYPE_COMMAND only
virtual bool get_full_name(pfc::string_base & out)=0;//TYPE_COMMAND only
virtual void * get_glyph()=0;//RESERVED, do not use
protected:
contextmenu_node() {}
~contextmenu_node() {}
};
class NOVTABLE contextmenu_manager : public service_base
{
public:
enum
{
flag_show_shortcuts = 1 << 0,
flag_show_shortcuts_global = 1 << 1,
//! \since 1.0
//! To control which commands are shown, you should specify either flag_view_reduced or flag_view_full. If neither is specified, the implementation will decide automatically based on shift key being pressed, for backwards compatibility.
flag_view_reduced = 1 << 2,
//! \since 1.0
//! To control which commands are shown, you should specify either flag_view_reduced or flag_view_full. If neither is specified, the implementation will decide automatically based on shift key being pressed, for backwards compatibility.
flag_view_full = 1 << 3,
//for compatibility
FLAG_SHOW_SHORTCUTS = 1,
FLAG_SHOW_SHORTCUTS_GLOBAL = 2,
};
virtual void init_context(metadb_handle_list_cref data,unsigned flags) = 0;
virtual void init_context_playlist(unsigned flags) = 0;
virtual contextmenu_node * get_root() = 0;//releasing contextmenu_manager service releaases nodes; root may be null in case of error or something
virtual contextmenu_node * find_by_id(unsigned id)=0;
virtual void set_shortcut_preference(const keyboard_shortcut_manager::shortcut_type * data,unsigned count)=0;
static void g_create(service_ptr_t<contextmenu_manager> & p_out) {standard_api_create_t(p_out);}
#ifdef WIN32
static void win32_build_menu(HMENU menu,contextmenu_node * parent,int base_id,int max_id);//menu item identifiers are base_id<=N<base_id+max_id (if theres too many items, they will be clipped)
static void win32_run_menu_context(HWND parent,metadb_handle_list_cref data, const POINT * pt = 0,unsigned flags = 0);
static void win32_run_menu_context_playlist(HWND parent,const POINT * pt = 0,unsigned flags = 0);
void win32_run_menu_popup(HWND parent,const POINT * pt = 0);
void win32_build_menu(HMENU menu,int base_id,int max_id) {win32_build_menu(menu,get_root(),base_id,max_id);}
#endif
virtual void init_context_ex(metadb_handle_list_cref data,unsigned flags,const GUID & caller)=0;
virtual bool init_context_now_playing(unsigned flags)=0;//returns false if not playing
bool execute_by_id(unsigned id);
bool get_description_by_id(unsigned id,pfc::string_base & out);
//! Safely prevent destruction from worker threads (some components attempt that).
static bool serviceRequiresMainThreadDestructor() { return true; }
FB2K_MAKE_SERVICE_COREAPI(contextmenu_manager);
};
//! \since 1.0
class NOVTABLE contextmenu_group_manager : public service_base {
FB2K_MAKE_SERVICE_COREAPI(contextmenu_group_manager)
public:
virtual GUID path_to_group(const char * path) = 0;
virtual void group_to_path(const GUID & group, pfc::string_base & path) = 0;
};

41
foobar2000/SDK/core_api.h Normal file
View File

@@ -0,0 +1,41 @@
#ifndef _FB2K_CORE_API_H_
#define _FB2K_CORE_API_H_
namespace core_api {
//! Retrieves HINSTANCE of calling DLL.
HINSTANCE get_my_instance();
//! Retrieves filename of calling dll, excluding extension, e.g. "foo_asdf"
const char * get_my_file_name();
//! Retrieves full path of calling dll, e.g. c:\blah\foobar2000\foo_asdf.dll . No file:// prefix, this path can interop with win32 API calls.
const char * get_my_full_path();
//! Retrieves main app window. WARNING: this is provided for parent of dialog windows and such only; using it for anything else (such as hooking windowproc to alter app behaviors) is absolutely illegal.
HWND get_main_window();
//! Tests whether services are available at this time. They are not available only during DLL startup or shutdown (e.g. inside static object constructors or destructors).
bool are_services_available();
//! Tests whether calling thread is main app thread, and shows diagnostic message in debugger output if it's not.
bool assert_main_thread();
//! Triggers a bug check if the calling thread is not the main app thread.
void ensure_main_thread();
//! Returns true if calling thread is main app thread, false otherwise.
bool is_main_thread();
//! Returns whether the app is currently shutting down.
bool is_shutting_down();
//! Returns whether the app is currently initializing.
bool is_initializing();
//! Returns filesystem path to directory with user settings, e.g. file://c:\documents_and_settings\username\blah\foobar2000
const char * get_profile_path();
//! Returns a path to <file name> in fb2k profile folder.
inline pfc::string8 pathInProfile(const char * fileName) { pfc::string8 p( core_api::get_profile_path() ); p.add_filename( fileName ); return p; }
//! Returns whether foobar2000 has been installed in "portable" mode.
bool is_portable_mode_enabled();
//! Returns whether foobar2000 is currently running in quiet mode. \n
//! Quiet mode bypasses all GUI features, disables Media Library and does not save any changes to app configuration. \n
//! Your component should not display any forms of user interface when running in quiet mode, as well as avoid saving configuration on its own (no need to worry if you only rely on cfg_vars or config_io_callback, they will simply be ignored on shutdown).
bool is_quiet_mode_enabled();
};
#endif

View File

@@ -0,0 +1,39 @@
#pragma once
class NOVTABLE core_version_info : public service_base {
FB2K_MAKE_SERVICE_COREAPI(core_version_info);
public:
virtual const char * get_version_string() = 0;
static const char * g_get_version_string() {return core_version_info::get()->get_version_string();}
};
struct t_core_version_data {
t_uint32 m_major, m_minor1, m_minor2, m_minor3;
};
//! New (0.9.4.2)
class NOVTABLE core_version_info_v2 : public core_version_info {
FB2K_MAKE_SERVICE_COREAPI_EXTENSION(core_version_info_v2, core_version_info);
public:
virtual const char * get_name() = 0;//"foobar2000"
virtual const char * get_version_as_text() = 0;//"N.N.N.N"
virtual t_core_version_data get_version() = 0;
//! Determine whether running foobar2000 version is newer or equal to the specified version, eg. test_version(0,9,5,0) for 0.9.5.
bool test_version(t_uint32 major, t_uint32 minor1, t_uint32 minor2, t_uint32 minor3) {
const t_core_version_data v = get_version();
if (v.m_major < major) return false;
else if (v.m_major > major) return true;
// major version matches
else if (v.m_minor1 < minor1) return false;
else if (v.m_minor1 > minor1) return true;
// minor1 version matches
else if (v.m_minor2 < minor2) return false;
else if (v.m_minor2 > minor2) return true;
// minor2 version matches
else if (v.m_minor3 < minor3) return false;
else return true;
}
};

View File

@@ -0,0 +1,170 @@
#pragma once
#ifdef FOOBAR2000_HAVE_DSP
//! \since 1.1
//! This service is essentially a special workaround to easily decode DTS/HDCD content stored in files pretending to contain plain PCM data. \n
//! Callers: Instead of calling this directly, you probably want to use input_postprocessed template. \n
//! Implementers: This service is called only by specific decoders, not by all of them! Implementing your own to provide additional functionality is not recommended!
class decode_postprocessor_instance : public service_base {
FB2K_MAKE_SERVICE_INTERFACE(decode_postprocessor_instance, service_base);
public:
enum {
//! End of stream. Flush any buffered data during this call.
flag_eof = 1 << 0,
//! Stream has already been altered by another instance.
flag_altered = 1 << 1,
};
//! @returns True if the chunk list has been altered by the call, false if not - to tell possible other running instances whether the stream has already been altered or not.
virtual bool run(dsp_chunk_list & p_chunk_list,t_uint32 p_flags,abort_callback & p_abort) = 0;
virtual bool get_dynamic_info(file_info & p_out) = 0;
virtual void flush() = 0;
virtual double get_buffer_ahead() = 0;
};
//! \since 1.1
//! Entrypoint class for instantiating decode_postprocessor_instance. See decode_postprocessor_instance documentation for more information. \n
//! Instead of calling this directly, you probably want to use input_postprocessed template.
class decode_postprocessor_entry : public service_base {
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(decode_postprocessor_entry)
public:
virtual bool instantiate(const file_info & info, decode_postprocessor_instance::ptr & out) = 0;
};
//! Helper class for managing decode_postprocessor_instance objects. See also: input_postprocessed.
class decode_postprocessor {
public:
typedef decode_postprocessor_instance::ptr item;
void initialize(const file_info & info) {
m_items.remove_all();
service_enum_t<decode_postprocessor_entry> e;
decode_postprocessor_entry::ptr ptr;
while(e.next(ptr)) {
item i;
if (ptr->instantiate(info, i)) m_items += i;
}
}
void run(dsp_chunk_list & p_chunk_list,bool p_eof,abort_callback & p_abort) {
t_uint32 flags = p_eof ? decode_postprocessor_instance::flag_eof : 0;
for(t_size walk = 0; walk < m_items.get_size(); ++walk) {
if (m_items[walk]->run(p_chunk_list, flags, p_abort)) flags |= decode_postprocessor_instance::flag_altered;
}
}
void flush() {
for(t_size walk = 0; walk < m_items.get_size(); ++walk) {
m_items[walk]->flush();
}
}
static bool should_bother() {
return service_factory_base::is_service_present(decode_postprocessor_entry::class_guid);
}
bool is_active() const {
return m_items.get_size() > 0;
}
bool get_dynamic_info(file_info & p_out) {
bool rv = false;
for(t_size walk = 0; walk < m_items.get_size(); ++walk) {
if (m_items[walk]->get_dynamic_info(p_out)) rv = true;
}
return rv;
}
void close() {
m_items.remove_all();
}
double get_buffer_ahead() {
double acc = 0;
for(t_size walk = 0; walk < m_items.get_size(); ++walk) {
pfc::max_acc(acc, m_items[walk]->get_buffer_ahead());
}
return acc;
}
private:
pfc::list_t<item> m_items;
};
//! Generic template to add decode_postprocessor support to your input class. Works with both single-track and multi-track inputs.
template<typename baseclass> class input_postprocessed : public baseclass {
public:
void decode_initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort) {
m_chunks.remove_all();
m_haveEOF = false;
m_toSkip = 0;
m_postproc.close();
if ((p_flags & input_flag_no_postproc) == 0 && m_postproc.should_bother()) {
file_info_impl info;
this->get_info(p_subsong, info, p_abort);
m_postproc.initialize(info);
}
baseclass::decode_initialize(p_subsong, p_flags, p_abort);
}
void decode_initialize(unsigned p_flags,abort_callback & p_abort) {
m_chunks.remove_all();
m_haveEOF = false;
m_toSkip = 0;
m_postproc.close();
if ((p_flags & input_flag_no_postproc) == 0 && m_postproc.should_bother()) {
file_info_impl info;
this->get_info(info, p_abort);
m_postproc.initialize(info);
}
baseclass::decode_initialize(p_flags, p_abort);
}
bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort) {
if (m_postproc.is_active()) {
for(;;) {
p_abort.check();
if (m_chunks.get_count() > 0) {
audio_chunk * c = m_chunks.get_item(0);
if (m_toSkip > 0) {
if (!c->process_skip(m_toSkip)) {
m_chunks.remove_by_idx(0);
continue;
}
}
p_chunk = *c;
m_chunks.remove_by_idx(0);
return true;
}
if (m_haveEOF) return false;
if (!baseclass::decode_run(*m_chunks.insert_item(0), p_abort)) {
m_haveEOF = true;
m_chunks.remove_by_idx(0);
}
m_postproc.run(m_chunks, m_haveEOF, p_abort);
}
} else {
return baseclass::decode_run(p_chunk, p_abort);
}
}
bool decode_run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort) {
if (m_postproc.is_active()) {
throw pfc::exception_not_implemented();
} else {
return baseclass::decode_run_raw(p_chunk, p_raw, p_abort);
}
}
bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta) {
bool rv = baseclass::decode_get_dynamic_info(p_out, p_timestamp_delta);
if (m_postproc.get_dynamic_info(p_out)) rv = true;
return rv;
}
void decode_seek(double p_seconds,abort_callback & p_abort) {
m_chunks.remove_all();
m_haveEOF = false;
m_postproc.flush();
double target = pfc::max_t<double>(0, p_seconds - m_postproc.get_buffer_ahead());
m_toSkip = p_seconds - target;
baseclass::decode_seek(target, p_abort);
}
private:
dsp_chunk_list_impl m_chunks;
bool m_haveEOF;
double m_toSkip;
decode_postprocessor m_postproc;
};
#else // FOOBAR2000_HAVE_DSP
template<typename baseclass> class input_postprocessed : public baseclass {};
#endif // FOOBAR2000_HAVE_DSP

556
foobar2000/SDK/dsp.cpp Normal file
View File

@@ -0,0 +1,556 @@
#include "foobar2000.h"
#ifdef FOOBAR2000_HAVE_DSP
#include <math.h>
audio_chunk * dsp_chunk_list::add_item(t_size hint_size) { return insert_item(get_count(), hint_size); }
void dsp_chunk_list::remove_all() { remove_mask(pfc::bit_array_true()); }
double dsp_chunk_list::get_duration() {
double rv = 0;
t_size n, m = get_count();
for (n = 0; n<m; n++) rv += get_item(n)->get_duration();
return rv;
}
void dsp_chunk_list::add_chunk(const audio_chunk * chunk) {
audio_chunk * dst = insert_item(get_count(), chunk->get_used_size());
if (dst) dst->copy(*chunk);
}
t_size dsp_chunk_list_impl::get_count() const {return m_data.get_count();}
audio_chunk * dsp_chunk_list_impl::get_item(t_size n) const {return n<m_data.get_count() ? &*m_data[n] : 0;}
void dsp_chunk_list_impl::remove_by_idx(t_size idx)
{
if (idx<m_data.get_count())
m_recycled.add_item(m_data.remove_by_idx(idx));
}
void dsp_chunk_list_impl::remove_mask(const bit_array & mask)
{
t_size n, m = m_data.get_count();
for(n=0;n<m;n++)
if (mask[m])
m_recycled.add_item(m_data[n]);
m_data.remove_mask(mask);
}
audio_chunk * dsp_chunk_list_impl::insert_item(t_size idx,t_size hint_size)
{
t_size max = get_count();
if (idx>max) idx = max;
pfc::rcptr_t<audio_chunk> ret;
if (m_recycled.get_count()>0)
{
t_size best;
if (hint_size>0)
{
best = 0;
t_size best_found = m_recycled[0]->get_data_size(), n, total = m_recycled.get_count();
for(n=1;n<total;n++)
{
if (best_found==hint_size) break;
t_size size = m_recycled[n]->get_data_size();
int delta_old = abs((int)best_found - (int)hint_size), delta_new = abs((int)size - (int)hint_size);
if (delta_new < delta_old)
{
best_found = size;
best = n;
}
}
}
else best = m_recycled.get_count()-1;
ret = m_recycled.remove_by_idx(best);
ret->set_sample_count(0);
ret->set_channels(0);
ret->set_srate(0);
}
else ret = pfc::rcnew_t<audio_chunk_impl>();
if (idx==max) m_data.add_item(ret);
else m_data.insert_item(ret,idx);
return &*ret;
}
void dsp_chunk_list::remove_bad_chunks()
{
bool blah = false;
t_size idx;
for(idx=0;idx<get_count();)
{
audio_chunk * chunk = get_item(idx);
if (!chunk->is_valid())
{
#if PFC_DEBUG
FB2K_console_formatter() << "Removing bad chunk: " << chunk->formatChunkSpec();
#endif
chunk->reset();
remove_by_idx(idx);
blah = true;
}
else idx++;
}
if (blah) console::info("one or more bad chunks removed from dsp chunk list");
}
bool dsp_entry_hidden::g_dsp_exists(const GUID & p_guid) {
dsp_entry_hidden::ptr p;
return g_get_interface(p, p_guid);
}
bool dsp_entry_hidden::g_get_interface( dsp_entry_hidden::ptr & out, const GUID & guid ) {
service_enum_t<dsp_entry_hidden> e; service_ptr_t<dsp_entry_hidden> p;
while( e.next(p) ) {
if (p->get_guid() == guid) {
out = p; return true;
}
}
return false;
}
bool dsp_entry_hidden::g_instantiate( dsp::ptr & out, const dsp_preset & preset ) {
dsp_entry_hidden::ptr i;
if (!g_get_interface(i, preset.get_owner())) return false;
return i->instantiate(out, preset);
}
bool dsp_entry::g_instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset)
{
service_ptr_t<dsp_entry> ptr;
if (!g_get_interface(ptr,p_preset.get_owner())) return false;
return ptr->instantiate(p_out,p_preset);
}
bool dsp_entry::g_instantiate_default(service_ptr_t<dsp> & p_out,const GUID & p_guid)
{
service_ptr_t<dsp_entry> ptr;
if (!g_get_interface(ptr,p_guid)) return false;
dsp_preset_impl preset;
if (!ptr->get_default_preset(preset)) return false;
return ptr->instantiate(p_out,preset);
}
bool dsp_entry::g_name_from_guid(pfc::string_base & p_out,const GUID & p_guid)
{
service_ptr_t<dsp_entry> ptr;
if (!g_get_interface(ptr,p_guid)) return false;
ptr->get_name(p_out);
return true;
}
bool dsp_entry::g_dsp_exists(const GUID & p_guid)
{
service_ptr_t<dsp_entry> blah;
return g_get_interface(blah,p_guid);
}
bool dsp_entry::g_get_default_preset(dsp_preset & p_out,const GUID & p_guid)
{
service_ptr_t<dsp_entry> ptr;
if (!g_get_interface(ptr,p_guid)) return false;
return ptr->get_default_preset(p_out);
}
void dsp_chain_config::contents_to_stream(stream_writer * p_stream,abort_callback & p_abort) const {
uint32_t n, count = pfc::downcast_guarded<uint32_t>( get_count() );
p_stream->write_lendian_t(count,p_abort);
for(n=0;n<count;n++) {
get_item(n).contents_to_stream(p_stream,p_abort);
}
}
void dsp_chain_config::contents_from_stream(stream_reader * p_stream,abort_callback & p_abort) {
t_uint32 n,count;
remove_all();
p_stream->read_lendian_t(count,p_abort);
dsp_preset_impl temp;
for(n=0;n<count;n++) {
temp.contents_from_stream(p_stream,p_abort);
add_item(temp);
}
}
void cfg_dsp_chain_config::get_data(dsp_chain_config & p_data) const {
p_data.copy(m_data);
}
void cfg_dsp_chain_config::set_data(const dsp_chain_config & p_data) {
m_data.copy(p_data);
}
void cfg_dsp_chain_config::reset() {
m_data.remove_all();
}
void cfg_dsp_chain_config::get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {
m_data.contents_to_stream(p_stream,p_abort);
}
void cfg_dsp_chain_config::set_data_raw(stream_reader * p_stream,t_size,abort_callback & p_abort) {
m_data.contents_from_stream(p_stream,p_abort);
}
void cfg_dsp_chain_config_mt::reset() {
dsp_chain_config_impl dummy; set_data(dummy);
}
void cfg_dsp_chain_config_mt::get_data(dsp_chain_config & p_data) {
inReadSync( m_sync );
p_data.copy(m_data);
}
void cfg_dsp_chain_config_mt::set_data(const dsp_chain_config & p_data) {
inWriteSync( m_sync );
m_data.copy( p_data );
}
void cfg_dsp_chain_config_mt::get_data_raw(stream_writer * p_stream, abort_callback & p_abort) {
dsp_chain_config_impl temp;
get_data( temp );
temp.contents_to_stream( p_stream, p_abort );
}
void cfg_dsp_chain_config_mt::set_data_raw(stream_reader * p_stream, t_size p_sizehint, abort_callback & p_abort) {
dsp_chain_config_impl temp; temp.contents_from_stream( p_stream, p_abort );
set_data( temp );
}
void dsp_chain_config::remove_item(t_size p_index)
{
remove_mask(pfc::bit_array_one(p_index));
}
void dsp_chain_config::add_item(const dsp_preset & p_data)
{
insert_item(p_data,get_count());
}
void dsp_chain_config::remove_all()
{
remove_mask(pfc::bit_array_true());
}
void dsp_chain_config::instantiate(service_list_t<dsp> & p_out)
{
p_out.remove_all();
t_size n, m = get_count();
for(n=0;n<m;n++)
{
service_ptr_t<dsp> temp;
auto const & preset = this->get_item(n);
if (dsp_entry::g_instantiate(temp,preset) || dsp_entry_hidden::g_instantiate(temp, preset))
p_out.add_item(temp);
}
}
void dsp_chain_config_impl::reorder(const size_t * order, size_t count) {
PFC_ASSERT( count == m_data.get_count() );
m_data.reorder( order );
}
t_size dsp_chain_config_impl::get_count() const
{
return m_data.get_count();
}
const dsp_preset & dsp_chain_config_impl::get_item(t_size p_index) const
{
return *m_data[p_index];
}
void dsp_chain_config_impl::replace_item(const dsp_preset & p_data,t_size p_index)
{
*m_data[p_index] = p_data;
}
void dsp_chain_config_impl::insert_item(const dsp_preset & p_data,t_size p_index)
{
m_data.insert_item(new dsp_preset_impl(p_data),p_index);
}
void dsp_chain_config_impl::remove_mask(const bit_array & p_mask)
{
m_data.delete_mask(p_mask);
}
dsp_chain_config_impl::~dsp_chain_config_impl()
{
m_data.delete_all();
}
pfc::string8 dsp_preset::get_owner_name() const {
pfc::string8 ret;
dsp_entry::ptr obj;
if (dsp_entry::g_get_interface(obj, this->get_owner())) {
obj->get_name(ret);
}
return ret;
}
pfc::string8 dsp_preset::get_owner_name_debug() const {
pfc::string8 ret;
dsp_entry::ptr obj;
if (dsp_entry::g_get_interface(obj, this->get_owner())) {
obj->get_name(ret);
} else {
ret = "[unknown]";
}
return ret;
}
pfc::string8 dsp_preset::debug() const {
pfc::string8 ret;
ret << this->get_owner_name_debug() << " :: " << pfc::print_guid(this->get_owner()) << " :: " << pfc::format_hexdump(this->get_data(), this->get_data_size());
return ret;
}
pfc::string8 dsp_chain_config::debug() const {
const size_t count = get_count();
pfc::string8 ret;
ret << "dsp_chain_config: " << count << " items";
for (size_t walk = 0; walk < count; ++walk) {
ret << "\n" << get_item(walk).debug();
}
return ret;
}
void dsp_preset::contents_to_stream(stream_writer * p_stream,abort_callback & p_abort) const {
t_uint32 size = pfc::downcast_guarded<t_uint32>(get_data_size());
p_stream->write_lendian_t(get_owner(),p_abort);
p_stream->write_lendian_t(size,p_abort);
if (size > 0) {
p_stream->write_object(get_data(),size,p_abort);
}
}
void dsp_preset::contents_from_stream(stream_reader * p_stream,abort_callback & p_abort) {
t_uint32 size;
GUID guid;
p_stream->read_lendian_t(guid,p_abort);
set_owner(guid);
p_stream->read_lendian_t(size,p_abort);
if (size > 1024*1024*32) throw exception_io_data();
set_data_from_stream(p_stream,size,p_abort);
}
void dsp_preset::g_contents_from_stream_skip(stream_reader * p_stream,abort_callback & p_abort) {
t_uint32 size;
GUID guid;
p_stream->read_lendian_t(guid,p_abort);
p_stream->read_lendian_t(size,p_abort);
if (size > 1024*1024*32) throw exception_io_data();
p_stream->skip_object(size,p_abort);
}
void dsp_preset_impl::set_data_from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort) {
m_data.set_size(p_bytes);
if (p_bytes > 0) p_stream->read_object(m_data.get_ptr(),p_bytes,p_abort);
}
void dsp_chain_config::copy(const dsp_chain_config & p_source) {
remove_all();
t_size n, m = p_source.get_count();
for(n=0;n<m;n++)
add_item(p_source.get_item(n));
}
bool dsp_entry::g_have_config_popup(const GUID & p_guid)
{
service_ptr_t<dsp_entry> entry;
if (!g_get_interface(entry,p_guid)) return false;
return entry->have_config_popup();
}
bool dsp_entry::g_have_config_popup(const dsp_preset & p_preset)
{
return g_have_config_popup(p_preset.get_owner());
}
bool dsp_entry::g_show_config_popup(dsp_preset & p_preset,HWND p_parent)
{
service_ptr_t<dsp_entry> entry;
if (!g_get_interface(entry,p_preset.get_owner())) return false;
return entry->show_config_popup(p_preset,p_parent);
}
void dsp_entry::g_show_config_popup_v2(const dsp_preset & p_preset,HWND p_parent,dsp_preset_edit_callback & p_callback) {
service_ptr_t<dsp_entry> entry;
if (g_get_interface(entry,p_preset.get_owner())) {
service_ptr_t<dsp_entry_v2> entry_v2;
if (entry->service_query_t(entry_v2)) {
entry_v2->show_config_popup_v2(p_preset,p_parent,p_callback);
} else {
dsp_preset_impl temp(p_preset);
if (entry->show_config_popup(temp,p_parent)) p_callback.on_preset_changed(temp);
}
}
}
bool dsp_entry::g_get_interface(service_ptr_t<dsp_entry> & p_out,const GUID & p_guid)
{
service_ptr_t<dsp_entry> ptr; service_enum_t<dsp_entry> e;
while(e.next(ptr)) {
if (ptr->get_guid() == p_guid) {
p_out = ptr;
return true;
}
}
return false;
}
bool resampler_entry::g_get_interface(service_ptr_t<resampler_entry> & p_out,unsigned p_srate_from,unsigned p_srate_to)
{
#if FOOBAR2000_TARGET_VERSION >= 79
auto r = resampler_manager::get()->get_resampler( p_srate_from, p_srate_to );
bool v = r.is_valid();
if ( v ) p_out = std::move(r);
return v;
#else
{
resampler_manager::ptr api;
if ( resampler_manager::tryGet(api) ) {
auto r = api->get_resampler( p_srate_from, p_srate_to );
bool v = r.is_valid();
if (v) p_out = std::move(r);
return v;
}
}
resampler_entry::ptr ptr_resampler;
service_enum_t<dsp_entry> e;
float found_priority = 0;
resampler_entry::ptr found;
while(e.next(ptr_resampler))
{
if (p_srate_from == 0 || ptr_resampler->is_conversion_supported(p_srate_from,p_srate_to))
{
float priority = ptr_resampler->get_priority();
if (found.is_empty() || priority > found_priority)
{
found = ptr_resampler;
found_priority = priority;
}
}
}
if (found.is_empty()) return false;
p_out = found;
return true;
#endif
}
bool resampler_entry::g_create_preset(dsp_preset & p_out,unsigned p_srate_from,unsigned p_srate_to,float p_qualityscale)
{
service_ptr_t<resampler_entry> entry;
if (!g_get_interface(entry,p_srate_from,p_srate_to)) return false;
return entry->create_preset(p_out,p_srate_to,p_qualityscale);
}
bool resampler_entry::g_create(service_ptr_t<dsp> & p_out,unsigned p_srate_from,unsigned p_srate_to,float p_qualityscale)
{
service_ptr_t<resampler_entry> entry;
if (!g_get_interface(entry,p_srate_from,p_srate_to)) return false;
dsp_preset_impl preset;
if (!entry->create_preset(preset,p_srate_to,p_qualityscale)) return false;
return entry->instantiate(p_out,preset);
}
bool dsp_chain_config::equals(dsp_chain_config const & v1, dsp_chain_config const & v2) {
const t_size count = v1.get_count();
if (count != v2.get_count()) return false;
for(t_size walk = 0; walk < count; ++walk) {
if (v1.get_item(walk) != v2.get_item(walk)) return false;
}
return true;
}
bool dsp_chain_config::equals_debug(dsp_chain_config const& v1, dsp_chain_config const& v2) {
FB2K_DebugLog() << "Comparing DSP chains";
const t_size count = v1.get_count();
if (count != v2.get_count()) {
FB2K_DebugLog() << "Count mismatch, " << count << " vs " << v2.get_count();
return false;
}
for (t_size walk = 0; walk < count; ++walk) {
if (v1.get_item(walk) != v2.get_item(walk)) {
FB2K_DebugLog() << "Item " << (walk+1) << " mismatch";
FB2K_DebugLog() << "Item 1: " << v1.get_item(walk).debug();
FB2K_DebugLog() << "Item 2: " << v2.get_item(walk).debug();
return false;
}
}
FB2K_DebugLog() << "DSP chains are identical";
return true;
}
void dsp_chain_config::get_name_list(pfc::string_base & p_out) const
{
const t_size count = get_count();
bool added = false;
for(unsigned n=0;n<count;n++)
{
service_ptr_t<dsp_entry> ptr;
if (dsp_entry::g_get_interface(ptr,get_item(n).get_owner()))
{
if (added) p_out += ", ";
added = true;
pfc::string8 temp;
ptr->get_name(temp);
p_out += temp;
}
}
}
void dsp::run_abortable(dsp_chunk_list * p_chunk_list,const metadb_handle_ptr & p_cur_file,int p_flags,abort_callback & p_abort) {
service_ptr_t<dsp_v2> this_v2;
if (this->service_query_t(this_v2)) this_v2->run_v2(p_chunk_list,p_cur_file,p_flags,p_abort);
else run(p_chunk_list,p_cur_file,p_flags);
}
namespace {
class dsp_preset_edit_callback_impl : public dsp_preset_edit_callback {
public:
dsp_preset_edit_callback_impl(dsp_preset & p_data) : m_data(p_data) {}
void on_preset_changed(const dsp_preset & p_data) {m_data = p_data;}
private:
dsp_preset & m_data;
};
};
bool dsp_entry_v2::show_config_popup(dsp_preset & p_data,HWND p_parent) {
PFC_ASSERT(p_data.get_owner() == get_guid());
dsp_preset_impl temp(p_data);
{
dsp_preset_edit_callback_impl cb(temp);
show_config_popup_v2(p_data,p_parent,cb);
}
PFC_ASSERT(temp.get_owner() == get_guid());
if (temp == p_data) return false;
p_data = temp;
return true;
}
void resampler_manager::make_chain_(dsp_chain_config& outChain, unsigned rateFrom, unsigned rateTo, float qualityScale) {
resampler_manager_v2::ptr v2;
if (v2 &= this) {
v2->make_chain(outChain, rateFrom, rateTo, qualityScale);
} else {
outChain.remove_all();
auto obj = this->get_resampler(rateFrom, rateTo);
if (obj.is_valid()) {
dsp_preset_impl p;
if (obj->create_preset(p, rateTo, qualityScale)) {
outChain.add_item(p);
}
}
}
}
#endif // FOOBAR2000_HAVE_DSP

558
foobar2000/SDK/dsp.h Normal file
View File

@@ -0,0 +1,558 @@
#pragma once
#ifdef FOOBAR2000_HAVE_DSP
//! Interface to a DSP chunk list. A DSP chunk list object is passed to the DSP chain each time, since DSPs are allowed to remove processed chunks or insert new ones.
class NOVTABLE dsp_chunk_list {
public:
virtual t_size get_count() const = 0;
virtual audio_chunk * get_item(t_size n) const = 0;
virtual void remove_by_idx(t_size idx) = 0;
virtual void remove_mask(const bit_array & mask) = 0;
virtual audio_chunk * insert_item(t_size idx,t_size hint_size=0) = 0;
audio_chunk * add_item(t_size hint_size=0);
void remove_all();
double get_duration();
void add_chunk(const audio_chunk * chunk);
void remove_bad_chunks();
protected:
dsp_chunk_list() {}
~dsp_chunk_list() {}
};
class dsp_chunk_list_impl : public dsp_chunk_list//implementation
{
pfc::list_t<pfc::rcptr_t<audio_chunk> > m_data, m_recycled;
public:
t_size get_count() const;
audio_chunk * get_item(t_size n) const;
void remove_by_idx(t_size idx);
void remove_mask(const bit_array & mask);
audio_chunk * insert_item(t_size idx,t_size hint_size=0);
};
//! Instance of a DSP.\n
//! Implementation: Derive from dsp_impl_base instead of deriving from dsp directly.\n
//! Instantiation: Use dsp_entry static helper methods to instantiate DSPs, or dsp_chain_config / dsp_manager to deal with entire DSP chains.
class NOVTABLE dsp : public service_base {
public:
enum {
//! Flush whatever you need to when tracks change.
END_OF_TRACK = 1,
//! Flush everything.
FLUSH = 2
};
//! @param p_chunk_list List of chunks to process. The implementation may alter the list in any way, inserting chunks of different sample rate / channel configuration etc.
//! @param p_cur_file Optional, location of currently decoded file. May be null.
//! @param p_flags Flags. Can be null, or a combination of END_OF_TRACK and FLUSH constants.
virtual void run(dsp_chunk_list * p_chunk_list,const metadb_handle_ptr & p_cur_file,int p_flags)=0;
//! Flushes the DSP (reinitializes / drops any buffered data). Called after seeking, etc.
virtual void flush() = 0;
//! Retrieves amount of data buffered by the DSP, for syncing visualisation.
//! @returns Amount of buffered audio data, in seconds.
virtual double get_latency() = 0;
//! Returns true if DSP needs to know exact track change point (eg. for crossfading, removing silence).\n
//! Signaling this will force-flush any DSPs placed before this DSP so when it gets END_OF_TRACK, relevant chunks contain last samples of the track.\n
//! Signaling this will often break regular gapless playback so don't use it unless you have reasons to.
virtual bool need_track_change_mark() = 0;
void run_abortable(dsp_chunk_list * p_chunk_list,const metadb_handle_ptr & p_cur_file,int p_flags,abort_callback & p_abort);
FB2K_MAKE_SERVICE_INTERFACE(dsp,service_base);
};
//! Backwards-compatible extension to dsp interface, allows abortable operation. Introduced in 0.9.2.
class NOVTABLE dsp_v2 : public dsp {
public:
//! Abortable version of dsp::run(). See dsp::run() for descriptions of parameters.
virtual void run_v2(dsp_chunk_list * p_chunk_list,const metadb_handle_ptr & p_cur_file,int p_flags,abort_callback & p_abort) = 0;
private:
void run(dsp_chunk_list * p_chunk_list,const metadb_handle_ptr & p_cur_file,int p_flags) {
run_v2(p_chunk_list,p_cur_file,p_flags,fb2k::noAbort);
}
FB2K_MAKE_SERVICE_INTERFACE(dsp_v2,dsp);
};
//! Helper class for implementing dsps. You should derive from dsp_impl_base instead of from dsp directly.\n
//! The dsp_impl_base_t template allows you to use a custom interface class as a base class for your implementation, in case you provide extended functionality.\n
//! Use dsp_factory_t<> template to register your dsp implementation.
//! The implementation - as required by dsp_factory_t<> template - must also provide following methods:\n
//! A constructor taking const dsp_preset&, initializing the DSP with specified preset data.\n
//! static void g_get_name(pfc::string_base &); - retrieving human-readable name of the DSP to display.\n
//! static bool g_get_default_preset(dsp_preset &); - retrieving default preset for this DSP. Return value is reserved for future use and should always be true.\n
//! static GUID g_get_guid(); - retrieving GUID of your DSP implementation, to be used to identify it when storing DSP chain configuration.\n
//! static bool g_have_config_popup(); - retrieving whether your DSP implementation supplies a popup dialog for configuring it.\n
//! static void g_show_config_popup(const dsp_preset & p_data,HWND p_parent, dsp_preset_edit_callback & p_callback); - displaying your DSP's settings dialog; called only when g_have_config_popup() returns true; call p_callback.on_preset_changed() whenever user has made adjustments to the preset data.\n
template<class t_baseclass>
class dsp_impl_base_t : public t_baseclass {
private:
typedef dsp_impl_base_t<t_baseclass> t_self;
dsp_chunk_list * m_list;
t_size m_chunk_ptr;
metadb_handle* m_cur_file;
void run_v2(dsp_chunk_list * p_list,const metadb_handle_ptr & p_cur_file,int p_flags,abort_callback & p_abort) override;
protected:
//! Call only from on_chunk / on_endoftrack (on_endoftrack will give info on track being finished).\n
//! May return false when there's no known track and the metadb_handle ptr will be empty/null.
bool get_cur_file(metadb_handle_ptr & p_out) {p_out = m_cur_file; return p_out.is_valid();}
dsp_impl_base_t() : m_list(NULL), m_cur_file(NULL), m_chunk_ptr(0) {}
//! Inserts a new chunk of audio data. \n
//! You can call this only from on_chunk(), on_endofplayback() and on_endoftrack(). You're NOT allowed to call this from flush() which should just drop any queued data.
//! @param hint_size Optional, amount of buffer space that you require (in audio_samples). This is just a hint for memory allocation logic and will not cause the framework to allocate the chunk for you.
//! @returns A pointer to the newly allocated chunk. Pass the audio data you want to insert to this chunk object. The chunk is owned by the framework, you can't delete it etc.
audio_chunk * insert_chunk(t_size p_hint_size = 0) {
PFC_ASSERT(m_list != NULL);
return m_list->insert_item(m_chunk_ptr++,p_hint_size);
}
audio_chunk * insert_chunk( const audio_chunk & sourceCopy ) {
audio_chunk * c = insert_chunk( sourceCopy.get_used_size() );
c->copy( sourceCopy );
return c;
}
//! To be overridden by a DSP implementation.\n
//! Called on track change. You can use insert_chunk() to dump any data you have to flush. \n
//! Note that you must implement need_track_change_mark() to return true if you need this method called.
virtual void on_endoftrack(abort_callback & p_abort) = 0;
//! To be overridden by a DSP implementation.\n
//! Called at the end of played stream, typically at the end of last played track, to allow the DSP to return all data it has buffered-ahead.\n
//! Use insert_chunk() to return any data you have buffered.\n
//! Note that this call does not imply that the DSP will be destroyed next. \n
//! This is also called on track changes if some DSP placed after your DSP requests track change marks.
virtual void on_endofplayback(abort_callback & p_abort) = 0;
//! To be overridden by a DSP implementation.\n
//! Processes a chunk of audio data.\n
//! You can call insert_chunk() from inside on_chunk() to insert any audio data before currently processed chunk.\n
//! @param p_chunk Current chunk being processed. You can alter it in any way you like.
//! @returns True to keep p_chunk (with alterations made inside on_chunk()) in the stream, false to remove it.
virtual bool on_chunk(audio_chunk * p_chunk,abort_callback & p_abort) = 0;
public:
//! To be overridden by a DSP implementation.\n
//! Flushes the DSP (drops any buffered data). The implementation should reset the DSP to the same state it was in before receiving any audio data. \n
//! Called after seeking, etc.
virtual void flush() = 0;
//! To be overridden by a DSP implementation.\n
//! Retrieves amount of data buffered by the DSP, for syncing visualisation.
//! @returns Amount of buffered audio data, in seconds.
virtual double get_latency() = 0;
//! To be overridden by a DSP implementation.\n
//! Returns true if DSP needs to know exact track change point (eg. for crossfading, removing silence).\n
//! Signaling this will force-flush any DSPs placed before this DSP so when it gets on_endoftrack(), relevant chunks contain last samples of the track.\n
//! Signaling this may interfere with gapless playback in certain scenarios (forces flush of DSPs placed before you) so don't use it unless you have reasons to.
virtual bool need_track_change_mark() = 0;
private:
dsp_impl_base_t(const t_self&) = delete;
const t_self & operator=(const t_self &) = delete;
};
template<class t_baseclass>
void dsp_impl_base_t<t_baseclass>::run_v2(dsp_chunk_list * p_list,const metadb_handle_ptr & p_cur_file,int p_flags,abort_callback & p_abort) {
pfc::vartoggle_t<dsp_chunk_list*> l_list_toggle(m_list,p_list);
pfc::vartoggle_t<metadb_handle*> l_cur_file_toggle(m_cur_file,p_cur_file.get_ptr());
for(m_chunk_ptr = 0;m_chunk_ptr<m_list->get_count();m_chunk_ptr++) {
audio_chunk * c = m_list->get_item(m_chunk_ptr);
if (c->is_empty() || !on_chunk(c,p_abort))
m_list->remove_by_idx(m_chunk_ptr--);
}
if (p_flags & dsp::FLUSH) {
on_endofplayback(p_abort);
} else if (p_flags & dsp::END_OF_TRACK) {
if (need_track_change_mark()) on_endoftrack(p_abort);
}
}
typedef dsp_impl_base_t<dsp_v2> dsp_impl_base;
class NOVTABLE dsp_preset {
public:
virtual GUID get_owner() const = 0;
virtual void set_owner(const GUID & p_owner) = 0;
virtual const void * get_data() const = 0;
virtual t_size get_data_size() const = 0;
virtual void set_data(const void * p_data,t_size p_data_size) = 0;
virtual void set_data_from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort) = 0;
const dsp_preset & operator=(const dsp_preset & p_source) {copy(p_source); return *this;}
void copy(const dsp_preset & p_source) {set_owner(p_source.get_owner());set_data(p_source.get_data(),p_source.get_data_size());}
void contents_to_stream(stream_writer * p_stream,abort_callback & p_abort) const;
void contents_from_stream(stream_reader * p_stream,abort_callback & p_abort);
static void g_contents_from_stream_skip(stream_reader * p_stream,abort_callback & p_abort);
bool operator==(const dsp_preset & p_other) const {
if (get_owner() != p_other.get_owner()) return false;
if (get_data_size() != p_other.get_data_size()) return false;
if (memcmp(get_data(),p_other.get_data(),get_data_size()) != 0) return false;
return true;
}
bool operator!=(const dsp_preset & p_other) const {
return !(*this == p_other);
}
pfc::string8 get_owner_name() const;
pfc::string8 get_owner_name_debug() const;
pfc::string8 debug() const;
protected:
dsp_preset() {}
~dsp_preset() {}
};
class dsp_preset_writer : public stream_writer {
public:
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
p_abort.check();
m_data.append_fromptr((const t_uint8 *) p_buffer,p_bytes);
}
void flush(dsp_preset & p_preset) {
p_preset.set_data(m_data.get_ptr(),m_data.get_size());
m_data.set_size(0);
}
private:
pfc::array_t<t_uint8,pfc::alloc_fast_aggressive> m_data;
};
class dsp_preset_reader : public stream_reader {
public:
dsp_preset_reader() : m_walk(0) {}
dsp_preset_reader(const dsp_preset_reader & p_source) : m_walk(0) {*this = p_source;}
void init(const dsp_preset & p_preset) {
m_data.set_data_fromptr( (const t_uint8*) p_preset.get_data(), p_preset.get_data_size() );
m_walk = 0;
}
t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
p_abort.check();
t_size todo = pfc::min_t<t_size>(p_bytes,m_data.get_size()-m_walk);
memcpy(p_buffer,m_data.get_ptr()+m_walk,todo);
m_walk += todo;
return todo;
}
bool is_finished() {return m_walk == m_data.get_size();}
private:
t_size m_walk;
pfc::array_t<t_uint8> m_data;
};
class dsp_preset_impl : public dsp_preset
{
public:
dsp_preset_impl() : m_owner() {}
dsp_preset_impl(const dsp_preset_impl & p_source) {copy(p_source);}
dsp_preset_impl(const dsp_preset & p_source) {copy(p_source);}
void clear() {m_owner = pfc::guid_null; m_data.set_size(0);}
bool is_valid() const { return m_owner != pfc::guid_null; }
const dsp_preset_impl& operator=(const dsp_preset_impl & p_source) {copy(p_source); return *this;}
const dsp_preset_impl& operator=(const dsp_preset & p_source) {copy(p_source); return *this;}
GUID get_owner() const {return m_owner;}
void set_owner(const GUID & p_owner) {m_owner = p_owner;}
const void * get_data() const {return m_data.get_ptr();}
t_size get_data_size() const {return m_data.get_size();}
void set_data(const void * p_data,t_size p_data_size) {m_data.set_data_fromptr((const t_uint8*)p_data,p_data_size);}
void set_data_from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort);
private:
GUID m_owner;
pfc::array_t<t_uint8> m_data;
};
class NOVTABLE dsp_preset_edit_callback {
public:
virtual void on_preset_changed(const dsp_preset &) = 0;
private:
dsp_preset_edit_callback(const dsp_preset_edit_callback&) = delete;
const dsp_preset_edit_callback & operator=(const dsp_preset_edit_callback &) = delete;
protected:
dsp_preset_edit_callback() {}
~dsp_preset_edit_callback() {}
};
class NOVTABLE dsp_entry : public service_base {
public:
virtual void get_name(pfc::string_base & p_out) = 0;
virtual bool get_default_preset(dsp_preset & p_out) = 0;
virtual bool instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset) = 0;
virtual GUID get_guid() = 0;
virtual bool have_config_popup() = 0;
virtual bool show_config_popup(dsp_preset & p_data,HWND p_parent) = 0;
//! Obsolete method, hidden DSPs now use a different entry class.
bool is_user_accessible() { return true; }
static bool g_get_interface(service_ptr_t<dsp_entry> & p_out,const GUID & p_guid);
static bool g_instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset);
static bool g_instantiate_default(service_ptr_t<dsp> & p_out,const GUID & p_guid);
static bool g_name_from_guid(pfc::string_base & p_out,const GUID & p_guid);
static bool g_dsp_exists(const GUID & p_guid);
static bool g_get_default_preset(dsp_preset & p_out,const GUID & p_guid);
static bool g_have_config_popup(const GUID & p_guid);
static bool g_have_config_popup(const dsp_preset & p_preset);
static bool g_show_config_popup(dsp_preset & p_preset,HWND p_parent);
static void g_show_config_popup_v2(const dsp_preset & p_preset,HWND p_parent,dsp_preset_edit_callback & p_callback);
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(dsp_entry);
};
class NOVTABLE dsp_entry_v2 : public dsp_entry {
public:
virtual void show_config_popup_v2(const dsp_preset & p_data,HWND p_parent,dsp_preset_edit_callback & p_callback) = 0;
private:
bool show_config_popup(dsp_preset & p_data,HWND p_parent);
FB2K_MAKE_SERVICE_INTERFACE(dsp_entry_v2,dsp_entry);
};
class NOVTABLE dsp_entry_hidden : public service_base {
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(dsp_entry_hidden);
public:
//! Obsolete method, hidden DSPs now use a different entry class from ordinary ones.
bool is_user_accessible() {return false; }
static bool g_get_interface( dsp_entry_hidden::ptr & out, const GUID & guid );
static bool g_instantiate( dsp::ptr & out, const dsp_preset & preset );
static bool g_dsp_exists(const GUID & p_guid);
virtual bool instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset) = 0;
virtual GUID get_guid() = 0;
};
template<class T,class t_entry = dsp_entry>
class dsp_entry_impl_nopreset_t : public t_entry {
public:
void get_name(pfc::string_base & p_out) {T::g_get_name(p_out);}
bool get_default_preset(dsp_preset & p_out)
{
p_out.set_owner(T::g_get_guid());
p_out.set_data(0,0);
return true;
}
bool instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset)
{
if (p_preset.get_owner() == T::g_get_guid() && p_preset.get_data_size() == 0)
{
p_out = new service_impl_t<T>();
return p_out.is_valid();
}
else return false;
}
GUID get_guid() {return T::g_get_guid();}
bool have_config_popup() {return false;}
bool show_config_popup(dsp_preset & p_data,HWND p_parent) {return false;}
};
template<class T, class t_entry = dsp_entry_v2>
class dsp_entry_impl_t : public t_entry {
public:
void get_name(pfc::string_base & p_out) {T::g_get_name(p_out);}
bool get_default_preset(dsp_preset & p_out) {return T::g_get_default_preset(p_out);}
bool instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset) {
if (p_preset.get_owner() == T::g_get_guid()) {
p_out = new service_impl_t<T>(p_preset);
return true;
}
else return false;
}
GUID get_guid() {return T::g_get_guid();}
bool have_config_popup() {return T::g_have_config_popup();}
bool show_config_popup(dsp_preset & p_data,HWND p_parent) {return T::g_show_config_popup(p_data,p_parent);}
//void show_config_popup_v2(const dsp_preset & p_data,HWND p_parent,dsp_preset_edit_callback & p_callback) {T::g_show_config_popup(p_data,p_parent,p_callback);}
};
template<class T, class t_entry = dsp_entry_v2>
class dsp_entry_v2_impl_t : public t_entry {
public:
void get_name(pfc::string_base & p_out) {T::g_get_name(p_out);}
bool get_default_preset(dsp_preset & p_out) {return T::g_get_default_preset(p_out);}
bool instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset) {
if (p_preset.get_owner() == T::g_get_guid()) {
p_out = new service_impl_t<T>(p_preset);
return true;
}
else return false;
}
GUID get_guid() {return T::g_get_guid();}
bool have_config_popup() {return T::g_have_config_popup();}
//bool show_config_popup(dsp_preset & p_data,HWND p_parent) {return T::g_show_config_popup(p_data,p_parent);}
void show_config_popup_v2(const dsp_preset & p_data,HWND p_parent,dsp_preset_edit_callback & p_callback) {T::g_show_config_popup(p_data,p_parent,p_callback);}
};
template<typename T>
class dsp_entry_hidden_t : public dsp_entry_hidden {
public:
bool instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset) {
if (p_preset.get_owner() == T::g_get_guid()) {
p_out = new service_impl_t<T>(p_preset);
return true;
} else return false;
}
GUID get_guid() {return T::g_get_guid();}
#if 0
void get_name( pfc::string_base& out ) {out = ""; }
bool get_default_preset(dsp_preset & p_out) { return false; }
bool have_config_popup() { return false; }
bool show_config_popup(dsp_preset & p_data,HWND p_parent) { uBugCheck(); }
void show_config_popup_v2(const dsp_preset & p_data,HWND p_parent,dsp_preset_edit_callback & p_callback) { uBugCheck(); }
bool is_user_accessible() { return false; }
#endif
};
template<class T>
class dsp_factory_nopreset_t : public service_factory_single_t<dsp_entry_impl_nopreset_t<T> > {};
template<class T>
class dsp_factory_t : public service_factory_single_t<dsp_entry_v2_impl_t<T> > {};
template<class T>
class dsp_factory_hidden_t : public service_factory_single_t< dsp_entry_hidden_t<T> > {};
class NOVTABLE dsp_chain_config
{
public:
virtual t_size get_count() const = 0;
virtual const dsp_preset & get_item(t_size p_index) const = 0;
virtual void replace_item(const dsp_preset & p_data,t_size p_index) = 0;
virtual void insert_item(const dsp_preset & p_data,t_size p_index) = 0;
virtual void remove_mask(const bit_array & p_mask) = 0;
void remove_item(t_size p_index);
void remove_all();
void add_item(const dsp_preset & p_data);
void copy(const dsp_chain_config & p_source);
const dsp_chain_config & operator=(const dsp_chain_config & p_source) {copy(p_source); return *this;}
void contents_to_stream(stream_writer * p_stream,abort_callback & p_abort) const;
void contents_from_stream(stream_reader * p_stream,abort_callback & p_abort);
void instantiate(service_list_t<dsp> & p_out);
void get_name_list(pfc::string_base & p_out) const;
static bool equals(dsp_chain_config const & v1, dsp_chain_config const & v2);
static bool equals_debug(dsp_chain_config const& v1, dsp_chain_config const& v2);
pfc::string8 debug() const;
bool operator==(const dsp_chain_config & other) const {return equals(*this, other);}
bool operator!=(const dsp_chain_config & other) const {return !equals(*this, other);}
};
FB2K_STREAM_READER_OVERLOAD(dsp_chain_config) {
value.contents_from_stream(&stream.m_stream, stream.m_abort); return stream;
}
FB2K_STREAM_WRITER_OVERLOAD(dsp_chain_config) {
value.contents_to_stream(&stream.m_stream, stream.m_abort); return stream;
}
class dsp_chain_config_impl : public dsp_chain_config
{
public:
dsp_chain_config_impl() {}
dsp_chain_config_impl(const dsp_chain_config & p_source) {copy(p_source);}
dsp_chain_config_impl(const dsp_chain_config_impl & p_source) {copy(p_source);}
t_size get_count() const;
const dsp_preset & get_item(t_size p_index) const;
void replace_item(const dsp_preset & p_data,t_size p_index);
void insert_item(const dsp_preset & p_data,t_size p_index);
void remove_mask(const bit_array & p_mask);
const dsp_chain_config_impl & operator=(const dsp_chain_config & p_source) {copy(p_source); return *this;}
const dsp_chain_config_impl & operator=(const dsp_chain_config_impl & p_source) {copy(p_source); return *this;}
~dsp_chain_config_impl();
void reorder( const size_t * order, size_t count );
private:
pfc::ptr_list_t<dsp_preset_impl> m_data;
};
class cfg_dsp_chain_config : public cfg_var {
protected:
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort);
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort);
public:
void reset();
inline cfg_dsp_chain_config(const GUID & p_guid) : cfg_var(p_guid) {}
t_size get_count() const {return m_data.get_count();}
const dsp_preset & get_item(t_size p_index) const {return m_data.get_item(p_index);}
void get_data(dsp_chain_config & p_data) const;
void set_data(const dsp_chain_config & p_data);
dsp_chain_config_impl & _data() {return m_data; }
private:
dsp_chain_config_impl m_data;
};
class cfg_dsp_chain_config_mt : private cfg_var {
public:
cfg_dsp_chain_config_mt( const GUID & id ) : cfg_var(id) {}
void reset();
void get_data(dsp_chain_config & p_data);
void set_data(const dsp_chain_config & p_data);
protected:
void get_data_raw(stream_writer * p_stream, abort_callback & p_abort);
void set_data_raw(stream_reader * p_stream, t_size p_sizehint, abort_callback & p_abort);
private:
pfc::readWriteLock m_sync;
dsp_chain_config_impl m_data;
};
//! Helper.
class dsp_preset_parser : public stream_reader_formatter<> {
public:
dsp_preset_parser(const dsp_preset & in) : m_data(in), _m_stream(in.get_data(),in.get_data_size()), stream_reader_formatter(_m_stream,fb2k::noAbort) {}
void reset() {_m_stream.reset();}
t_size get_remaining() const {return _m_stream.get_remaining();}
void assume_empty() const {
if (get_remaining() != 0) throw exception_io_data();
}
GUID get_owner() const {return m_data.get_owner();}
private:
const dsp_preset & m_data;
stream_reader_memblock_ref _m_stream;
};
//! Helper.
class dsp_preset_builder : public stream_writer_formatter<> {
public:
dsp_preset_builder() : stream_writer_formatter(_m_stream,fb2k::noAbort) {}
void finish(const GUID & id, dsp_preset & out) {
out.set_owner(id);
out.set_data(_m_stream.m_buffer.get_ptr(), _m_stream.m_buffer.get_size());
}
void reset() {
_m_stream.m_buffer.set_size(0);
}
private:
stream_writer_buffer_simple _m_stream;
};
#endif

View File

@@ -0,0 +1,209 @@
#include "foobar2000.h"
#ifdef FOOBAR2000_HAVE_DSP
void dsp_manager::close() {
m_chain.remove_all();
m_config_changed = true;
}
void dsp_manager::set_config( const dsp_chain_config & p_data )
{
//dsp_chain_config::g_instantiate(m_dsp_list,p_data);
m_config.copy(p_data);
m_config_changed = true;
}
bool dsp_manager::need_track_change_mark() const {
for ( auto i = this->m_chain.first(); i.is_valid(); ++ i ) {
if ( i->m_dsp->need_track_change_mark() ) return true;
}
return false;
}
void dsp_manager::dsp_run(t_dsp_chain::const_iterator p_iter,dsp_chunk_list * p_list,const metadb_handle_ptr & cur_file,unsigned flags,double & latency,abort_callback & p_abort)
{
p_list->remove_bad_chunks();
TRACK_CODE("dsp::run",p_iter->m_dsp->run_abortable(p_list,cur_file,flags,p_abort));
TRACK_CODE("dsp::get_latency",latency += p_iter->m_dsp->get_latency());
}
double dsp_manager::run(dsp_chunk_list * p_list,const metadb_handle_ptr & p_cur_file,unsigned p_flags,abort_callback & p_abort) {
TRACK_CALL_TEXT("dsp_manager::run");
try {
#if defined(_MSC_VER) && defined(_M_IX86)
fpu_control_default l_fpu_control;
#endif
double latency=0;
bool done = false;
t_dsp_chain::const_iterator flush_mark;
if ((p_flags & dsp::END_OF_TRACK) && ! (p_flags & dsp::FLUSH)) {
for(t_dsp_chain::const_iterator iter = m_chain.first(); iter.is_valid(); ++iter) {
if (iter->m_dsp->need_track_change_mark()) flush_mark = iter;
}
}
if (m_config_changed)
{
t_dsp_chain newchain;
bool recycle_available = true;
for(t_size n=0;n<m_config.get_count();n++) {
service_ptr_t<dsp> temp;
const dsp_preset & preset = m_config.get_item(n);
const GUID owner = preset.get_owner();
if (dsp_entry::g_dsp_exists(owner) || dsp_entry_hidden::g_dsp_exists(owner)) {
t_dsp_chain::iterator iter = newchain.insert_last();
iter->m_preset = m_config.get_item(n);
iter->m_recycle_flag = false;
}
}
// Recycle existing DSPs in a special case when user has apparently only altered settings of one of DSPs.
if (newchain.get_count() == m_chain.get_count()) {
t_size data_mismatch_count = 0;
t_size owner_mismatch_count = 0;
t_dsp_chain::iterator iter_src, iter_dst;
iter_src = m_chain.first(); iter_dst = newchain.first();
while(iter_src.is_valid() && iter_dst.is_valid()) {
if (iter_src->m_preset.get_owner() != iter_dst->m_preset.get_owner()) {
owner_mismatch_count++;
} else if (iter_src->m_preset != iter_dst->m_preset) {
data_mismatch_count++;
}
++iter_src; ++iter_dst;
}
recycle_available = (owner_mismatch_count == 0 && data_mismatch_count <= 1);
} else {
recycle_available = false;
}
if (recycle_available) {
t_dsp_chain::iterator iter_src, iter_dst;
iter_src = m_chain.first(); iter_dst = newchain.first();
while(iter_src.is_valid() && iter_dst.is_valid()) {
if (iter_src->m_preset == iter_dst->m_preset) {
iter_src->m_recycle_flag = true;
iter_dst->m_dsp = iter_src->m_dsp;
}
++iter_src; ++iter_dst;
}
}
for(t_dsp_chain::iterator iter = newchain.first(); iter.is_valid(); ++iter) {
if (iter->m_dsp.is_empty()) {
if (!dsp_entry::g_instantiate(iter->m_dsp,iter->m_preset) && !dsp_entry_hidden::g_instantiate(iter->m_dsp, iter->m_preset)) uBugCheck();
}
}
if (m_chain.get_count()>0) {
bool flushflag = flush_mark.is_valid();
for(t_dsp_chain::const_iterator iter = m_chain.first(); iter.is_valid(); ++iter) {
unsigned flags2 = p_flags;
if (iter == flush_mark) flushflag = false;
if (flushflag || !iter->m_recycle_flag) flags2|=dsp::FLUSH;
dsp_run(iter,p_list,p_cur_file,flags2,latency,p_abort);
}
done = true;
}
m_chain = newchain;
m_config_changed = false;
}
if (!done)
{
bool flushflag = flush_mark.is_valid();
for(t_dsp_chain::const_iterator iter = m_chain.first(); iter.is_valid(); ++iter) {
unsigned flags2 = p_flags;
if (iter == flush_mark) flushflag = false;
if (flushflag) flags2|=dsp::FLUSH;
dsp_run(iter,p_list,p_cur_file,flags2,latency,p_abort);
}
done = true;
}
p_list->remove_bad_chunks();
return latency;
} catch(...) {
p_list->remove_all();
throw;
}
}
void dsp_manager::flush()
{
for(t_dsp_chain::const_iterator iter = m_chain.first(); iter.is_valid(); ++iter) {
TRACK_CODE("dsp::flush",iter->m_dsp->flush());
}
}
bool dsp_manager::is_active() const {return m_config.get_count()>0;}
void dsp_config_manager::core_enable_dsp(const dsp_preset & preset, default_insert_t insertWhere ) {
dsp_chain_config_impl cfg;
get_core_settings(cfg);
bool found = false;
bool changed = false;
t_size n,m = cfg.get_count();
for(n=0;n<m;n++) {
if (cfg.get_item(n).get_owner() == preset.get_owner()) {
found = true;
if (cfg.get_item(n) != preset) {
cfg.replace_item(preset,n);
changed = true;
}
break;
}
}
if (!found) {
if ( insertWhere == default_insert_last ) {
cfg.add_item( preset );
} else {
cfg.insert_item(preset,0);
}
changed = true;
}
if (changed) set_core_settings(cfg);
}
void dsp_config_manager::core_disable_dsp(const GUID & id) {
dsp_chain_config_impl cfg;
get_core_settings(cfg);
t_size n,m = cfg.get_count();
pfc::bit_array_bittable mask(m);
bool changed = false;
for(n=0;n<m;n++) {
bool axe = (cfg.get_item(n).get_owner() == id) ? true : false;
if (axe) changed = true;
mask.set(n,axe);
}
if (changed) {
cfg.remove_mask(mask);
set_core_settings(cfg);
}
}
bool dsp_config_manager::core_query_dsp(const GUID & id, dsp_preset & out) {
dsp_chain_config_impl cfg;
get_core_settings(cfg);
for(t_size n=0;n<cfg.get_count();n++) {
const dsp_preset & entry = cfg.get_item(n);
if (entry.get_owner() == id) {
out = entry; return true;
}
}
return false;
}
#endif

View File

@@ -0,0 +1,105 @@
#pragma once
#ifdef FOOBAR2000_HAVE_DSP
//! Helper class for running audio data through a DSP chain.
class dsp_manager {
public:
dsp_manager() {}
//! Alters the DSP chain configuration. Should be called before the first run() to set the configuration but can be also called anytime later between run() calls.
void set_config( const dsp_chain_config & p_data );
//! Runs DSP on the specified chunk list.
//! @returns Current DSP latency in seconds.
double run(dsp_chunk_list * p_list,const metadb_handle_ptr & p_cur_file,unsigned p_flags,abort_callback & p_abort);
//! Flushes the DSP (e.g. when seeking).
void flush();
//! Equivalent to set_config() with empty configuration.
void close();
//! Returns whether there's at least one active DSP in the configuration.
bool is_active() const;
bool need_track_change_mark() const;
private:
struct t_dsp_chain_entry {
service_ptr_t<dsp> m_dsp;
dsp_preset_impl m_preset;
bool m_recycle_flag;
};
typedef pfc::chain_list_v2_t<t_dsp_chain_entry> t_dsp_chain;
t_dsp_chain m_chain;
dsp_chain_config_impl m_config;
bool m_config_changed = false;
void dsp_run(t_dsp_chain::const_iterator p_iter,dsp_chunk_list * list,const metadb_handle_ptr & cur_file,unsigned flags,double & latency,abort_callback&);
dsp_manager(const dsp_manager &) = delete;
const dsp_manager & operator=(const dsp_manager&) = delete;
};
//! Core API for accessing core playback DSP settings as well as spawning DSP configuration dialogs. \n
//! Use dsp_config_manager::get() to obtain an instance.
class dsp_config_manager : public service_base {
FB2K_MAKE_SERVICE_COREAPI(dsp_config_manager);
public:
//! Retrieves current core playback DSP settings.
virtual void get_core_settings(dsp_chain_config & p_out) = 0;
//! Changes current core playback DSP settings.
virtual void set_core_settings(const dsp_chain_config & p_data) = 0;
//! Runs a modal DSP settings dialog.
//! @param p_data DSP chain configuration to edit - contains initial configuration to put in the dialog when called, receives the new configuration on successful edit.
//! @returns True when user approved DSP configuration changes (pressed the "OK" button), false when the user cancelled them ("Cancel" button).
virtual bool configure_popup(dsp_chain_config & p_data,HWND p_parent,const char * p_title) = 0;
//! Spawns an embedded DSP settings dialog.
//! @param p_initdata Initial DSP chain configuration to put in the dialog.
//! @param p_parent Parent window to contain the embedded dialog.
//! @param p_id Control ID of the embedded dialog. The parent window will receive a WM_COMMAND with BN_CLICKED and this identifier when user changes settings in the embedded dialog.
//! @param p_from_modal Must be set to true when the parent window is a modal dialog, false otherwise.
virtual HWND configure_embedded(const dsp_chain_config & p_initdata,HWND p_parent,unsigned p_id,bool p_from_modal) = 0;
//! Retrieves current settings from an embedded DSP settings dialog. See also: configure_embedded().
virtual void configure_embedded_retrieve(HWND wnd,dsp_chain_config & p_data) = 0;
//! Changes current settings in an embedded DSP settings dialog. See also: configure_embedded().
virtual void configure_embedded_change(HWND wnd,const dsp_chain_config & p_data) = 0;
enum default_insert_t {
default_insert_last,
default_insert_first,
};
//! Helper - enables a DSP in core playback settings.
void core_enable_dsp(const dsp_preset & preset, default_insert_t insertWhere = default_insert_first );
//! Helper - disables a DSP in core playback settings.
void core_disable_dsp(const GUID & id);
//! Helper - if a DSP with the specified identifier is present in playback settings, retrieves its configuration and returns true, otherwise returns false.
bool core_query_dsp(const GUID & id, dsp_preset & out);
};
//! \since 1.4
//! Allows manipulation of DSP presets saved by user. \n
//! Note that there's no multi thread safety implemented, all methods are valid from main thread only.
class dsp_config_manager_v2 : public dsp_config_manager {
FB2K_MAKE_SERVICE_COREAPI_EXTENSION(dsp_config_manager_v2, dsp_config_manager)
public:
virtual size_t get_preset_count() = 0;
virtual void get_preset_name( size_t index, pfc::string_base & out ) = 0;
virtual void get_preset_data( size_t index, dsp_chain_config & out ) = 0;
virtual void select_preset( size_t which ) = 0;
virtual size_t get_selected_preset() = 0;
};
//! Callback class for getting notified about core playback DSP settings getting altered. \n
//! Register your implementations with static service_factory_single_t<myclass> g_myclass_factory;
class NOVTABLE dsp_config_callback : public service_base {
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(dsp_config_callback);
public:
//! Called when core playback DSP settings change. \n
//! Note: you must not try to alter core playback DSP settings inside this callback, or call anything else that possibly alters core playback DSP settings.
virtual void on_core_settings_change(const dsp_chain_config & p_newdata) = 0;
};
#endif // FOOBAR2000_HAVE_DSP

View File

@@ -0,0 +1,50 @@
#pragma once
#if defined(FOOBAR2000_DESKTOP) || PFC_DEBUG
// RATIONALE
// Mobile target doesn't really care about event logging, logger interface exists there only for source compat
// We can use macros to suppress all PFC_string_formatter bloat for targets that do not care about any of this
#define FB2K_HAVE_EVENT_LOGGER
#endif
class NOVTABLE event_logger : public service_base {
FB2K_MAKE_SERVICE_INTERFACE(event_logger, service_base);
public:
enum {
severity_status,
severity_warning,
severity_error
};
void log_status(const char * line) {log_entry(line, severity_status);}
void log_warning(const char * line) {log_entry(line, severity_warning);}
void log_error(const char * line) {log_entry(line, severity_error);}
virtual void log_entry(const char * line, unsigned severity) = 0;
};
class event_logger_fallback : public event_logger {
public:
void log_entry(const char * line, unsigned) {console::print(line);}
};
class NOVTABLE event_logger_recorder : public event_logger {
FB2K_MAKE_SERVICE_INTERFACE( event_logger_recorder , event_logger );
public:
virtual void playback( event_logger::ptr playTo ) = 0;
static event_logger_recorder::ptr create();
};
#ifdef FB2K_HAVE_EVENT_LOGGER
#define FB2K_LOG_STATUS(X,Y) (X)->log_status(Y)
#define FB2K_LOG_WARNING(X,Y) (X)->log_warning(Y)
#define FB2K_LOG_ERROR(X,Y) (X)->log_error(Y)
#else
#define FB2K_LOG_STATUS(X,Y) ((void)0)
#define FB2K_LOG_WARNING(X,Y) ((void)0)
#define FB2K_LOG_ERROR(X,Y) ((void)0)
#endif

View File

@@ -0,0 +1,13 @@
//! Base class for exceptions that should show a human readable message when caught in app entrypoint function rather than crash and send a crash report.
PFC_DECLARE_EXCEPTION(exception_messagebox,pfc::exception,"Internal Error");
//! Base class for exceptions that should result in a quiet app shutdown.
PFC_DECLARE_EXCEPTION(exception_shutdownrequest,pfc::exception,"Shutdown Request");
PFC_DECLARE_EXCEPTION(exception_installdamaged, exception_messagebox, "Internal error - one or more of the installed components have been damaged.");
PFC_DECLARE_EXCEPTION(exception_osfailure, exception_messagebox, "Internal error - broken Windows installation?");
PFC_DECLARE_EXCEPTION(exception_out_of_resources, exception_messagebox, "Not enough system resources available.");
PFC_DECLARE_EXCEPTION(exception_configdamaged, exception_messagebox, "Internal error - configuration files are unreadable.");

View File

@@ -0,0 +1,387 @@
#include "foobar2000.h"
namespace {
#define FILE_CACHED_DEBUG_LOG 0
class file_cached_impl_v2 : public service_multi_inherit< file_cached, file_lowLevelIO > {
public:
enum {minBlockSize = 4096};
enum {maxSkipSize = 128*1024};
file_cached_impl_v2(size_t maxBlockSize) : m_maxBlockSize(maxBlockSize) {
//m_buffer.set_size(blocksize);
}
size_t get_cache_block_size() {return m_maxBlockSize;}
void suggest_grow_cache(size_t suggestSize) {
if (m_maxBlockSize < suggestSize) m_maxBlockSize = suggestSize;
}
void initialize(service_ptr_t<file> p_base,abort_callback & p_abort) {
m_base = p_base;
m_can_seek = m_base->can_seek();
_reinit(p_abort);
}
size_t lowLevelIO(const GUID & guid, size_t arg1, void * arg2, size_t arg2size, abort_callback & abort) override {
abort.check();
file_lowLevelIO::ptr ll;
if ( ll &= m_base ) {
flush_buffer();
return ll->lowLevelIO(guid, arg1, arg2, arg2size, abort );
}
return 0;
}
private:
void _reinit(abort_callback & p_abort) {
m_position = 0;
if (m_can_seek) {
m_position_base = m_base->get_position(p_abort);
} else {
m_position_base = 0;
}
m_size = m_base->get_size(p_abort);
flush_buffer();
}
public:
t_filesize skip(t_filesize p_bytes,abort_callback & p_abort) {
if (p_bytes > maxSkipSize) {
const t_filesize size = get_size(p_abort);
if (size != filesize_invalid) {
const t_filesize position = get_position(p_abort);
const t_filesize toskip = pfc::min_t( p_bytes, size - position );
seek(position + toskip,p_abort);
return toskip;
}
}
return skip_( p_bytes, p_abort );
}
t_filesize skip_(t_filesize p_bytes,abort_callback & p_abort) {
#if FILE_CACHED_DEBUG_LOG
FB2K_DebugLog() << "Skipping bytes: " << p_bytes;
#endif
t_filesize todo = p_bytes;
for(;;) {
size_t inBuffer = this->bufferRemaining();
size_t delta = (size_t) pfc::min_t<t_filesize>(inBuffer, todo);
m_bufferReadPtr += delta;
m_position += delta;
todo -= delta;
if (todo == 0) break;
p_abort.check();
this->m_bufferState = 0; // null it early to leave in a consistent state if base read fails
this->m_bufferReadPtr = 0;
baseSeek(m_position,p_abort);
m_readSize = pfc::min_t<size_t>(m_readSize << 1, this->m_maxBlockSize);
if (m_readSize < minBlockSize) m_readSize = minBlockSize;
#if FILE_CACHED_DEBUG_LOG
FB2K_DebugLog() << "Growing read size: " << m_readSize;
#endif
m_buffer.grow_size(m_readSize);
m_bufferState = m_base->read(m_buffer.get_ptr(), m_readSize, p_abort);
if (m_bufferState == 0) break;
m_position_base += m_bufferState;
}
return p_bytes - todo;
}
t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
#if FILE_CACHED_DEBUG_LOG
FB2K_DebugLog() << "Reading bytes: " << p_bytes;
#endif
t_uint8 * outptr = (t_uint8*)p_buffer;
size_t todo = p_bytes;
for(;;) {
size_t inBuffer = this->bufferRemaining();
size_t delta = pfc::min_t<size_t>(inBuffer, todo);
memcpy(outptr, this->m_buffer.get_ptr() + m_bufferReadPtr, delta);
m_bufferReadPtr += delta;
m_position += delta;
todo -= delta;
if (todo == 0) break;
p_abort.check();
outptr += delta;
this->m_bufferState = 0; // null it early to leave in a consistent state if base read fails
this->m_bufferReadPtr = 0;
baseSeek(m_position,p_abort);
m_readSize = pfc::min_t<size_t>(m_readSize << 1, this->m_maxBlockSize);
if (m_readSize < minBlockSize) m_readSize = minBlockSize;
#if FILE_CACHED_DEBUG_LOG
FB2K_DebugLog() << "Growing read size: " << m_readSize;
#endif
m_buffer.grow_size(m_readSize);
m_bufferState = m_base->read(m_buffer.get_ptr(), m_readSize, p_abort);
if (m_bufferState == 0) break;
m_position_base += m_bufferState;
}
return p_bytes - todo;
}
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
#if FILE_CACHED_DEBUG_LOG
FB2K_DebugLog() << "Writing bytes: " << p_bytes;
#endif
p_abort.check();
baseSeek(m_position,p_abort);
m_base->write(p_buffer,p_bytes,p_abort);
m_position_base = m_position = m_position + p_bytes;
if (m_size < m_position) m_size = m_position;
flush_buffer();
}
t_filesize get_size(abort_callback & p_abort) {
p_abort.check();
return m_size;
}
t_filesize get_position(abort_callback & p_abort) {
p_abort.check();
return m_position;
}
void set_eof(abort_callback & p_abort) {
p_abort.check();
baseSeek(m_position,p_abort);
m_base->set_eof(p_abort);
flush_buffer();
}
void seek(t_filesize p_position,abort_callback & p_abort) {
#if FILE_CACHED_DEBUG_LOG
FB2K_DebugLog() << "Seeking: " << p_position;
#endif
p_abort.check();
if (!m_can_seek) throw exception_io_object_not_seekable();
if (p_position > m_size) throw exception_io_seek_out_of_range();
int64_t delta = p_position - m_position;
// special case
if (delta >= 0 && delta <= this->minBlockSize) {
#if FILE_CACHED_DEBUG_LOG
FB2K_DebugLog() << "Skip-seeking: " << p_position;
#endif
t_filesize skipped = this->skip_( delta, p_abort );
PFC_ASSERT( skipped == delta ); (void) skipped;
return;
}
m_position = p_position;
// within currently buffered data?
if ((delta >= 0 && (uint64_t) delta <= bufferRemaining()) || (delta < 0 && (uint64_t)(-delta) <= m_bufferReadPtr)) {
#if FILE_CACHED_DEBUG_LOG
FB2K_DebugLog() << "Quick-seeking: " << p_position;
#endif
m_bufferReadPtr += (ptrdiff_t)delta;
} else {
#if FILE_CACHED_DEBUG_LOG
FB2K_DebugLog() << "Slow-seeking: " << p_position;
#endif
this->flush_buffer();
}
}
void reopen(abort_callback & p_abort) {
if (this->m_can_seek) {
seek(0,p_abort);
} else {
this->m_base->reopen( p_abort );
this->_reinit( p_abort );
}
}
bool can_seek() {return m_can_seek;}
bool get_content_type(pfc::string_base & out) {return m_base->get_content_type(out);}
void on_idle(abort_callback & p_abort) {p_abort.check();m_base->on_idle(p_abort);}
t_filetimestamp get_timestamp(abort_callback & p_abort) {p_abort.check(); return m_base->get_timestamp(p_abort);}
bool is_remote() {return m_base->is_remote();}
void resize(t_filesize p_size,abort_callback & p_abort) {
flush_buffer();
m_base->resize(p_size,p_abort);
m_size = p_size;
if (m_position > m_size) m_position = m_size;
if (m_position_base > m_size) m_position_base = m_size;
}
private:
size_t bufferRemaining() const {return m_bufferState - m_bufferReadPtr;}
void baseSeek(t_filesize p_target,abort_callback & p_abort) {
if (p_target != m_position_base) {
m_base->seek(p_target,p_abort);
m_position_base = p_target;
}
}
void flush_buffer() {
m_bufferState = m_bufferReadPtr = 0;
m_readSize = 0;
}
service_ptr_t<file> m_base;
t_filesize m_position,m_position_base,m_size;
bool m_can_seek;
size_t m_bufferState, m_bufferReadPtr;
pfc::array_t<t_uint8> m_buffer;
size_t m_maxBlockSize;
size_t m_readSize;
};
class file_cached_impl : public service_multi_inherit< file_cached, file_lowLevelIO > {
public:
file_cached_impl(t_size blocksize) {
m_buffer.set_size(blocksize);
}
size_t get_cache_block_size() {return m_buffer.get_size();}
void suggest_grow_cache(size_t suggestSize) {}
void initialize(service_ptr_t<file> p_base,abort_callback & p_abort) {
m_base = p_base;
m_can_seek = m_base->can_seek();
_reinit(p_abort);
}
private:
void _reinit(abort_callback & p_abort) {
m_position = 0;
if (m_can_seek) {
m_position_base = m_base->get_position(p_abort);
} else {
m_position_base = 0;
}
m_size = m_base->get_size(p_abort);
flush_buffer();
}
public:
size_t lowLevelIO(const GUID & guid, size_t arg1, void * arg2, size_t arg2size, abort_callback & abort) override {
abort.check();
file_lowLevelIO::ptr ll;
if ( ll &= m_base ) {
flush_buffer();
return ll->lowLevelIO(guid, arg1, arg2, arg2size, abort);
}
return 0;
}
t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
t_uint8 * outptr = (t_uint8*)p_buffer;
t_size done = 0;
while(done < p_bytes && m_position < m_size) {
p_abort.check();
if (m_position >= m_buffer_position && m_position < m_buffer_position + m_buffer_status) {
t_size delta = pfc::min_t<t_size>((t_size)(m_buffer_position + m_buffer_status - m_position),p_bytes - done);
t_size bufptr = (t_size)(m_position - m_buffer_position);
memcpy(outptr+done,m_buffer.get_ptr()+bufptr,delta);
done += delta;
m_position += delta;
if (m_buffer_status != m_buffer.get_size() && done < p_bytes) break;//EOF before m_size is hit
} else {
m_buffer_position = m_position - m_position % m_buffer.get_size();
baseSeek(m_buffer_position,p_abort);
m_buffer_status = m_base->read(m_buffer.get_ptr(),m_buffer.get_size(),p_abort);
m_position_base += m_buffer_status;
if (m_buffer_status <= (t_size)(m_position - m_buffer_position)) break;
}
}
return done;
}
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
p_abort.check();
baseSeek(m_position,p_abort);
m_base->write(p_buffer,p_bytes,p_abort);
m_position_base = m_position = m_position + p_bytes;
if (m_size < m_position) m_size = m_position;
flush_buffer();
}
t_filesize get_size(abort_callback & p_abort) {
p_abort.check();
return m_size;
}
t_filesize get_position(abort_callback & p_abort) {
p_abort.check();
return m_position;
}
void set_eof(abort_callback & p_abort) {
p_abort.check();
baseSeek(m_position,p_abort);
m_base->set_eof(p_abort);
flush_buffer();
}
void seek(t_filesize p_position,abort_callback & p_abort) {
p_abort.check();
if (!m_can_seek) throw exception_io_object_not_seekable();
if (p_position > m_size) throw exception_io_seek_out_of_range();
m_position = p_position;
}
void reopen(abort_callback & p_abort) {
if (this->m_can_seek) {
seek(0,p_abort);
} else {
this->m_base->reopen( p_abort );
this->_reinit( p_abort );
}
}
bool can_seek() {return m_can_seek;}
bool get_content_type(pfc::string_base & out) {return m_base->get_content_type(out);}
void on_idle(abort_callback & p_abort) {p_abort.check();m_base->on_idle(p_abort);}
t_filetimestamp get_timestamp(abort_callback & p_abort) {p_abort.check(); return m_base->get_timestamp(p_abort);}
bool is_remote() {return m_base->is_remote();}
void resize(t_filesize p_size,abort_callback & p_abort) {
flush_buffer();
m_base->resize(p_size,p_abort);
m_size = p_size;
if (m_position > m_size) m_position = m_size;
if (m_position_base > m_size) m_position_base = m_size;
}
private:
void baseSeek(t_filesize p_target,abort_callback & p_abort) {
if (p_target != m_position_base) {
m_base->seek(p_target,p_abort);
m_position_base = p_target;
}
}
void flush_buffer() {
m_buffer_status = 0;
m_buffer_position = 0;
}
service_ptr_t<file> m_base;
t_filesize m_position,m_position_base,m_size;
bool m_can_seek;
t_filesize m_buffer_position;
t_size m_buffer_status;
pfc::array_t<t_uint8> m_buffer;
};
}
file::ptr file_cached::g_create(service_ptr_t<file> p_base,abort_callback & p_abort, t_size blockSize) {
if (p_base->is_in_memory()) {
return p_base; // do not want
}
{ // do not duplicate cache layers, check if the file we're being handed isn't already cached
file_cached::ptr c;
if (p_base->service_query_t(c)) {
c->suggest_grow_cache(blockSize);
return p_base;
}
}
service_ptr_t<file_cached_impl_v2> temp = new service_impl_t<file_cached_impl_v2>(blockSize);
temp->initialize(p_base,p_abort);
return temp;
}
void file_cached::g_create(service_ptr_t<file> & p_out,service_ptr_t<file> p_base,abort_callback & p_abort, t_size blockSize) {
p_out = g_create(p_base, p_abort, blockSize);
}
void file_cached::g_decodeInitCache(file::ptr & theFile, abort_callback & abort, size_t blockSize) {
if (theFile->is_remote() || !theFile->can_seek()) return;
g_create(theFile, theFile, abort, blockSize);
}

View File

@@ -0,0 +1,27 @@
#pragma once
#ifdef FOOBAR2000_HAVE_FILE_FORMAT_SANITIZER
//! Utility service to perform file format specific cleanup routines, optimize tags layout, remove padding, etc.
class NOVTABLE file_format_sanitizer : public service_base {
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT( file_format_sanitizer );
public:
//! Returns whether the file path appears to be of a supported format. \n
//! Used for display purposes (menu command will be disabled when no selected file can be cleaned up).
virtual bool is_supported_format( const char * path, const char * ext ) = 0;
//! Performs file format specific cleanup of the file: \n
//! Strips excessive padding, optimizes file layout for network streaming (MP4). \n
//! @param path File path to clean up. The file must be writeable. \n
//! @param bMinimizeSize Set to true to throw away all padding. If set to false, some padding will be left to allow future tag updates without full file rewrite.
//! @returns True if the file has been successfully processed, false if we do not resupport this file format.
virtual bool sanitize_file( const char * path, bool bMinimizeSize, abort_callback & aborter ) = 0;
};
//! Utility service to perform sanitization of generic ID3v2 tags. Called by format-specific implementations of file_format_sanitizer.
class NOVTABLE file_format_sanitizer_stdtags : public service_base {
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT( file_format_sanitizer_stdtags );
public:
//! Similar to file_format_sanitizer method of the same name. Performs sanitization of generic ID3v2 tags.
virtual bool sanitize_file( const char * path, bool bMinimizeSize, abort_callback & aborter ) = 0;
};
#endif // FOOBAR2000_HAVE_FILE_FORMAT_SANITIZER

View File

@@ -0,0 +1,769 @@
#include "foobar2000.h"
#ifndef _MSC_VER
#define strcat_s strcat
#define _atoi64 atoll
#endif
const float replaygain_info::peak_invalid = -1;
const float replaygain_info::gain_invalid = -1000;
t_size file_info::meta_find_ex(const char * p_name,t_size p_name_length) const
{
t_size n, m = meta_get_count();
for(n=0;n<m;n++)
{
if (pfc::stricmp_ascii_ex(meta_enum_name(n),pfc_infinite,p_name,p_name_length) == 0) return n;
}
return pfc_infinite;
}
bool file_info::meta_exists_ex(const char * p_name,t_size p_name_length) const
{
return meta_find_ex(p_name,p_name_length) != pfc_infinite;
}
void file_info::meta_remove_field_ex(const char * p_name,t_size p_name_length)
{
t_size index = meta_find_ex(p_name,p_name_length);
if (index!=pfc_infinite) meta_remove_index(index);
}
void file_info::meta_remove_index(t_size p_index)
{
meta_remove_mask(pfc::bit_array_one(p_index));
}
void file_info::meta_remove_all()
{
meta_remove_mask(pfc::bit_array_true());
}
void file_info::meta_remove_value(t_size p_index,t_size p_value)
{
meta_remove_values(p_index, pfc::bit_array_one(p_value));
}
t_size file_info::meta_get_count_by_name_ex(const char * p_name,t_size p_name_length) const
{
t_size index = meta_find_ex(p_name,p_name_length);
if (index == pfc_infinite) return 0;
return meta_enum_value_count(index);
}
t_size file_info::info_find_ex(const char * p_name,t_size p_name_length) const
{
t_size n, m = info_get_count();
for(n=0;n<m;n++) {
if (pfc::stricmp_ascii_ex(info_enum_name(n),pfc_infinite,p_name,p_name_length) == 0) return n;
}
return pfc_infinite;
}
bool file_info::info_exists_ex(const char * p_name,t_size p_name_length) const
{
return info_find_ex(p_name,p_name_length) != pfc_infinite;
}
void file_info::info_remove_index(t_size p_index)
{
info_remove_mask(pfc::bit_array_one(p_index));
}
void file_info::info_remove_all()
{
info_remove_mask(pfc::bit_array_true());
}
bool file_info::info_remove_ex(const char * p_name,t_size p_name_length)
{
t_size index = info_find_ex(p_name,p_name_length);
if (index != pfc_infinite)
{
info_remove_index(index);
return true;
}
else return false;
}
void file_info::overwrite_meta(const file_info & p_source) {
const t_size total = p_source.meta_get_count();
for(t_size walk = 0; walk < total; ++walk) {
copy_meta_single(p_source, walk);
}
}
void file_info::copy_meta_single(const file_info & p_source,t_size p_index)
{
copy_meta_single_rename(p_source,p_index,p_source.meta_enum_name(p_index));
}
void file_info::copy_meta_single_nocheck(const file_info & p_source,t_size p_index)
{
const char * name = p_source.meta_enum_name(p_index);
t_size n, m = p_source.meta_enum_value_count(p_index);
t_size new_index = pfc_infinite;
for(n=0;n<m;n++)
{
const char * value = p_source.meta_enum_value(p_index,n);
if (n == 0) new_index = meta_set_nocheck(name,value);
else meta_add_value(new_index,value);
}
}
void file_info::copy_meta_single_by_name_ex(const file_info & p_source,const char * p_name,t_size p_name_length)
{
t_size index = p_source.meta_find_ex(p_name,p_name_length);
if (index != pfc_infinite) copy_meta_single(p_source,index);
}
void file_info::copy_info_single_by_name_ex(const file_info & p_source,const char * p_name,t_size p_name_length)
{
t_size index = p_source.info_find_ex(p_name,p_name_length);
if (index != pfc_infinite) copy_info_single(p_source,index);
}
void file_info::copy_meta_single_by_name_nocheck_ex(const file_info & p_source,const char * p_name,t_size p_name_length)
{
t_size index = p_source.meta_find_ex(p_name,p_name_length);
if (index != pfc_infinite) copy_meta_single_nocheck(p_source,index);
}
void file_info::copy_info_single_by_name_nocheck_ex(const file_info & p_source,const char * p_name,t_size p_name_length)
{
t_size index = p_source.info_find_ex(p_name,p_name_length);
if (index != pfc_infinite) copy_info_single_nocheck(p_source,index);
}
void file_info::copy_info_single(const file_info & p_source,t_size p_index)
{
info_set(p_source.info_enum_name(p_index),p_source.info_enum_value(p_index));
}
void file_info::copy_info_single_nocheck(const file_info & p_source,t_size p_index)
{
info_set_nocheck(p_source.info_enum_name(p_index),p_source.info_enum_value(p_index));
}
void file_info::copy_meta(const file_info & p_source)
{
if (&p_source != this) {
meta_remove_all();
t_size n, m = p_source.meta_get_count();
for(n=0;n<m;n++)
copy_meta_single_nocheck(p_source,n);
}
}
void file_info::copy_info(const file_info & p_source)
{
if (&p_source != this) {
info_remove_all();
t_size n, m = p_source.info_get_count();
for(n=0;n<m;n++)
copy_info_single_nocheck(p_source,n);
}
}
void file_info::copy(const file_info & p_source)
{
if (&p_source != this) {
copy_meta(p_source);
copy_info(p_source);
set_length(p_source.get_length());
set_replaygain(p_source.get_replaygain());
}
}
const char * file_info::meta_get_ex(const char * p_name,t_size p_name_length,t_size p_index) const
{
t_size index = meta_find_ex(p_name,p_name_length);
if (index == pfc_infinite) return 0;
t_size max = meta_enum_value_count(index);
if (p_index >= max) return 0;
return meta_enum_value(index,p_index);
}
const char * file_info::info_get_ex(const char * p_name,t_size p_name_length) const
{
t_size index = info_find_ex(p_name,p_name_length);
if (index == pfc_infinite) return 0;
return info_enum_value(index);
}
t_int64 file_info::info_get_int(const char * name) const
{
PFC_ASSERT(pfc::is_valid_utf8(name));
const char * val = info_get(name);
if (val==0) return 0;
return _atoi64(val);
}
t_int64 file_info::info_get_length_samples() const
{
t_int64 ret = 0;
double len = get_length();
t_int64 srate = info_get_int("samplerate");
if (srate>0 && len>0)
{
ret = audio_math::time_to_samples(len,(unsigned)srate);
}
return ret;
}
double file_info::info_get_float(const char * name) const
{
const char * ptr = info_get(name);
if (ptr) return pfc::string_to_float(ptr);
else return 0;
}
void file_info::info_set_int(const char * name,t_int64 value)
{
PFC_ASSERT(pfc::is_valid_utf8(name));
info_set(name,pfc::format_int(value));
}
void file_info::info_set_float(const char * name,double value,unsigned precision,bool force_sign,const char * unit)
{
PFC_ASSERT(pfc::is_valid_utf8(name));
PFC_ASSERT(unit==0 || strlen(unit) <= 64);
char temp[128];
pfc::float_to_string(temp,64,value,precision,force_sign);
temp[63] = 0;
if (unit)
{
strcat_s(temp," ");
strcat_s(temp,unit);
}
info_set(name,temp);
}
void file_info::info_set_replaygain_album_gain(float value)
{
replaygain_info temp = get_replaygain();
temp.m_album_gain = value;
set_replaygain(temp);
}
void file_info::info_set_replaygain_album_peak(float value)
{
replaygain_info temp = get_replaygain();
temp.m_album_peak = value;
set_replaygain(temp);
}
void file_info::info_set_replaygain_track_gain(float value)
{
replaygain_info temp = get_replaygain();
temp.m_track_gain = value;
set_replaygain(temp);
}
void file_info::info_set_replaygain_track_peak(float value)
{
replaygain_info temp = get_replaygain();
temp.m_track_peak = value;
set_replaygain(temp);
}
static bool is_valid_bps(t_int64 val)
{
return val>0 && val<=256;
}
unsigned file_info::info_get_decoded_bps() const
{
t_int64 val = info_get_int("decoded_bitspersample");
if (is_valid_bps(val)) return (unsigned)val;
val = info_get_int("bitspersample");
if (is_valid_bps(val)) return (unsigned)val;
return 0;
}
void file_info::reset()
{
info_remove_all();
meta_remove_all();
set_length(0);
reset_replaygain();
}
void file_info::reset_replaygain()
{
replaygain_info temp;
temp.reset();
set_replaygain(temp);
}
void file_info::copy_meta_single_rename_ex(const file_info & p_source,t_size p_index,const char * p_new_name,t_size p_new_name_length)
{
t_size n, m = p_source.meta_enum_value_count(p_index);
t_size new_index = pfc_infinite;
for(n=0;n<m;n++)
{
const char * value = p_source.meta_enum_value(p_index,n);
if (n == 0) new_index = meta_set_ex(p_new_name,p_new_name_length,value,pfc_infinite);
else meta_add_value(new_index,value);
}
}
t_size file_info::meta_add_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length)
{
t_size index = meta_find_ex(p_name,p_name_length);
if (index == pfc_infinite) return meta_set_nocheck_ex(p_name,p_name_length,p_value,p_value_length);
else
{
meta_add_value_ex(index,p_value,p_value_length);
return index;
}
}
void file_info::meta_add_value_ex(t_size p_index,const char * p_value,t_size p_value_length)
{
meta_insert_value_ex(p_index,meta_enum_value_count(p_index),p_value,p_value_length);
}
t_size file_info::meta_calc_total_value_count() const
{
t_size n, m = meta_get_count(), ret = 0;
for(n=0;n<m;n++) ret += meta_enum_value_count(n);
return ret;
}
bool file_info::info_set_replaygain_ex(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len)
{
replaygain_info temp = get_replaygain();
if (temp.set_from_meta_ex(p_name,p_name_len,p_value,p_value_len))
{
set_replaygain(temp);
return true;
}
else return false;
}
void file_info::info_set_replaygain_auto_ex(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len)
{
if (!info_set_replaygain_ex(p_name,p_name_len,p_value,p_value_len))
info_set_ex(p_name,p_name_len,p_value,p_value_len);
}
static bool _matchGain(float g1, float g2) {
if (g1 == replaygain_info::gain_invalid && g2 == replaygain_info::gain_invalid) return true;
else if (g1 == replaygain_info::gain_invalid || g2 == replaygain_info::gain_invalid) return false;
else return fabs(g1-g2) < 0.1;
}
static bool _matchPeak(float p1, float p2) {
if (p1 == replaygain_info::peak_invalid && p2 == replaygain_info::peak_invalid) return true;
else if (p1 == replaygain_info::peak_invalid || p2 == replaygain_info::peak_invalid) return false;
else return fabs(p1-p2) < 0.01;
}
bool replaygain_info::g_equalLoose( const replaygain_info & i1, const replaygain_info & i2) {
return _matchGain(i1.m_track_gain, i2.m_track_gain) && _matchGain(i1.m_album_gain, i2.m_album_gain) && _matchPeak(i1.m_track_peak, i2.m_track_peak) && _matchPeak(i1.m_album_peak, i2.m_album_peak);
}
bool replaygain_info::g_equal(const replaygain_info & item1,const replaygain_info & item2)
{
return item1.m_album_gain == item2.m_album_gain &&
item1.m_track_gain == item2.m_track_gain &&
item1.m_album_peak == item2.m_album_peak &&
item1.m_track_peak == item2.m_track_peak;
}
bool file_info::are_meta_fields_identical(t_size p_index1,t_size p_index2) const
{
const t_size count = meta_enum_value_count(p_index1);
if (count != meta_enum_value_count(p_index2)) return false;
t_size n;
for(n=0;n<count;n++)
{
if (strcmp(meta_enum_value(p_index1,n),meta_enum_value(p_index2,n))) return false;
}
return true;
}
void file_info::meta_format_entry(t_size index, pfc::string_base & out, const char * separator) const {
out.reset();
t_size val, count = meta_enum_value_count(index);
PFC_ASSERT( count > 0);
for(val=0;val<count;val++)
{
if (val > 0) out += separator;
out += meta_enum_value(index,val);
}
}
bool file_info::meta_format(const char * p_name,pfc::string_base & p_out, const char * separator) const {
p_out.reset();
t_size index = meta_find(p_name);
if (index == pfc_infinite) return false;
meta_format_entry(index, p_out, separator);
return true;
}
void file_info::info_calculate_bitrate(t_filesize p_filesize,double p_length)
{
unsigned b = audio_math::bitrate_kbps( p_filesize, p_length );
if ( b > 0 ) info_set_bitrate(b);
}
bool file_info::is_encoding_overkill() const {
auto bs = info_get_int("bitspersample");
auto extra = info_get("bitspersample_extra");
if ( bs <= 24 ) return false; // fixedpoint up to 24bit, OK
if ( bs > 32 ) return true; // fixed or float beyond 32bit, overkill
if ( extra != nullptr ) {
if (strcmp(extra, "fixed-point") == 0) return true; // int32, overkill
}
return false;
}
bool file_info::is_encoding_lossy() const {
const char * encoding = info_get("encoding");
if (encoding != NULL) {
if (pfc::stricmp_ascii(encoding,"lossy") == 0 /*|| pfc::stricmp_ascii(encoding,"hybrid") == 0*/) return true;
} else {
//the old way
//disabled: don't whine if we're not sure what we're dealing with - might be a file with info not-yet-loaded in oddball cases or a mod file
//if (info_get("bitspersample") == NULL) return true;
}
return false;
}
bool file_info::g_is_meta_equal(const file_info & p_item1,const file_info & p_item2) {
const t_size count = p_item1.meta_get_count();
if (count != p_item2.meta_get_count()) {
//uDebugLog() << "meta count mismatch";
return false;
}
pfc::map_t<const char*,t_size,field_name_comparator> item2_meta_map;
for(t_size n=0; n<count; n++) {
item2_meta_map.set(p_item2.meta_enum_name(n),n);
}
for(t_size n1=0; n1<count; n1++) {
t_size n2;
if (!item2_meta_map.query(p_item1.meta_enum_name(n1),n2)) {
//uDebugLog() << "item2 doesn't have " << p_item1.meta_enum_name(n1);
return false;
}
t_size value_count = p_item1.meta_enum_value_count(n1);
if (value_count != p_item2.meta_enum_value_count(n2)) {
//uDebugLog() << "meta value count mismatch: " << p_item1.meta_enum_name(n1) << " : " << value_count << " vs " << p_item2.meta_enum_value_count(n2);
return false;
}
for(t_size v = 0; v < value_count; v++) {
if (strcmp(p_item1.meta_enum_value(n1,v),p_item2.meta_enum_value(n2,v)) != 0) {
//uDebugLog() << "meta mismatch: " << p_item1.meta_enum_name(n1) << " : " << p_item1.meta_enum_value(n1,v) << " vs " << p_item2.meta_enum_value(n2,v);
return false;
}
}
}
return true;
}
bool file_info::g_is_meta_equal_debug(const file_info & p_item1,const file_info & p_item2) {
const t_size count = p_item1.meta_get_count();
if (count != p_item2.meta_get_count()) {
FB2K_DebugLog() << "meta count mismatch";
return false;
}
pfc::map_t<const char*,t_size,field_name_comparator> item2_meta_map;
for(t_size n=0; n<count; n++) {
item2_meta_map.set(p_item2.meta_enum_name(n),n);
}
for(t_size n1=0; n1<count; n1++) {
t_size n2;
if (!item2_meta_map.query(p_item1.meta_enum_name(n1),n2)) {
FB2K_DebugLog() << "item2 doesn't have " << p_item1.meta_enum_name(n1);
return false;
}
t_size value_count = p_item1.meta_enum_value_count(n1);
if (value_count != p_item2.meta_enum_value_count(n2)) {
FB2K_DebugLog() << "meta value count mismatch: " << p_item1.meta_enum_name(n1) << " : " << (uint32_t)value_count << " vs " << (uint32_t)p_item2.meta_enum_value_count(n2);
return false;
}
for(t_size v = 0; v < value_count; v++) {
if (strcmp(p_item1.meta_enum_value(n1,v),p_item2.meta_enum_value(n2,v)) != 0) {
FB2K_DebugLog() << "meta mismatch: " << p_item1.meta_enum_name(n1) << " : " << p_item1.meta_enum_value(n1,v) << " vs " << p_item2.meta_enum_value(n2,v);
return false;
}
}
}
return true;
}
bool file_info::g_is_info_equal(const file_info & p_item1,const file_info & p_item2) {
t_size count = p_item1.info_get_count();
if (count != p_item2.info_get_count()) {
//uDebugLog() << "info count mismatch";
return false;
}
for(t_size n1=0; n1<count; n1++) {
t_size n2 = p_item2.info_find(p_item1.info_enum_name(n1));
if (n2 == pfc_infinite) {
//uDebugLog() << "item2 does not have " << p_item1.info_enum_name(n1);
return false;
}
if (strcmp(p_item1.info_enum_value(n1),p_item2.info_enum_value(n2)) != 0) {
//uDebugLog() << "value mismatch: " << p_item1.info_enum_name(n1);
return false;
}
}
return true;
}
static bool is_valid_field_name_char(char p_char) {
return p_char >= 32 && p_char < 127 && p_char != '=' && p_char != '%' && p_char != '<' && p_char != '>';
}
bool file_info::g_is_valid_field_name(const char * p_name,t_size p_length) {
t_size walk;
for(walk = 0; walk < p_length && p_name[walk] != 0; walk++) {
if (!is_valid_field_name_char(p_name[walk])) return false;
}
return walk > 0;
}
void file_info::to_formatter(pfc::string_formatter& out) const {
out << "File info dump:\n";
if (get_length() > 0) out<< "Duration: " << pfc::format_time_ex(get_length(), 6) << "\n";
pfc::string_formatter temp;
for(t_size metaWalk = 0; metaWalk < meta_get_count(); ++metaWalk) {
meta_format_entry(metaWalk, temp);
out << "Meta: " << meta_enum_name(metaWalk) << " = " << temp << "\n";
}
for(t_size infoWalk = 0; infoWalk < info_get_count(); ++infoWalk) {
out << "Info: " << info_enum_name(infoWalk) << " = " << info_enum_value(infoWalk) << "\n";
}
auto rg = this->get_replaygain();
replaygain_info::t_text_buffer rgbuf;
if (rg.format_track_gain(rgbuf)) out << "RG track gain: " << rgbuf << "\n";
if (rg.format_track_peak(rgbuf)) out << "RG track peak: " << rgbuf << "\n";
if (rg.format_album_gain(rgbuf)) out << "RG album gain: " << rgbuf << "\n";
if (rg.format_album_peak(rgbuf)) out << "RG album peak: " << rgbuf << "\n";
}
void file_info::to_console() const {
FB2K_console_formatter1() << "File info dump:";
if (get_length() > 0) FB2K_console_formatter() << "Duration: " << pfc::format_time_ex(get_length(), 6);
pfc::string_formatter temp;
for(t_size metaWalk = 0; metaWalk < meta_get_count(); ++metaWalk) {
const char * name = meta_enum_name( metaWalk );
const auto valCount = meta_enum_value_count( metaWalk );
for ( size_t valWalk = 0; valWalk < valCount; ++valWalk ) {
FB2K_console_formatter() << "Meta: " << name << " = " << meta_enum_value( metaWalk, valWalk );
}
/*
meta_format_entry(metaWalk, temp);
FB2K_console_formatter() << "Meta: " << meta_enum_name(metaWalk) << " = " << temp;
*/
}
for(t_size infoWalk = 0; infoWalk < info_get_count(); ++infoWalk) {
FB2K_console_formatter() << "Info: " << info_enum_name(infoWalk) << " = " << info_enum_value(infoWalk);
}
}
void file_info::info_set_wfx_chanMask(uint32_t val) {
switch(val) {
case 0:
case 4:
case 3:
break;
default:
info_set ("WAVEFORMATEXTENSIBLE_CHANNEL_MASK", PFC_string_formatter() << "0x" << pfc::format_hex(val) );
break;
}
}
uint32_t file_info::info_get_wfx_chanMask() const {
const char * str = this->info_get("WAVEFORMATEXTENSIBLE_CHANNEL_MASK");
if (str == NULL) return 0;
if (pfc::strcmp_partial( str, "0x") != 0) return 0;
try {
return pfc::atohex<uint32_t>( str + 2, strlen(str+2) );
} catch(...) { return 0;}
}
bool file_info::field_is_person(const char * fieldName) {
return field_name_equals(fieldName, "artist") ||
field_name_equals(fieldName, "album artist") ||
field_name_equals(fieldName, "composer") ||
field_name_equals(fieldName, "performer") ||
field_name_equals(fieldName, "conductor") ||
field_name_equals(fieldName, "orchestra") ||
field_name_equals(fieldName, "ensemble") ||
field_name_equals(fieldName, "engineer");
}
bool file_info::field_is_title(const char * fieldName) {
return field_name_equals(fieldName, "title") || field_name_equals(fieldName, "album");
}
void file_info::to_stream( stream_writer * stream, abort_callback & abort ) const {
stream_writer_formatter<> out(* stream, abort );
out << this->get_length();
{
const auto rg = this->get_replaygain();
out << rg.m_track_gain << rg.m_album_gain << rg.m_track_peak << rg.m_album_peak;
}
{
const uint32_t metaCount = pfc::downcast_guarded<uint32_t>( this->meta_get_count() );
for(uint32_t metaWalk = 0; metaWalk < metaCount; ++metaWalk) {
const char * name = this->meta_enum_name( metaWalk );
if (*name) {
out.write_string_nullterm( this->meta_enum_name( metaWalk ) );
const size_t valCount = this->meta_enum_value_count( metaWalk );
for(size_t valWalk = 0; valWalk < valCount; ++valWalk) {
const char * value = this->meta_enum_value( metaWalk, valWalk );
if (*value) {
out.write_string_nullterm( value );
}
}
out.write_int<char>(0);
}
}
out.write_int<char>(0);
}
{
const uint32_t infoCount = pfc::downcast_guarded<uint32_t>( this->info_get_count() );
for(uint32_t infoWalk = 0; infoWalk < infoCount; ++infoWalk) {
const char * name = this->info_enum_name( infoWalk );
const char * value = this->info_enum_value( infoWalk );
if (*name && *value) {
out.write_string_nullterm(name); out.write_string_nullterm(value);
}
}
out.write_int<char>(0);
}
}
void file_info::from_stream( stream_reader * stream, abort_callback & abort ) {
stream_reader_formatter<> in( *stream, abort );
pfc::string_formatter tempName, tempValue;
{
double len; in >> len; this->set_length( len );
}
{
replaygain_info rg;
in >> rg.m_track_gain >> rg.m_album_gain >> rg.m_track_peak >> rg.m_album_peak;
}
{
this->meta_remove_all();
for(;;) {
in.read_string_nullterm( tempName );
if (tempName.length() == 0) break;
size_t metaIndex = pfc_infinite;
for(;;) {
in.read_string_nullterm( tempValue );
if (tempValue.length() == 0) break;
if (metaIndex == pfc_infinite) metaIndex = this->meta_add( tempName, tempValue );
else this->meta_add_value( metaIndex, tempValue );
}
}
}
{
this->info_remove_all();
for(;;) {
in.read_string_nullterm( tempName );
if (tempName.length() == 0) break;
in.read_string_nullterm( tempValue );
this->info_set( tempName, tempValue );
}
}
}
static const char * _readString( const uint8_t * & ptr, size_t & remaining ) {
const char * rv = (const char*)ptr;
for(;;) {
if (remaining == 0) throw exception_io_data();
uint8_t byte = *ptr++; --remaining;
if (byte == 0) break;
}
return rv;
}
template<typename int_t> void _readInt( int_t & out, const uint8_t * &ptr, size_t & remaining) {
if (remaining < sizeof(out)) throw exception_io_data();
pfc::decode_little_endian( out, ptr ); ptr += sizeof(out); remaining -= sizeof(out);
}
template<typename float_t> static void _readFloat(float_t & out, const uint8_t * &ptr, size_t & remaining) {
union {
typename pfc::sized_int_t<sizeof(float_t)>::t_unsigned i;
float_t f;
} u;
_readInt(u.i, ptr, remaining);
out = u.f;
}
void file_info::from_mem( const void * memPtr, size_t memSize ) {
size_t remaining = memSize;
const uint8_t * walk = (const uint8_t*) memPtr;
{
double len; _readFloat(len, walk, remaining);
this->set_length( len );
}
{
replaygain_info rg;
_readFloat(rg.m_track_gain, walk, remaining );
_readFloat(rg.m_album_gain, walk, remaining );
_readFloat(rg.m_track_peak, walk, remaining );
_readFloat(rg.m_album_peak, walk, remaining );
this->set_replaygain( rg );
}
{
this->meta_remove_all();
for(;;) {
const char * metaName = _readString( walk, remaining );
if (*metaName == 0) break;
size_t metaIndex = pfc_infinite;
for(;;) {
const char * metaValue = _readString( walk, remaining );
if (*metaValue == 0) break;
if (metaIndex == pfc_infinite) metaIndex = this->meta_add( metaName, metaValue );
else this->meta_add_value( metaIndex, metaName );
}
}
}
{
this->info_remove_all();
for(;;) {
const char * infoName = _readString( walk, remaining );
if (*infoName == 0) break;
const char * infoValue = _readString( walk, remaining );
this->info_set( infoName, infoValue );
}
}
}
audio_chunk::spec_t file_info::audio_chunk_spec() const
{
audio_chunk::spec_t rv = {};
rv.sampleRate = (uint32_t)this->info_get_int("samplerate");
rv.chanCount = (uint32_t)this->info_get_int("channels");
rv.chanMask = (uint32_t)this->info_get_wfx_chanMask();
if (audio_chunk::g_count_channels( rv.chanMask ) != rv.chanCount ) {
rv.chanMask = audio_chunk::g_guess_channel_config( rv.chanCount );
}
return rv;
}

287
foobar2000/SDK/file_info.h Normal file
View File

@@ -0,0 +1,287 @@
//! Structure containing ReplayGain scan results from some playable object, also providing various helper methods to manipulate those results.
struct replaygain_info
{
float m_album_gain,m_track_gain;
float m_album_peak,m_track_peak;
enum {text_buffer_size = 16 };
typedef char t_text_buffer[text_buffer_size];
static const float peak_invalid, gain_invalid;
static bool g_format_gain(float p_value,char p_buffer[text_buffer_size]);
static bool g_format_peak(float p_value,char p_buffer[text_buffer_size]);
static bool g_format_peak_db(float p_value, char p_buffer[text_buffer_size]);
inline bool format_album_gain(char p_buffer[text_buffer_size]) const {return g_format_gain(m_album_gain,p_buffer);}
inline bool format_track_gain(char p_buffer[text_buffer_size]) const {return g_format_gain(m_track_gain,p_buffer);}
inline bool format_album_peak(char p_buffer[text_buffer_size]) const {return g_format_peak(m_album_peak,p_buffer);}
inline bool format_track_peak(char p_buffer[text_buffer_size]) const {return g_format_peak(m_track_peak,p_buffer);}
static float g_parse_gain_text(const char * p_text, t_size p_text_len = SIZE_MAX);
void set_album_gain_text(const char * p_text,t_size p_text_len = SIZE_MAX);
void set_track_gain_text(const char * p_text,t_size p_text_len = SIZE_MAX);
void set_album_peak_text(const char * p_text,t_size p_text_len = SIZE_MAX);
void set_track_peak_text(const char * p_text,t_size p_text_len = SIZE_MAX);
static bool g_is_meta_replaygain(const char * p_name,t_size p_name_len = SIZE_MAX);
bool set_from_meta_ex(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len);
inline bool set_from_meta(const char * p_name,const char * p_value) {return set_from_meta_ex(p_name,SIZE_MAX,p_value,SIZE_MAX);}
inline bool is_album_gain_present() const {return m_album_gain != gain_invalid;}
inline bool is_track_gain_present() const {return m_track_gain != gain_invalid;}
inline bool is_album_peak_present() const {return m_album_peak != peak_invalid;}
inline bool is_track_peak_present() const {return m_track_peak != peak_invalid;}
inline void remove_album_gain() {m_album_gain = gain_invalid;}
inline void remove_track_gain() {m_track_gain = gain_invalid;}
inline void remove_album_peak() {m_album_peak = peak_invalid;}
inline void remove_track_peak() {m_track_peak = peak_invalid;}
float anyGain(bool bPreferAlbum = false) const;
t_size get_value_count();
static replaygain_info g_merge(replaygain_info r1,replaygain_info r2);
static bool g_equalLoose( const replaygain_info & item1, const replaygain_info & item2);
static bool g_equal(const replaygain_info & item1,const replaygain_info & item2);
void reset();
};
class format_rg_gain {
public:
format_rg_gain(float val) {replaygain_info::g_format_gain(val, m_buffer);}
operator const char * () const {return m_buffer;}
const char * c_str() const { return m_buffer; }
private:
replaygain_info::t_text_buffer m_buffer;
};
class format_rg_peak {
public:
format_rg_peak(float val) {replaygain_info::g_format_peak(val, m_buffer);}
operator const char * () const {return m_buffer;}
const char * c_str() const { return m_buffer; }
private:
replaygain_info::t_text_buffer m_buffer;
};
inline bool operator==(const replaygain_info & item1,const replaygain_info & item2) {return replaygain_info::g_equal(item1,item2);}
inline bool operator!=(const replaygain_info & item1,const replaygain_info & item2) {return !replaygain_info::g_equal(item1,item2);}
static const replaygain_info replaygain_info_invalid = {replaygain_info::gain_invalid,replaygain_info::gain_invalid,replaygain_info::peak_invalid,replaygain_info::peak_invalid};
//! Main interface class for information about some playable object.
class NOVTABLE file_info {
public:
//! Retrieves audio duration, in seconds. \n
//! Note that the reported duration should not be assumed to be the exact length of the track -\n
//! with many popular audio formats, exact duration is impossible to determine without performing a full decode pass;\n
//! with other formats, the decoded data may be shorter than reported due to truncation other damage.
virtual double get_length() const = 0;
//! Sets audio duration, in seconds. \n
//! Note that the reported duration should not be assumed to be the exact length of the track -\n
//! with many popular audio formats, exact duration is impossible to determine without performing a full decode pass;\n
//! with other formats, the decoded data may be shorter than reported due to truncation other damage.
virtual void set_length(double p_length) = 0;
//! Sets ReplayGain information.
virtual void set_replaygain(const replaygain_info & p_info) = 0;
//! Retrieves ReplayGain information.
virtual replaygain_info get_replaygain() const = 0;
//! Retrieves count of metadata entries.
virtual t_size meta_get_count() const = 0;
//! Retrieves the name of metadata entry of specified index. Return value is a null-terminated UTF-8 encoded string.
virtual const char* meta_enum_name(t_size p_index) const = 0;
//! Retrieves count of values in metadata entry of specified index. The value is always equal to or greater than 1.
virtual t_size meta_enum_value_count(t_size p_index) const = 0;
//! Retrieves specified value from specified metadata entry. Return value is a null-terminated UTF-8 encoded string.
virtual const char* meta_enum_value(t_size p_index,t_size p_value_number) const = 0;
//! Finds index of metadata entry of specified name. Returns infinite when not found.
virtual t_size meta_find_ex(const char * p_name,t_size p_name_length) const;
//! Creates a new metadata entry of specified name with specified value. If an entry of same name already exists, it is erased. Return value is the index of newly created metadata entry.
virtual t_size meta_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) = 0;
//! Inserts a new value into specified metadata entry.
virtual void meta_insert_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length) = 0;
//! Removes metadata entries according to specified bit mask.
virtual void meta_remove_mask(const bit_array & p_mask) = 0;
//! Reorders metadata entries according to specified permutation.
virtual void meta_reorder(const t_size * p_order) = 0;
//! Removes values according to specified bit mask from specified metadata entry. If all values are removed, entire metadata entry is removed as well.
virtual void meta_remove_values(t_size p_index,const bit_array & p_mask) = 0;
//! Alters specified value in specified metadata entry.
virtual void meta_modify_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length) = 0;
//! Retrieves number of technical info entries.
virtual t_size info_get_count() const = 0;
//! Retrieves the name of specified technical info entry. Return value is a null-terminated UTF-8 encoded string.
virtual const char* info_enum_name(t_size p_index) const = 0;
//! Retrieves the value of specified technical info entry. Return value is a null-terminated UTF-8 encoded string.
virtual const char* info_enum_value(t_size p_index) const = 0;
//! Creates a new technical info entry with specified name and specified value. If an entry of the same name already exists, it is erased. Return value is the index of newly created entry.
virtual t_size info_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) = 0;
//! Removes technical info entries indicated by specified bit mask.
virtual void info_remove_mask(const bit_array & p_mask) = 0;
//! Finds technical info entry of specified name. Returns index of found entry on success, infinite on failure.
virtual t_size info_find_ex(const char * p_name,t_size p_name_length) const;
//! Copies entire file_info contents from specified file_info object.
virtual void copy(const file_info & p_source);//virtualized for performance reasons, can be faster in two-pass
//! Copies metadata from specified file_info object.
virtual void copy_meta(const file_info & p_source);//virtualized for performance reasons, can be faster in two-pass
//! Copies technical info from specified file_info object.
virtual void copy_info(const file_info & p_source);//virtualized for performance reasons, can be faster in two-pass
bool meta_exists_ex(const char * p_name,t_size p_name_length) const;
void meta_remove_field_ex(const char * p_name,t_size p_name_length);
void meta_remove_index(t_size p_index);
void meta_remove_all();
void meta_remove_value(t_size p_index,t_size p_value);
const char * meta_get_ex(const char * p_name,t_size p_name_length,t_size p_index) const;
t_size meta_get_count_by_name_ex(const char * p_name,t_size p_name_length) const;
void meta_add_value_ex(t_size p_index,const char * p_value,t_size p_value_length);
t_size meta_add_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length);
t_size meta_calc_total_value_count() const;
bool meta_format(const char * p_name,pfc::string_base & p_out, const char * separator = ", ") const;
void meta_format_entry(t_size index, pfc::string_base & p_out, const char * separator = ", ") const;//same as meta_format but takes index instead of meta name.
bool info_exists_ex(const char * p_name,t_size p_name_length) const;
void info_remove_index(t_size p_index);
void info_remove_all();
bool info_remove_ex(const char * p_name,t_size p_name_length);
const char * info_get_ex(const char * p_name,t_size p_name_length) const;
inline t_size meta_find(const char * p_name) const {return meta_find_ex(p_name,SIZE_MAX);}
inline bool meta_exists(const char * p_name) const {return meta_exists_ex(p_name,SIZE_MAX);}
inline void meta_remove_field(const char * p_name) {meta_remove_field_ex(p_name,SIZE_MAX);}
inline t_size meta_set(const char * p_name,const char * p_value) {return meta_set_ex(p_name,SIZE_MAX,p_value,SIZE_MAX);}
inline void meta_insert_value(t_size p_index,t_size p_value_index,const char * p_value) {meta_insert_value_ex(p_index,p_value_index,p_value,SIZE_MAX);}
inline void meta_add_value(t_size p_index,const char * p_value) {meta_add_value_ex(p_index,p_value,SIZE_MAX);}
inline const char* meta_get(const char * p_name,t_size p_index) const {return meta_get_ex(p_name,SIZE_MAX,p_index);}
inline t_size meta_get_count_by_name(const char * p_name) const {return meta_get_count_by_name_ex(p_name,SIZE_MAX);}
inline t_size meta_add(const char * p_name,const char * p_value) {return meta_add_ex(p_name,SIZE_MAX,p_value,SIZE_MAX);}
inline void meta_modify_value(t_size p_index,t_size p_value_index,const char * p_value) {meta_modify_value_ex(p_index,p_value_index,p_value,SIZE_MAX);}
inline t_size info_set(const char * p_name,const char * p_value) {return info_set_ex(p_name,SIZE_MAX,p_value,SIZE_MAX);}
inline t_size info_find(const char * p_name) const {return info_find_ex(p_name,SIZE_MAX);}
inline bool info_exists(const char * p_name) const {return info_exists_ex(p_name,SIZE_MAX);}
inline bool info_remove(const char * p_name) {return info_remove_ex(p_name,SIZE_MAX);}
inline const char * info_get(const char * p_name) const {return info_get_ex(p_name,SIZE_MAX);}
bool info_set_replaygain_ex(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len);
inline bool info_set_replaygain(const char * p_name,const char * p_value) {return info_set_replaygain_ex(p_name,SIZE_MAX,p_value,SIZE_MAX);}
void info_set_replaygain_auto_ex(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len);
inline void info_set_replaygain_auto(const char * p_name,const char * p_value) {info_set_replaygain_auto_ex(p_name,SIZE_MAX,p_value,SIZE_MAX);}
void copy_meta_single(const file_info & p_source,t_size p_index);
void copy_info_single(const file_info & p_source,t_size p_index);
void copy_meta_single_by_name_ex(const file_info & p_source,const char * p_name,t_size p_name_length);
void copy_info_single_by_name_ex(const file_info & p_source,const char * p_name,t_size p_name_length);
inline void copy_meta_single_by_name(const file_info & p_source,const char * p_name) {copy_meta_single_by_name_ex(p_source,p_name,SIZE_MAX);}
inline void copy_info_single_by_name(const file_info & p_source,const char * p_name) {copy_info_single_by_name_ex(p_source,p_name,SIZE_MAX);}
void reset();
void reset_replaygain();
void copy_meta_single_rename_ex(const file_info & p_source,t_size p_index,const char * p_new_name,t_size p_new_name_length);
inline void copy_meta_single_rename(const file_info & p_source,t_size p_index,const char * p_new_name) {copy_meta_single_rename_ex(p_source,p_index,p_new_name,SIZE_MAX);}
void overwrite_info(const file_info & p_source);
void overwrite_meta(const file_info & p_source);
t_int64 info_get_int(const char * name) const;
t_int64 info_get_length_samples() const;
double info_get_float(const char * name) const;
void info_set_int(const char * name,t_int64 value);
void info_set_float(const char * name,double value,unsigned precision,bool force_sign = false,const char * unit = 0);
void info_set_replaygain_track_gain(float value);
void info_set_replaygain_album_gain(float value);
void info_set_replaygain_track_peak(float value);
void info_set_replaygain_album_peak(float value);
inline t_int64 info_get_bitrate_vbr() const {return info_get_int("bitrate_dynamic");}
inline void info_set_bitrate_vbr(t_int64 val) {info_set_int("bitrate_dynamic",val);}
inline t_int64 info_get_bitrate() const {return info_get_int("bitrate");}
inline void info_set_bitrate(t_int64 val) {info_set_int("bitrate",val);}
void info_set_wfx_chanMask(uint32_t val);
uint32_t info_get_wfx_chanMask() const;
bool is_encoding_lossy() const;
bool is_encoding_overkill() const;
void info_calculate_bitrate(t_filesize p_filesize,double p_length);
unsigned info_get_decoded_bps() const;//what bps the stream originally was (before converting to audio_sample), 0 if unknown
private:
void merge(const pfc::list_base_const_t<const file_info*> & p_sources);
public:
void _set_tag(const file_info & tag);
void _add_tag(const file_info & otherTag);
void merge_fallback(const file_info & fallback);
bool are_meta_fields_identical(t_size p_index1,t_size p_index2) const;
inline const file_info & operator=(const file_info & p_source) {copy(p_source);return *this;}
static bool g_is_meta_equal(const file_info & p_item1,const file_info & p_item2);
static bool g_is_meta_equal_debug(const file_info & p_item1,const file_info & p_item2);
static bool g_is_info_equal(const file_info & p_item1,const file_info & p_item2);
//! Unsafe - does not check whether the field already exists and will result in duplicates if it does - call only when appropriate checks have been applied externally.
t_size __meta_add_unsafe_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) {return meta_set_nocheck_ex(p_name,p_name_length,p_value,p_value_length);}
//! Unsafe - does not check whether the field already exists and will result in duplicates if it does - call only when appropriate checks have been applied externally.
t_size __meta_add_unsafe(const char * p_name,const char * p_value) {return meta_set_nocheck_ex(p_name,SIZE_MAX,p_value,SIZE_MAX);}
//! Unsafe - does not check whether the field already exists and will result in duplicates if it does - call only when appropriate checks have been applied externally.
t_size __info_add_unsafe_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) {return info_set_nocheck_ex(p_name,p_name_length,p_value,p_value_length);}
//! Unsafe - does not check whether the field already exists and will result in duplicates if it does - call only when appropriate checks have been applied externally.
t_size __info_add_unsafe(const char * p_name,const char * p_value) {return info_set_nocheck_ex(p_name,SIZE_MAX,p_value,SIZE_MAX);}
void _copy_meta_single_nocheck(const file_info & p_source,t_size p_index) {copy_meta_single_nocheck(p_source, p_index);}
static bool g_is_valid_field_name(const char * p_name,t_size p_length = SIZE_MAX);
//typedef pfc::comparator_stricmp_ascii field_name_comparator;
typedef pfc::string::comparatorCaseInsensitiveASCII field_name_comparator;
static bool field_name_equals(const char * n1, const char * n2) {return field_name_comparator::compare(n1, n2) == 0;}
void to_console() const;
void to_formatter(pfc::string_formatter&) const;
static bool field_is_person(const char * fieldName);
static bool field_is_title(const char * fieldName);
void to_stream( stream_writer * stream, abort_callback & abort ) const;
void from_stream( stream_reader * stream, abort_callback & abort );
void from_mem( const void * memPtr, size_t memSize);
//! Returns ESTIMATED audio chunk spec from what has been put in the file_info. \n
//! Provided for convenience. Do not rely on it for processing decoded data.
audio_chunk::spec_t audio_chunk_spec() const;
protected:
file_info() {}
~file_info() {}
void copy_meta_single_nocheck(const file_info & p_source,t_size p_index);
void copy_info_single_nocheck(const file_info & p_source,t_size p_index);
void copy_meta_single_by_name_nocheck_ex(const file_info & p_source,const char * p_name,t_size p_name_length);
void copy_info_single_by_name_nocheck_ex(const file_info & p_source,const char * p_name,t_size p_name_length);
inline void copy_meta_single_by_name_nocheck(const file_info & p_source,const char * p_name) {copy_meta_single_by_name_nocheck_ex(p_source,p_name,SIZE_MAX);}
inline void copy_info_single_by_name_nocheck(const file_info & p_source,const char * p_name) {copy_info_single_by_name_nocheck_ex(p_source,p_name,SIZE_MAX);}
virtual t_size meta_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) = 0;
virtual t_size info_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) = 0;
inline t_size meta_set_nocheck(const char * p_name,const char * p_value) {return meta_set_nocheck_ex(p_name,SIZE_MAX,p_value,SIZE_MAX);}
inline t_size info_set_nocheck(const char * p_name,const char * p_value) {return info_set_nocheck_ex(p_name,SIZE_MAX,p_value,SIZE_MAX);}
};

View File

@@ -0,0 +1,46 @@
#pragma once
#include "tracks.h"
//! Implementing this class gives you direct control over which part of file_info gets altered during a tag update uperation. To be used with metadb_io_v2::update_info_async().
class NOVTABLE file_info_filter : public service_base {
FB2K_MAKE_SERVICE_INTERFACE(file_info_filter, service_base);
public:
//! Alters specified file_info entry; called as a part of tag update process. Specified file_info has been read from a file, and will be written back.\n
//! WARNING: This will be typically called from another thread than main app thread (precisely, from thread created by tag updater). You should copy all relevant data to members of your file_info_filter instance in constructor and reference only member data in apply_filter() implementation.
//! @returns True when you have altered file_info and changes need to be written back to the file; false if no changes have been made.
virtual bool apply_filter(trackRef p_location, t_filestats p_stats, file_info & p_info) = 0;
typedef std::function< bool (trackRef, t_filestats, file_info & ) > func_t;
static file_info_filter::ptr create( func_t f );
};
//! Extended file_info_filter allowing the caller to do their own manipulation of the file before and after the metadata update takes place. \n
//! Respected by foobar2000 v1.5 and up; if metadb_io_v4 is supported, then file_info_filter_v2 is understood.
class NOVTABLE file_info_filter_v2 : public file_info_filter {
FB2K_MAKE_SERVICE_INTERFACE(file_info_filter_v2, file_info_filter);
public:
enum filterStatus_t {
filterNoUpdate = 0,
filterProceed,
filterAlreadyUpdated
};
//! Called after just before rewriting metadata. The file is not yet opened for writing, but a file_lock has already been granted (so don't call it on your own). \n
//! You can use this method to perform album art updates (via album_art_editor API) alongside metadata updates. \n
//! Return value can be used to stop fb2k from proceeding with metadata update on this file. \n
//! If your own operations on this file fail, just pass the exceptions to the caller and they will be reported just as other tag update errors.
//! @param fileIfAlreadyOpened Reference to an already opened file object, if already opened by the caller. May be null.
virtual filterStatus_t before_tag_update(const char * location, file::ptr fileIfAlreadyOpened, abort_callback & aborter) = 0;
//! Called after metadata has been updated. \n
//! If you wish to alter the file on your own, use before_tag_update() for this instead. \n
//! If your own operations on this file fail, just pass the exceptions to the caller and they will be reported just as other tag update errors. \n
//! The passed reader object can be used to read the properties of the updated file back. In most cases it will be the writer that was used to update the tags. Do not call tag writing methods on it from this function.
virtual void after_tag_update(const char * location, service_ptr_t<class input_info_reader> reader, abort_callback & aborter) = 0;
virtual void after_all_tag_updates(abort_callback & aborter) = 0;
//! Allows you to do your own error logging.
//! @returns True if the error has been noted by your code and does not need to be shown to the user.
virtual bool filter_error(const char * location, const char * msg) = 0;
};

View File

@@ -0,0 +1,33 @@
#pragma once
//! Generic implementation of file_info_filter_impl.
class file_info_filter_impl : public file_info_filter {
public:
file_info_filter_impl(const pfc::list_base_const_t<metadb_handle_ptr> & p_list, const pfc::list_base_const_t<const file_info*> & p_new_info) {
FB2K_DYNAMIC_ASSERT(p_list.get_count() == p_new_info.get_count());
pfc::array_t<t_size> order;
order.set_size(p_list.get_count());
order_helper::g_fill(order.get_ptr(), order.get_size());
p_list.sort_get_permutation_t(pfc::compare_t<metadb_handle_ptr, metadb_handle_ptr>, order.get_ptr());
m_handles.set_count(order.get_size());
m_infos.set_size(order.get_size());
for (t_size n = 0; n < order.get_size(); n++) {
m_handles[n] = p_list[order[n]];
m_infos[n] = *p_new_info[order[n]];
}
}
bool apply_filter(metadb_handle_ptr p_location, t_filestats p_stats, file_info & p_info) {
t_size index;
if (m_handles.bsearch_t(pfc::compare_t<metadb_handle_ptr, metadb_handle_ptr>, p_location, index)) {
p_info = m_infos[index];
return true;
}
else {
return false;
}
}
private:
metadb_handle_list m_handles;
pfc::array_t<file_info_impl> m_infos;
};

View File

@@ -0,0 +1,243 @@
#include "foobar2000.h"
t_size file_info_impl::meta_get_count() const
{
return m_meta.get_count();
}
const char* file_info_impl::meta_enum_name(t_size p_index) const
{
return m_meta.get_name(p_index);
}
t_size file_info_impl::meta_enum_value_count(t_size p_index) const
{
return m_meta.get_value_count(p_index);
}
const char* file_info_impl::meta_enum_value(t_size p_index,t_size p_value_number) const
{
return m_meta.get_value(p_index,p_value_number);
}
t_size file_info_impl::meta_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length)
{
meta_remove_field_ex(p_name,p_name_length);
return meta_set_nocheck_ex(p_name,p_name_length,p_value,p_value_length);
}
t_size file_info_impl::meta_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length)
{
return m_meta.add_entry(p_name,p_name_length,p_value,p_value_length);
}
void file_info_impl::meta_insert_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length)
{
m_meta.insert_value(p_index,p_value_index,p_value,p_value_length);
}
void file_info_impl::meta_remove_mask(const bit_array & p_mask)
{
m_meta.remove_mask(p_mask);
}
void file_info_impl::meta_reorder(const t_size * p_order)
{
m_meta.reorder(p_order);
}
void file_info_impl::meta_remove_values(t_size p_index,const bit_array & p_mask)
{
m_meta.remove_values(p_index,p_mask);
if (m_meta.get_value_count(p_index) == 0)
m_meta.remove_mask(pfc::bit_array_one(p_index));
}
t_size file_info_impl::info_get_count() const
{
return m_info.get_count();
}
const char* file_info_impl::info_enum_name(t_size p_index) const
{
return m_info.get_name(p_index);
}
const char* file_info_impl::info_enum_value(t_size p_index) const
{
return m_info.get_value(p_index);
}
t_size file_info_impl::info_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length)
{
info_remove_ex(p_name,p_name_length);
return info_set_nocheck_ex(p_name,p_name_length,p_value,p_value_length);
}
t_size file_info_impl::info_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length)
{
return m_info.add_item(p_name,p_name_length,p_value,p_value_length);
}
void file_info_impl::info_remove_mask(const bit_array & p_mask)
{
m_info.remove_mask(p_mask);
}
file_info_impl::file_info_impl(const file_info & p_source) : m_length(0)
{
copy(p_source);
}
file_info_impl::file_info_impl(const file_info_impl & p_source) : m_length(0)
{
copy(p_source);
}
const file_info_impl & file_info_impl::operator=(const file_info_impl & p_source)
{
copy(p_source);
return *this;
}
file_info_impl::file_info_impl() : m_length(0)
{
m_replaygain.reset();
}
double file_info_impl::get_length() const
{
return m_length;
}
void file_info_impl::set_length(double p_length)
{
m_length = p_length;
}
void file_info_impl::meta_modify_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length)
{
m_meta.modify_value(p_index,p_value_index,p_value,p_value_length);
}
replaygain_info file_info_impl::get_replaygain() const
{
return m_replaygain;
}
void file_info_impl::set_replaygain(const replaygain_info & p_info)
{
m_replaygain = p_info;
}
file_info_impl::~file_info_impl()
{
}
t_size file_info_impl_utils::info_storage::add_item(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) {
t_size index = m_info.get_size();
m_info.set_size(index + 1);
m_info[index].init(p_name,p_name_length,p_value,p_value_length);
return index;
}
void file_info_impl_utils::info_storage::remove_mask(const bit_array & p_mask) {
pfc::remove_mask_t(m_info,p_mask);
}
t_size file_info_impl_utils::meta_storage::add_entry(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length)
{
meta_entry temp(p_name,p_name_length,p_value,p_value_length);
return pfc::append_swap_t(m_data,temp);
}
void file_info_impl_utils::meta_storage::insert_value(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length)
{
m_data[p_index].insert_value(p_value_index,p_value,p_value_length);
}
void file_info_impl_utils::meta_storage::modify_value(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length)
{
m_data[p_index].modify_value(p_value_index,p_value,p_value_length);
}
void file_info_impl_utils::meta_storage::remove_values(t_size p_index,const bit_array & p_mask)
{
m_data[p_index].remove_values(p_mask);
}
void file_info_impl_utils::meta_storage::remove_mask(const bit_array & p_mask)
{
pfc::remove_mask_t(m_data,p_mask);
}
file_info_impl_utils::meta_entry::meta_entry(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len)
{
m_name.set_string(p_name,p_name_len);
m_values.set_size(1);
m_values[0].set_string(p_value,p_value_len);
}
void file_info_impl_utils::meta_entry::remove_values(const bit_array & p_mask)
{
pfc::remove_mask_t(m_values,p_mask);
}
void file_info_impl_utils::meta_entry::insert_value(t_size p_value_index,const char * p_value,t_size p_value_length)
{
pfc::string_simple temp;
temp.set_string(p_value,p_value_length);
pfc::insert_t(m_values,temp,p_value_index);
}
void file_info_impl_utils::meta_entry::modify_value(t_size p_value_index,const char * p_value,t_size p_value_length)
{
m_values[p_value_index].set_string(p_value,p_value_length);
}
void file_info_impl_utils::meta_storage::reorder(const t_size * p_order)
{
pfc::reorder_t(m_data,p_order,m_data.get_size());
}
void file_info_impl::copy_meta(const file_info & p_source)
{
m_meta.copy_from(p_source);
}
void file_info_impl::copy_info(const file_info & p_source)
{
m_info.copy_from(p_source);
}
void file_info_impl_utils::meta_storage::copy_from(const file_info & p_info)
{
t_size meta_index,meta_count = p_info.meta_get_count();
m_data.set_size(meta_count);
for(meta_index=0;meta_index<meta_count;meta_index++)
{
meta_entry & entry = m_data[meta_index];
t_size value_index,value_count = p_info.meta_enum_value_count(meta_index);
entry.m_name = p_info.meta_enum_name(meta_index);
entry.m_values.set_size(value_count);
for(value_index=0;value_index<value_count;value_index++)
entry.m_values[value_index] = p_info.meta_enum_value(meta_index,value_index);
}
}
void file_info_impl_utils::info_storage::copy_from(const file_info & p_info)
{
t_size n, count;
count = p_info.info_get_count();
m_info.set_count(count);
for(n=0;n<count;n++) m_info[n].init(p_info.info_enum_name(n),~0,p_info.info_enum_value(n),~0);
}

View File

@@ -0,0 +1,141 @@
#pragma once
namespace file_info_impl_utils {
struct info_entry {
void init(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len) {
m_name.set_string(p_name,p_name_len);
m_value.set_string(p_value,p_value_len);
}
inline const char * get_name() const {return m_name;}
inline const char * get_value() const {return m_value;}
pfc::string_simple m_name,m_value;
};
typedef pfc::array_t<info_entry,pfc::alloc_fast> info_entry_array;
}
namespace pfc {
template<> class traits_t<file_info_impl_utils::info_entry> : public traits_t<pfc::string_simple> {};
};
namespace file_info_impl_utils {
class info_storage
{
public:
t_size add_item(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length);
void remove_mask(const bit_array & p_mask);
inline t_size get_count() const {return m_info.get_count();}
inline const char * get_name(t_size p_index) const {return m_info[p_index].get_name();}
inline const char * get_value(t_size p_index) const {return m_info[p_index].get_value();}
void copy_from(const file_info & p_info);
private:
info_entry_array m_info;
};
}
namespace file_info_impl_utils {
typedef pfc::array_hybrid_t<pfc::string_simple,1,pfc::alloc_fast > meta_value_array;
struct meta_entry {
meta_entry() {}
meta_entry(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len);
void remove_values(const bit_array & p_mask);
void insert_value(t_size p_value_index,const char * p_value,t_size p_value_length);
void modify_value(t_size p_value_index,const char * p_value,t_size p_value_length);
inline const char * get_name() const {return m_name;}
inline const char * get_value(t_size p_index) const {return m_values[p_index];}
inline t_size get_value_count() const {return m_values.get_size();}
pfc::string_simple m_name;
meta_value_array m_values;
};
typedef pfc::array_hybrid_t<meta_entry,10, pfc::alloc_fast> meta_entry_array;
}
namespace pfc {
template<> class traits_t<file_info_impl_utils::meta_entry> : public pfc::traits_combined<pfc::string_simple,file_info_impl_utils::meta_value_array> {};
}
namespace file_info_impl_utils {
class meta_storage
{
public:
t_size add_entry(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length);
void insert_value(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length);
void modify_value(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length);
void remove_values(t_size p_index,const bit_array & p_mask);
void remove_mask(const bit_array & p_mask);
void copy_from(const file_info & p_info);
inline void reorder(const t_size * p_order);
inline t_size get_count() const {return m_data.get_size();}
inline const char * get_name(t_size p_index) const {PFC_ASSERT(p_index < m_data.get_size()); return m_data[p_index].get_name();}
inline const char * get_value(t_size p_index,t_size p_value_index) const {PFC_ASSERT(p_index < m_data.get_size()); return m_data[p_index].get_value(p_value_index);}
inline t_size get_value_count(t_size p_index) const {PFC_ASSERT(p_index < m_data.get_size()); return m_data[p_index].get_value_count();}
private:
meta_entry_array m_data;
};
}
//! Implements file_info.
class file_info_impl : public file_info
{
public:
file_info_impl(const file_info_impl & p_source);
file_info_impl(const file_info & p_source);
file_info_impl();
~file_info_impl();
double get_length() const;
void set_length(double p_length);
void copy_meta(const file_info & p_source);//virtualized for performance reasons, can be faster in two-pass
void copy_info(const file_info & p_source);//virtualized for performance reasons, can be faster in two-pass
t_size meta_get_count() const;
const char* meta_enum_name(t_size p_index) const;
t_size meta_enum_value_count(t_size p_index) const;
const char* meta_enum_value(t_size p_index,t_size p_value_number) const;
t_size meta_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length);
void meta_insert_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length);
void meta_remove_mask(const bit_array & p_mask);
void meta_reorder(const t_size * p_order);
void meta_remove_values(t_size p_index,const bit_array & p_mask);
void meta_modify_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length);
t_size info_get_count() const;
const char* info_enum_name(t_size p_index) const;
const char* info_enum_value(t_size p_index) const;
t_size info_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length);
void info_remove_mask(const bit_array & p_mask);
const file_info_impl & operator=(const file_info_impl & p_source);
replaygain_info get_replaygain() const;
void set_replaygain(const replaygain_info & p_info);
protected:
t_size meta_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length);
t_size info_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length);
private:
file_info_impl_utils::meta_storage m_meta;
file_info_impl_utils::info_storage m_info;
double m_length;
replaygain_info m_replaygain;
};

View File

@@ -0,0 +1,190 @@
#include "foobar2000.h"
static t_size merge_tags_calc_rating_by_index(const file_info & p_info,t_size p_index) {
t_size n,m = p_info.meta_enum_value_count(p_index);
t_size ret = 0;
for(n=0;n<m;n++)
ret += strlen(p_info.meta_enum_value(p_index,n)) + 10;//yes, strlen on utf8 data, plus a slight bump to prefer multivalue over singlevalue w/ separator
return ret;
}
#if 0
static t_size merge_tags_calc_rating(const file_info & p_info,const char * p_field) {
t_size field_index = p_info.meta_find(p_field);
if (field_index != ~0) {
return merge_tags_calc_rating_by_index(p_info,field_index);
} else {
return 0;
}
}
static void merge_tags_copy_info(const char * field,const file_info * from,file_info * to)
{
const char * val = from->info_get(field);
if (val) to->info_set(field,val);
}
#endif
namespace {
struct meta_merge_entry {
meta_merge_entry() : m_rating(0) {}
t_size m_rating;
pfc::array_t<const char *> m_data;
};
class meta_merge_map_enumerator {
public:
meta_merge_map_enumerator(file_info & p_out) : m_out(p_out) {
m_out.meta_remove_all();
}
void operator() (const char * p_name, const meta_merge_entry & p_entry) {
if (p_entry.m_data.get_size() > 0) {
t_size index = m_out.__meta_add_unsafe(p_name,p_entry.m_data[0]);
for(t_size walk = 1; walk < p_entry.m_data.get_size(); ++walk) {
m_out.meta_add_value(index,p_entry.m_data[walk]);
}
}
}
private:
file_info & m_out;
};
}
static void merge_meta(file_info & p_out,const pfc::list_base_const_t<const file_info*> & p_in) {
pfc::map_t<const char *,meta_merge_entry,pfc::comparator_stricmp_ascii> map;
for(t_size in_walk = 0; in_walk < p_in.get_count(); in_walk++) {
const file_info & in = * p_in[in_walk];
for(t_size meta_walk = 0, meta_count = in.meta_get_count(); meta_walk < meta_count; meta_walk++ ) {
meta_merge_entry & entry = map.find_or_add(in.meta_enum_name(meta_walk));
t_size rating = merge_tags_calc_rating_by_index(in,meta_walk);
if (rating > entry.m_rating) {
entry.m_rating = rating;
const t_size value_count = in.meta_enum_value_count(meta_walk);
entry.m_data.set_size(value_count);
for(t_size value_walk = 0; value_walk < value_count; value_walk++ ) {
entry.m_data[value_walk] = in.meta_enum_value(meta_walk,value_walk);
}
}
}
}
meta_merge_map_enumerator en(p_out);
map.enumerate(en);
}
void file_info::merge(const pfc::list_base_const_t<const file_info*> & p_in)
{
t_size in_count = p_in.get_count();
if (in_count == 0)
{
meta_remove_all();
return;
}
else if (in_count == 1)
{
const file_info * info = p_in[0];
copy_meta(*info);
set_replaygain(replaygain_info::g_merge(get_replaygain(),info->get_replaygain()));
overwrite_info(*info);
//copy_info_single_by_name(*info,"tagtype");
return;
}
merge_meta(*this,p_in);
{
pfc::string8_fastalloc tagtype;
replaygain_info rg = get_replaygain();
t_size in_ptr;
for(in_ptr = 0; in_ptr < in_count; in_ptr++ )
{
const file_info * info = p_in[in_ptr];
rg = replaygain_info::g_merge(rg, info->get_replaygain());
t_size field_ptr, field_max = info->info_get_count();
for(field_ptr = 0; field_ptr < field_max; field_ptr++ )
{
const char * field_name = info->info_enum_name(field_ptr), * field_value = info->info_enum_value(field_ptr);
if (*field_value)
{
if (!pfc::stricmp_ascii(field_name,"tagtype"))
{
if (!tagtype.is_empty()) tagtype += "|";
tagtype += field_value;
}
}
}
}
if (!tagtype.is_empty()) info_set("tagtype",tagtype);
set_replaygain(rg);
}
}
void file_info::overwrite_info(const file_info & p_source) {
t_size count = p_source.info_get_count();
for(t_size n=0;n<count;n++) {
info_set(p_source.info_enum_name(n),p_source.info_enum_value(n));
}
}
void file_info::merge_fallback(const file_info & source) {
set_replaygain( replaygain_info::g_merge(get_replaygain(), source.get_replaygain() ) );
if (get_length() <= 0) set_length(source.get_length());
t_size count = source.info_get_count();
for(t_size infoWalk = 0; infoWalk < count; ++infoWalk) {
const char * name = source.info_enum_name(infoWalk);
if (!info_exists(name)) __info_add_unsafe(name, source.info_enum_value(infoWalk));
}
count = source.meta_get_count();
for(t_size metaWalk = 0; metaWalk < count; ++metaWalk) {
const char * name = source.meta_enum_name(metaWalk);
if (!meta_exists(name)) _copy_meta_single_nocheck(source, metaWalk);
}
}
static const char _tagtype[] = "tagtype";
static bool isSC( const char * n ) {
return pfc::string_has_prefix_i(n, "Apple SoundCheck" );
}
void file_info::_set_tag(const file_info & tag) {
this->copy_meta(tag);
this->set_replaygain( replaygain_info::g_merge( this->get_replaygain(), tag.get_replaygain() ) );
const size_t iCount = tag.info_get_count();
for( size_t iWalk = 0; iWalk < iCount; ++iWalk ) {
auto n = tag.info_enum_name(iWalk);
if ( pfc::stringEqualsI_ascii( n, _tagtype ) || isSC(n) ) {
this->info_set(n, tag.info_enum_value( iWalk ) );
}
}
}
void file_info::_add_tag(const file_info & otherTag) {
this->set_replaygain( replaygain_info::g_merge( this->get_replaygain(), otherTag.get_replaygain() ) );
const char * tt1 = this->info_get(_tagtype);
const char * tt2 = otherTag.info_get(_tagtype);
if (tt2) {
if (tt1) {
this->info_set(_tagtype, PFC_string_formatter() << tt1 << "|" << tt2);
} else {
this->info_set(_tagtype, tt2);
}
}
{
const size_t iCount = otherTag.info_get_count();
for( size_t w = 0; w < iCount; ++ w ) {
auto n = otherTag.info_enum_name(w);
if (isSC(n) && !this->info_get(n)) {
this->info_set( n, otherTag.info_enum_value(w) );
}
}
}
}

View File

@@ -0,0 +1,77 @@
#pragma once
/*
File lock management API
Historical note: while this API was first published in a 2018 release of the SDK, it had been around for at least a decade and is supported in all fb2k versions from 0.9 up.
The semantics are similar to those of blocking POSIX flock().
Since read locks are expected to be held for a long period of time - for an example, when playing an audio track, a holder of such lock can query whether someone else is waiting for this lock to be released.
Various audio file operations performed by fb2k core use file locks to synchronize access to the files - in particular, to allow graceful updates of tags on the currently playing track.
Usage examples:
If you want to write tags to an audio file, using low level methods such as input or album_art_editor APIs-
Obtain a write lock before accessing your file and release it when done.
If you want to keep an audio file open for an extended period of time and allow others to write to it-
Obtain a read lock, check is_release_requested() on it periodically; when it returns true, close the file, release the lock, obtain a new lock (which will block you until the peding write operation has completed), reopen the file and resume decoding where you left.
Final note:
Majority of the fb2k components will never need this API.
If you carry out tag updates via metadb_io, the locking is already dealt with by fb2k core.
*/
//! An instance of a file lock object. Use file_lock_manager to instantiate.
class NOVTABLE file_lock : public service_base {
public:
//! Returns whether we're blocking other attempts to access this file. A read lock does not block other read locks, but a write lock requires exclusive access.\n
//! Typically, time consuming read operations check this periodically and close the file / release the lock / reacquire the lock / reopen the file when signaled.
virtual bool is_release_requested() = 0;
FB2K_MAKE_SERVICE_INTERFACE(file_lock, service_base);
};
typedef service_ptr_t<file_lock> file_lock_ptr;
//! \since 1.5
//! Modern version of file locking. \n
//! A read lock can be interrupted by a write lock request, from the thread that requested writing. \n
class NOVTABLE file_lock_interrupt : public service_base {
FB2K_MAKE_SERVICE_INTERFACE(file_lock_interrupt, service_base);
public:
//! Please note that interrupt() is called outside any sync scopes and may be called after lock reference has been released. \n
//! It is implementer's responsibility to safeguard against such. \n
//! The interrupt() function must *never* fail, unless aborted by calling context - which means that whoever asked for write access is aborting whatever they're doing. \n
//! This function may block for as long as it takes to release the owned resources, but must be able to abort cleanly if doing so. \n
//! If the function was aborted, it may be called again on the same object. \n
//! If the function succeeded, it will not be called again on the same object; the object will be released immediately after.
virtual void interrupt( abort_callback & aborter ) = 0;
static file_lock_interrupt::ptr create( std::function< void (abort_callback&)> );
};
//! Entry point class for obtaining file_lock objects.
class NOVTABLE file_lock_manager : public service_base {
public:
enum t_mode {
mode_read = 0,
mode_write
};
//! Acquires a read or write lock for this file path. \n
//! If asked for read access, waits until nobody else holds a write lock for this path (but others may read at the same time).
//! If asked for write access, access until nobody else holds a read or write lock for this path. \n
//! The semantics are similar to those of blocking POSIX flock().
virtual file_lock_ptr acquire(const char * p_path, t_mode p_mode, abort_callback & p_abort) = 0;
//! Helper, calls acquire() with mode_read.
file_lock_ptr acquire_read(const char * p_path, abort_callback & p_abort) { return acquire(p_path, mode_read, p_abort); }
//! Helper, calls acquire() with mode_write.
file_lock_ptr acquire_write(const char * p_path, abort_callback & p_abort) { return acquire(p_path, mode_write, p_abort); }
FB2K_MAKE_SERVICE_COREAPI(file_lock_manager);
};
// \since 1.5
class NOVTABLE file_lock_manager_v2 : public file_lock_manager {
FB2K_MAKE_SERVICE_COREAPI_EXTENSION( file_lock_manager_v2, file_lock_manager );
public:
virtual fb2k::objRef acquire_read_v2(const char * p_path, file_lock_interrupt::ptr interruptHandler, abort_callback & p_abort) = 0;
};

View File

@@ -0,0 +1,131 @@
#include "foobar2000.h"
static void g_on_files_deleted_sorted(const pfc::list_base_const_t<const char *> & p_items)
{
//library_manager::get()->on_files_deleted_sorted(p_items);
playlist_manager::get()->on_files_deleted_sorted(p_items);
FB2K_FOR_EACH_SERVICE(file_operation_callback, on_files_deleted_sorted(p_items));
}
static void g_on_files_moved_sorted(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to)
{
{
auto api = playlist_manager::get();
api->on_files_moved_sorted(p_from,p_to);
api->on_files_deleted_sorted(p_from);
}
FB2K_FOR_EACH_SERVICE(file_operation_callback, on_files_moved_sorted(p_from,p_to));
}
static void g_on_files_copied_sorted(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to)
{
FB2K_FOR_EACH_SERVICE(file_operation_callback, on_files_copied_sorted(p_from,p_to));
}
void file_operation_callback::g_on_files_deleted(const pfc::list_base_const_t<const char *> & p_items)
{
core_api::ensure_main_thread();
t_size count = p_items.get_count();
if (count > 0)
{
if (count == 1) g_on_files_deleted_sorted(p_items);
else
{
pfc::array_t<t_size> order; order.set_size(count);
order_helper::g_fill(order);
p_items.sort_get_permutation_t(metadb::path_compare,order.get_ptr());
g_on_files_deleted_sorted(pfc::list_permutation_t<const char*>(p_items,order.get_ptr(),count));
}
}
}
void file_operation_callback::g_on_files_moved(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to)
{
core_api::ensure_main_thread();
pfc::dynamic_assert(p_from.get_count() == p_to.get_count());
t_size count = p_from.get_count();
if (count > 0)
{
if (count == 1) g_on_files_moved_sorted(p_from,p_to);
else
{
pfc::array_t<t_size> order; order.set_size(count);
order_helper::g_fill(order);
p_from.sort_get_permutation_t(metadb::path_compare,order.get_ptr());
g_on_files_moved_sorted(pfc::list_permutation_t<const char*>(p_from,order.get_ptr(),count),pfc::list_permutation_t<const char*>(p_to,order.get_ptr(),count));
}
}
}
void file_operation_callback::g_on_files_copied(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to)
{
if (core_api::assert_main_thread())
{
assert(p_from.get_count() == p_to.get_count());
t_size count = p_from.get_count();
if (count > 0)
{
if (count == 1) g_on_files_copied_sorted(p_from,p_to);
else
{
pfc::array_t<t_size> order; order.set_size(count);
order_helper::g_fill(order);
p_from.sort_get_permutation_t(metadb::path_compare,order.get_ptr());
g_on_files_copied_sorted(pfc::list_permutation_t<const char*>(p_from,order.get_ptr(),count),pfc::list_permutation_t<const char*>(p_to,order.get_ptr(),count));
}
}
}
}
bool file_operation_callback::g_search_sorted_list(const pfc::list_base_const_t<const char*> & p_list,const char * p_string,t_size & p_index) {
return pfc::binarySearch<metadb::path_comparator>::run(p_list,0,p_list.get_count(),p_string,p_index);
}
bool file_operation_callback::g_update_list_on_moved_ex(metadb_handle_list_ref p_list,t_pathlist p_from,t_pathlist p_to, metadb_handle_list_ref itemsAdded, metadb_handle_list_ref itemsRemoved) {
auto api = metadb::get();
bool changed = false;
itemsAdded.remove_all(); itemsRemoved.remove_all();
for(t_size walk = 0; walk < p_list.get_count(); ++walk) {
metadb_handle_ptr item = p_list[walk];
t_size index;
if (g_search_sorted_list(p_from,item->get_path(),index)) {
metadb_handle_ptr newItem;
api->handle_create_replace_path_canonical(newItem,item,p_to[index]);
p_list.replace_item(walk,newItem);
changed = true;
itemsAdded.add_item(newItem); itemsRemoved.add_item(item);
}
}
return changed;
}
bool file_operation_callback::g_update_list_on_moved(metadb_handle_list_ref p_list,const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to) {
auto api = metadb::get();
bool changed = false;
for(t_size walk = 0; walk < p_list.get_count(); ++walk) {
metadb_handle_ptr item = p_list[walk];
t_size index;
if (g_search_sorted_list(p_from,item->get_path(),index)) {
metadb_handle_ptr newItem;
api->handle_create_replace_path_canonical(newItem,item,p_to[index]);
p_list.replace_item(walk,newItem);
changed = true;
}
}
return changed;
}
bool file_operation_callback::g_mark_dead_entries(metadb_handle_list_cref items, bit_array_var & mask, t_pathlist deadPaths) {
bool found = false;
const t_size total = items.get_count();
for(t_size walk = 0; walk < total; ++walk) {
t_size index;
if (g_search_sorted_list(deadPaths,items[walk]->get_path(),index)) {
mask.set(walk,true); found = true;
} else {
mask.set(walk,false);
}
}
return found;
}

View File

@@ -0,0 +1,62 @@
#pragma once
//! Interface to notify component system about files being deleted or moved. Operates in app's main thread only.
class NOVTABLE file_operation_callback : public service_base {
public:
typedef const pfc::list_base_const_t<const char *> & t_pathlist;
//! p_items is a metadb::path_compare sorted list of files that have been deleted.
virtual void on_files_deleted_sorted(t_pathlist p_items) = 0;
//! p_from is a metadb::path_compare sorted list of files that have been moved, p_to is a list of corresponding target locations.
virtual void on_files_moved_sorted(t_pathlist p_from,t_pathlist p_to) = 0;
//! p_from is a metadb::path_compare sorted list of files that have been copied, p_to is a list of corresponding target locations.
virtual void on_files_copied_sorted(t_pathlist p_from,t_pathlist p_to) = 0;
static void g_on_files_deleted(const pfc::list_base_const_t<const char *> & p_items);
static void g_on_files_moved(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to);
static void g_on_files_copied(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to);
static bool g_search_sorted_list(const pfc::list_base_const_t<const char*> & p_list,const char * p_string,t_size & p_index);
static bool g_update_list_on_moved(metadb_handle_list_ref p_list,t_pathlist p_from,t_pathlist p_to);
static bool g_update_list_on_moved_ex(metadb_handle_list_ref p_list,t_pathlist p_from,t_pathlist p_to, metadb_handle_list_ref itemsAdded, metadb_handle_list_ref itemsRemoved);
static bool g_mark_dead_entries(metadb_handle_list_cref items, bit_array_var & mask, t_pathlist deadPaths);
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(file_operation_callback);
};
//! New in 0.9.5.
class NOVTABLE file_operation_callback_dynamic {
public:
//! p_items is a metadb::path_compare sorted list of files that have been deleted.
virtual void on_files_deleted_sorted(const pfc::list_base_const_t<const char *> & p_items) = 0;
//! p_from is a metadb::path_compare sorted list of files that have been moved, p_to is a list of corresponding target locations.
virtual void on_files_moved_sorted(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to) = 0;
//! p_from is a metadb::path_compare sorted list of files that have been copied, p_to is a list of corresponding target locations.
virtual void on_files_copied_sorted(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to) = 0;
};
//! New in 0.9.5.
class NOVTABLE file_operation_callback_dynamic_manager : public service_base {
public:
virtual void register_callback(file_operation_callback_dynamic * p_callback) = 0;
virtual void unregister_callback(file_operation_callback_dynamic * p_callback) = 0;
FB2K_MAKE_SERVICE_COREAPI(file_operation_callback_dynamic_manager);
};
//! New in 0.9.5.
class file_operation_callback_dynamic_impl_base : public file_operation_callback_dynamic {
public:
file_operation_callback_dynamic_impl_base() {file_operation_callback_dynamic_manager::get()->register_callback(this);}
~file_operation_callback_dynamic_impl_base() {file_operation_callback_dynamic_manager::get()->unregister_callback(this);}
void on_files_deleted_sorted(const pfc::list_base_const_t<const char *> & p_items) {}
void on_files_moved_sorted(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to) {}
void on_files_copied_sorted(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to) {}
PFC_CLASS_NOT_COPYABLE_EX(file_operation_callback_dynamic_impl_base);
};

File diff suppressed because it is too large Load Diff

837
foobar2000/SDK/filesystem.h Normal file
View File

@@ -0,0 +1,837 @@
#pragma once
class file_info;
class mem_block_container;
//! Contains various I/O related structures and interfaces.
namespace foobar2000_io
{
//! Type used for file size related variables.
typedef t_uint64 t_filesize;
//! Type used for file size related variables when a signed value is needed.
typedef t_int64 t_sfilesize;
//! Type used for file timestamp related variables. 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601; 0 for invalid/unknown time.
typedef t_uint64 t_filetimestamp;
//! Invalid/unknown file timestamp constant. Also see: t_filetimestamp.
const t_filetimestamp filetimestamp_invalid = 0;
//! Invalid/unknown file size constant. Also see: t_filesize.
static const t_filesize filesize_invalid = (t_filesize)(~0);
static const t_filetimestamp filetimestamp_1second_increment = 10000000;
//! Generic I/O error. Root class for I/O failure exception. See relevant default message for description of each derived exception class.
PFC_DECLARE_EXCEPTION(exception_io, pfc::exception,"I/O error");
//! Object not found.
PFC_DECLARE_EXCEPTION(exception_io_not_found, exception_io,"Object not found");
//! Access denied. \n
//! Special Windows note: this MAY be thrown instead of exception_io_sharing_violation by operations that rename/move files due to Win32 MoveFile() bugs.
PFC_DECLARE_EXCEPTION(exception_io_denied, exception_io,"Access denied");
//! Access denied.
PFC_DECLARE_EXCEPTION(exception_io_denied_readonly, exception_io_denied,"File is read-only");
//! Unsupported format or corrupted file (unexpected data encountered).
PFC_DECLARE_EXCEPTION(exception_io_data, exception_io,"Unsupported format or corrupted file");
//! Unsupported format or corrupted file (truncation encountered).
PFC_DECLARE_EXCEPTION(exception_io_data_truncation, exception_io_data,"Unsupported format or corrupted file");
//! Unsupported format (a subclass of "unsupported format or corrupted file" exception).
PFC_DECLARE_EXCEPTION(exception_io_unsupported_format, exception_io_data,"Unsupported file format");
//! Decode error - subsong index out of expected range
PFC_DECLARE_EXCEPTION(exception_io_bad_subsong_index, exception_io_data,"Unexpected subsong index");
//! Object is remote, while specific operation is supported only for local objects.
PFC_DECLARE_EXCEPTION(exception_io_object_is_remote, exception_io,"This operation is not supported on remote objects");
//! Sharing violation.
PFC_DECLARE_EXCEPTION(exception_io_sharing_violation, exception_io,"File is already in use");
//! Device full.
PFC_DECLARE_EXCEPTION(exception_io_device_full, exception_io,"Device full");
//! Attempt to seek outside valid range.
PFC_DECLARE_EXCEPTION(exception_io_seek_out_of_range, exception_io,"Seek offset out of range");
//! This operation requires a seekable object.
PFC_DECLARE_EXCEPTION(exception_io_object_not_seekable, exception_io,"Object is not seekable");
//! This operation requires an object with known length.
PFC_DECLARE_EXCEPTION(exception_io_no_length, exception_io,"Length of object is unknown");
//! Invalid path.
PFC_DECLARE_EXCEPTION(exception_io_no_handler_for_path, exception_io,"Invalid path");
//! Object already exists.
PFC_DECLARE_EXCEPTION(exception_io_already_exists, exception_io,"Object already exists");
//! Pipe error.
PFC_DECLARE_EXCEPTION(exception_io_no_data, exception_io,"The process receiving or sending data has terminated");
//! Network not reachable.
PFC_DECLARE_EXCEPTION(exception_io_network_not_reachable,exception_io,"Network not reachable");
//! Media is write protected.
PFC_DECLARE_EXCEPTION(exception_io_write_protected, exception_io_denied,"The media is write protected");
//! File is corrupted. This indicates filesystem call failure, not actual invalid data being read by the app.
PFC_DECLARE_EXCEPTION(exception_io_file_corrupted, exception_io,"The file is corrupted");
//! The disc required for requested operation is not available.
PFC_DECLARE_EXCEPTION(exception_io_disk_change, exception_io,"Disc not available");
//! The directory is not empty.
PFC_DECLARE_EXCEPTION(exception_io_directory_not_empty, exception_io,"Directory not empty");
//! A network connectivity error
PFC_DECLARE_EXCEPTION( exception_io_net, exception_io, "Network error");
//! A network security error
PFC_DECLARE_EXCEPTION( exception_io_net_security, exception_io_net, "Network security error");
//! A network connectivity error, specifically a DNS query failure
PFC_DECLARE_EXCEPTION( exception_io_dns, exception_io_net, "DNS error");
//! The path does not point to a directory.
PFC_DECLARE_EXCEPTION(exception_io_not_directory, exception_io, "Not a directory");
//! Stores file stats (size and timestamp).
struct t_filestats {
//! Size of the file.
t_filesize m_size;
//! Time of last file modification.
t_filetimestamp m_timestamp;
inline bool operator==(const t_filestats & param) const {return m_size == param.m_size && m_timestamp == param.m_timestamp;}
inline bool operator!=(const t_filestats & param) const {return m_size != param.m_size || m_timestamp != param.m_timestamp;}
};
//! Invalid/unknown file stats constant. See: t_filestats.
static const t_filestats filestats_invalid = {filesize_invalid,filetimestamp_invalid};
#ifdef _WIN32
PFC_NORETURN void exception_io_from_win32(DWORD p_code);
#define WIN32_IO_OP(X) {SetLastError(NO_ERROR); if (!(X)) exception_io_from_win32(GetLastError());}
// SPECIAL WORKAROUND: throw "file is read-only" rather than "access denied" where appropriate
PFC_NORETURN void win32_file_write_failure(DWORD p_code, const char * path);
#endif
//! Generic interface to read data from a nonseekable stream. Also see: stream_writer, file. \n
//! Error handling: all methods may throw exception_io or one of derivatives on failure; exception_aborted when abort_callback is signaled.
class NOVTABLE stream_reader {
public:
//! Attempts to reads specified number of bytes from the stream.
//! @param p_buffer Receives data being read. Must have at least p_bytes bytes of space allocated.
//! @param p_bytes Number of bytes to read.
//! @param p_abort abort_callback object signaling user aborting the operation.
//! @returns Number of bytes actually read. May be less than requested when EOF was reached.
virtual t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) = 0;
//! Reads specified number of bytes from the stream. If requested amount of bytes can't be read (e.g. EOF), throws exception_io_data_truncation.
//! @param p_buffer Receives data being read. Must have at least p_bytes bytes of space allocated.
//! @param p_bytes Number of bytes to read.
//! @param p_abort abort_callback object signaling user aborting the operation.
virtual void read_object(void * p_buffer,t_size p_bytes,abort_callback & p_abort);
//! Attempts to skip specified number of bytes in the stream.
//! @param p_bytes Number of bytes to skip.
//! @param p_abort abort_callback object signaling user aborting the operation.
//! @returns Number of bytes actually skipped, May be less than requested when EOF was reached.
virtual t_filesize skip(t_filesize p_bytes,abort_callback & p_abort);
//! Skips specified number of bytes in the stream. If requested amount of bytes can't be skipped (e.g. EOF), throws exception_io_data_truncation.
//! @param p_bytes Number of bytes to skip.
//! @param p_abort abort_callback object signaling user aborting the operation.
virtual void skip_object(t_filesize p_bytes,abort_callback & p_abort);
//! Helper template built around read_object. Reads single raw object from the stream.
//! @param p_object Receives object read from the stream on success.
//! @param p_abort abort_callback object signaling user aborting the operation.
template<typename T> inline void read_object_t(T& p_object,abort_callback & p_abort) {pfc::assert_raw_type<T>(); read_object(&p_object,sizeof(p_object),p_abort);}
//! Helper template built around read_object. Reads single raw object from the stream; corrects byte order assuming stream uses little endian order.
//! @param p_object Receives object read from the stream on success.
//! @param p_abort abort_callback object signaling user aborting the operation.
template<typename T> inline void read_lendian_t(T& p_object,abort_callback & p_abort) {read_object_t(p_object,p_abort); byte_order::order_le_to_native_t(p_object);}
//! Helper template built around read_object. Reads single raw object from the stream; corrects byte order assuming stream uses big endian order.
//! @param p_object Receives object read from the stream on success.
//! @param p_abort abort_callback object signaling user aborting the operation.
template<typename T> inline void read_bendian_t(T& p_object,abort_callback & p_abort) {read_object_t(p_object,p_abort); byte_order::order_be_to_native_t(p_object);}
//! Helper function; reads a string (with a 32-bit header indicating length in bytes followed by UTF-8 encoded data without a null terminator).
void read_string(pfc::string_base & p_out,abort_callback & p_abort);
//! Helper function; alternate way of storing strings; assumes string takes space up to end of stream.
void read_string_raw(pfc::string_base & p_out,abort_callback & p_abort);
//! Helper function; reads a string (with a 32-bit header indicating length in bytes followed by UTF-8 encoded data without a null terminator).
pfc::string read_string(abort_callback & p_abort);
//! Helper function; reads a string of specified length from the stream.
void read_string_ex(pfc::string_base & p_out,t_size p_bytes,abort_callback & p_abort);
//! Helper function; reads a string of specified length from the stream.
pfc::string read_string_ex(t_size p_len,abort_callback & p_abort);
void read_string_nullterm( pfc::string_base & out, abort_callback & abort );
t_filesize skip_till_eof(abort_callback & abort);
template<typename t_outArray>
void read_till_eof(t_outArray & out, abort_callback & abort) {
pfc::assert_raw_type<typename t_outArray::t_item>();
const t_size itemWidth = sizeof(typename t_outArray::t_item);
out.set_size(pfc::max_t<t_size>(1,256 / itemWidth)); t_size done = 0;
for(;;) {
t_size delta = out.get_size() - done;
t_size delta2 = read(out.get_ptr() + done, delta * itemWidth, abort ) / itemWidth;
done += delta2;
if (delta2 != delta) break;
out.set_size(out.get_size() << 1);
}
out.set_size(done);
}
uint8_t read_byte( abort_callback & abort );
protected:
stream_reader() {}
~stream_reader() {}
};
//! Generic interface to write data to a nonseekable stream. Also see: stream_reader, file. \n
//! Error handling: all methods may throw exception_io or one of derivatives on failure; exception_aborted when abort_callback is signaled.
class NOVTABLE stream_writer {
public:
//! Writes specified number of bytes from specified buffer to the stream.
//! @param p_buffer Buffer with data to write. Must contain at least p_bytes bytes.
//! @param p_bytes Number of bytes to write.
//! @param p_abort abort_callback object signaling user aborting the operation.
virtual void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) = 0;
//! Helper. Same as write(), provided for consistency.
inline void write_object(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {write(p_buffer,p_bytes,p_abort);}
//! Helper template. Writes single raw object to the stream.
//! @param p_object Object to write.
//! @param p_abort abort_callback object signaling user aborting the operation.
template<typename T> inline void write_object_t(const T & p_object, abort_callback & p_abort) {pfc::assert_raw_type<T>(); write_object(&p_object,sizeof(p_object),p_abort);}
//! Helper template. Writes single raw object to the stream; corrects byte order assuming stream uses little endian order.
//! @param p_object Object to write.
//! @param p_abort abort_callback object signaling user aborting the operation.
template<typename T> inline void write_lendian_t(const T & p_object, abort_callback & p_abort) {T temp = p_object; byte_order::order_native_to_le_t(temp); write_object_t(temp,p_abort);}
//! Helper template. Writes single raw object to the stream; corrects byte order assuming stream uses big endian order.
//! @param p_object Object to write.
//! @param p_abort abort_callback object signaling user aborting the operation.
template<typename T> inline void write_bendian_t(const T & p_object, abort_callback & p_abort) {T temp = p_object; byte_order::order_native_to_be_t(temp); write_object_t(temp,p_abort);}
//! Helper function; writes string (with 32-bit header indicating length in bytes followed by UTF-8 encoded data without null terminator).
void write_string(const char * p_string,abort_callback & p_abort);
void write_string(const char * p_string,t_size p_len,abort_callback & p_abort);
template<typename T>
void write_string(const T& val,abort_callback & p_abort) {write_string(pfc::stringToPtr(val),p_abort);}
//! Helper function; writes raw string to the stream, with no length info or null terminators.
void write_string_raw(const char * p_string,abort_callback & p_abort);
void write_string_nullterm( const char * p_string, abort_callback & p_abort) {this->write( p_string, strlen(p_string)+1, p_abort); }
protected:
stream_writer() {}
~stream_writer() {}
};
//! A class providing abstraction for an open file object, with reading/writing/seeking methods. See also: stream_reader, stream_writer (which it inherits read/write methods from). \n
//! Error handling: all methods may throw exception_io or one of derivatives on failure; exception_aborted when abort_callback is signaled.
class NOVTABLE file : public service_base, public stream_reader, public stream_writer {
public:
//! Seeking mode constants. Note: these are purposedly defined to same values as standard C SEEK_* constants
enum t_seek_mode {
//! Seek relative to beginning of file (same as seeking to absolute offset).
seek_from_beginning = 0,
//! Seek relative to current position.
seek_from_current = 1,
//! Seek relative to end of file.
seek_from_eof = 2,
};
//! Retrieves size of the file.
//! @param p_abort abort_callback object signaling user aborting the operation.
//! @returns File size on success; filesize_invalid if unknown (nonseekable stream etc).
virtual t_filesize get_size(abort_callback & p_abort) = 0;
//! Retrieves read/write cursor position in the file. In case of non-seekable stream, this should return number of bytes read so far since open/reopen call.
//! @param p_abort abort_callback object signaling user aborting the operation.
//! @returns Read/write cursor position
virtual t_filesize get_position(abort_callback & p_abort) = 0;
//! Resizes file to the specified size in bytes.
//! @param p_abort abort_callback object signaling user aborting the operation.
virtual void resize(t_filesize p_size,abort_callback & p_abort) = 0;
//! Sets read/write cursor position to the specified offset. Throws exception_io_seek_out_of_range if the specified offset is outside the valid range.
//! @param p_position position to seek to.
//! @param p_abort abort_callback object signaling user aborting the operation.
virtual void seek(t_filesize p_position,abort_callback & p_abort) = 0;
//! Same as seek() but throws exception_io_data instead of exception_io_seek_out_of_range.
void seek_probe(t_filesize p_position, abort_callback & p_abort);
//! Sets read/write cursor position to the specified offset; extended form allowing seeking relative to current position or to end of file.
//! @param p_position Position to seek to; interpretation of this value depends on p_mode parameter.
//! @param p_mode Seeking mode; see t_seek_mode enum values for further description.
//! @param p_abort abort_callback object signaling user aborting the operation.
virtual void seek_ex(t_sfilesize p_position,t_seek_mode p_mode,abort_callback & p_abort);
//! Returns whether the file is seekable or not. If can_seek() returns false, all seek() or seek_ex() calls will fail; reopen() is still usable on nonseekable streams.
virtual bool can_seek() = 0;
//! Retrieves mime type of the file.
//! @param p_out Receives content type string on success.
virtual bool get_content_type(pfc::string_base & p_out) = 0;
//! Hint, returns whether the file is already fully buffered into memory.
virtual bool is_in_memory() {return false;}
//! Optional, called by owner thread before sleeping.
//! @param p_abort abort_callback object signaling user aborting the operation.
virtual void on_idle(abort_callback & p_abort) {(void)p_abort;}
//! Retrieves last modification time of the file.
//! @param p_abort abort_callback object signaling user aborting the operation.
//! @returns Last modification time o fthe file; filetimestamp_invalid if N/A.
virtual t_filetimestamp get_timestamp(abort_callback & p_abort) {(void)p_abort;return filetimestamp_invalid;}
//! Resets non-seekable stream, or seeks to zero on seekable file.
//! @param p_abort abort_callback object signaling user aborting the operation.
virtual void reopen(abort_callback & p_abort) = 0;
//! Indicates whether the file is a remote resource and non-sequential access may be slowed down by lag. This is typically returns to true on non-seekable sources but may also return true on seekable sources indicating that seeking is supported but will be relatively slow.
virtual bool is_remote() = 0;
//! Retrieves file stats structure. Uses get_size() and get_timestamp().
t_filestats get_stats(abort_callback & p_abort);
//! Returns whether read/write cursor position is at the end of file.
bool is_eof(abort_callback & p_abort);
//! Truncates file to specified size (while preserving read/write cursor position if possible); uses set_eof().
void truncate(t_filesize p_position,abort_callback & p_abort);
//! Truncates the file at current read/write cursor position.
void set_eof(abort_callback & p_abort) {resize(get_position(p_abort),p_abort);}
//! Helper; retrieves size of the file. If size is not available (get_size() returns filesize_invalid), throws exception_io_no_length.
t_filesize get_size_ex(abort_callback & p_abort);
//! Helper; retrieves amount of bytes between read/write cursor position and end of file. Fails when length can't be determined.
t_filesize get_remaining(abort_callback & p_abort);
//! Security helper; fails early with exception_io_data_truncation if it is not possible to read this amount of bytes from this file at this position.
void probe_remaining(t_filesize bytes, abort_callback & p_abort);
//! Helper; throws exception_io_object_not_seekable if file is not seekable.
void ensure_seekable();
//! Helper; throws exception_io_object_is_remote if the file is remote.
void ensure_local();
//! Helper; transfers specified number of bytes between streams.
//! @returns number of bytes actually transferred. May be less than requested if e.g. EOF is reached.
static t_filesize g_transfer(stream_reader * src,stream_writer * dst,t_filesize bytes,abort_callback & p_abort);
//! Helper; transfers specified number of bytes between streams. Throws exception if requested number of bytes could not be read (EOF).
static void g_transfer_object(stream_reader * src,stream_writer * dst,t_filesize bytes,abort_callback & p_abort);
//! Helper; transfers entire file content from one file to another, erasing previous content.
static void g_transfer_file(const service_ptr_t<file> & p_from,const service_ptr_t<file> & p_to,abort_callback & p_abort);
//! Helper; transfers file modification times from one file to another, if supported by underlying objects. Returns true on success, false if the operation doesn't appear to be supported.
static bool g_copy_timestamps(service_ptr_t<file> from, service_ptr_t<file> to, abort_callback& abort);
static bool g_copy_creation_time(service_ptr_t<file> from, service_ptr_t<file> to, abort_callback& abort);
//! Helper; improved performance over g_transfer on streams (avoids disk fragmentation when transferring large blocks).
static t_filesize g_transfer(service_ptr_t<file> p_src,service_ptr_t<file> p_dst,t_filesize p_bytes,abort_callback & p_abort);
//! Helper; improved performance over g_transfer_file on streams (avoids disk fragmentation when transferring large blocks).
static void g_transfer_object(service_ptr_t<file> p_src,service_ptr_t<file> p_dst,t_filesize p_bytes,abort_callback & p_abort);
//! file_lowLevelIO wrapper
void flushFileBuffers_(abort_callback &);
//! file_lowLevelIO wrapper
size_t lowLevelIO_(const GUID & guid, size_t arg1, void * arg2, size_t arg2size, abort_callback & abort);
t_filesize skip(t_filesize p_bytes,abort_callback & p_abort);
t_filesize skip_seek(t_filesize p_bytes,abort_callback & p_abort);
FB2K_MAKE_SERVICE_INTERFACE(file,service_base);
};
typedef service_ptr_t<file> file_ptr;
//! Extension for shoutcast dynamic metadata handling.
class file_dynamicinfo : public file {
FB2K_MAKE_SERVICE_INTERFACE(file_dynamicinfo,file);
public:
//! Retrieves "static" info that doesn't change in the middle of stream, such as station names etc. Returns true on success; false when static info is not available.
virtual bool get_static_info(class file_info & p_out) = 0;
//! Returns whether dynamic info is available on this stream or not.
virtual bool is_dynamic_info_enabled() = 0;
//! Retrieves dynamic stream info (e.g. online stream track titles). Returns true on success, false when info has not changed since last call.
virtual bool get_dynamic_info(class file_info & p_out) = 0;
};
//! \since 1.4.1
//! Extended version of file_dynamicinfo
class file_dynamicinfo_v2 : public file_dynamicinfo {
FB2K_MAKE_SERVICE_INTERFACE(file_dynamicinfo_v2, file_dynamicinfo);
public:
virtual bool get_dynamic_info_v2( class file_info & out, t_filesize & outOffset ) = 0;
protected:
// Obsolete
bool get_dynamic_info(class file_info & p_out);
};
//! Extension for cached file access - allows callers to know that they're dealing with a cache layer, to prevent cache duplication.
class file_cached : public file {
FB2K_MAKE_SERVICE_INTERFACE(file_cached, file);
public:
virtual size_t get_cache_block_size() = 0;
virtual void suggest_grow_cache(size_t suggestSize) = 0;
static file::ptr g_create(service_ptr_t<file> p_base,abort_callback & p_abort, t_size blockSize);
static void g_create(service_ptr_t<file> & p_out,service_ptr_t<file> p_base,abort_callback & p_abort, t_size blockSize);
static void g_decodeInitCache(file::ptr & theFile, abort_callback & abort, size_t blockSize);
};
//! \since 1.5
//! Additional service implemented by standard file object providing access to low level OS specific APIs.
class file_lowLevelIO : public service_base {
FB2K_MAKE_SERVICE_INTERFACE(file_lowLevelIO, service_base );
public:
//! @returns 0 if the command was not recognized, a command-defined non zero value otherwise.
virtual size_t lowLevelIO( const GUID & guid, size_t arg1, void * arg2, size_t arg2size, abort_callback & abort ) = 0;
//! Win32 FlushFileBuffers() wrapper. \n
//! Throws exception_io_denied on a file opened for reading. \n
//! No arguments are defined. \n
//! Returns 1 if handled, 0 if unsupported.
static const GUID guid_flushFileBuffers;
//! Retrieves file creation / last access / last write times. \n
//! Parameters: arg2 points to a filetimes_t struct to receive the data; arg2size must be set to sizeof(filetimes_t). \n
//! If the filesystem does not support a specific portion of the information, relevant struct member will be set to filetimestamp_invalid. \n
//! Returns 1 if handled, 0 if unsupported.
static const GUID guid_getFileTimes;
//! Sets file creation / last access / last write times. \n
//! Parameters: arg2 points to a filetimes_t struct holding the new data; arg2size must be set to sizeof(filetimes_t). \n
//! Individual members of the filetimes_t struct can be set to filetimestamp_invalid, if not all of the values are to be altered on the file. \n
//! Returns 1 if handled, 0 if unsupported.
static const GUID guid_setFileTimes;
//! Struct to be used with guid_getFileTimes / guid_setFileTimes.
struct filetimes_t {
t_filetimestamp creation = filetimestamp_invalid;
t_filetimestamp lastAccess = filetimestamp_invalid;
t_filetimestamp lastWrite = filetimestamp_invalid;
};
//! Helper
bool flushFileBuffers(abort_callback &);
//! Helper
bool getFileTimes( filetimes_t & out, abort_callback &);
//! Helper
bool setFileTimes( filetimes_t const & in, abort_callback &);
};
//! Implementation helper - contains dummy implementations of methods that modify the file
template<typename t_base> class file_readonly_t : public t_base {
public:
void resize(t_filesize p_size,abort_callback & p_abort) {throw exception_io_denied();}
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {throw exception_io_denied();}
};
typedef file_readonly_t<file> file_readonly;
class file_streamstub : public file_readonly {
public:
t_size read(void *,t_size,abort_callback &) {return 0;}
t_filesize get_size(abort_callback &) {return filesize_invalid;}
t_filesize get_position(abort_callback &) {return 0;}
bool get_content_type(pfc::string_base &) {return false;}
bool is_remote() {return true;}
void reopen(abort_callback&) {}
void seek(t_filesize,abort_callback &) {throw exception_io_object_not_seekable();}
bool can_seek() {return false;}
};
class filesystem;
class NOVTABLE directory_callback {
public:
//! @returns true to continue enumeration, false to abort.
virtual bool on_entry(filesystem * p_owner,abort_callback & p_abort,const char * p_url,bool p_is_subdirectory,const t_filestats & p_stats)=0;
};
//! Entrypoint service for all filesystem operations.\n
//! Implementation: standard implementations for local filesystem etc are provided by core.\n
//! Instantiation: use static helper functions rather than calling filesystem interface methods directly, e.g. filesystem::g_open() to open a file.
class NOVTABLE filesystem : public service_base {
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(filesystem);
public:
//! Enumeration specifying how to open a file. See: filesystem::open(), filesystem::g_open().
typedef uint32_t t_open_mode;
enum {
//! Opens an existing file for reading; if the file does not exist, the operation will fail.
open_mode_read,
//! Opens an existing file for writing; if the file does not exist, the operation will fail.
open_mode_write_existing,
//! Opens a new file for writing; if the file exists, its contents will be wiped.
open_mode_write_new,
open_mode_mask = 0xFF,
};
virtual bool get_canonical_path(const char * p_path,pfc::string_base & p_out)=0;
virtual bool is_our_path(const char * p_path)=0;
virtual bool get_display_path(const char * p_path,pfc::string_base & p_out)=0;
virtual void open(service_ptr_t<file> & p_out,const char * p_path, t_open_mode p_mode,abort_callback & p_abort)=0;
virtual void remove(const char * p_path,abort_callback & p_abort)=0;
//! Moves/renames a file. Will fail if the destination file already exists. \n
//! Note that this function may throw exception_io_denied instead of exception_io_sharing_violation when the file is momentarily in use, due to bugs in Windows MoveFile() API. There is no legitimate way for us to distinguish between the two scenarios.
virtual void move(const char * p_src,const char * p_dst,abort_callback & p_abort)=0;
//! Queries whether a file at specified path belonging to this filesystem is a remote object or not.
virtual bool is_remote(const char * p_src) = 0;
//! Retrieves stats of a file at specified path.
virtual void get_stats(const char * p_path,t_filestats & p_stats,bool & p_is_writeable,abort_callback & p_abort) = 0;
//! Helper
t_filestats get_stats( const char * path, abort_callback & abort );
virtual bool relative_path_create(const char * file_path,const char * playlist_path,pfc::string_base & out) {return false;}
virtual bool relative_path_parse(const char * relative_path,const char * playlist_path,pfc::string_base & out) {return false;}
//! Creates a directory.
virtual void create_directory(const char * p_path,abort_callback & p_abort) = 0;
virtual void list_directory(const char * p_path,directory_callback & p_out,abort_callback & p_abort)=0;
//! Hint; returns whether this filesystem supports mime types. \n
//! When this returns false, all file::get_content_type() calls on files opened thru this filesystem implementation will return false; otherwise, file::get_content_type() calls may return true depending on the file.
virtual bool supports_content_types() = 0;
static void g_get_canonical_path(const char * path,pfc::string_base & out);
static void g_get_display_path(const char * path,pfc::string_base & out);
//! Extracts the native filesystem path, sets out to the input path if native path cannot be extracted so the output is always set.
//! @returns True if native path was extracted successfully, false otherwise (but output is set anyway).
static bool g_get_native_path( const char * path, pfc::string_base & out);
static bool g_get_interface(service_ptr_t<filesystem> & p_out,const char * path);//path is AFTER get_canonical_path
static filesystem::ptr g_get_interface(const char * path);// throws exception_io_no_handler_for_path on failure
static filesystem::ptr get( const char * path ) { return g_get_interface(path); } // shortened
static bool g_is_remote(const char * p_path);//path is AFTER get_canonical_path
static bool g_is_recognized_and_remote(const char * p_path);//path is AFTER get_canonical_path
static bool g_is_remote_safe(const char * p_path) {return g_is_recognized_and_remote(p_path);}
static bool g_is_remote_or_unrecognized(const char * p_path);
static bool g_is_recognized_path(const char * p_path);
//! Opens file at specified path, with specified access privileges.
static void g_open(service_ptr_t<file> & p_out,const char * p_path,t_open_mode p_mode,abort_callback & p_abort);
//! Attempts to open file at specified path; if the operation fails with sharing violation error, keeps retrying (with short sleep period between retries) for specified amount of time.
static void g_open_timeout(service_ptr_t<file> & p_out,const char * p_path,t_open_mode p_mode,double p_timeout,abort_callback & p_abort);
static void g_open_write_new(service_ptr_t<file> & p_out,const char * p_path,abort_callback & p_abort);
static void g_open_read(service_ptr_t<file> & p_out,const char * path,abort_callback & p_abort) {return g_open(p_out,path,open_mode_read,p_abort);}
static void g_open_precache(service_ptr_t<file> & p_out,const char * path,abort_callback & p_abort);//open only for precaching data (eg. will fail on http etc)
static bool g_exists(const char * p_path,abort_callback & p_abort);
static bool g_exists_writeable(const char * p_path,abort_callback & p_abort);
//! Removes file at specified path.
static void g_remove(const char * p_path,abort_callback & p_abort);
//! Attempts to remove file at specified path; if the operation fails with a sharing violation error, keeps retrying (with short sleep period between retries) for specified amount of time.
static void g_remove_timeout(const char * p_path,double p_timeout,abort_callback & p_abort);
//! Moves file from one path to another.
//! Note that this function may throw exception_io_denied instead of exception_io_sharing_violation when the file is momentarily in use, due to bugs in Windows MoveFile() API. There is no legitimate way for us to distinguish between the two scenarios.
static void g_move(const char * p_src,const char * p_dst,abort_callback & p_abort);
//! Attempts to move file from one path to another; if the operation fails with a sharing violation error, keeps retrying (with short sleep period between retries) for specified amount of time.
static void g_move_timeout(const char * p_src,const char * p_dst,double p_timeout,abort_callback & p_abort);
static void g_link(const char * p_src,const char * p_dst,abort_callback & p_abort);
static void g_link_timeout(const char * p_src,const char * p_dst,double p_timeout,abort_callback & p_abort);
static void g_copy(const char * p_src,const char * p_dst,abort_callback & p_abort);//needs canonical path
static void g_copy_timeout(const char * p_src,const char * p_dst,double p_timeout,abort_callback & p_abort);//needs canonical path
static void g_copy_directory(const char * p_src,const char * p_dst,abort_callback & p_abort);//needs canonical path
static void g_get_stats(const char * p_path,t_filestats & p_stats,bool & p_is_writeable,abort_callback & p_abort);
static bool g_relative_path_create(const char * p_file_path,const char * p_playlist_path,pfc::string_base & out);
static bool g_relative_path_parse(const char * p_relative_path,const char * p_playlist_path,pfc::string_base & out);
static void g_create_directory(const char * p_path,abort_callback & p_abort);
//! If for some bloody reason you ever need stream io compatibility, use this, INSTEAD of calling fopen() on the path string you've got; will only work with file:// (and not with http://, unpack:// or whatever)
static FILE * streamio_open(const char * p_path,const char * p_flags);
static void g_open_temp(service_ptr_t<file> & p_out,abort_callback & p_abort);
static void g_open_tempmem(service_ptr_t<file> & p_out,abort_callback & p_abort);
static file::ptr g_open_tempmem();
static void g_list_directory(const char * p_path,directory_callback & p_out,abort_callback & p_abort);// path must be canonical
static bool g_is_valid_directory(const char * path,abort_callback & p_abort);
static bool g_is_empty_directory(const char * path,abort_callback & p_abort);
void remove_object_recur(const char * path, abort_callback & abort);
void remove_directory_content(const char * path, abort_callback & abort);
static void g_remove_object_recur(const char * path, abort_callback & abort);
static void g_remove_object_recur_timeout(const char * path, double timeout, abort_callback & abort);
// Presumes both source and destination belong to this filesystem.
void copy_directory(const char * p_src, const char * p_dst, abort_callback & p_abort);
//! Moves/renames a file, overwriting the destination atomically if exists. \n
//! Note that this function may throw exception_io_denied instead of exception_io_sharing_violation when the file is momentarily in use, due to bugs in Windows MoveFile() API. There is no legitimate way for us to distinguish between the two scenarios.
void move_overwrite( const char * src, const char * dst, abort_callback & abort);
//! Moves/renames a file, overwriting the destination atomically if exists. \n
//! Meant to retain destination attributes if feasible. Otherwise identical to move_overwrite(). \n
//! Note that this function may throw exception_io_denied instead of exception_io_sharing_violation when the file is momentarily in use, due to bugs in Windows MoveFile() API. There is no legitimate way for us to distinguish between the two scenarios.
void replace_file(const char * src, const char * dst, abort_callback & abort);
//! Create a directory, without throwing an exception if it already exists.
//! @param didCreate bool flag indicating whether a new directory was created or not. \n
//! This should be a retval, but because it's messy to obtain this information with certain APIs, the caller can opt out of receiving this information,.
void make_directory( const char * path, abort_callback & abort, bool * didCreate = nullptr );
//! Bool retval version of make_directory().
bool make_directory_check( const char * path, abort_callback & abort );
bool directory_exists(const char * path, abort_callback & abort);
bool file_exists( const char * path, abort_callback & abort );
char pathSeparator();
//! Extracts the filename.ext portion of the path. \n
//! The filename is ready to be presented to the user - URL decoding and such (similar to get_display_path()) is applied.
void extract_filename_ext(const char * path, pfc::string_base & outFN);
//! Retrieves the parent path.
bool get_parent_path(const char * path, pfc::string_base & out);
file::ptr openWriteNew( const char * path, abort_callback & abort, double timeout );
file::ptr openWriteExisting(const char * path, abort_callback & abort, double timeout);
file::ptr openRead( const char * path, abort_callback & abort, double timeout);
file::ptr openEx( const char * path, t_open_mode mode, abort_callback & abort, double timeout);
void read_whole_file(const char * path, mem_block_container & out, pfc::string_base & outContentType, size_t maxBytes, abort_callback & abort );
bool is_transacted();
bool commit_if_transacted(abort_callback &abort);
//! Full file rewrite helper that automatically does the right thing to ensure atomic update. \n
//! If this is a transacted filesystem, a simple in-place rewrite is performed. \n
//! If this is not a transacted filesystem, your content first goes to a temporary file, which then replaces the original. \n
//! See also: filesystem_transacted. \n
//! In order to perform transacted operations, you must obtain a transacted filesystem explicitly, or get one passed down from a higher level context (example: in config_io_callback_v3).
void rewrite_file( const char * path, abort_callback & abort, double opTimeout, std::function<void (file::ptr) > worker );
//! Full directory rewrite helper that automatically does the right thing to ensure atomic update. \n
//! If this is a transacted filesystem, a simple in-place rewrite is performed. \n
//! If this is not a transacted filesystem, your content first goes to a temporary folder, which then replaces the original. \n
//! It is encouraged to perform flushFileBuffers on all files accessed from within. \n
//! See also: filesystem_transacted. \n
//! In order to perform transacted operations, you must obtain a transacted filesystem explicitly, or get one passed down from a higher level context (example: in config_io_callback_v3).
void rewrite_directory(const char * path, abort_callback & abort, double opTimeout, std::function<void(const char *) > worker);
protected:
static bool get_parent_helper(const char * path, char separator, pfc::string_base & out);
void read_whole_file_fallback( const char * path, mem_block_container & out, pfc::string_base & outContentType, size_t maxBytes, abort_callback & abort );
};
namespace listMode {
enum {
//! Return files
files = 1,
//! Return folders
folders = 2,
//! Return both files and flders
filesAndFolders = files | folders,
//! Return hidden files
hidden = 4,
//! Do not hand over filestats unless they come for free with folder enumeration
suppressStats = 8,
};
}
class filesystem_v2 : public filesystem {
FB2K_MAKE_SERVICE_INTERFACE( filesystem_v2, filesystem )
public:
//! Moves/renames a file, overwriting the destination atomically if exists. \n
//! Note that this function may throw exception_io_denied instead of exception_io_sharing_violation when the file is momentarily in use, due to bugs in Windows MoveFile() API. There is no legitimate way for us to distinguish between the two scenarios.
virtual void move_overwrite(const char * src, const char * dst, abort_callback & abort) = 0;
//! Moves/renames a file, overwriting the destination atomically if exists. \n
//! Meant to retain destination attributes if feasible. Otherwise identical to move_overwrite(). \n
//! Note that this function may throw exception_io_denied instead of exception_io_sharing_violation when the file is momentarily in use, due to bugs in Windows MoveFile() API. There is no legitimate way for us to distinguish between the two scenarios.
virtual void replace_file(const char * src, const char * dst, abort_callback & abort);
//! Create a directory, without throwing an exception if it already exists.
//! @param didCreate bool flag indicating whether a new directory was created or not. \n
//! This should be a retval, but because it's messy to obtain this information with certain APIs, the caller can opt out of receiving this information,.
virtual void make_directory(const char * path, abort_callback & abort, bool * didCreate = nullptr) = 0;
virtual bool directory_exists(const char * path, abort_callback & abort) = 0;
virtual bool file_exists(const char * path, abort_callback & abort) = 0;
virtual char pathSeparator() = 0;
virtual void extract_filename_ext(const char * path, pfc::string_base & outFN);
virtual bool get_parent_path( const char * path, pfc::string_base & out);
virtual void list_directory_ex(const char * p_path, directory_callback & p_out, unsigned listMode, abort_callback & p_abort) = 0;
virtual void read_whole_file(const char * path, mem_block_container & out, pfc::string_base & outContentType, size_t maxBytes, abort_callback & abort);
//! Wrapper to list_directory_ex
void list_directory( const char * p_path, directory_callback & p_out, abort_callback & p_abort );
bool make_directory_check(const char * path, abort_callback & abort);
};
class directory_callback_impl : public directory_callback
{
struct t_entry
{
pfc::string_simple m_path;
t_filestats m_stats;
t_entry(const char * p_path, const t_filestats & p_stats) : m_path(p_path), m_stats(p_stats) {}
};
pfc::list_t<pfc::rcptr_t<t_entry> > m_data;
bool m_recur;
static int sortfunc(const pfc::rcptr_t<const t_entry> & p1, const pfc::rcptr_t<const t_entry> & p2) {return pfc::io::path::compare(p1->m_path,p2->m_path);}
public:
bool on_entry(filesystem * owner,abort_callback & p_abort,const char * url,bool is_subdirectory,const t_filestats & p_stats);
directory_callback_impl(bool p_recur) : m_recur(p_recur) {}
t_size get_count() {return m_data.get_count();}
const char * operator[](t_size n) const {return m_data[n]->m_path;}
const char * get_item(t_size n) const {return m_data[n]->m_path;}
const t_filestats & get_item_stats(t_size n) const {return m_data[n]->m_stats;}
void sort() {m_data.sort_t(sortfunc);}
};
t_filetimestamp filetimestamp_from_system_timer();
t_filetimestamp import_DOS_time(uint32_t);
#ifdef _WIN32
inline t_filetimestamp import_filetimestamp(FILETIME ft) {
return *reinterpret_cast<t_filetimestamp*>(&ft);
}
#endif
void generate_temp_location_for_file(pfc::string_base & p_out, const char * p_origpath,const char * p_extension,const char * p_magic);
inline file_ptr fileOpen(const char * p_path,filesystem::t_open_mode p_mode,abort_callback & p_abort,double p_timeout) {
file_ptr temp; filesystem::g_open_timeout(temp,p_path,p_mode,p_timeout,p_abort); PFC_ASSERT(temp.is_valid()); return temp;
}
inline file_ptr fileOpenReadExisting(const char * p_path,abort_callback & p_abort,double p_timeout = 0) {
return fileOpen(p_path,filesystem::open_mode_read,p_abort,p_timeout);
}
inline file_ptr fileOpenWriteExisting(const char * p_path,abort_callback & p_abort,double p_timeout = 0) {
return fileOpen(p_path,filesystem::open_mode_write_existing,p_abort,p_timeout);
}
inline file_ptr fileOpenWriteNew(const char * p_path,abort_callback & p_abort,double p_timeout = 0) {
return fileOpen(p_path,filesystem::open_mode_write_new,p_abort,p_timeout);
}
template<typename t_list>
class directory_callback_retrieveList : public directory_callback {
public:
directory_callback_retrieveList(t_list & p_list,bool p_getFiles,bool p_getSubDirectories) : m_list(p_list), m_getFiles(p_getFiles), m_getSubDirectories(p_getSubDirectories) {}
bool on_entry(filesystem * p_owner,abort_callback & p_abort,const char * p_url,bool p_is_subdirectory,const t_filestats & p_stats) {
p_abort.check();
if (p_is_subdirectory ? m_getSubDirectories : m_getFiles) {
m_list.add_item(p_url);
}
return true;
}
private:
const bool m_getSubDirectories;
const bool m_getFiles;
t_list & m_list;
};
template<typename t_list>
class directory_callback_retrieveListEx : public directory_callback {
public:
directory_callback_retrieveListEx(t_list & p_files, t_list & p_directories) : m_files(p_files), m_directories(p_directories) {}
bool on_entry(filesystem * p_owner,abort_callback & p_abort,const char * p_url,bool p_is_subdirectory,const t_filestats & p_stats) {
p_abort.check();
if (p_is_subdirectory) m_directories += p_url;
else m_files += p_url;
return true;
}
private:
t_list & m_files;
t_list & m_directories;
};
template<typename t_list> class directory_callback_retrieveListRecur : public directory_callback {
public:
directory_callback_retrieveListRecur(t_list & p_list) : m_list(p_list) {}
bool on_entry(filesystem * owner,abort_callback & p_abort,const char * path, bool isSubdir, const t_filestats&) {
if (isSubdir) {
try { owner->list_directory(path,*this,p_abort); } catch(exception_io) {}
} else {
m_list.add_item(path);
}
return true;
}
private:
t_list & m_list;
};
template<typename t_list>
static void listFiles(const char * p_path,t_list & p_out,abort_callback & p_abort) {
directory_callback_retrieveList<t_list> callback(p_out,true,false);
filesystem::g_list_directory(p_path,callback,p_abort);
}
template<typename t_list>
static void listDirectories(const char * p_path,t_list & p_out,abort_callback & p_abort) {
directory_callback_retrieveList<t_list> callback(p_out,false,true);
filesystem::g_list_directory(p_path,callback,p_abort);
}
template<typename t_list>
static void listFilesAndDirectories(const char * p_path,t_list & p_files,t_list & p_directories,abort_callback & p_abort) {
directory_callback_retrieveListEx<t_list> callback(p_files,p_directories);
filesystem::g_list_directory(p_path,callback,p_abort);
}
template<typename t_list>
static void listFilesRecur(const char * p_path,t_list & p_out,abort_callback & p_abort) {
directory_callback_retrieveListRecur<t_list> callback(p_out);
filesystem::g_list_directory(p_path,callback,p_abort);
}
bool extract_native_path(const char * p_fspath,pfc::string_base & p_native);
bool _extract_native_path_ptr(const char * & p_fspath);
bool is_native_filesystem( const char * p_fspath );
bool extract_native_path_ex(const char * p_fspath, pfc::string_base & p_native);//prepends \\?\ where needed
bool extract_native_path_archive_aware( const char * fspatch, pfc::string_base & out );
template<typename T>
pfc::string getPathDisplay(const T& source) {
const char * c = pfc::stringToPtr(source);
if ( *c == 0 ) return c;
pfc::string_formatter temp;
filesystem::g_get_display_path(c,temp);
return temp.toString();
}
template<typename T>
pfc::string getPathCanonical(const T& source) {
const char * c = pfc::stringToPtr(source);
if ( *c == 0 ) return c;
pfc::string_formatter temp;
filesystem::g_get_canonical_path(c,temp);
return temp.toString();
}
bool matchContentType(const char * fullString, const char * ourType);
bool matchProtocol(const char * fullString, const char * protocolName);
const char * afterProtocol( const char * fullString );
void substituteProtocol(pfc::string_base & out, const char * fullString, const char * protocolName);
bool matchContentType_MP3( const char * fullString);
bool matchContentType_MP4audio( const char * fullString);
bool matchContentType_MP4( const char * fullString);
bool matchContentType_Ogg( const char * fullString);
bool matchContentType_Opus( const char * fullString);
bool matchContentType_FLAC( const char * fullString);
bool matchContentType_WavPack( const char * fullString);
bool matchContentType_WAV( const char * fullString);
bool matchContentType_Musepack( const char * fullString);
const char * extensionFromContentType( const char * contentType );
const char * contentTypeFromExtension( const char * ext );
void purgeOldFiles(const char * directory, t_filetimestamp period, abort_callback & abort);
//! \since 1.6
class read_ahead_tools : public service_base {
FB2K_MAKE_SERVICE_COREAPI(read_ahead_tools);
public:
//! Turn any file object into asynchronous read-ahead-buffered file.
//! @param f File object to wrap. Do not call this object's method after a successful call to add_read_ahead; new file object takes over the ownership of it.
//! @param size Requested read-ahead bytes. Pass 0 to use user settings for local/remote playback.
virtual file::ptr add_read_ahead(file::ptr f, size_t size, abort_callback & aborter) = 0;
//! A helper method to use prior to opening decoders. \n
//! May open the file if needed or leave it blank for the decoder to open.
//! @param f File object to open if needed (buffering mandated by user settings). May be valid or null prior to call. May be valid or null (no buffering) after call.
//! @param path Path to open. May be null if f is not null. At least one of f and path must be valid prior to call.
virtual void open_file_helper(file::ptr & f, const char * path, abort_callback & aborter) = 0;
};
}
using namespace foobar2000_io;
#include "filesystem_helper.h"

View File

@@ -0,0 +1,259 @@
#include "foobar2000.h"
void stream_writer_chunk::write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
t_size remaining = p_bytes, written = 0;
while(remaining > 0) {
t_size delta = sizeof(m_buffer) - m_buffer_state;
if (delta > remaining) delta = remaining;
memcpy(m_buffer,(const t_uint8*)p_buffer + written,delta);
written += delta;
remaining -= delta;
if (m_buffer_state == sizeof(m_buffer)) {
m_writer->write_lendian_t((t_uint8)m_buffer_state,p_abort);
m_writer->write_object(m_buffer,m_buffer_state,p_abort);
m_buffer_state = 0;
}
}
}
void stream_writer_chunk::flush(abort_callback & p_abort)
{
m_writer->write_lendian_t((t_uint8)m_buffer_state,p_abort);
if (m_buffer_state > 0) {
m_writer->write_object(m_buffer,m_buffer_state,p_abort);
m_buffer_state = 0;
}
}
/*
stream_writer * m_writer;
unsigned m_buffer_state;
unsigned char m_buffer[255];
*/
t_size stream_reader_chunk::read(void * p_buffer,t_size p_bytes,abort_callback & p_abort)
{
t_size todo = p_bytes, done = 0;
while(todo > 0) {
if (m_buffer_size == m_buffer_state) {
if (m_eof) break;
t_uint8 temp;
m_reader->read_lendian_t(temp,p_abort);
m_buffer_size = temp;
if (temp != sizeof(m_buffer)) m_eof = true;
m_buffer_state = 0;
if (m_buffer_size>0) {
m_reader->read_object(m_buffer,m_buffer_size,p_abort);
}
}
t_size delta = m_buffer_size - m_buffer_state;
if (delta > todo) delta = todo;
if (delta > 0) {
memcpy((unsigned char*)p_buffer + done,m_buffer + m_buffer_state,delta);
todo -= delta;
done += delta;
m_buffer_state += delta;
}
}
return done;
}
void stream_reader_chunk::flush(abort_callback & p_abort) {
while(!m_eof) {
p_abort.check_e();
t_uint8 temp;
m_reader->read_lendian_t(temp,p_abort);
m_buffer_size = temp;
if (temp != sizeof(m_buffer)) m_eof = true;
m_buffer_state = 0;
if (m_buffer_size>0) {
m_reader->skip_object(m_buffer_size,p_abort);
}
}
}
/*
stream_reader * m_reader;
unsigned m_buffer_state, m_buffer_size;
bool m_eof;
unsigned char m_buffer[255];
*/
void stream_reader_chunk::g_skip(stream_reader * p_stream,abort_callback & p_abort) {
stream_reader_chunk(p_stream).flush(p_abort);
}
static void fileSanitySeek(file::ptr f, pfc::array_t<uint8_t> const & content, size_t offset, abort_callback & aborter) {
const size_t readAmount = 64 * 1024;
pfc::array_staticsize_t<uint8_t> buf; buf.set_size_discard(readAmount);
f->seek(offset, aborter);
t_filesize positionGot = f->get_position(aborter);
if (positionGot != offset) {
FB2K_console_formatter() << "File sanity: at " << offset << " reported position became " << positionGot;
throw std::runtime_error("Seek test failure");
}
size_t did = f->read(buf.get_ptr(), readAmount, aborter);
size_t expected = pfc::min_t<size_t>(readAmount, content.get_size() - offset);
if (expected != did) {
FB2K_console_formatter() << "File sanity: at " << offset << " bytes, expected read size of " << expected << ", got " << did;
if (did > expected) FB2K_console_formatter() << "Read past EOF";
else FB2K_console_formatter() << "Premature EOF";
throw std::runtime_error("Seek test failure");
}
if (memcmp(buf.get_ptr(), content.get_ptr() + offset, did) != 0) {
FB2K_console_formatter() << "File sanity: data mismatch at " << offset << " - " << (offset + did) << " bytes";
throw std::runtime_error("Seek test failure");
}
positionGot = f->get_position(aborter);
if (positionGot != offset + did) {
FB2K_console_formatter() << "File sanity: at " << offset << "+" << did << "=" << (offset + did) << " reported position became " << positionGot;
throw std::runtime_error("Seek test failure");
}
}
bool fb2kFileSelfTest(file::ptr f, abort_callback & aborter) {
try {
pfc::array_t<uint8_t> fileContent;
f->reopen(aborter);
f->read_till_eof(fileContent, aborter);
{
t_filesize sizeClaimed = f->get_size(aborter);
if (sizeClaimed == filesize_invalid) {
FB2K_console_formatter() << "File sanity: file reports unknown size, actual size read is " << fileContent.get_size();
}
else {
if (sizeClaimed != fileContent.get_size()) {
FB2K_console_formatter() << "File sanity: file reports size of " << sizeClaimed << ", actual size read is " << fileContent.get_size();
throw std::runtime_error("File size mismatch");
}
else {
FB2K_console_formatter() << "File sanity: file size check OK: " << sizeClaimed;
}
}
}
{
FB2K_console_formatter() << "File sanity: testing N-first-bytes reads...";
const size_t sizeUpTo = pfc::min_t<size_t>(fileContent.get_size(), 1024 * 1024);
pfc::array_staticsize_t<uint8_t> buf1;
buf1.set_size_discard(sizeUpTo);
for (size_t w = 1; w <= sizeUpTo; w <<= 1) {
f->reopen(aborter);
size_t did = f->read(buf1.get_ptr(), w, aborter);
if (did != w) {
FB2K_console_formatter() << "File sanity: premature EOF reading first " << w << " bytes, got " << did;
throw std::runtime_error("Premature EOF");
}
if (memcmp(fileContent.get_ptr(), buf1.get_ptr(), did) != 0) {
FB2K_console_formatter() << "File sanity: file content mismatch reading first " << w << " bytes";
throw std::runtime_error("File content mismatch");
}
}
}
if (f->can_seek()) {
FB2K_console_formatter() << "File sanity: testing random access...";
{
size_t sizeUpTo = pfc::min_t<size_t>(fileContent.get_size(), 1024 * 1024);
for (size_t w = 1; w < sizeUpTo; w <<= 1) {
fileSanitySeek(f, fileContent, w, aborter);
}
fileSanitySeek(f, fileContent, fileContent.get_size(), aborter);
for (size_t w = 1; w < sizeUpTo; w <<= 1) {
fileSanitySeek(f, fileContent, fileContent.get_size() - w, aborter);
}
fileSanitySeek(f, fileContent, fileContent.get_size() / 2, aborter);
}
}
FB2K_console_formatter() << "File sanity test: all OK";
return true;
}
catch (std::exception const & e) {
FB2K_console_formatter() << "File sanity test failure: " << e.what();
return false;
}
}
namespace foobar2000_io {
void retryFileDelete(double timeout, abort_callback & a, std::function<void()> f) {
FB2K_RETRY_ON_EXCEPTION3(f(), a, timeout, exception_io_sharing_violation, exception_io_denied, exception_io_directory_not_empty);
}
void retryFileMove(double timeout, abort_callback & a, std::function<void() > f) {
FB2K_RETRY_FILE_MOVE( f(), a, timeout );
}
void retryOnSharingViolation(double timeout, abort_callback & a, std::function<void() > f) {
FB2K_RETRY_ON_SHARING_VIOLATION(f(), a, timeout);
}
void retryOnSharingViolation(std::function<void() > f, double timeout, abort_callback & a) {
FB2K_RETRY_ON_SHARING_VIOLATION( f(), a, timeout );
}
void listDirectory( const char * path, abort_callback & aborter, listDirectoryFunc_t func) {
listDirectoryCallbackImpl cb; cb.m_func = func;
filesystem::g_list_directory(path, cb, aborter);
}
#ifdef _WIN32
pfc::string8 stripParentFolders( const char * inPath ) {
PFC_ASSERT( strstr(inPath, "://" ) == nullptr || matchProtocol( inPath, "file" ) );
size_t prefixLen = pfc::string_find_first(inPath, "://");
if ( prefixLen != pfc_infinite ) prefixLen += 3;
else prefixLen = 0;
pfc::chain_list_v2_t<pfc::string_part_ref> segments;
pfc::splitStringByChar(segments, inPath + prefixLen, '\\' );
for ( auto i = segments.first(); i.is_valid(); ) {
auto n = i; ++n;
if ( i->equals( "." ) ) {
segments.remove_single( i );
} else if ( i->equals( ".." ) ) {
auto p = i; --p;
if ( p.is_valid() ) segments.remove_single( p );
segments.remove_single( i );
}
i = n;
}
pfc::string8 ret;
if ( prefixLen > 0 ) ret.add_string( inPath, prefixLen );
bool bFirst = true;
for ( auto i = segments.first(); i.is_valid(); ++ i ) {
if (!bFirst) ret << "\\";
ret << *i;
bFirst = false;
}
return ret;
}
pfc::string8 winGetVolumePath(const char * fb2kPath) {
PFC_ASSERT(matchProtocol(fb2kPath, "file"));
pfc::string8 native;
if (!filesystem::g_get_native_path(fb2kPath, native)) throw pfc::exception_invalid_params();
TCHAR outBuffer[MAX_PATH+1] = {};
WIN32_IO_OP( GetVolumePathName( pfc::stringcvt::string_os_from_utf8( native ), outBuffer, MAX_PATH ) );
return pfc::stringcvt::string_utf8_from_os( outBuffer ).get_ptr();
}
DWORD winVolumeFlags( const char * fb2kPath ) {
PFC_ASSERT(matchProtocol(fb2kPath, "file"));
pfc::string8 native;
if (!filesystem::g_get_native_path(fb2kPath, native)) throw pfc::exception_invalid_params();
TCHAR outBuffer[MAX_PATH + 1] = {};
WIN32_IO_OP(GetVolumePathName(pfc::stringcvt::string_os_from_utf8(native), outBuffer, MAX_PATH));
DWORD flags = 0;
WIN32_IO_OP(GetVolumeInformation(outBuffer, nullptr, 0, nullptr, nullptr, &flags, nullptr, 0));
return flags;
}
#endif
}

View File

@@ -0,0 +1,616 @@
#pragma once
#include <functional>
namespace foobar2000_io {
typedef std::function< void (const char *, t_filestats const & , bool ) > listDirectoryFunc_t;
void listDirectory( const char * path, abort_callback & aborter, listDirectoryFunc_t func);
#ifdef _WIN32
pfc::string8 stripParentFolders( const char * inPath );
#endif
void retryOnSharingViolation( std::function<void () > f, double timeout, abort_callback & a);
void retryOnSharingViolation( double timeout, abort_callback & a, std::function<void() > f);
// **** WINDOWS SUCKS ****
// Special version of retryOnSharingViolation with workarounds for known MoveFile() bugs.
void retryFileMove( double timeout, abort_callback & a, std::function<void()> f);
// **** WINDOWS SUCKS ****
// Special version of retryOnSharingViolation with workarounds for known idiotic problems with folder removal.
void retryFileDelete( double timeout, abort_callback & a, std::function<void()> f);
class listDirectoryCallbackImpl : public directory_callback {
public:
listDirectoryCallbackImpl() {}
listDirectoryCallbackImpl( listDirectoryFunc_t f ) : m_func(f) {}
bool on_entry(filesystem * p_owner, abort_callback & p_abort, const char * p_url, bool p_is_subdirectory, const t_filestats & p_stats) {
m_func(p_url, p_stats, p_is_subdirectory);
return true;
}
listDirectoryFunc_t m_func;
};
#ifdef _WIN32
pfc::string8 winGetVolumePath(const char * fb2kPath );
DWORD winVolumeFlags( const char * fb2kPath );
#endif
}
//helper
class file_path_canonical {
public:
file_path_canonical(const char * src) {filesystem::g_get_canonical_path(src,m_data);}
operator const char * () const {return m_data.get_ptr();}
const char * get_ptr() const {return m_data.get_ptr();}
t_size get_length() const {return m_data.get_length();}
private:
pfc::string8 m_data;
};
class file_path_display {
public:
file_path_display(const char * src) {filesystem::g_get_display_path(src,m_data);}
operator const char * () const {return m_data.get_ptr();}
const char * get_ptr() const {return m_data.get_ptr();}
t_size get_length() const {return m_data.get_length();}
private:
pfc::string8 m_data;
};
class stream_reader_memblock_ref : public stream_reader
{
public:
template<typename t_array> stream_reader_memblock_ref(const t_array & p_array) : m_data(p_array.get_ptr()), m_data_size(p_array.get_size()), m_pointer(0) {
pfc::assert_byte_type<typename t_array::t_item>();
}
stream_reader_memblock_ref(const void * p_data,t_size p_data_size) : m_data((const unsigned char*)p_data), m_data_size(p_data_size), m_pointer(0) {}
stream_reader_memblock_ref() : m_data(NULL), m_data_size(0), m_pointer(0) {}
template<typename t_array> void set_data(const t_array & data) {
pfc::assert_byte_type<typename t_array::t_item>();
set_data(data.get_ptr(), data.get_size());
}
void set_data(const void * data, t_size dataSize) {
m_pointer = 0;
m_data = reinterpret_cast<const unsigned char*>(data);
m_data_size = dataSize;
}
t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
t_size delta = pfc::min_t(p_bytes, get_remaining());
memcpy(p_buffer,m_data+m_pointer,delta);
m_pointer += delta;
return delta;
}
void read_object(void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
if (p_bytes > get_remaining()) throw exception_io_data_truncation();
memcpy(p_buffer,m_data+m_pointer,p_bytes);
m_pointer += p_bytes;
}
t_filesize skip(t_filesize p_bytes,abort_callback & p_abort) {
t_size remaining = get_remaining();
if (p_bytes >= remaining) {
m_pointer = m_data_size; return remaining;
} else {
m_pointer += (t_size)p_bytes; return p_bytes;
}
}
void skip_object(t_filesize p_bytes,abort_callback & p_abort) {
if (p_bytes > get_remaining()) {
throw exception_io_data_truncation();
} else {
m_pointer += (t_size)p_bytes;
}
}
void seek_(t_size offset) {
PFC_ASSERT( offset <= m_data_size );
m_pointer = offset;
}
const void * get_ptr_() const {return m_data + m_pointer;}
t_size get_remaining() const {return m_data_size - m_pointer;}
void reset() {m_pointer = 0;}
private:
const unsigned char * m_data;
t_size m_data_size,m_pointer;
};
class stream_writer_buffer_simple : public stream_writer {
public:
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
p_abort.check();
t_size base = m_buffer.get_size();
if (base + p_bytes < base) throw std::bad_alloc();
m_buffer.set_size(base + p_bytes);
memcpy( (t_uint8*) m_buffer.get_ptr() + base, p_buffer, p_bytes );
}
typedef pfc::array_t<t_uint8,pfc::alloc_fast> t_buffer;
pfc::array_t<t_uint8,pfc::alloc_fast> m_buffer;
};
template<class t_storage>
class stream_writer_buffer_append_ref_t : public stream_writer
{
public:
stream_writer_buffer_append_ref_t(t_storage & p_output) : m_output(p_output) {}
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
PFC_STATIC_ASSERT( sizeof(m_output[0]) == 1 );
p_abort.check();
t_size base = m_output.get_size();
if (base + p_bytes < base) throw std::bad_alloc();
m_output.set_size(base + p_bytes);
memcpy( (t_uint8*) m_output.get_ptr() + base, p_buffer, p_bytes );
}
private:
t_storage & m_output;
};
class stream_reader_limited_ref : public stream_reader {
public:
stream_reader_limited_ref(stream_reader * p_reader,t_filesize p_limit) : m_reader(p_reader), m_remaining(p_limit) {}
t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
if (p_bytes > m_remaining) p_bytes = (t_size)m_remaining;
t_size done = m_reader->read(p_buffer,p_bytes,p_abort);
m_remaining -= done;
return done;
}
inline t_filesize get_remaining() const {return m_remaining;}
t_filesize skip(t_filesize p_bytes,abort_callback & p_abort) {
if (p_bytes > m_remaining) p_bytes = m_remaining;
t_filesize done = m_reader->skip(p_bytes,p_abort);
m_remaining -= done;
return done;
}
void flush_remaining(abort_callback & p_abort) {
if (m_remaining > 0) skip_object(m_remaining,p_abort);
}
private:
stream_reader * m_reader;
t_filesize m_remaining;
};
class stream_writer_chunk_dwordheader : public stream_writer
{
public:
stream_writer_chunk_dwordheader(const service_ptr_t<file> & p_writer) : m_writer(p_writer) {}
void initialize(abort_callback & p_abort) {
m_headerposition = m_writer->get_position(p_abort);
m_written = 0;
m_writer->write_lendian_t((t_uint32)0,p_abort);
}
void finalize(abort_callback & p_abort) {
t_filesize end_offset;
end_offset = m_writer->get_position(p_abort);
m_writer->seek(m_headerposition,p_abort);
m_writer->write_lendian_t(pfc::downcast_guarded<t_uint32>(m_written),p_abort);
m_writer->seek(end_offset,p_abort);
}
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
m_writer->write(p_buffer,p_bytes,p_abort);
m_written += p_bytes;
}
private:
service_ptr_t<file> m_writer;
t_filesize m_headerposition;
t_filesize m_written;
};
class stream_writer_chunk : public stream_writer
{
public:
stream_writer_chunk(stream_writer * p_writer) : m_writer(p_writer), m_buffer_state(0) {}
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort);
void flush(abort_callback & p_abort);//must be called after writing before object is destroyed
private:
stream_writer * m_writer;
unsigned m_buffer_state;
unsigned char m_buffer[255];
};
class stream_reader_chunk : public stream_reader
{
public:
stream_reader_chunk(stream_reader * p_reader) : m_reader(p_reader), m_buffer_state(0), m_buffer_size(0), m_eof(false) {}
t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort);
void flush(abort_callback & p_abort);//must be called after reading before object is destroyed
static void g_skip(stream_reader * p_stream,abort_callback & p_abort);
private:
stream_reader * m_reader;
t_size m_buffer_state, m_buffer_size;
bool m_eof;
unsigned char m_buffer[255];
};
class stream_reader_dummy : public stream_reader { t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) {return 0;} };
template<bool isBigEndian = false> class stream_reader_formatter {
public:
stream_reader_formatter(stream_reader & p_stream,abort_callback & p_abort) : m_stream(p_stream), m_abort(p_abort) {}
template<typename t_int> void read_int(t_int & p_out) {
if (isBigEndian) m_stream.read_bendian_t(p_out,m_abort);
else m_stream.read_lendian_t(p_out,m_abort);
}
void read_raw(void * p_buffer,t_size p_bytes) {
m_stream.read_object(p_buffer,p_bytes,m_abort);
}
void skip(t_size p_bytes) {m_stream.skip_object(p_bytes,m_abort);}
template<typename TArray> void read_raw(TArray& data) {
pfc::assert_byte_type<typename TArray::t_item>();
read_raw(data.get_ptr(),data.get_size());
}
template<typename TArray> void read_byte_block(TArray & data) {
pfc::assert_byte_type<typename TArray::t_item>();
t_uint32 size; read_int(size); data.set_size(size);
read_raw(data);
}
template<typename TArray> void read_array(TArray & data) {
t_uint32 size; *this >> size; data.set_size(size);
for(t_uint32 walk = 0; walk < size; ++walk) *this >> data[walk];
}
void read_string_nullterm( pfc::string_base & out ) {
m_stream.read_string_nullterm( out, m_abort );
}
stream_reader & m_stream;
abort_callback & m_abort;
};
template<bool isBigEndian = false> class stream_writer_formatter {
public:
stream_writer_formatter(stream_writer & p_stream,abort_callback & p_abort) : m_stream(p_stream), m_abort(p_abort) {}
template<typename t_int> void write_int(t_int p_int) {
if (isBigEndian) m_stream.write_bendian_t(p_int,m_abort);
else m_stream.write_lendian_t(p_int,m_abort);
}
void write_raw(const void * p_buffer,t_size p_bytes) {
m_stream.write_object(p_buffer,p_bytes,m_abort);
}
template<typename TArray> void write_raw(const TArray& data) {
pfc::assert_byte_type<typename TArray::t_item>();
write_raw(data.get_ptr(),data.get_size());
}
template<typename TArray> void write_byte_block(const TArray& data) {
pfc::assert_byte_type<typename TArray::t_item>();
write_int( pfc::downcast_guarded<t_uint32>(data.get_size()) );
write_raw( data );
}
template<typename TArray> void write_array(const TArray& data) {
const t_uint32 size = pfc::downcast_guarded<t_uint32>(data.get_size());
*this << size;
for(t_uint32 walk = 0; walk < size; ++walk) *this << data[walk];
}
void write_string(const char * str) {
const t_size len = strlen(str);
*this << pfc::downcast_guarded<t_uint32>(len);
write_raw(str, len);
}
void write_string(const char * str, t_size len_) {
const t_size len = pfc::strlen_max(str, len_);
*this << pfc::downcast_guarded<t_uint32>(len);
write_raw(str, len);
}
void write_string_nullterm( const char * str ) {
this->write_raw( str, strlen(str)+1 );
}
stream_writer & m_stream;
abort_callback & m_abort;
};
#define __DECLARE_INT_OVERLOADS(TYPE) \
template<bool isBigEndian> inline stream_reader_formatter<isBigEndian> & operator>>(stream_reader_formatter<isBigEndian> & p_stream,TYPE & p_int) {typename pfc::sized_int_t<sizeof(TYPE)>::t_unsigned temp;p_stream.read_int(temp); p_int = (TYPE) temp; return p_stream;} \
template<bool isBigEndian> inline stream_writer_formatter<isBigEndian> & operator<<(stream_writer_formatter<isBigEndian> & p_stream,TYPE p_int) {p_stream.write_int((typename pfc::sized_int_t<sizeof(TYPE)>::t_unsigned)p_int); return p_stream;}
__DECLARE_INT_OVERLOADS(char);
__DECLARE_INT_OVERLOADS(signed char);
__DECLARE_INT_OVERLOADS(unsigned char);
__DECLARE_INT_OVERLOADS(signed short);
__DECLARE_INT_OVERLOADS(unsigned short);
__DECLARE_INT_OVERLOADS(signed int);
__DECLARE_INT_OVERLOADS(unsigned int);
__DECLARE_INT_OVERLOADS(signed long);
__DECLARE_INT_OVERLOADS(unsigned long);
__DECLARE_INT_OVERLOADS(signed long long);
__DECLARE_INT_OVERLOADS(unsigned long long);
__DECLARE_INT_OVERLOADS(wchar_t);
#undef __DECLARE_INT_OVERLOADS
template<typename TVal> class _IsTypeByte {
public:
enum {value = pfc::is_same_type<TVal,char>::value || pfc::is_same_type<TVal,unsigned char>::value || pfc::is_same_type<TVal,signed char>::value};
};
template<bool isBigEndian,typename TVal,size_t Count> stream_reader_formatter<isBigEndian> & operator>>(stream_reader_formatter<isBigEndian> & p_stream,TVal (& p_array)[Count]) {
if (_IsTypeByte<TVal>::value) {
p_stream.read_raw(p_array,Count);
} else {
for(t_size walk = 0; walk < Count; ++walk) p_stream >> p_array[walk];
}
return p_stream;
}
template<bool isBigEndian,typename TVal,size_t Count> stream_writer_formatter<isBigEndian> & operator<<(stream_writer_formatter<isBigEndian> & p_stream,TVal const (& p_array)[Count]) {
if (_IsTypeByte<TVal>::value) {
p_stream.write_raw(p_array,Count);
} else {
for(t_size walk = 0; walk < Count; ++walk) p_stream << p_array[walk];
}
return p_stream;
}
#define FB2K_STREAM_READER_OVERLOAD(type) \
template<bool isBigEndian> stream_reader_formatter<isBigEndian> & operator>>(stream_reader_formatter<isBigEndian> & stream,type & value)
#define FB2K_STREAM_WRITER_OVERLOAD(type) \
template<bool isBigEndian> stream_writer_formatter<isBigEndian> & operator<<(stream_writer_formatter<isBigEndian> & stream,const type & value)
FB2K_STREAM_READER_OVERLOAD(GUID) {
return stream >> value.Data1 >> value.Data2 >> value.Data3 >> value.Data4;
}
FB2K_STREAM_WRITER_OVERLOAD(GUID) {
return stream << value.Data1 << value.Data2 << value.Data3 << value.Data4;
}
FB2K_STREAM_READER_OVERLOAD(pfc::string) {
t_uint32 len; stream >> len;
value = stream.m_stream.read_string_ex(len,stream.m_abort);
return stream;
}
FB2K_STREAM_WRITER_OVERLOAD(pfc::string) {
stream << pfc::downcast_guarded<t_uint32>(value.length());
stream.write_raw(value.ptr(),value.length());
return stream;
}
FB2K_STREAM_READER_OVERLOAD(pfc::string_base) {
stream.m_stream.read_string(value, stream.m_abort);
return stream;
}
FB2K_STREAM_WRITER_OVERLOAD(pfc::string_base) {
const char * val = value.get_ptr();
const t_size len = strlen(val);
stream << pfc::downcast_guarded<t_uint32>(len);
stream.write_raw(val,len);
return stream;
}
FB2K_STREAM_WRITER_OVERLOAD(float) {
union {
float f; t_uint32 i;
} u; u.f = value;
return stream << u.i;
}
FB2K_STREAM_READER_OVERLOAD(float) {
union { float f; t_uint32 i;} u;
stream >> u.i; value = u.f;
return stream;
}
FB2K_STREAM_WRITER_OVERLOAD(double) {
union {
double f; t_uint64 i;
} u; u.f = value;
return stream << u.i;
}
FB2K_STREAM_READER_OVERLOAD(double) {
union { double f; t_uint64 i;} u;
stream >> u.i; value = u.f;
return stream;
}
FB2K_STREAM_WRITER_OVERLOAD(bool) {
t_uint8 temp = value ? 1 : 0;
return stream << temp;
}
FB2K_STREAM_READER_OVERLOAD(bool) {
t_uint8 temp; stream >> temp; value = temp != 0;
return stream;
}
template<bool BE = false>
class stream_writer_formatter_simple : public stream_writer_formatter<BE> {
public:
stream_writer_formatter_simple() : stream_writer_formatter<BE>(_m_stream,fb2k::noAbort), m_buffer(_m_stream.m_buffer) {}
typedef stream_writer_buffer_simple::t_buffer t_buffer;
t_buffer & m_buffer;
private:
stream_writer_buffer_simple _m_stream;
};
template<bool BE = false>
class stream_reader_formatter_simple_ref : public stream_reader_formatter<BE> {
public:
stream_reader_formatter_simple_ref(const void * source, t_size sourceSize) : stream_reader_formatter<BE>(_m_stream,fb2k::noAbort), _m_stream(source,sourceSize) {}
template<typename TSource> stream_reader_formatter_simple_ref(const TSource& source) : stream_reader_formatter<BE>(_m_stream,fb2k::noAbort), _m_stream(source) {}
stream_reader_formatter_simple_ref() : stream_reader_formatter<BE>(_m_stream,fb2k::noAbort) {}
void set_data(const void * source, t_size sourceSize) {_m_stream.set_data(source,sourceSize);}
template<typename TSource> void set_data(const TSource & source) {_m_stream.set_data(source);}
void reset() {_m_stream.reset();}
t_size get_remaining() {return _m_stream.get_remaining();}
const void * get_ptr_() const {return _m_stream.get_ptr_();}
private:
stream_reader_memblock_ref _m_stream;
};
template<bool BE = false>
class stream_reader_formatter_simple : public stream_reader_formatter_simple_ref<BE> {
public:
stream_reader_formatter_simple() {}
stream_reader_formatter_simple(const void * source, t_size sourceSize) {set_data(source,sourceSize);}
template<typename TSource> stream_reader_formatter_simple(const TSource & source) {set_data(source);}
void set_data(const void * source, t_size sourceSize) {
m_content.set_data_fromptr(reinterpret_cast<const t_uint8*>(source), sourceSize);
onContentChange();
}
template<typename TSource> void set_data(const TSource & source) {
m_content = source;
onContentChange();
}
private:
void onContentChange() {
stream_reader_formatter_simple_ref<BE>::set_data(m_content);
}
pfc::array_t<t_uint8> m_content;
};
template<bool isBigEndian> class _stream_reader_formatter_translator {
public:
_stream_reader_formatter_translator(stream_reader_formatter<isBigEndian> & stream) : m_stream(stream) {}
typedef _stream_reader_formatter_translator<isBigEndian> t_self;
template<typename t_what> t_self & operator||(t_what & out) {m_stream >> out; return *this;}
private:
stream_reader_formatter<isBigEndian> & m_stream;
};
template<bool isBigEndian> class _stream_writer_formatter_translator {
public:
_stream_writer_formatter_translator(stream_writer_formatter<isBigEndian> & stream) : m_stream(stream) {}
typedef _stream_writer_formatter_translator<isBigEndian> t_self;
template<typename t_what> t_self & operator||(const t_what & in) {m_stream << in; return *this;}
private:
stream_writer_formatter<isBigEndian> & m_stream;
};
#define FB2K_STREAM_RECORD_OVERLOAD(type, code) \
FB2K_STREAM_READER_OVERLOAD(type) { \
_stream_reader_formatter_translator<isBigEndian> streamEx(stream); \
streamEx || code; \
return stream; \
} \
FB2K_STREAM_WRITER_OVERLOAD(type) { \
_stream_writer_formatter_translator<isBigEndian> streamEx(stream); \
streamEx || code; \
return stream; \
}
#define FB2K_RETRY_ON_EXCEPTION(OP, ABORT, TIMEOUT, EXCEPTION) \
{ \
pfc::lores_timer timer; timer.start(); \
for(;;) { \
try { {OP;} break; } \
catch(EXCEPTION) { if (timer.query() > TIMEOUT) throw;} \
ABORT.sleep(0.05); \
} \
}
#define FB2K_RETRY_ON_EXCEPTION2(OP, ABORT, TIMEOUT, EXCEPTION1, EXCEPTION2) \
{ \
pfc::lores_timer timer; timer.start(); \
for(;;) { \
try { {OP;} break; } \
catch(EXCEPTION1) { if (timer.query() > TIMEOUT) throw;} \
catch(EXCEPTION2) { if (timer.query() > TIMEOUT) throw;} \
ABORT.sleep(0.05); \
} \
}
#define FB2K_RETRY_ON_EXCEPTION3(OP, ABORT, TIMEOUT, EXCEPTION1, EXCEPTION2, EXCEPTION3) \
{ \
pfc::lores_timer timer; timer.start(); \
for(;;) { \
try { {OP;} break; } \
catch(EXCEPTION1) { if (timer.query() > TIMEOUT) throw;} \
catch(EXCEPTION2) { if (timer.query() > TIMEOUT) throw;} \
catch(EXCEPTION3) { if (timer.query() > TIMEOUT) throw;} \
ABORT.sleep(0.05); \
} \
}
#define FB2K_RETRY_ON_SHARING_VIOLATION(OP, ABORT, TIMEOUT) FB2K_RETRY_ON_EXCEPTION(OP, ABORT, TIMEOUT, exception_io_sharing_violation)
// **** WINDOWS SUCKS ****
// File move ops must be retried on all these because you get access-denied when someone is holding open handles to something you're trying to move, or already-exists on something you just told Windows to move away
#define FB2K_RETRY_FILE_MOVE(OP, ABORT, TIMEOUT) FB2K_RETRY_ON_EXCEPTION3(OP, ABORT, TIMEOUT, exception_io_sharing_violation, exception_io_denied, exception_io_already_exists)
class fileRestorePositionScope {
public:
fileRestorePositionScope(file::ptr f, abort_callback & a) : m_file(f), m_abort(a) {
m_offset = f->get_position(a);
}
~fileRestorePositionScope() {
try {
if (!m_abort.is_aborting()) m_file->seek(m_offset, m_abort);
} catch(...) {}
}
private:
file::ptr m_file;
t_filesize m_offset;
abort_callback & m_abort;
};
//! Debug self-test function for testing a file object implementation, performs various behavior validity checks, random access etc. Output goes to fb2k console.
//! Returns true on success, false on failure (buggy file object implementation).
bool fb2kFileSelfTest(file::ptr f, abort_callback & aborter);

View File

@@ -0,0 +1,44 @@
#pragma once
#include "filesystem.h"
//! Object cannot be opened in transacted mode.
PFC_DECLARE_EXCEPTION(exception_io_transactions_unsupported, exception_io, "Transactions unsupported on this volume");
PFC_DECLARE_EXCEPTION(exception_io_transactional_conflict, exception_io, "Transactional conflict");
PFC_DECLARE_EXCEPTION(exception_io_transaction_aborted, exception_io, "Transaction aborted");
//! An instance of a filesystem transaction. Inherits from filesystem API and provides all the methods. \n
//! To perform a transacted filesystem update, you must call methods on this object specifically - not static methods of filesystem class, not methods of a filesystem instance obtained from someplace else. \n
//! Call commit() when done, then release the object. If you release the object without having called commit(), the update will be rolled back. \n
//! Please keep in mind that you must not explicitly rely on this API and always provide a fallback mechanism. \n
//! A transacted operation may be impossible for the following reasons: \n
//! Too old foobar2000 version - filesystem_transacted was first published at version 1.4 beta 7 - obtaining a filesystem_transacted instance will fail. \n
//! Too old Windows OS - transacted APIs are available starting from Vista, not available on XP - obtaining a filesystem_transacted instance will fail. \n
//! Functionality disabled by user - obtaining a filesystem_transacted instance will fail. \n
//! The volume you're trying to work with does not support transacted updates - network share, non-NTFS USB stick, etc - create() will succeed but operations will fail with exception_io_transactions_unsupported. \n
class filesystem_transacted : public filesystem_v2 {
FB2K_MAKE_SERVICE_INTERFACE(filesystem_transacted, filesystem_v2);
public:
//! Commits the transaction. You should release this filesystem_transacted object when done. \n
//! If you don't call commit, all operations made with this filesystem_transacted instance will be rolled back.
virtual void commit(abort_callback & abort) = 0;
//! Helper to obtain a new instance. Will return null if filesystem_transacted is unavailable.
static filesystem_transacted::ptr create( const char * pathFor );
};
//! \since 1.4
//! An entrypoint interface to create filesystem_transacted instances. Use filesystem_transacted::create() instead of calling this directly.
class filesystem_transacted_entry : public service_base {
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(filesystem_transacted_entry);
public:
//! May return null if transacted ops are not available in this location for one reason or another.
virtual filesystem_transacted::ptr create( const char * pathFor ) = 0;
virtual bool is_our_path( const char * path ) = 0;
};
// Since 1.5, transacted filesystem is no longer supported
// as it adds extra complexity without actually solving any problems.
// Even Microsoft recommends not to use this API.
#define FB2K_SUPPORT_TRANSACTED_FILESYSTEM 0

View File

@@ -0,0 +1,5 @@
#pragma once
// placeholder added for foobar2000 mobile source compatibility
#include "foobar2000.h"

View File

@@ -0,0 +1,14 @@
#pragma once
#include "../../pfc/pfc.h"
// These were in a global namespace before and are commonly referenced as such.
using pfc::bit_array;
using pfc::bit_array_var;
using pfc::bit_array_true;
using pfc::bit_array_false;
using pfc::bit_array_val;
using pfc::bit_array_bittable;
using pfc::bit_array_one;
using pfc::bit_array_range;
using pfc::LastErrorRevertScope;

View File

@@ -0,0 +1,15 @@
#pragma once
#define FOOBAR2000_DESKTOP
#define FOOBAR2000_DESKTOP_WINDOWS
#define FOOBAR2000_DESKTOP_WINDOWS_OR_BOOM
#define FOOBAR2000_HAVE_FILE_FORMAT_SANITIZER
#define FOOBAR2000_HAVE_CHAPTERIZER
#define FOOBAR2000_HAVE_ALBUM_ART
#define FOOBAR2000_DECLARE_FILE_TYPES
#define FOOBAR2000_HAVE_DSP
#define FOOBAR2000_HAVE_CONSOLE
#define FOOBAR2000_INTERACTIVE
#define FOOBAR2000_WINAPI_CLASSIC
#define FOOBAR2000_HAVE_METADB

130
foobar2000/SDK/foobar2000.h Normal file
View File

@@ -0,0 +1,130 @@
// This is the master foobar2000 SDK header file; it includes headers for all functionality exposed through the SDK project. #include this in your source code, never reference any of the other headers directly.
#ifndef _FOOBAR2000_H_
#define _FOOBAR2000_H_
#include "foobar2000-winver.h"
// #define FOOBAR2000_TARGET_VERSION 75 // 0.9.6
// #define FOOBAR2000_TARGET_VERSION 76 // 1.0
// #define FOOBAR2000_TARGET_VERSION 77 // 1.1, 1.2
// #define FOOBAR2000_TARGET_VERSION 78 // 1.3
#define FOOBAR2000_TARGET_VERSION 79 // 1.4
// #define FOOBAR2000_TARGET_VERSION 80 // 1.5, 1.6
// Use this to determine what foobar2000 SDK version is in use, undefined for releases older than 2018
#define FOOBAR2000_SDK_VERSION 20210223
#include "foobar2000-pfc.h"
#include "../shared/shared.h"
#ifndef NOTHROW
#ifdef _MSC_VER
#define NOTHROW __declspec(nothrow)
#else
#define NOTHROW
#endif
#endif
#define FB2KAPI /*NOTHROW*/
typedef const char * pcchar;
#include "core_api.h"
#include "service.h"
#include "service_impl.h"
#include "service_by_guid.h"
#include "service_compat.h"
#include "completion_notify.h"
#include "abort_callback.h"
#include "componentversion.h"
#include "preferences_page.h"
#include "coreversion.h"
#include "filesystem.h"
#include "filesystem_transacted.h"
#include "archive.h"
#include "audio_chunk.h"
#include "cfg_var.h"
#include "mem_block_container.h"
#include "audio_postprocessor.h"
#include "playable_location.h"
#include "file_info.h"
#include "file_info_impl.h"
#include "hasher_md5.h"
#include "metadb_handle.h"
#include "metadb.h"
#include "file_info_filter.h"
#include "console.h"
#include "dsp.h"
#include "dsp_manager.h"
#include "initquit.h"
#include "event_logger.h"
#include "input.h"
#include "input_impl.h"
#include "decode_postprocessor.h"
#include "menu.h"
#include "contextmenu.h"
#include "contextmenu_manager.h"
#include "menu_helpers.h"
#include "modeless_dialog.h"
#include "playback_control.h"
#include "play_callback.h"
#include "playlist.h"
#include "playlist_loader.h"
#include "replaygain.h"
#include "resampler.h"
#include "tag_processor.h"
#include "titleformat.h"
#include "ui.h"
#include "unpack.h"
#include "vis.h"
#include "packet_decoder.h"
#include "commandline.h"
#include "genrand.h"
#include "file_operation_callback.h"
#include "library_manager.h"
#include "config_io_callback.h"
#include "popup_message.h"
#include "app_close_blocker.h"
#include "config_object.h"
#include "config_object_impl.h"
#include "threaded_process.h"
#include "message_loop.h"
#include "input_file_type.h"
#include "chapterizer.h"
#include "link_resolver.h"
#include "main_thread_callback.h"
#include "advconfig.h"
#include "info_lookup_handler.h"
#include "track_property.h"
#include "album_art.h"
#include "album_art_helpers.h"
#include "icon_remap.h"
#include "ui_element.h"
#include "ole_interaction.h"
#include "search_tools.h"
#include "autoplaylist.h"
#include "replaygain_scanner.h"
#include "ui_edit_context.h"
#include "system_time_keeper.h"
#include "playback_stream_capture.h"
#include "http_client.h"
#include "exceptions.h"
#include "progress_meter.h"
#include "output.h"
#include "file_format_sanitizer.h"
#include "commonObjects.h"
#include "file_lock_manager.h"
#include "imageLoaderLite.h"
#include "imageViewer.h"
#endif //_FOOBAR2000_H_

View File

@@ -0,0 +1,292 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Globals">
<ProjectGuid>{E8091321-D79D-4575-86EF-064EA1A4A20D}</ProjectGuid>
<RootNamespace>foobar2000_SDK</RootNamespace>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseOfMfc>false</UseOfMfc>
<CharacterSet>Unicode</CharacterSet>
<WholeProgramOptimization>true</WholeProgramOptimization>
<PlatformToolset>v141</PlatformToolset>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
<ConfigurationType>StaticLibrary</ConfigurationType>
<UseOfMfc>false</UseOfMfc>
<CharacterSet>Unicode</CharacterSet>
<PlatformToolset>v141</PlatformToolset>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<PropertyGroup Label="UserMacros" />
<PropertyGroup>
<_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
</PropertyGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
<ClCompile>
<Optimization>Disabled</Optimization>
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>foobar2000.h</PrecompiledHeaderFile>
<WarningLevel>Level3</WarningLevel>
<SuppressStartupBanner>true</SuppressStartupBanner>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
<TreatSpecificWarningsAsErrors>4715</TreatSpecificWarningsAsErrors>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
</ClCompile>
<ResourceCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Culture>0x0409</Culture>
</ResourceCompile>
<Lib>
<SuppressStartupBanner>true</SuppressStartupBanner>
</Lib>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
<ClCompile>
<Optimization>MinSpace</Optimization>
<StringPooling>true</StringPooling>
<BufferSecurityCheck>false</BufferSecurityCheck>
<FloatingPointModel>Fast</FloatingPointModel>
<RuntimeTypeInfo>false</RuntimeTypeInfo>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>foobar2000.h</PrecompiledHeaderFile>
<WarningLevel>Level3</WarningLevel>
<SuppressStartupBanner>true</SuppressStartupBanner>
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
<AdditionalOptions>/d2notypeopt %(AdditionalOptions)</AdditionalOptions>
<TreatSpecificWarningsAsErrors>4715</TreatSpecificWarningsAsErrors>
<MultiProcessorCompilation>true</MultiProcessorCompilation>
<OmitFramePointers>true</OmitFramePointers>
<IntrinsicFunctions>true</IntrinsicFunctions>
<PreprocessorDefinitions>NDEBUG;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<ResourceCompile>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<Culture>0x0409</Culture>
</ResourceCompile>
<Lib>
<SuppressStartupBanner>true</SuppressStartupBanner>
</Lib>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="abort_callback.h" />
<ClInclude Include="advconfig.h" />
<ClInclude Include="album_art.h" />
<ClInclude Include="album_art_helpers.h" />
<ClInclude Include="app_close_blocker.h" />
<ClInclude Include="archive.h" />
<ClInclude Include="audio_chunk.h" />
<ClInclude Include="audio_chunk_impl.h" />
<ClInclude Include="audio_postprocessor.h" />
<ClInclude Include="autoplaylist.h" />
<ClInclude Include="cfg_var.h" />
<ClInclude Include="chapterizer.h" />
<ClInclude Include="commandline.h" />
<ClInclude Include="commonObjects.h" />
<ClInclude Include="completion_notify.h" />
<ClInclude Include="component.h" />
<ClInclude Include="componentversion.h" />
<ClInclude Include="config_io_callback.h" />
<ClInclude Include="config_object.h" />
<ClInclude Include="config_object_impl.h" />
<ClInclude Include="console.h" />
<ClInclude Include="contextmenu.h" />
<ClInclude Include="contextmenu_manager.h" />
<ClInclude Include="core_api.h" />
<ClInclude Include="coreversion.h" />
<ClInclude Include="decode_postprocessor.h" />
<ClInclude Include="dsp.h" />
<ClInclude Include="dsp_manager.h" />
<ClInclude Include="event_logger.h" />
<ClInclude Include="exceptions.h" />
<ClInclude Include="filesystem_transacted.h" />
<ClInclude Include="file_format_sanitizer.h" />
<ClInclude Include="file_info.h" />
<ClInclude Include="file_info_filter.h" />
<ClInclude Include="file_info_filter_impl.h" />
<ClInclude Include="file_info_impl.h" />
<ClInclude Include="file_lock_manager.h" />
<ClInclude Include="file_operation_callback.h" />
<ClInclude Include="filesystem.h" />
<ClInclude Include="filesystem_helper.h" />
<ClInclude Include="foobar2000-dsp.h" />
<ClInclude Include="foobar2000-pfc.h" />
<ClInclude Include="foobar2000-winver.h" />
<ClInclude Include="foobar2000.h" />
<ClInclude Include="foosort.h" />
<ClInclude Include="genrand.h" />
<ClInclude Include="hasher_md5.h" />
<ClInclude Include="http_client.h" />
<ClInclude Include="icon_remap.h" />
<ClInclude Include="imageLoaderLite.h" />
<ClInclude Include="imageViewer.h" />
<ClInclude Include="info_lookup_handler.h" />
<ClInclude Include="initquit.h" />
<ClInclude Include="input.h" />
<ClInclude Include="input_file_type.h" />
<ClInclude Include="input_impl.h" />
<ClInclude Include="library_manager.h" />
<ClInclude Include="link_resolver.h" />
<ClInclude Include="main_thread_callback.h" />
<ClInclude Include="mem_block_container.h" />
<ClInclude Include="menu.h" />
<ClInclude Include="menu_helpers.h" />
<ClInclude Include="message_loop.h" />
<ClInclude Include="metadb.h" />
<ClInclude Include="metadb_handle.h" />
<ClInclude Include="modeless_dialog.h" />
<ClInclude Include="ole_interaction.h" />
<ClInclude Include="output.h" />
<ClInclude Include="packet_decoder.h" />
<ClInclude Include="play_callback.h" />
<ClInclude Include="playable_location.h" />
<ClInclude Include="playback_control.h" />
<ClInclude Include="playback_stream_capture.h" />
<ClInclude Include="playlist.h" />
<ClInclude Include="playlist_loader.h" />
<ClInclude Include="popup_message.h" />
<ClInclude Include="preferences_page.h" />
<ClInclude Include="progress_meter.h" />
<ClInclude Include="replaygain.h" />
<ClInclude Include="replaygain_scanner.h" />
<ClInclude Include="resampler.h" />
<ClInclude Include="search_tools.h" />
<ClInclude Include="service.h" />
<ClInclude Include="service_by_guid.h" />
<ClInclude Include="service_compat.h" />
<ClInclude Include="service_impl.h" />
<ClInclude Include="system_time_keeper.h" />
<ClInclude Include="tag_processor.h" />
<ClInclude Include="threaded_process.h" />
<ClInclude Include="titleformat.h" />
<ClInclude Include="tracks.h" />
<ClInclude Include="track_property.h" />
<ClInclude Include="ui.h" />
<ClInclude Include="ui_edit_context.h" />
<ClInclude Include="ui_element.h" />
<ClInclude Include="ui_element_typable_window_manager.h" />
<ClInclude Include="unpack.h" />
<ClInclude Include="vis.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="abort_callback.cpp" />
<ClCompile Include="advconfig.cpp" />
<ClCompile Include="album_art.cpp" />
<ClCompile Include="app_close_blocker.cpp" />
<ClCompile Include="audio_chunk.cpp" />
<ClCompile Include="audio_chunk_channel_config.cpp" />
<ClCompile Include="cfg_var.cpp" />
<ClCompile Include="chapterizer.cpp" />
<ClCompile Include="commandline.cpp" />
<ClCompile Include="completion_notify.cpp" />
<ClCompile Include="componentversion.cpp" />
<ClCompile Include="config_io_callback.cpp" />
<ClCompile Include="config_object.cpp" />
<ClCompile Include="console.cpp">
</ClCompile>
<ClCompile Include="dsp.cpp">
</ClCompile>
<ClCompile Include="dsp_manager.cpp">
</ClCompile>
<ClCompile Include="file_cached_impl.cpp" />
<ClCompile Include="file_info.cpp">
</ClCompile>
<ClCompile Include="file_info_impl.cpp">
</ClCompile>
<ClCompile Include="file_info_merge.cpp">
</ClCompile>
<ClCompile Include="file_operation_callback.cpp">
</ClCompile>
<ClCompile Include="filesystem.cpp">
</ClCompile>
<ClCompile Include="filesystem_helper.cpp">
</ClCompile>
<ClCompile Include="foosort.cpp" />
<ClCompile Include="guids.cpp">
</ClCompile>
<ClCompile Include="hasher_md5.cpp">
</ClCompile>
<ClCompile Include="input.cpp">
</ClCompile>
<ClCompile Include="input_file_type.cpp" />
<ClCompile Include="link_resolver.cpp" />
<ClCompile Include="mainmenu.cpp" />
<ClCompile Include="main_thread_callback.cpp" />
<ClCompile Include="mem_block_container.cpp" />
<ClCompile Include="menu_helpers.cpp">
</ClCompile>
<ClCompile Include="menu_item.cpp">
</ClCompile>
<ClCompile Include="menu_manager.cpp">
</ClCompile>
<ClCompile Include="metadb.cpp">
</ClCompile>
<ClCompile Include="metadb_handle.cpp">
</ClCompile>
<ClCompile Include="metadb_handle_list.cpp">
</ClCompile>
<ClCompile Include="output.cpp" />
<ClCompile Include="packet_decoder.cpp">
</ClCompile>
<ClCompile Include="playable_location.cpp">
</ClCompile>
<ClCompile Include="playback_control.cpp">
</ClCompile>
<ClCompile Include="playlist.cpp">
</ClCompile>
<ClCompile Include="playlist_loader.cpp">
</ClCompile>
<ClCompile Include="popup_message.cpp">
</ClCompile>
<ClCompile Include="preferences_page.cpp" />
<ClCompile Include="replaygain.cpp">
</ClCompile>
<ClCompile Include="replaygain_info.cpp">
</ClCompile>
<ClCompile Include="search_tools.cpp" />
<ClCompile Include="service.cpp">
</ClCompile>
<ClCompile Include="stdafx.cpp">
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="tag_processor.cpp">
</ClCompile>
<ClCompile Include="tag_processor_id3v2.cpp">
</ClCompile>
<ClCompile Include="threaded_process.cpp">
</ClCompile>
<ClCompile Include="titleformat.cpp">
</ClCompile>
<ClCompile Include="track_property.cpp" />
<ClCompile Include="ui.cpp">
</ClCompile>
<ClCompile Include="ui_element.cpp" />
<ClCompile Include="utility.cpp" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ExtensionTargets">
</ImportGroup>
</Project>

View File

@@ -0,0 +1,488 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Header Files">
<UniqueIdentifier>{6c35c7a7-723a-401f-acdc-c63af942abae}</UniqueIdentifier>
<Extensions>*.h</Extensions>
</Filter>
<Filter Include="Source Files">
<UniqueIdentifier>{3b2ccd60-8f0c-4241-830a-fda069a5d440}</UniqueIdentifier>
<Extensions>*.cpp</Extensions>
</Filter>
</ItemGroup>
<ItemGroup>
<ClInclude Include="abort_callback.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="advconfig.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="album_art.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="app_close_blocker.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="audio_chunk.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="audio_postprocessor.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="autoplaylist.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="cfg_var.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="chapterizer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="commandline.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="completion_notify.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="component.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="componentversion.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="config_io_callback.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="config_object.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="config_object_impl.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="console.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="contextmenu.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="contextmenu_manager.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="core_api.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="coreversion.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="decode_postprocessor.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="dsp.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="dsp_manager.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="event_logger.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="exceptions.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="file_info.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="file_info_impl.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="file_operation_callback.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="filesystem.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="filesystem_helper.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="foobar2000.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="genrand.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="hasher_md5.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="http_client.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="icon_remap.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="info_lookup_handler.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="initquit.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="input.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="input_file_type.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="input_impl.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="library_manager.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="link_resolver.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="main_thread_callback.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="mem_block_container.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="menu.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="menu_helpers.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="message_loop.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="metadb.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="metadb_handle.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="modeless_dialog.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ole_interaction.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="packet_decoder.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="play_callback.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="playable_location.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="playback_control.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="playback_stream_capture.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="playlist.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="playlist_loader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="popup_message.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="preferences_page.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="replaygain.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="replaygain_scanner.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="resampler.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="search_tools.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="service.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="service_impl.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="system_time_keeper.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="tag_processor.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="threaded_process.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="titleformat.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="track_property.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ui.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ui_edit_context.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ui_element.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="unpack.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="vis.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="progress_meter.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="album_art_helpers.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="output.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="foobar2000-winver.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="foobar2000-dsp.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="audio_chunk_impl.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="file_format_sanitizer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="commonObjects.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="service_by_guid.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="foobar2000-pfc.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="filesystem_transacted.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="foosort.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="file_lock_manager.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="archive.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="ui_element_typable_window_manager.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="service_compat.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="tracks.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="file_info_filter.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="file_info_filter_impl.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="imageLoaderLite.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="imageViewer.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<ClCompile Include="abort_callback.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="advconfig.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="album_art.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="app_close_blocker.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="audio_chunk.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="audio_chunk_channel_config.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="cfg_var.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="chapterizer.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="commandline.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="completion_notify.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="config_object.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="console.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="dsp.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="dsp_manager.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="file_info.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="file_info_impl.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="file_info_merge.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="file_operation_callback.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="filesystem.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="filesystem_helper.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="guids.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="hasher_md5.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="input.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="input_file_type.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="link_resolver.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mainmenu.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="mem_block_container.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="menu_helpers.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="menu_item.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="menu_manager.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="metadb.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="metadb_handle.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="metadb_handle_list.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="packet_decoder.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="playable_location.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="playback_control.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="playlist.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="playlist_loader.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="popup_message.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="preferences_page.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="replaygain.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="replaygain_info.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="service.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="stdafx.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="tag_processor.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="tag_processor_id3v2.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="threaded_process.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="titleformat.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ui.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="ui_element.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="componentversion.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="search_tools.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="file_cached_impl.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="main_thread_callback.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="output.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="utility.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="config_io_callback.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="foosort.cpp">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="track_property.cpp">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
</Project>

154
foobar2000/SDK/foosort.cpp Normal file
View File

@@ -0,0 +1,154 @@
#include "foobar2000.h"
#include "foosort.h"
#define FOOSORT_PROFILING 0
namespace {
#if FOOSORT_PROFILING
typedef pfc::hires_timer foosort_timer;
#endif
class foosort {
public:
foosort(pfc::sort_callback & cb, abort_callback & a) : m_abort(a), p_callback(cb) {}
foosort fork() {
foosort ret ( * this );
ret.genrand = genrand_service::g_create();
return ret;
}
size_t myrand(size_t cnt) {
return genrand->genrand(cnt);
}
void squaresort(t_size const p_base, t_size const p_count) {
const t_size max = p_base + p_count;
for (t_size walk = p_base + 1; walk < max; ++walk) {
for (t_size prev = p_base; prev < walk; ++prev) {
p_callback.swap_check(prev, walk);
}
}
}
void _sort_2elem_helper(t_size & p_elem1, t_size & p_elem2) {
if (p_callback.compare(p_elem1, p_elem2) > 0) pfc::swap_t(p_elem1, p_elem2);
}
t_size _pivot_helper(t_size const p_base, t_size const p_count) {
PFC_ASSERT(p_count > 2);
//t_size val1 = p_base, val2 = p_base + (p_count / 2), val3 = p_base + (p_count - 1);
t_size val1 = myrand(p_count), val2 = myrand(p_count - 1), val3 = myrand(p_count - 2);
if (val2 >= val1) val2++;
if (val3 >= val1) val3++;
if (val3 >= val2) val3++;
val1 += p_base; val2 += p_base; val3 += p_base;
_sort_2elem_helper(val1, val2);
_sort_2elem_helper(val1, val3);
_sort_2elem_helper(val2, val3);
return val2;
}
void newsort(t_size const p_base, t_size const p_count, size_t concurrency) {
if (p_count <= 4) {
squaresort(p_base, p_count);
return;
}
this->m_abort.check();
#if FOOSORT_PROFILING
foosort_timer t;
if ( concurrency > 1 ) t.start();
#endif
t_size pivot = _pivot_helper(p_base, p_count);
{
const t_size target = p_base + p_count - 1;
if (pivot != target) {
p_callback.swap(pivot, target); pivot = target;
}
}
t_size partition = p_base;
{
bool asdf = false;
for (t_size walk = p_base; walk < pivot; ++walk) {
const int comp = p_callback.compare(walk, pivot);
bool trigger = false;
if (comp == 0) {
trigger = asdf;
asdf = !asdf;
} else if (comp < 0) {
trigger = true;
}
if (trigger) {
if (partition != walk) p_callback.swap(partition, walk);
partition++;
}
}
}
if (pivot != partition) {
p_callback.swap(pivot, partition); pivot = partition;
}
const auto base1 = p_base, count1 = pivot - p_base;
const auto base2 = pivot + 1, count2 = p_count - (pivot + 1 - p_base);
if (concurrency > 1) {
size_t total = count1 + count2;
size_t con1 = (size_t)((uint64_t)count1 * (uint64_t)concurrency / (uint64_t)total);
if (con1 < 1) con1 = 1;
if (con1 > concurrency - 1) con1 = concurrency - 1;
size_t con2 = concurrency - con1;
#if FOOSORT_PROFILING
FB2K_console_formatter() << "foosort pass: " << p_base << "+" << p_count << "(" << concurrency << ") took " << t.queryString();
FB2K_console_formatter() << "foosort forking: " << base1 << "+" << count1 << "(" << con1 << ") + " << base2 << "+" << count2 << "(" << con2 << ")";
#endif
pfc::thread2 other;
other.startHere([&] {
try {
foosort subsort = fork();
subsort.newsort(base1, count1, con1);
} catch (exception_aborted) {}
});
try {
newsort(base2, count2, con2);
} catch (exception_aborted) {}
other.waitTillDone();
m_abort.check();
} else {
newsort(base1, count1, 1);
newsort(base2, count2, 1);
}
}
private:
abort_callback & m_abort;
pfc::sort_callback & p_callback;
genrand_service::ptr genrand = genrand_service::g_create();
};
}
namespace fb2k {
void sort(pfc::sort_callback & cb, size_t count, size_t concurrency, abort_callback & aborter) {
#if FOOSORT_PROFILING
foosort_timer t; t.start();
#endif
foosort theFooSort(cb, aborter);
theFooSort.newsort(0, count, concurrency);
#if FOOSORT_PROFILING
FB2K_console_formatter() << "foosort took: " << t.queryString();
#endif
}
}

10
foobar2000/SDK/foosort.h Normal file
View File

@@ -0,0 +1,10 @@
#pragma once
namespace fb2k {
// foosort
// abortable multithreaded quicksort
// expects cb to handle concurrent calls as long as they do not touch the same items concurrently
void sort(pfc::sort_callback & cb, size_t count, size_t concurrency, abort_callback & aborter);
}

42
foobar2000/SDK/genrand.h Normal file
View File

@@ -0,0 +1,42 @@
//! PRNG service. Implemented by the core, do not reimplement. Use g_create() helper function to instantiate.
class NOVTABLE genrand_service : public service_base
{
public:
//! Seeds the PRNG with specified value.
virtual void seed(unsigned val) = 0;
//! Returns random value N, where 0 <= N < range.
virtual unsigned genrand(unsigned range)=0;
double genrand_f() { return (double)genrand(0xFFFFFFFF) / (double)0xFFFFFFFF; }
void genrand_blob( void * out, size_t bytes ) {
size_t dwords = bytes/4;
uint32_t * out32 = (uint32_t*) out;
for(size_t w = 0; w < dwords; ++w ) {
out32[w] = genrand32();
}
size_t left = bytes % 4;
if (left > 0) {
auto leftptr = (uint8_t*) out + (bytes-left);
for( size_t w = 0; w < left; ++w) leftptr[w] = genrand8();
}
}
uint32_t genrand32() {
return (uint32_t) genrand(0xFFFFFFFF);
}
uint8_t genrand8() {
return (uint8_t) genrand(0x100);
}
static service_ptr_t<genrand_service> g_create() {return standard_api_create_t<genrand_service>();}
void generate_random_order(t_size * out, t_size count) {
unsigned genrandMax = (unsigned) pfc::min_t<size_t>(count, 0xFFFFFFFF);
t_size n;
for(n=0;n<count;n++) out[n]=n;
for(n=0;n<count;n++) pfc::swap_t(out[n],out[genrand(genrandMax)]);
}
FB2K_MAKE_SERVICE_COREAPI(genrand_service);
};

1413
foobar2000/SDK/guids.cpp Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,43 @@
#include "foobar2000.h"
GUID hasher_md5::guid_from_result(const hasher_md5_result & param)
{
static_assert(sizeof(GUID) == sizeof(hasher_md5_result), "sanity");
GUID temp = * reinterpret_cast<const GUID*>(&param);
byte_order::order_le_to_native_t(temp);
return temp;
}
GUID hasher_md5_result::asGUID() const {
return hasher_md5::guid_from_result( *this );
}
hasher_md5_result hasher_md5::process_single(const void * p_buffer,t_size p_bytes)
{
hasher_md5_state state;
initialize(state);
process(state,p_buffer,p_bytes);
return get_result(state);
}
GUID hasher_md5::process_single_guid(const void * p_buffer,t_size p_bytes)
{
return guid_from_result(process_single(p_buffer,p_bytes));
}
t_uint64 hasher_md5_result::xorHalve() const {
#if PFC_BYTE_ORDER_IS_BIG_ENDIAN
t_uint64 ret = 0;
for(int walk = 0; walk < 8; ++walk) {
ret |= (t_uint64)((t_uint8)m_data[walk] ^ (t_uint8)m_data[walk+8]) << (walk * 8);
}
return ret;
#else
const t_uint64 * v = reinterpret_cast<const t_uint64*>(&m_data);
return v[0] ^ v[1];
#endif
}
pfc::string8 hasher_md5_result::asString() const {
return pfc::format_hexdump( this->m_data, sizeof(m_data), "").get_ptr();
}

View File

@@ -0,0 +1,94 @@
#pragma once
struct hasher_md5_state {
char m_data[128];
};
struct hasher_md5_result {
char m_data[16];
t_uint64 xorHalve() const;
GUID asGUID() const;
pfc::string8 asString() const;
static hasher_md5_result null() {hasher_md5_result h = {}; return h;}
};
FB2K_STREAM_READER_OVERLOAD(hasher_md5_result) {
stream.read_raw(&value, sizeof(value)); return stream;
}
FB2K_STREAM_WRITER_OVERLOAD(hasher_md5_result) {
stream.write_raw(&value, sizeof(value)); return stream;
}
inline bool operator==(const hasher_md5_result & p_item1,const hasher_md5_result & p_item2) {return memcmp(&p_item1,&p_item2,sizeof(hasher_md5_result)) == 0;}
inline bool operator!=(const hasher_md5_result & p_item1,const hasher_md5_result & p_item2) {return memcmp(&p_item1,&p_item2,sizeof(hasher_md5_result)) != 0;}
namespace pfc {
template<> class traits_t<hasher_md5_state> : public traits_rawobject {};
template<> class traits_t<hasher_md5_result> : public traits_rawobject {};
template<> inline int compare_t(const hasher_md5_result & p_item1, const hasher_md5_result & p_item2) {
return memcmp(&p_item1, &p_item2, sizeof(hasher_md5_result));
}
}
class NOVTABLE hasher_md5 : public service_base
{
public:
virtual void initialize(hasher_md5_state & p_state) = 0;
virtual void process(hasher_md5_state & p_state,const void * p_buffer,t_size p_bytes) = 0;
virtual hasher_md5_result get_result(const hasher_md5_state & p_state) = 0;
static GUID guid_from_result(const hasher_md5_result & param);
hasher_md5_result process_single(const void * p_buffer,t_size p_bytes);
hasher_md5_result process_single_string(const char * str) {return process_single(str, strlen(str));}
GUID process_single_guid(const void * p_buffer,t_size p_bytes);
GUID get_result_guid(const hasher_md5_state & p_state) {return guid_from_result(get_result(p_state));}
//! Helper
void process_string(hasher_md5_state & p_state,const char * p_string,t_size p_length = ~0) {return process(p_state,p_string,pfc::strlen_max(p_string,p_length));}
FB2K_MAKE_SERVICE_COREAPI(hasher_md5);
};
class stream_writer_hasher_md5 : public stream_writer {
public:
stream_writer_hasher_md5() {
m_hasher->initialize(m_state);
}
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
p_abort.check();
m_hasher->process(m_state,p_buffer,p_bytes);
}
hasher_md5_result result() const {
return m_hasher->get_result(m_state);
}
GUID resultGuid() const {
return hasher_md5::guid_from_result(result());
}
private:
hasher_md5_state m_state;
const hasher_md5::ptr m_hasher = hasher_md5::get();
};
template<bool isBigEndian = false>
class stream_formatter_hasher_md5 : public stream_writer_formatter<isBigEndian> {
public:
stream_formatter_hasher_md5() : stream_writer_formatter<isBigEndian>(_m_stream,fb2k::noAbort) {}
hasher_md5_result result() const {
return _m_stream.result();
}
GUID resultGuid() const {
return hasher_md5::guid_from_result(result());
}
private:
stream_writer_hasher_md5 _m_stream;
};

View File

@@ -0,0 +1,57 @@
#pragma once
//! Implemented by file object returned by http_request::run methods. Allows you to retrieve various additional information returned by the server. \n
//! Warning: reply status may change when seeking on the file object since seek operations often require a new HTTP request to be fired.
class NOVTABLE http_reply : public service_base {
FB2K_MAKE_SERVICE_INTERFACE(http_reply, service_base)
public:
//! Retrieves the status line, eg. "200 OK".
virtual void get_status(pfc::string_base & out) = 0;
//! Retrieves a HTTP header value, eg. "content-type". Note that get_http_header("content-type", out) is equivalent to get_content_type(out). If there are multiple matching header entries, value of the first one will be returned.
virtual bool get_http_header(const char * name, pfc::string_base & out) = 0;
//! Retrieves a HTTP header value, eg. "content-type". If there are multiple matching header entries, this will return all their values, delimited by \r\n.
virtual bool get_http_header_multi(const char * name, pfc::string_base & out) = 0;
};
class NOVTABLE http_request : public service_base {
FB2K_MAKE_SERVICE_INTERFACE(http_request, service_base)
public:
//! Adds a HTTP request header line.
//! @param line Request to be added, without trailing \r\n.
virtual void add_header(const char * line) = 0;
//! Runs the request on the specified URL. Throws an exception on failure (connection error, invalid response from the server, reply code other than 2XX), returns a file::ptr interface to the stream on success.
virtual file::ptr run(const char * url, abort_callback & abort) = 0;
//! Runs the request on the specified URL. Throws an exception on failure but returns normally if the HTTP server returned a valid response other than 2XX, so the caller can still parse the received data stream if the server has returned an error.
virtual file::ptr run_ex(const char * url, abort_callback & abort) = 0;
void add_header(const char * name, const char * value) {
add_header(PFC_string_formatter() << name << ": " << value);
}
};
class NOVTABLE http_request_post : public http_request {
FB2K_MAKE_SERVICE_INTERFACE(http_request_post, http_request);
public:
//! Adds a HTTP POST field.
//! @param name Field name.
//! @param fileName File name to be included in the POST request; leave empty ("") not to send a file name.
//! @param contentType Content type of the entry; leave empty ("") not to send content type.
virtual void add_post_data(const char * name, const void * data, t_size dataSize, const char * fileName, const char * contentType) = 0;
void add_post_data(const char * name, const char * value) { add_post_data(name, value, strlen(value), "", ""); }
};
//! \since 1.5
class NOVTABLE http_request_post_v2 : public http_request_post {
FB2K_MAKE_SERVICE_INTERFACE(http_request_post_v2, http_request_post);
public:
virtual void set_post_data(const void* blob, size_t bytes, const char* contentType) = 0;
};
class NOVTABLE http_client : public service_base {
FB2K_MAKE_SERVICE_COREAPI(http_client)
public:
//! Creates a HTTP request object.
//! @param type Request type. Currently supported: "GET" and "POST". Throws pfc::exception_not_implemented for unsupported values.
virtual http_request::ptr create_request(const char * type) = 0;
};

View File

@@ -0,0 +1,28 @@
#pragma once
//! New in 0.9.5; allows your file format to use another icon than <extension>.ico when registering the file type with Windows shell. \n
//! Implementation: use icon_remapping_impl, or simply: static service_factory_single_t<icon_remapping_impl> myicon("ext","iconname.ico");
class icon_remapping : public service_base {
public:
//! @param p_extension File type extension being queried.
//! @param p_iconname Receives the icon name to use, including the .ico extension.
//! @returns True when p_iconname has been set, false if we don't recognize the specified extension.
virtual bool query(const char * p_extension,pfc::string_base & p_iconname) = 0;
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(icon_remapping);
};
//! Standard implementation of icon_remapping.
class icon_remapping_impl : public icon_remapping {
public:
icon_remapping_impl(const char * p_extension,const char * p_iconname) : m_extension(p_extension), m_iconname(p_iconname) {}
bool query(const char * p_extension,pfc::string_base & p_iconname) {
if (pfc::stricmp_ascii(p_extension,m_extension) == 0) {
p_iconname = m_iconname; return true;
} else {
return false;
}
}
private:
pfc::string8 m_extension,m_iconname;
};

View File

@@ -0,0 +1,52 @@
#pragma once
#ifdef _WIN32
namespace Gdiplus {
class Image;
}
#endif
namespace fb2k {
#ifdef _WIN32
typedef Gdiplus::Image * nativeImage_t;
#else
typedef void * nativeImage_t;
#endif
struct imageInfo_t {
uint32_t width, height, bitDepth;
bool haveAlpha;
const char * formatName;
const char * mime;
};
//! \since 1.6
//! Interface to common image loader routines that turn a bytestream into a image that can be drawn in a window. \n
//! Windows: Using imageLoaderLite methods initializes gdiplus if necessary, leaving gdiplus initialized for the rest of app lifetime. \n
//! If your component supports running on foobar2000 older than 1.6, use tryGet() to gracefully fall back to your own image loader: \n
//! auto api = fb2k::imageLoaderLite::tryGet(); if (api.is_valid()) { do stuff with api; } else { use fallbacks; }
class imageLoaderLite : public service_base {
FB2K_MAKE_SERVICE_COREAPI(imageLoaderLite);
public:
//! Throws excpetions on failure, returns valid image otherwise.\n
//! Caller takes ownership of the returned object. \n
//! @param outInfo Optional struct to receive information about the loaded image.
virtual nativeImage_t load(const void * data, size_t bytes, imageInfo_t * outInfo = nullptr, abort_callback & aborter = fb2k::noAbort) = 0;
//! Parses the image data just enough to hand over basic info about what's inside. \n
//! Much faster than load(). \n
//! Supports all formats recognized by load().
virtual imageInfo_t getInfo(const void * data, size_t bytes, abort_callback & aborter = fb2k::noAbort) = 0;
//! Helper - made virtual so it can be possibly specialized in the future
virtual nativeImage_t load(album_art_data_ptr data, imageInfo_t * outInfo = nullptr, abort_callback & aborter = fb2k::noAbort) {
return load(data->get_ptr(), data->get_size(), outInfo, aborter);
}
//! Helper - made virtual so it can be possibly specialized in the future
virtual imageInfo_t getInfo(album_art_data_ptr data, abort_callback & aborter = fb2k::noAbort) {
return getInfo(data->get_ptr(), data->get_size(), aborter);
}
};
}
#define FB2K_GETOPENFILENAME_PICTUREFILES "Picture files|*.jpg;*.jpeg;*.png;*.bmp;*.gif;*.webp"
#define FB2K_GETOPENFILENAME_PICTUREFILES_ALL FB2K_GETOPENFILENAME_PICTUREFILES "|All files|*.*"

View File

@@ -0,0 +1,15 @@
#pragma once
namespace fb2k {
//! \since 1.6.2
class imageViewer : public service_base {
FB2K_MAKE_SERVICE_COREAPI(imageViewer);
public:
//! Spawns an image viewer window, showing the specified picture already loaded into application memory.
virtual void show(HWND parent, fb2k::memBlockRef data) = 0;
//! Spawns an image viewer window, showing album art from the specified list of items.
//! @param aaType Type of picture to load, front cover, back cover or other.
//! @param pageno Reserved for future use, set to 0.
virtual void load_and_show(HWND parent, metadb_handle_list_cref items, const GUID & aaType, unsigned pageno = 0) = 0;
};
}

View File

@@ -0,0 +1,32 @@
//! Service used to access various external (online) track info lookup services, such as freedb, to update file tags with info retrieved from those services.
class NOVTABLE info_lookup_handler : public service_base {
public:
enum {
flag_album_lookup = 1 << 0,
flag_track_lookup = 1 << 1,
};
//! Retrieves human-readable name of the lookup handler to display in user interface.
virtual void get_name(pfc::string_base & p_out) = 0;
//! Returns one or more of flag_track_lookup, and flag_album_lookup.
virtual t_uint32 get_flags() = 0;
virtual HICON get_icon(int p_width, int p_height) = 0;
//! Performs a lookup. Creates a modeless dialog and returns immediately.
//! @param p_items Items to look up.
//! @param p_notify Callback to notify caller when the operation has completed. Call on_completion with status code 0 to signal failure/abort, or with code 1 to signal success / new infos in metadb.
//! @param p_parent Parent window for the lookup dialog. Caller will typically disable the window while lookup is in progress and enable it back when completion is signaled.
virtual void lookup(metadb_handle_list_cref items,completion_notify::ptr notify,HWND parent) = 0;
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(info_lookup_handler);
};
class NOVTABLE info_lookup_handler_v2 : public info_lookup_handler {
FB2K_MAKE_SERVICE_INTERFACE(info_lookup_handler_v2, info_lookup_handler);
public:
virtual double merit() {return 0;}
virtual void lookup_noninteractive(metadb_handle_list_cref items, completion_notify::ptr notify, HWND parent) = 0;
};

41
foobar2000/SDK/initquit.h Normal file
View File

@@ -0,0 +1,41 @@
#pragma once
//! Basic callback startup/shutdown callback, on_init is called after the main window has been created, on_quit is called before the main window is destroyed. \n
//! To register: static initquit_factory_t<myclass> myclass_factory; \n
//! Note that you should be careful with calling other components during on_init/on_quit or \n
//! initializing services that are possibly used by other components by on_init/on_quit - \n
//! initialization order of components is undefined.
//! If some other service that you publish is not properly functional before you receive an on_init() call, \n
//! someone else might call this service before >your< on_init is invoked.
class NOVTABLE initquit : public service_base {
public:
virtual void on_init() {}
virtual void on_quit() {}
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(initquit);
};
template<typename T>
class initquit_factory_t : public service_factory_single_t<T> {};
//! \since 1.1
namespace init_stages {
enum {
before_config_read = 10,
after_config_read = 20,
before_library_init = 30,
// Since foobar2000 v2.0, after_library_init is fired OUT OF ORDER with the rest, after ASYNCHRONOUS library init has completed.
after_library_init = 40,
before_ui_init = 50,
after_ui_init = 60,
};
};
//! \since 1.1
class NOVTABLE init_stage_callback : public service_base {
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(init_stage_callback)
public:
virtual void on_init_stage(t_uint32 stage) = 0;
static void dispatch(t_uint32 stage) {FB2K_FOR_EACH_SERVICE(init_stage_callback, on_init_stage(stage));}
};

450
foobar2000/SDK/input.cpp Normal file
View File

@@ -0,0 +1,450 @@
#include "foobar2000.h" // PCH
#ifdef FOOBAR2000_MODERN
#include "foobar2000-input.h"
#include <pfc/list.h>
#include <pfc/timers.h>
#endif
#include <exception>
#include "album_art.h"
#include "file_info_impl.h"
service_ptr input_entry::open(const GUID & whatFor, file::ptr hint, const char * path, event_logger::ptr logger, abort_callback & aborter) {
#ifdef FOOBAR2000_DESKTOP
if ( whatFor == input_stream_info_reader::class_guid ) {
input_entry_v2::ptr v2;
if ( v2 &= this ) {
GUID g = v2->get_guid();
service_enum_t<input_stream_info_reader_entry> e;
service_ptr_t<input_stream_info_reader_entry> p;
while (e.next(p)) {
if (p->get_guid() == g) {
return p->open( path, hint, aborter );
}
}
}
throw exception_io_unsupported_format();
}
#endif
if ( whatFor == album_art_extractor_instance::class_guid ) {
input_entry_v2::ptr v2;
if (v2 &= this) {
GUID g = v2->get_guid();
service_enum_t<album_art_extractor> e;
album_art_extractor_v2::ptr p;
while( e.next(p) ) {
if ( p->get_guid() == g ) {
return p->open( hint, path, aborter );
}
}
}
throw exception_io_unsupported_format();
}
if ( whatFor == album_art_editor_instance::class_guid ) {
input_entry_v2::ptr v2;
if (v2 &= this) {
GUID g = v2->get_guid();
service_enum_t<album_art_editor> e;
album_art_editor_v2::ptr p;
while( e.next(p) ) {
if ( p->get_guid() == g ) {
return p->open( hint, path, aborter );
}
}
}
throw exception_io_unsupported_format();
}
input_entry_v3::ptr v3;
if (v3 &= this) {
return v3->open_v3( whatFor, hint, path, logger, aborter );
} else {
if (whatFor == input_decoder::class_guid) {
input_decoder::ptr obj;
open(obj, hint, path, aborter);
if ( logger.is_valid() ) {
input_decoder_v2::ptr v2;
if (v2 &= obj) v2->set_logger(logger);
}
return obj;
}
if (whatFor == input_info_reader::class_guid) {
input_info_reader::ptr obj;
open(obj, hint, path, aborter);
return obj;
}
if (whatFor == input_info_writer::class_guid) {
input_info_writer::ptr obj;
open(obj, hint, path, aborter);
return obj;
}
}
throw pfc::exception_not_implemented();
}
bool input_entry::g_find_service_by_path(service_ptr_t<input_entry> & p_out,const char * p_path)
{
auto ext = pfc::string_extension(p_path);
return g_find_service_by_path(p_out, p_path, ext );
}
bool input_entry::g_find_service_by_path(service_ptr_t<input_entry> & p_out,const char * p_path, const char * p_ext)
{
service_ptr_t<input_entry> ptr;
service_enum_t<input_entry> e;
while(e.next(ptr))
{
if (ptr->is_our_path(p_path,p_ext))
{
p_out = ptr;
return true;
}
}
return false;
}
bool input_entry::g_find_service_by_content_type(service_ptr_t<input_entry> & p_out,const char * p_content_type)
{
service_ptr_t<input_entry> ptr;
service_enum_t<input_entry> e;
while(e.next(ptr))
{
if (ptr->is_our_content_type(p_content_type))
{
p_out = ptr;
return true;
}
}
return false;
}
#if 0
static void prepare_for_open(service_ptr_t<input_entry> & p_service,service_ptr_t<file> & p_file,const char * p_path,filesystem::t_open_mode p_open_mode,abort_callback & p_abort,bool p_from_redirect)
{
if (p_file.is_empty())
{
service_ptr_t<filesystem> fs;
if (filesystem::g_get_interface(fs,p_path)) {
if (fs->supports_content_types()) {
fs->open(p_file,p_path,p_open_mode,p_abort);
}
}
}
if (p_file.is_valid())
{
pfc::string8 content_type;
if (p_file->get_content_type(content_type))
{
if (input_entry::g_find_service_by_content_type(p_service,content_type))
return;
}
}
if (input_entry::g_find_service_by_path(p_service,p_path))
{
if (p_from_redirect && p_service->is_redirect()) throw exception_io_unsupported_format();
return;
}
throw exception_io_unsupported_format();
}
#endif
bool input_entry::g_find_inputs_by_content_type(pfc::list_base_t<service_ptr_t<input_entry> > & p_out, const char * p_content_type, bool p_from_redirect) {
auto filter = [=] (input_entry::ptr p) {
return !(p_from_redirect && p->is_redirect());
};
return g_find_inputs_by_content_type_ex(p_out, p_content_type, filter );
}
bool input_entry::g_find_inputs_by_path(pfc::list_base_t<service_ptr_t<input_entry> > & p_out, const char * p_path, bool p_from_redirect) {
auto filter = [=] (input_entry::ptr p) {
return !(p_from_redirect && p->is_redirect());
};
return g_find_inputs_by_path_ex(p_out, p_path, filter);
}
bool input_entry::g_find_inputs_by_content_type_ex(pfc::list_base_t<service_ptr_t<input_entry> > & p_out, const char * p_content_type, input_filter_t filter ) {
service_enum_t<input_entry> e;
service_ptr_t<input_entry> ptr;
bool ret = false;
while (e.next(ptr)) {
if ( filter(ptr) ) {
if (ptr->is_our_content_type(p_content_type)) { p_out.add_item(ptr); ret = true; }
}
}
return ret;
}
bool input_entry::g_find_inputs_by_path_ex(pfc::list_base_t<service_ptr_t<input_entry> > & p_out, const char * p_path, input_filter_t filter ) {
service_enum_t<input_entry> e;
service_ptr_t<input_entry> ptr;
auto extension = pfc::string_extension(p_path);
bool ret = false;
while (e.next(ptr)) {
GUID guid = pfc::guid_null;
input_entry_v3::ptr ex;
if ( ex &= ptr ) guid = ex->get_guid();
if ( filter(ptr) ) {
if (ptr->is_our_path(p_path, extension)) { p_out.add_item(ptr); ret = true; }
}
}
return ret;
}
static GUID input_get_guid( input_entry::ptr e ) {
#ifdef FOOBAR2000_DESKTOP
input_entry_v2::ptr p;
if ( p &= e ) return p->get_guid();
#endif
return pfc::guid_null;
}
service_ptr input_entry::g_open_from_list(input_entry_list_t const & p_list, const GUID & whatFor, service_ptr_t<file> p_filehint, const char * p_path, event_logger::ptr logger, abort_callback & p_abort, GUID * outGUID) {
const t_size count = p_list.get_count();
if ( count == 0 ) {
// sanity
throw exception_io_unsupported_format();
} else if (count == 1) {
auto ret = p_list[0]->open(whatFor, p_filehint, p_path, logger, p_abort);
if ( outGUID != nullptr ) * outGUID = input_get_guid( p_list[0] );
return ret;
} else {
std::exception_ptr errData, errUnsupported;
for (t_size n = 0; n < count; n++) {
try {
auto ret = p_list[n]->open(whatFor, p_filehint, p_path, logger, p_abort);
if (outGUID != nullptr) * outGUID = input_get_guid(p_list[n]);
return ret;
} catch (exception_io_no_handler_for_path) {
//do nothing, skip over
} catch(exception_io_unsupported_format) {
if (!errUnsupported) errUnsupported = std::current_exception();
} catch (exception_io_data) {
if (!errData) errData = std::current_exception();
}
}
if (errData) std::rethrow_exception(errData);
if (errUnsupported) std::rethrow_exception(errUnsupported);
throw exception_io_unsupported_format();
}
}
#ifdef FOOBAR2000_DESKTOP
service_ptr input_manager::open_v2(const GUID & whatFor, file::ptr hint, const char * path, bool fromRedirect, event_logger::ptr logger, abort_callback & aborter, GUID * outUsedEntry) {
// We're wrapping open_v2() on top of old open().
// Assert on GUIDs that old open() is known to recognize.
PFC_ASSERT(whatFor == input_decoder::class_guid || whatFor == input_info_reader::class_guid || whatFor == input_info_writer::class_guid || whatFor == input_stream_selector::class_guid);
{
input_manager_v2::ptr v2;
if ( v2 &= this ) {
return v2->open_v2( whatFor, hint, path, fromRedirect, logger, aborter, outUsedEntry );
}
}
auto ret = open( whatFor, hint, path, fromRedirect, aborter, outUsedEntry );
#ifdef FB2K_HAVE_EVENT_LOGGER
if ( logger.is_valid() ) {
input_decoder_v2::ptr dec;
if (dec &= ret) {
dec->set_logger(logger);
}
}
#endif
return ret;
}
#endif
service_ptr input_entry::g_open(const GUID & whatFor, file::ptr p_filehint, const char * p_path, event_logger::ptr logger, abort_callback & p_abort, bool p_from_redirect) {
#ifdef FOOBAR2000_DESKTOP
// #define rationale: not all FOOBAR2000_MODERN flavours come with input_manager implementation, but classic 1.4+ does
#if !defined(FOOBAR2000_MODERN) && FOOBAR2000_TARGET_VERSION >= 79
#if FOOBAR2000_TARGET_VERSION > 79
return input_manager_v2::get()->open_v2(whatFor, p_filehint, p_path, p_from_redirect, logger, p_abort);
#else
return input_manager::get()->open_v2(whatFor, p_filehint, p_path, p_from_redirect, logger, p_abort);
#endif
#endif
{
input_manager::ptr m;
service_enum_t<input_manager> e;
if (e.next(m)) {
return m->open_v2(whatFor, p_filehint, p_path, p_from_redirect, logger, p_abort);
}
}
#endif
const bool needWriteAcecss = !!(whatFor == input_info_writer::class_guid);
service_ptr_t<file> l_file = p_filehint;
if (l_file.is_empty()) {
service_ptr_t<filesystem> fs;
if (filesystem::g_get_interface(fs, p_path)) {
if (fs->supports_content_types()) {
fs->open(l_file, p_path, needWriteAcecss ? filesystem::open_mode_write_existing : filesystem::open_mode_read, p_abort);
}
}
}
if (l_file.is_valid()) {
pfc::string8 content_type;
if (l_file->get_content_type(content_type)) {
pfc::list_t< input_entry::ptr > list;
#if PFC_DEBUG
FB2K_DebugLog() << "attempting input open by content type: " << content_type;
#endif
if (g_find_inputs_by_content_type(list, content_type, p_from_redirect)) {
try {
return g_open_from_list(list, whatFor, l_file, p_path, logger, p_abort);
} catch (exception_io_unsupported_format) {
#if PFC_DEBUG
FB2K_DebugLog() << "Failed to open by content type, using fallback";
#endif
}
}
}
}
#if PFC_DEBUG
FB2K_DebugLog() << "attempting input open by path: " << p_path;
#endif
{
pfc::list_t< input_entry::ptr > list;
if (g_find_inputs_by_path(list, p_path, p_from_redirect)) {
return g_open_from_list(list, whatFor, l_file, p_path, logger, p_abort);
}
}
throw exception_io_unsupported_format();
}
void input_entry::g_open_for_decoding(service_ptr_t<input_decoder> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect) {
TRACK_CALL_TEXT("input_entry::g_open_for_decoding");
p_instance ^= g_open(input_decoder::class_guid, p_filehint, p_path, nullptr, p_abort, p_from_redirect);
}
void input_entry::g_open_for_info_read(service_ptr_t<input_info_reader> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect) {
TRACK_CALL_TEXT("input_entry::g_open_for_info_read");
p_instance ^= g_open(input_info_reader::class_guid, p_filehint, p_path, nullptr, p_abort, p_from_redirect);
}
void input_entry::g_open_for_info_write(service_ptr_t<input_info_writer> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect) {
TRACK_CALL_TEXT("input_entry::g_open_for_info_write");
p_instance ^= g_open(input_info_writer::class_guid, p_filehint, p_path, nullptr, p_abort, p_from_redirect);
}
void input_entry::g_open_for_info_write_timeout(service_ptr_t<input_info_writer> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,double p_timeout,bool p_from_redirect) {
pfc::lores_timer timer;
timer.start();
for(;;) {
try {
g_open_for_info_write(p_instance,p_filehint,p_path,p_abort,p_from_redirect);
break;
} catch(exception_io_sharing_violation) {
if (timer.query() > p_timeout) throw;
p_abort.sleep(0.01);
}
}
}
bool input_entry::g_is_supported_path(const char * p_path)
{
service_ptr_t<input_entry> ptr;
service_enum_t<input_entry> e;
auto ext = pfc::string_extension (p_path);
while(e.next(ptr))
{
if (ptr->is_our_path(p_path,ext)) return true;
}
return false;
}
void input_open_file_helper(service_ptr_t<file> & p_file,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort)
{
if (p_file.is_empty()) {
switch(p_reason) {
default:
uBugCheck();
case input_open_info_read:
case input_open_decode:
filesystem::g_open(p_file,p_path,filesystem::open_mode_read,p_abort);
break;
case input_open_info_write:
filesystem::g_open(p_file,p_path,filesystem::open_mode_write_existing,p_abort);
break;
}
} else {
p_file->reopen(p_abort);
}
}
uint32_t input_entry::g_flags_for_path( const char * path, uint32_t mask ) {
#if FOOBAR2000_TARGET_VERSION >= 80
return input_manager_v3::get()->flags_for_path(path, mask);
#else
input_manager_v3::ptr api;
if ( input_manager_v3::tryGet(api) ) {
return api->flags_for_path(path, mask);
}
uint32_t ret = 0;
service_enum_t<input_entry> e; input_entry::ptr p;
auto ext = pfc::string_extension(path);
while(e.next(p)) {
uint32_t f = p->get_flags() & mask;
if ( f != 0 && p->is_our_path( path, ext ) ) ret |= f;;
}
return ret;
#endif
}
uint32_t input_entry::g_flags_for_content_type( const char * ct, uint32_t mask ) {
#if FOOBAR2000_TARGET_VERSION >= 80
return input_manager_v3::get()->flags_for_content_type(ct, mask);
#else
input_manager_v3::ptr api;
if ( input_manager_v3::tryGet(api) ) {
return api->flags_for_content_type( ct, mask );
}
uint32_t ret = 0;
service_enum_t<input_entry> e; input_entry::ptr p;
while(e.next(p)) {
uint32_t f = p->get_flags() & mask;
if ( f != 0 && p->is_our_content_type(ct) ) ret |= f;
}
return ret;
#endif
}
bool input_entry::g_are_parallel_reads_slow(const char * path) {
return g_flags_for_path(path, flag_parallel_reads_slow) != 0;
}
void input_entry_v3::open_for_decoding(service_ptr_t<input_decoder> & p_instance, service_ptr_t<file> p_filehint, const char * p_path, abort_callback & p_abort) {
p_instance ^= open_v3( input_decoder::class_guid, p_filehint, p_path, nullptr, p_abort );
}
void input_entry_v3::open_for_info_read(service_ptr_t<input_info_reader> & p_instance, service_ptr_t<file> p_filehint, const char * p_path, abort_callback & p_abort) {
p_instance ^= open_v3(input_info_reader::class_guid, p_filehint, p_path, nullptr, p_abort);
}
void input_entry_v3::open_for_info_write(service_ptr_t<input_info_writer> & p_instance, service_ptr_t<file> p_filehint, const char * p_path, abort_callback & p_abort) {
p_instance ^= open_v3(input_info_writer::class_guid, p_filehint, p_path, nullptr, p_abort);
}
void input_info_writer::remove_tags_fallback(abort_callback & abort) {
uint32_t total = this->get_subsong_count();
file_info_impl blank;
for( uint32_t walk = 0; walk < total; ++ walk ) {
this->set_info( this->get_subsong(walk), blank, abort );
}
this->commit( abort );
}

545
foobar2000/SDK/input.h Normal file
View File

@@ -0,0 +1,545 @@
#pragma once
#include <functional>
#include "event_logger.h"
#include "audio_chunk.h"
PFC_DECLARE_EXCEPTION(exception_tagging_unsupported, exception_io_data, "Tagging of this file format is not supported")
enum {
input_flag_no_seeking = 1 << 0,
input_flag_no_looping = 1 << 1,
input_flag_playback = 1 << 2,
input_flag_testing_integrity = 1 << 3,
input_flag_allow_inaccurate_seeking = 1 << 4,
input_flag_no_postproc = 1 << 5,
input_flag_simpledecode = input_flag_no_seeking|input_flag_no_looping,
};
//! Class providing interface for retrieval of information (metadata, duration, replaygain, other tech infos) from files. Also see: file_info. \n
//! Instantiating: see input_entry.\n
//! Implementing: see input_impl.
class NOVTABLE input_info_reader : public service_base
{
public:
//! Retrieves count of subsongs in the file. 1 for non-multisubsong-enabled inputs.
//! Note: multi-subsong handling is disabled for remote files (see: filesystem::is_remote) for performance reasons. Remote files are always assumed to be single-subsong, with null index.
virtual t_uint32 get_subsong_count() = 0;
//! Retrieves identifier of specified subsong; this identifier is meant to be used in playable_location as well as a parameter for input_info_reader::get_info().
//! @param p_index Index of subsong to query. Must be >=0 and < get_subsong_count().
virtual t_uint32 get_subsong(t_uint32 p_index) = 0;
//! Retrieves information about specified subsong.
//! @param p_subsong Identifier of the subsong to query. See: input_info_reader::get_subsong(), playable_location.
//! @param p_info file_info object to fill. Must be empty on entry.
//! @param p_abort abort_callback object signaling user aborting the operation.
virtual void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) = 0;
//! Retrieves file stats. Equivalent to calling get_stats() on file object.
virtual t_filestats get_file_stats(abort_callback & p_abort) = 0;
FB2K_MAKE_SERVICE_INTERFACE(input_info_reader,service_base);
};
//! Class providing interface for retrieval of PCM audio data from files.\n
//! Instantiating: see input_entry.\n
//! Implementing: see input_impl.
class NOVTABLE input_decoder : public input_info_reader
{
public:
//! Prepares to decode specified subsong; resets playback position to the beginning of specified subsong. This must be called first, before any other input_decoder methods (other than those inherited from input_info_reader). \n
//! It is legal to set initialize() more than once, with same or different subsong, to play either the same subsong again or another subsong from same file without full reopen.\n
//! Warning: this interface inherits from input_info_reader, it is legal to call any input_info_reader methods even during decoding! Call order is not defined, other than initialize() requirement before calling other input_decoder methods.\n
//! @param p_subsong Subsong to decode. Should always be 0 for non-multi-subsong-enabled inputs.
//! @param p_flags Specifies additional hints for decoding process. It can be null, or a combination of one or more following constants: \n
//! input_flag_no_seeking - Indicates that seek() will never be called. Can be used to avoid building potentially expensive seektables when only sequential reading is needed.\n
//! input_flag_no_looping - Certain input implementations can be configured to utilize looping info from file formats they process and keep playing single file forever, or keep repeating it specified number of times. This flag indicates that such features should be disabled, for e.g. ReplayGain scan or conversion.\n
//! input_flag_playback - Indicates that decoding process will be used for realtime playback rather than conversion. This can be used to reconfigure features that are relevant only for conversion and take a lot of resources, such as very slow secure CDDA reading. \n
//! input_flag_testing_integrity - Indicates that we're testing integrity of the file. Any recoverable problems where decoding would normally continue should cause decoder to fail with exception_io_data.
//! @param p_abort abort_callback object signaling user aborting the operation.
virtual void initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort) = 0;
//! Reads/decodes one chunk of audio data. Use false return value to signal end of file (no more data to return). Before calling run(), decoding must be initialized by initialize() call.
//! @param p_chunk audio_chunk object receiving decoded data. Contents are valid only the method returns true.
//! @param p_abort abort_callback object signaling user aborting the operation.
//! @returns true on success (new data decoded), false on EOF.
virtual bool run(audio_chunk & p_chunk,abort_callback & p_abort) = 0;
//! Seeks to specified time offset. Before seeking or other decoding calls, decoding must be initialized with initialize() call.
//! @param p_seconds Time to seek to, in seconds. If p_seconds exceeds length of the object being decoded, succeed, and then return false from next run() call.
//! @param p_abort abort_callback object signaling user aborting the operation.
virtual void seek(double p_seconds,abort_callback & p_abort) = 0;
//! Queries whether resource being read/decoded is seekable. If p_value is set to false, all seek() calls will fail. Before calling can_seek() or other decoding calls, decoding must be initialized with initialize() call.
virtual bool can_seek() = 0;
//! This function is used to signal dynamic VBR bitrate, etc. Called after each run() (or not called at all if caller doesn't care about dynamic info).
//! @param p_out Initially contains currently displayed info (either last get_dynamic_info result or current cached info), use this object to return changed info.
//! @param p_timestamp_delta Indicates when returned info should be displayed (in seconds, relative to first sample of last decoded chunk), initially set to 0.
//! @returns false to keep old info, or true to indicate that changes have been made to p_info and those should be displayed.
virtual bool get_dynamic_info(file_info & p_out, double & p_timestamp_delta) = 0;
//! This function is used to signal dynamic live stream song titles etc. Called after each run() (or not called at all if caller doesn't care about dynamic info). The difference between this and get_dynamic_info() is frequency and relevance of dynamic info changes - get_dynamic_info_track() returns new info only on track change in the stream, returning new titles etc.
//! @param p_out Initially contains currently displayed info (either last get_dynamic_info_track result or current cached info), use this object to return changed info.
//! @param p_timestamp_delta Indicates when returned info should be displayed (in seconds, relative to first sample of last decoded chunk), initially set to 0.
//! @returns false to keep old info, or true to indicate that changes have been made to p_info and those should be displayed.
virtual bool get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) = 0;
//! Called from playback thread before sleeping.
//! @param p_abort abort_callback object signaling user aborting the operation.
virtual void on_idle(abort_callback & p_abort) = 0;
FB2K_MAKE_SERVICE_INTERFACE(input_decoder,input_info_reader);
};
class NOVTABLE input_decoder_v2 : public input_decoder {
FB2K_MAKE_SERVICE_INTERFACE(input_decoder_v2, input_decoder)
public:
//! OPTIONAL, throws pfc::exception_not_implemented() when not supported by this implementation.
//! Special version of run(). Returns an audio_chunk object as well as a raw data block containing original PCM stream. This is mainly used for MD5 checks on lossless formats. \n
//! If you set a "MD5" tech info entry in get_info(), you should make sure that run_raw() returns data stream that can be used to verify it. \n
//! Returned raw data should be possible to cut into individual samples; size in bytes should be divisible by audio_chunk's sample count for splitting in case partial output is needed (with cuesheets etc).
virtual bool run_raw(audio_chunk & out, mem_block_container & outRaw, abort_callback & abort) = 0;
//! OBSOLETE since 1.5 \n
//! Specify logger when opening to reliably get info generated during input open operation.
virtual void set_logger(event_logger::ptr ptr) = 0;
};
class NOVTABLE input_decoder_v3 : public input_decoder_v2 {
FB2K_MAKE_SERVICE_INTERFACE(input_decoder_v3, input_decoder_v2);
public:
//! OBSOLETE, functionality implemented by core.
virtual void set_pause(bool paused) = 0;
//! OPTIONAL, should return false in most cases; return true to force playback buffer flush on unpause. Valid only after initialize() with input_flag_playback.
virtual bool flush_on_pause() = 0;
};
class NOVTABLE input_decoder_v4 : public input_decoder_v3 {
FB2K_MAKE_SERVICE_INTERFACE( input_decoder_v4, input_decoder_v3 );
public:
//! OPTIONAL, return 0 if not implemented. \n
//! Provides means for communication of context specific data with the decoder. The decoder should do nothing and return 0 if it does not recognize the passed arguments.
virtual size_t extended_param( const GUID & type, size_t arg1, void * arg2, size_t arg2size) = 0;
};
//! Parameter GUIDs for input_decoder_v3::extended_param().
class input_params {
public:
//! Signals whether unnecessary seeking should be avoided with this decoder for performance reasons. \n
//! Arguments disregarded, return value 1 or 0.
static const GUID seeking_expensive;
//! Tells the decoder to output at this sample rate if the decoder's sample rate is adjustable. \n
//! Sample rate signaled in arg1.
static const GUID set_preferred_sample_rate;
//! Retrieves logical decode position from the decoder. Implemented only in some rare cases where logical position does not match duration of returned data so far.
//! arg2 points to double position in seconds.
//! Return 1 if position was written to arg2, 0 if n/a.
static const GUID query_position;
struct continue_stream_t {
file::ptr reader;
const char * path;
};
//! Tells the decoder to continue decoding from another URL, without flushing etc. Mainly used by HLS streams.
//! arg2: continue_stream_t
//! Return 1 to acknowledge, 0 if unsupported.
//! A call to decode_initialize() will follow if you return 1; perform actual file open from there.
static const GUID continue_stream;
};
//! Class providing interface for writing metadata and replaygain info to files. Also see: file_info. \n
//! Instantiating: see input_entry.\n
//! Implementing: see input_impl.
class NOVTABLE input_info_writer : public input_info_reader
{
public:
//! Tells the service to update file tags with new info for specified subsong.
//! @param p_subsong Subsong to update. Should be always 0 for non-multisubsong-enabled inputs.
//! @param p_info New info to write. Sometimes not all contents of p_info can be written. Caller will typically read info back after successful write, so e.g. tech infos that change with retag are properly maintained.
//! @param p_abort abort_callback object signaling user aborting the operation. WARNING: abort_callback object is provided for consistency; if writing tags actually gets aborted, user will be likely left with corrupted file. Anything calling this should make sure that aborting is either impossible, or gives appropriate warning to the user first.
virtual void set_info(t_uint32 p_subsong,const file_info & p_info,abort_callback & p_abort) = 0;
//! Commits pending updates. In case of multisubsong inputs, set_info should queue the update and perform actual file access in commit(). Otherwise, actual writing can be done in set_info() and then commit() can just do nothing and always succeed.
//! @param p_abort abort_callback object signaling user aborting the operation. WARNING: abort_callback object is provided for consistency; if writing tags actually gets aborted, user will be likely left with corrupted file. Anything calling this should make sure that aborting is either impossible, or gives appropriate warning to the user first.
virtual void commit(abort_callback & p_abort) = 0;
//! Helper for writers not implementing input_info_writer_v2::remove_tags().
void remove_tags_fallback(abort_callback & abort);
FB2K_MAKE_SERVICE_INTERFACE(input_info_writer,input_info_reader);
};
//! Extended input_info_writer. Not every input implements it. \n
//! Provides an explicit remove_tags(), which erases all supported tags from the file.
class NOVTABLE input_info_writer_v2 : public input_info_writer {
public:
//! Removes all tags from this file. Cancels any set_info() requests on this object. Does not require a commit() afterwards.
//! If no input_info_writer_v2 is provided, similar affect can be achieved by set_info()+commit() with blank file_info, but may not be as thorough; will typically result in blank tags rather than total removal fo tags.
virtual void remove_tags(abort_callback & abort) = 0;
FB2K_MAKE_SERVICE_INTERFACE(input_info_writer_v2, input_info_writer);
};
class NOVTABLE input_entry : public service_base {
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(input_entry);
public:
//! Determines whether specified content type can be handled by this input.
//! @param p_type Content type string to test.
virtual bool is_our_content_type(const char * p_type)=0;
//! Determines whether specified file type can be handled by this input. This must not use any kind of file access; the result should be only based on file path / extension.
//! @param p_full_path Full URL of file being tested.
//! @param p_extension Extension of file being tested, provided by caller for performance reasons.
virtual bool is_our_path(const char * p_full_path,const char * p_extension)=0;
//! Opens specified resource for decoding.
//! @param p_instance Receives new input_decoder instance if successful.
//! @param p_filehint Optional; passes file object to use for the operation; if set to null, the service will handle opening file by itself. Note that not all inputs operate on physical files that can be reached through filesystem API, some of them require this parameter to be set to null (tone and silence generators for an example).
//! @param p_path URL of resource being opened.
//! @param p_abort abort_callback object signaling user aborting the operation.
virtual void open_for_decoding(service_ptr_t<input_decoder> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort) = 0;
//! Opens specified file for reading info.
//! @param p_instance Receives new input_info_reader instance if successful.
//! @param p_filehint Optional; passes file object to use for the operation; if set to null, the service will handle opening file by itself. Note that not all inputs operate on physical files that can be reached through filesystem API, some of them require this parameter to be set to null (tone and silence generators for an example).
//! @param p_path URL of resource being opened.
//! @param p_abort abort_callback object signaling user aborting the operation.
virtual void open_for_info_read(service_ptr_t<input_info_reader> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort) = 0;
//! Opens specified file for writing info.
//! @param p_instance Receives new input_info_writer instance if successful.
//! @param p_filehint Optional; passes file object to use for the operation; if set to null, the service will handle opening file by itself. Note that not all inputs operate on physical files that can be reached through filesystem API, some of them require this parameter to be set to null (tone and silence generators for an example).
//! @param p_path URL of resource being opened.
//! @param p_abort abort_callback object signaling user aborting the operation.
virtual void open_for_info_write(service_ptr_t<input_info_writer> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort) = 0;
//! Reserved for future use. Do nothing and return until specifications are finalized.
virtual void get_extended_data(service_ptr_t<file> p_filehint,const playable_location & p_location,const GUID & p_guid,mem_block_container & p_out,abort_callback & p_abort) = 0;
enum {
//! Indicates that this service implements some kind of redirector that opens another input for decoding, used to avoid circular call possibility.
flag_redirect = 1,
//! Indicates that multi-CPU optimizations should not be used.
flag_parallel_reads_slow = 2,
};
//! See flag_* enums.
virtual unsigned get_flags() = 0;
inline bool is_redirect() {return (get_flags() & flag_redirect) != 0;}
inline bool are_parallel_reads_slow() {return (get_flags() & flag_parallel_reads_slow) != 0;}
static bool g_find_service_by_path(service_ptr_t<input_entry> & p_out,const char * p_path);
static bool g_find_service_by_path(service_ptr_t<input_entry> & p_out,const char * p_path, const char * p_ext);
static bool g_find_service_by_content_type(service_ptr_t<input_entry> & p_out,const char * p_content_type);
static void g_open_for_decoding(service_ptr_t<input_decoder> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect = false);
static void g_open_for_info_read(service_ptr_t<input_info_reader> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect = false);
static void g_open_for_info_write(service_ptr_t<input_info_writer> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect = false);
static void g_open_for_info_write_timeout(service_ptr_t<input_info_writer> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,double p_timeout,bool p_from_redirect = false);
static bool g_is_supported_path(const char * p_path);
typedef std::function<bool ( input_entry::ptr ) > input_filter_t;
static bool g_find_inputs_by_content_type(pfc::list_base_t<service_ptr_t<input_entry> > & p_out, const char * p_content_type, bool p_from_redirect);
static bool g_find_inputs_by_path(pfc::list_base_t<service_ptr_t<input_entry> > & p_out, const char * p_path, bool p_from_redirect );
static bool g_find_inputs_by_content_type_ex(pfc::list_base_t<service_ptr_t<input_entry> > & p_out, const char * p_content_type, input_filter_t filter );
static bool g_find_inputs_by_path_ex(pfc::list_base_t<service_ptr_t<input_entry> > & p_out, const char * p_path, input_filter_t filter );
static service_ptr g_open(const GUID & whatFor, file::ptr hint, const char * path, event_logger::ptr logger, abort_callback & aborter, bool fromRedirect = false);
void open(service_ptr_t<input_decoder> & p_instance,service_ptr_t<file> const & p_filehint,const char * p_path,abort_callback & p_abort) {open_for_decoding(p_instance,p_filehint,p_path,p_abort);}
void open(service_ptr_t<input_info_reader> & p_instance,service_ptr_t<file> const & p_filehint,const char * p_path,abort_callback & p_abort) {open_for_info_read(p_instance,p_filehint,p_path,p_abort);}
void open(service_ptr_t<input_info_writer> & p_instance,service_ptr_t<file> const & p_filehint,const char * p_path,abort_callback & p_abort) {open_for_info_write(p_instance,p_filehint,p_path,p_abort);}
service_ptr open(const GUID & whatFor, file::ptr hint, const char * path, event_logger::ptr logger, abort_callback & aborter);
typedef pfc::list_base_const_t< input_entry::ptr > input_entry_list_t;
static service_ptr g_open_from_list(input_entry_list_t const & list, const GUID & whatFor, file::ptr hint, const char * path, event_logger::ptr logger, abort_callback & aborter, GUID * outGUID = nullptr);
static bool g_are_parallel_reads_slow( const char * path );
static uint32_t g_flags_for_path( const char * pathFor, uint32_t mask = UINT32_MAX );
static uint32_t g_flags_for_content_type( const char * ct, uint32_t mask = UINT32_MAX );
};
//! \since 1.4
//! Extended input_entry methods provided by decoders. \n
//! Can be implemented by 1.3-compatible components but will not be called in fb2k versions prior to 1.4.
class input_entry_v2 : public input_entry {
FB2K_MAKE_SERVICE_INTERFACE(input_entry_v2, input_entry);
public:
//! @returns GUID used to identify us among other decoders in the decoder priority table.
virtual GUID get_guid() = 0;
//! @returns Name to present to the user in the decoder priority table.
virtual const char * get_name() = 0;
//! @returns GUID of this decoder's preferences page (optional), null guid if there's no page to present
virtual GUID get_preferences_guid() = 0;
//! @returns true if the decoder should be put at the end of the list when it's first sighted, false otherwise (will be put at the beginning of the list).
virtual bool is_low_merit() = 0;
};
//! \since 1.5
class input_entry_v3 : public input_entry_v2 {
FB2K_MAKE_SERVICE_INTERFACE(input_entry_v3, input_entry_v2);
public:
//! New unified open() function for all supported interfaces. Supports any future interfaces via alternate GUIDs, as well as allows the event logger to be set prior to the open() call.
//! @param whatFor The class GUID of the service we want. \n
//! Currently allowed are: input_decoder::class_guid, input_info_reader::class_guid, input_info_writer::class_guid. \n
//! This method must throw pfc::exception_not_implemented for any GUIDs it does not recognize.
virtual service_ptr open_v3( const GUID & whatFor, file::ptr hint, const char * path, event_logger::ptr logger, abort_callback & aborter ) = 0;
void open_for_decoding(service_ptr_t<input_decoder> & p_instance, service_ptr_t<file> p_filehint, const char * p_path, abort_callback & p_abort) ;
void open_for_info_read(service_ptr_t<input_info_reader> & p_instance, service_ptr_t<file> p_filehint, const char * p_path, abort_callback & p_abort);
void open_for_info_write(service_ptr_t<input_info_writer> & p_instance, service_ptr_t<file> p_filehint, const char * p_path, abort_callback & p_abort);
};
#ifdef FOOBAR2000_DESKTOP
//! \since 1.4
//! Core API to perform input open operations respecting user settings for decoder priority. \n
//! Unavailable prior to 1.4.
class input_manager : public service_base {
FB2K_MAKE_SERVICE_COREAPI(input_manager);
public:
virtual service_ptr open(const GUID & whatFor, file::ptr hint, const char * path, bool fromRedirect, abort_callback & aborter, GUID * outUsedEntry = nullptr) = 0;
//! input_manager_v2 wrapper.
service_ptr open_v2(const GUID & whatFor, file::ptr hint, const char * path, bool fromRedirect, event_logger::ptr logger, abort_callback & aborter, GUID * outUsedEntry = nullptr);
};
//! \since 1.5
//! Extension of input_manager. \n
//! Extended open_v2() supports album_art_extractor and album_art_editor. It reliably throws pfc::exception_not_implemented() for unsupported GUIDs (old version would bugcheck). \n
//! It also allows event_logger to be specified in advance so open() implementation can already use it.
class input_manager_v2 : public input_manager {
FB2K_MAKE_SERVICE_COREAPI_EXTENSION(input_manager_v2, input_manager)
public:
virtual service_ptr open_v2(const GUID & whatFor, file::ptr hint, const char * path, bool fromRedirect, event_logger::ptr logger, abort_callback & aborter, GUID * outUsedEntry = nullptr) = 0;
};
//! \since 1.5
class input_manager_v3 : public input_manager_v2 {
FB2K_MAKE_SERVICE_COREAPI_EXTENSION(input_manager_v3, input_manager_v2);
public:
//! Retrieves list of enabled inputs, in user-specified order. \n
//! This is rarely needed. If you need this function, consider redesigning your code to call input_manager open methods instead.
virtual void get_enabled_inputs( pfc::list_base_t<input_entry::ptr> & out ) = 0;
//! Returns input_entry get_flags() values for this path, as returned by enabled inputs.
virtual uint32_t flags_for_path( const char * pathFor, uint32_t mask = UINT32_MAX ) = 0;
//! Returns input_entry get_flags() values for this content type, as returned by enabled inputs.
virtual uint32_t flags_for_content_type( const char * ct, uint32_t mask = UINT32_MAX ) = 0;
enum {
flagFromRedirect = 1 << 0,
flagSuppressFilters = 1 << 1,
};
virtual service_ptr open_v3(const GUID & whatFor, file::ptr hint, const char * path, uint32_t flags, event_logger::ptr logger, abort_callback & aborter, GUID * outUsedEntry = nullptr) = 0;
};
//! \since 1.4
//! Core API for determining which audio stream to decode, in a multi-stream enabled input. \n
//! Unavailable prior to 1.4 - decode the default stream if input_stream_selector isn't present. \n
//! In foobar2000 v1.4 and up, this API allows decoders to determine which stream the user opted to decode for a specific file. \n
//! Use input_stream_selector::tryGet() to safely instantiate.
class input_stream_selector : public service_base {
FB2K_MAKE_SERVICE_COREAPI(input_stream_selector);
public:
//! Returns index of stream that should be presented for this file. \n
//! If not set by user, 0xFFFFFFFF will be returned and the default stream should be presented. \n
//! @param guid GUID of the input asking for the stream.
virtual uint32_t select_stream( const GUID & guid, const char * path ) = 0;
};
//! \since 1.4
//! Interface provided by multi-stream enabled inputs to let the stream picker dialog show available streams. \n
//! Can be implemented by 1.3-compatible components but will not be called in fb2k versions prior to 1.4.
class input_stream_info_reader : public service_base {
FB2K_MAKE_SERVICE_INTERFACE(input_stream_info_reader, service_base);
public:
//! @returns Number of audio streams found.
virtual uint32_t get_stream_count() = 0;
//! Retrieves information about the specified stream; most importantly the codec name and bitrate.
virtual void get_stream_info(uint32_t index, file_info & out, abort_callback & aborter) = 0;
//! @returns Index of default stream to decode if there is no user preference.
virtual uint32_t get_default_stream() = 0;
};
//! \since 1.4
//! Entrypoint interface for spawning input_stream_info_reader. \n
//! Can be implemented by 1.3-compatible components but will not be called in fb2k versions prior to 1.4.
class input_stream_info_reader_entry : public service_base {
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(input_stream_info_reader_entry);
public:
//! Open file for reading stream infos.
virtual input_stream_info_reader::ptr open( const char * path, file::ptr fileHint, abort_callback & abort ) = 0;
//! Return GUID of the matching input_entry.
virtual GUID get_guid() = 0;
};
//! \since 1.4
//! Callback for input_stream_manipulator \n
//! Used for applying ReplayGain to encoded audio streams.
class input_stream_manipulator_callback : public service_base {
FB2K_MAKE_SERVICE_INTERFACE(input_stream_manipulator_callback, service_base);
public:
//! Called first before other methods. Throw an exception if the file cannot be processed. \n
//! The arguments are the same as packet_decoder open() arguments.
virtual void set_decode_info(const GUID & p_owner, t_size p_param1, const void * p_param2, t_size p_param2size ) = 0;
virtual void first_frame( const void * data, size_t bytes ) = 0;
//! Called with progress value, in 0..1 range.
virtual void on_progress( float progress ) = 0;
//! @returns true if the frame has been altered and should be written back, false otherwise.
virtual bool process_frame( void * data, size_t size ) = 0;
};
//! \since 1.4
//! Manipulate audio stream payload in files. \n
//! Used for applying ReplayGain to encoded audio streams.
class input_stream_manipulator : public service_base {
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(input_stream_manipulator);
public:
enum op_t {
//! Probe the file for codec information; calls set_decode_info() + first_frame() only.
op_probe = 0,
//! Read the entire stream - same as op_probe but then calls on_progress() + process_frame() with the entire file payload. \n
//! No writing to the file is performed - process_frame() results are disregarded.
op_read,
//! Rewrite the stream. Similar to op_read, but frames altered by process_frame() are written back to the file.
op_rewrite
};
//! @param path Path of file to process.
//! @param fileHint optional file object, must be opened for read+write if bWalk is true.
//! @param callback Callback object for this operation.
//! @param opType Operation to perform, see op_t enum for details.
//! @param abort abort_callback object for this operating. Aborting with bWalk set to true will leave the file partially altered, use with caution!
virtual void process( const char * path, file::ptr fileHint, input_stream_manipulator_callback::ptr callback, op_t opType, abort_callback & abort ) = 0;
//! Return GUID of the matching input_entry.
virtual GUID get_guid() = 0;
};
//! \since 1.5
//! An input_info_filter lets you hook into all performed tag read & write operations. \n
//! Your tag manipulations will be transparent to all fb2k components, as if the tags were read/written by relevant inputs. \n
//! Your input_info_filter needs to be enabled in Preferences in order to become active. Newly added ones are inactive by default.
class input_info_filter : public service_base {
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT( input_info_filter );
public:
//! Tags are being read from a file.
virtual void filter_info_read( const playable_location & loc,file_info & info,abort_callback & abort ) = 0;
//! Tags are being written to a file. \n
//! Return true to continue, false to suppress writing of tags.
virtual bool filter_info_write( const playable_location & loc, file_info & info, abort_callback & abort ) = 0;
//! Tags are being removed from a file.
virtual void on_info_remove( const char * path, abort_callback & abort ) = 0;
//! Return GUID of your filter.
virtual GUID get_guid() = 0;
//! Return preferences page or advconfig branch GUID of your filter.
virtual GUID get_preferences_guid() = 0;
//! Return user-friendly name of your filter to be shown in preferences.
virtual const char * get_name() = 0;
//! Optional backwards compatibility method. \n
//! If you also provide input services for old foobar2000 versions which don't recognize input_info_filter, report their GUIDs here so they can be ignored. \n
//! @param outGUIDs empty on entry, contains GUIDs of ignored inputs (if any) on return.
virtual void get_suppressed_inputs( pfc::list_base_t<GUID> & outGUIDs ) {outGUIDs.remove_all();}
//! write_fallback() supported or not? \n
//! Used if your filter can store tags for untaggable files.
virtual bool supports_fallback() = 0;
//! Optional; called when user attempted to tag an untaggable/readonly file. \n
//! Used if your filter can store tags for untaggable files.
virtual bool write_fallback( const playable_location & loc, file_info const & info, abort_callback & abort ) = 0;
//! Optional; called when user attempted to remove tags from an untaggable/readonly file.\ n
//! Used if your filter can store tags for untaggable files.
virtual void remove_tags_fallback( const char * path, abort_callback & abort ) = 0;
};
//! \since 1.5
class input_stream_info_filter : public service_base {
FB2K_MAKE_SERVICE_INTERFACE( input_stream_info_filter, service_base );
public:
virtual void filter_dynamic_info( file_info & info ) = 0;
virtual void filter_dynamic_info_track( file_info & info ) = 0;
};
class album_art_data;
//! \since 1.5
//! Extended input_info_filter.
class input_info_filter_v2 : public input_info_filter {
FB2K_MAKE_SERVICE_INTERFACE( input_info_filter_v2, input_info_filter );
public:
//! Creates an object which then can work with dynamic track titles etc of a decoded track. \n
//! Returning null to filter the info is allowed.
virtual input_stream_info_filter::ptr open_stream(playable_location const & loc, abort_callback & abort) = 0;
typedef service_ptr_t<album_art_data> aaptr_t;
//! Album art is being read from the file. \n
//! info may be null if file had no such picture. \n
//! Return passed info, altered info or null.
virtual aaptr_t filter_album_art_read( const char * path, const GUID & type, aaptr_t info, abort_callback & aborter ) = 0;
//! Album art is being written to the file. \n
//! Return passed info, altered info or null to suppress writing.
virtual aaptr_t filter_album_art_write( const char * path, const GUID & type, aaptr_t info, abort_callback & aborter ) = 0;
//! Specific album art is being removed from the file. \n
//! Return true to go on, false to suppress file update.
virtual bool filter_album_art_remove( const char * path, const GUID & type, abort_callback & aborter ) = 0;
//! All album art is being removed from the file. \n
//! Return true to go on, false to suppress file update.
virtual bool filter_album_art_remove_all( const char * path, abort_callback & aborter ) = 0;
//! Valid with supports_fallback() = true \n
//! Album art is being written to an untaggable file.
virtual void write_album_art_fallback( const char * path, const GUID & type, aaptr_t info, abort_callback & aborter ) = 0;
//! Valid with supports_fallback() = true \n
//! Specific album art is being removed from an untaggable file.
virtual void remove_album_art_fallback( const char * path, const GUID & type, abort_callback & aborter ) = 0;
//! Valid with supports_fallback() = true \n
//! All album art is being removed from an untaggable file.
virtual void remove_all_album_art_fallback( const char * path, abort_callback & aborter ) = 0;
};
class dsp_preset;
//! \since 1.5
//! An input_playback_shim adds additional functionality to a DSP, allowing full control of the decoder. \n
//! Currently, input_playback_shim can only exist alongside a DSP, must have the same GUID as a DSP. \n
//! It will only be used in supported scenarios when the user has put your DSP in the chain. \n
//! Your DSP will be deactivated in such case when your input_playback_shim is active. \n
//! input_playback_shim is specifically intended to be instantiated for playback. Do not call this service from your component. \n/
//! Implement this service ONLY IF NECESSARY. Very few tasks really need it, primarily DSPs that manipulate logical playback time & seeking.
class input_playback_shim : public service_base {
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT( input_playback_shim );
public:
//! Same GUID as your DSP.
virtual GUID get_guid() = 0;
//! Preferences page / advconfig branch GUID of your shim, pfc::guid_null if none. \n
//! This is currently unused / reserved for future use.
virtual GUID get_preferences_guid() = 0;
//! Same as your DSP. \n
//! This is currently unused / reserved for future use.
virtual const char * get_name() = 0;
//! Instantiates your shim on top of existing input_decoder. \n
//! If you don't want to do anything with this specific decoder, just return the passed decoder.
virtual input_decoder::ptr shim( input_decoder::ptr dec, const char * path, dsp_preset const & preset, abort_callback & aborter ) = 0;
//! Optional backwards compatibility method. \n
//! If you also provide input services for old versions of foobar2000 which don't recognize input_playback_shim, report their GUIDs here so they can be ignored. \n
//! @param outGUIDs empty on entry, contains GUIDs of ignored inputs (if any) on return.
virtual void get_suppressed_inputs( pfc::list_base_t<GUID> & outGUIDs ) {outGUIDs.remove_all();}
};
#endif // #ifdef FOOBAR2000_DESKTOP
typedef input_info_writer_v2 input_info_writer_vhighest;
typedef input_decoder_v4 input_decoder_vhighest;
typedef input_info_reader input_info_reader_vhighest;

View File

@@ -0,0 +1,130 @@
#include "foobar2000.h"
#if FOOBAR2000_TARGET_VERSION >= 76
typedef pfc::avltree_t<pfc::string8,pfc::io::path::comparator> t_fnList;
static void formatMaskList(pfc::string_base & out, t_fnList const & in) {
auto walk = in.cfirst();
if (walk.is_valid()) {
out << *walk; ++walk;
while(walk.is_valid()) {
out << ";" << *walk; ++walk;
}
}
}
static void formatMaskList(pfc::string_base & out, t_fnList const & in, const char * label) {
if (in.get_count() > 0) {
out << label << "|";
formatMaskList(out,in);
out << "|";
}
}
void input_file_type::make_filetype_support_fingerprint(pfc::string_base & str) {
pfc::string_formatter out;
pfc::avltree_t<pfc::string8, pfc::string::comparatorCaseInsensitive> names;
{
componentversion::ptr ptr; service_enum_t<componentversion> e;
pfc::string_formatter name;
while(e.next(ptr)) {
name = "";
ptr->get_component_name(name);
if (strstr(name, "decoder") != NULL || strstr(name, "Decoder") != NULL) names += name;
}
}
make_extension_support_fingerprint(out);
for(auto walk = names.cfirst(); walk.is_valid(); ++walk) {
if (!out.is_empty()) str << "|";
out << *walk;
}
str = out;
}
void input_file_type::make_extension_support_fingerprint(pfc::string_base & str) {
pfc::avltree_t<pfc::string8, pfc::string::comparatorCaseInsensitive> masks;
{
service_enum_t<input_file_type> e;
service_ptr_t<input_file_type> ptr;
pfc::string_formatter mask;
while(e.next(ptr)) {
const unsigned count = ptr->get_count();
for(unsigned n=0;n<count;n++) {
mask.reset();
if (ptr->get_mask(n,mask)) {
if (strchr(mask,'|') == NULL) masks += mask;
}
}
}
}
pfc::string_formatter out;
for(auto walk = masks.cfirst(); walk.is_valid(); ++walk) {
if (!out.is_empty()) out << "|";
out << *walk;
}
str = out;
}
void input_file_type::build_openfile_mask(pfc::string_base & out, bool b_include_playlists, bool b_include_archives)
{
t_fnList extensionsAll, extensionsPl, extensionsArc;
if (b_include_playlists) {
service_enum_t<playlist_loader> e; service_ptr_t<playlist_loader> ptr;
while(e.next(ptr)) {
if (ptr->is_associatable()) {
pfc::string_formatter temp; temp << "*." << ptr->get_extension();
extensionsPl += temp;
extensionsAll += temp;
}
}
}
if (b_include_archives) {
service_enum_t<filesystem> e;
archive_v3::ptr p;
pfc::string_formatter temp;
while (e.next(p)) {
p->list_extensions(temp);
pfc::chain_list_v2_t<pfc::string8> lst;
pfc::splitStringByChar(lst, temp, ',');
for (auto iter = lst.first(); iter.is_valid(); ++iter) {
extensionsArc += PFC_string_formatter() << "*." << *iter;
}
}
}
typedef pfc::map_t<pfc::string8,t_fnList,pfc::string::comparatorCaseInsensitive> t_masks;
t_masks masks;
{
service_enum_t<input_file_type> e;
service_ptr_t<input_file_type> ptr;
pfc::string_formatter name, mask;
while(e.next(ptr)) {
const unsigned count = ptr->get_count();
for(unsigned n=0;n<count;n++) {
name.reset();
mask.reset();
if (ptr->get_name(n,name) && ptr->get_mask(n,mask)) {
if (!strchr(name,'|') && !strchr(mask,'|')) {
masks.find_or_add(name) += mask;
extensionsAll += mask;
}
}
}
}
}
pfc::string_formatter outBuf;
outBuf << "All files|*.*|";
formatMaskList(outBuf, extensionsAll, "All supported media types");
formatMaskList(outBuf, extensionsPl, "Playlists");
formatMaskList(outBuf, extensionsArc, "Archives");
for(auto walk = masks.cfirst(); walk.is_valid(); ++walk) {
formatMaskList(outBuf,walk->m_value,walk->m_key);
}
out = outBuf;
}
#endif

View File

@@ -0,0 +1,109 @@
//! Entrypoint interface for registering media file types that can be opened through "open file" dialogs or associated with foobar2000 application in Windows shell. \n
//! Instead of implementing this directly, use DECLARE_FILE_TYPE() / DECLARE_FILE_TYPE_EX() macros.
class input_file_type : public service_base {
public:
virtual unsigned get_count()=0;
virtual bool get_name(unsigned idx,pfc::string_base & out)=0;//eg. "MPEG files"
virtual bool get_mask(unsigned idx,pfc::string_base & out)=0;//eg. "*.MP3;*.MP2"; separate with semicolons
virtual bool is_associatable(unsigned idx) = 0;
#if FOOBAR2000_TARGET_VERSION >= 76
static void build_openfile_mask(pfc::string_base & out,bool b_include_playlists=true, bool b_include_archives = false);
static void make_extension_support_fingerprint(pfc::string_base & str);
static void make_filetype_support_fingerprint(pfc::string_base & str);
#endif
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(input_file_type);
};
//! Extended interface for registering media file types that can be associated with foobar2000 application in Windows shell. \n
//! Instead of implementing this directly, use DECLARE_FILE_TYPE() / DECLARE_FILE_TYPE_EX() macros.
class input_file_type_v2 : public input_file_type {
public:
virtual void get_format_name(unsigned idx, pfc::string_base & out, bool isPlural) = 0;
virtual void get_extensions(unsigned idx, pfc::string_base & out) = 0;
//Deprecated input_file_type method implementations:
bool get_name(unsigned idx, pfc::string_base & out) {get_format_name(idx, out, true); return true;}
bool get_mask(unsigned idx, pfc::string_base & out) {
pfc::string_formatter temp; get_extensions(idx,temp);
pfc::chain_list_v2_t<pfc::string> exts; pfc::splitStringSimple_toList(exts,";",temp);
if (exts.get_count() == 0) return false;//should not happen
temp.reset();
for(auto walk = exts.cfirst(); walk.is_valid(); ++walk) {
if (!temp.is_empty()) temp << ";";
temp << "*." << walk->get_ptr();
}
out = temp;
return true;
}
FB2K_MAKE_SERVICE_INTERFACE(input_file_type_v2,input_file_type)
};
//! Implementation helper.
class input_file_type_impl : public service_impl_single_t<input_file_type>
{
const char * name, * mask;
bool m_associatable;
public:
input_file_type_impl(const char * p_name, const char * p_mask,bool p_associatable) : name(p_name), mask(p_mask), m_associatable(p_associatable) {}
unsigned get_count() {return 1;}
bool get_name(unsigned idx,pfc::string_base & out) {if (idx==0) {out = name; return true;} else return false;}
bool get_mask(unsigned idx,pfc::string_base & out) {if (idx==0) {out = mask; return true;} else return false;}
bool is_associatable(unsigned idx) {return m_associatable;}
};
//! Helper macro for registering our media file types.
//! Usage: DECLARE_FILE_TYPE("Blah files","*.blah;*.bleh");
#define DECLARE_FILE_TYPE(NAME,MASK) \
namespace { static input_file_type_impl g_filetype_instance(NAME,MASK,true); \
static service_factory_single_ref_t<input_file_type_impl> g_filetype_service(g_filetype_instance); }
//! Implementation helper.
//! Usage: static input_file_type_factory mytype("blah type","*.bla;*.meh",true);
class input_file_type_factory : private service_factory_single_transparent_t<input_file_type_impl>
{
public:
input_file_type_factory(const char * p_name,const char * p_mask,bool p_associatable)
: service_factory_single_transparent_t<input_file_type_impl>(p_name,p_mask,p_associatable) {}
};
class input_file_type_v2_impl : public input_file_type_v2 {
public:
input_file_type_v2_impl(const char * extensions,const char * name, const char * namePlural) : m_extensions(extensions), m_name(name), m_namePlural(namePlural) {}
unsigned get_count() {return 1;}
bool is_associatable(unsigned idx) {return true;}
void get_format_name(unsigned idx, pfc::string_base & out, bool isPlural) {
out = isPlural ? m_namePlural : m_name;
}
void get_extensions(unsigned idx, pfc::string_base & out) {
out = m_extensions;
}
private:
const pfc::string8 m_name, m_namePlural, m_extensions;
};
//! Helper macro for registering our media file types, extended version providing separate singular/plural type names.
//! Usage: DECLARE_FILE_TYPE_EX("mp1;mp2;mp3","MPEG file","MPEG files")
#define DECLARE_FILE_TYPE_EX(extensions, name, namePlural) \
namespace { static service_factory_single_t<input_file_type_v2_impl> g_myfiletype(extensions, name, namePlural); }
//! Service for registering protocol types that can be associated with foobar2000.
class input_protocol_type : public service_base {
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(input_protocol_type)
public:
//! Returns the name of the protocol, such as "ftp" or "http".
virtual void get_protocol_name(pfc::string_base & out) = 0;
//! Returns a human-readable description of the protocol.
virtual void get_description(pfc::string_base & out) = 0;
};

450
foobar2000/SDK/input_impl.h Normal file
View File

@@ -0,0 +1,450 @@
#pragma once
#include "input.h"
#include "mem_block_container.h"
enum t_input_open_reason {
input_open_info_read,
input_open_decode,
input_open_info_write
};
//! Helper function for input implementation use; ensures that file is open with relevant access mode. This is typically called from input_impl::open() and such.
//! @param p_file File object pointer to process. If passed pointer is non-null, the function does nothing and always succeeds; otherwise it attempts to open the file using filesystem API methods.
//! @param p_path Path to the file.
//! @param p_reason Type of input operation requested. See: input_impl::open() parameters.
//! @param p_abort abort_callback object signaling user aborting the operation.
void input_open_file_helper(service_ptr_t<file> & p_file,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort);
//! This is a class that just declares prototypes of functions that each input needs to implement. See input_decoder / input_info_reader / input_info_writer interfaces for full descriptions of member functions. Since input implementation class is instantiated using a template, you don't need to derive from input_impl as virtual functions are not used on implementation class level. Use input_factory_t template to register input class based on input_impl.
class input_impl
{
public:
//! Opens specified file for info read / decoding / info write. This is called only once, immediately after object creation, before any other methods, and no other methods are called if open() fails.
//! @param p_filehint Optional; passes file object to use for the operation; if set to null, the implementation should handle opening file by itself. Note that not all inputs operate on physical files that can be reached through filesystem API, some of them require this parameter to be set to null (tone and silence generators for an example). Typically, an input implementation that requires file access calls input_open_file_helper() function to ensure that file is open with relevant access mode (read or read/write).
//! @param p_path URL of resource being opened.
//! @param p_reason Type of operation requested. Possible values are: \n
//! - input_open_info_read - info retrieval methods are valid; \n
//! - input_open_decode - info retrieval and decoding methods are valid; \n
//! - input_open_info_write - info retrieval and retagging methods are valid; \n
//! Note that info retrieval methods are valid in all cases, and they may be called at any point of decoding/retagging process. Results of info retrieval methods (other than get_subsong_count() / get_subsong()) between retag_set_info() and retag_commit() are undefined however; those should not be called during that period.
//! @param p_abort abort_callback object signaling user aborting the operation.
void open(service_ptr_t<file> p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort);
//! See: input_info_reader::get_subsong_count(). Valid after open() with any reason.
unsigned get_subsong_count();
//! See: input_info_reader::get_subsong(). Valid after open() with any reason.
t_uint32 get_subsong(unsigned p_index);
//! See: input_info_reader::get_info(). Valid after open() with any reason.
void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort);
//! See: input_info_reader::get_file_stats(). Valid after open() with any reason.
t_filestats get_file_stats(abort_callback & p_abort);
//! See: input_decoder::initialize(). Valid after open() with input_open_decode reason.
void decode_initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort);
//! See: input_decoder::run(). Valid after decode_initialize().
bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort);
//! See: input_decoder::seek(). Valid after decode_initialize().
void decode_seek(double p_seconds,abort_callback & p_abort);
//! See: input_decoder::can_seek(). Valid after decode_initialize().
bool decode_can_seek();
//! See: input_decoder::get_dynamic_info(). Valid after decode_initialize().
bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta);
//! See: input_decoder::get_dynamic_info_track(). Valid after decode_initialize().
bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta);
//! See: input_decoder::on_idle(). Valid after decode_initialize().
void decode_on_idle(abort_callback & p_abort);
//! See: input_info_writer::set_info(). Valid after open() with input_open_info_write reason.
void retag_set_info(t_uint32 p_subsong,const file_info & p_info,abort_callback & p_abort);
//! See: input_info_writer::commit(). Valid after open() with input_open_info_write reason.
void retag_commit(abort_callback & p_abort);
//! See: input_entry::is_our_content_type().
static bool g_is_our_content_type(const char * p_content_type);
//! See: input_entry::is_our_path().
static bool g_is_our_path(const char * p_path,const char * p_extension);
//! See: input_entry::get_guid().
static GUID g_get_guid();
//! See: input_entry::get_name().
static const char * g_get_name();
//! See: input_entry::get_preferences_guid().
static GUID g_get_preferences_guid();
//! See: input_entry::is_low_merit().
static bool g_is_low_merit();
//! See: input_decoder_v2::run_raw(). Relevant only when implementing input_decoder_v2. Valid after decode_initialize().
bool decode_run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort);
//! OLD way: may be called at any time from input_decoder_v2 \n
//! NEW way (1.5): called prior to open().
void set_logger(event_logger::ptr ptr);
protected:
input_impl() {}
~input_impl() {}
};
//! A base class that provides stub implementations of all optional input methods. \n
//! Inherit from this and you implement input_decoder_v4 without having to provide all the methods you don't actually need.
class input_stubs {
public:
bool decode_run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort) { throw pfc::exception_not_implemented(); }
void set_logger(event_logger::ptr ptr) {}
void set_pause(bool paused) {}
bool flush_on_pause() { return false; }
size_t extended_param(const GUID & type, size_t arg1, void * arg2, size_t arg2size) { return 0; }
static GUID g_get_preferences_guid() {return pfc::guid_null;}
static bool g_is_low_merit() { return false; }
bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta) { return false; }
bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) { return false; }
void decode_on_idle(abort_callback & p_abort) { }
//! These typedefs indicate which interfaces your class actually supports. You can override them to support non default input API interfaces without specifying input_factory parameters.
typedef input_decoder_v4 interface_decoder_t;
typedef input_info_reader interface_info_reader_t;
typedef input_info_writer interface_info_writer_t;
};
//! This is a class that just declares prototypes of functions that each non-multitrack-enabled input needs to implement. See input_decoder / input_info_reader / input_info_writer interfaces for full descriptions of member functions. Since input implementation class is instantiated using a template, you don't need to derive from input_singletrack_impl as virtual functions are not used on implementation class level. Use input_singletrack_factory_t template to register input class based on input_singletrack_impl.
class input_singletrack_impl
{
public:
//! Opens specified file for info read / decoding / info write. This is called only once, immediately after object creation, before any other methods, and no other methods are called if open() fails.
//! @param p_filehint Optional; passes file object to use for the operation; if set to null, the implementation should handle opening file by itself. Note that not all inputs operate on physical files that can be reached through filesystem API, some of them require this parameter to be set to null (tone and silence generators for an example). Typically, an input implementation that requires file access calls input_open_file_helper() function to ensure that file is open with relevant access mode (read or read/write).
//! @param p_path URL of resource being opened.
//! @param p_reason Type of operation requested. Possible values are: \n
//! - input_open_info_read - info retrieval methods are valid; \n
//! - input_open_decode - info retrieval and decoding methods are valid; \n
//! - input_open_info_write - info retrieval and retagging methods are valid; \n
//! Note that info retrieval methods are valid in all cases, and they may be called at any point of decoding/retagging process. Results of info retrieval methods (other than get_subsong_count() / get_subsong()) between retag_set_info() and retag_commit() are undefined however; those should not be called during that period.
//! @param p_abort abort_callback object signaling user aborting the operation.
void open(service_ptr_t<file> p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort);
//! See: input_info_reader::get_info(). Valid after open() with any reason. \n
//! Implementation warning: this is typically also called immediately after tag update and should return newly written content then.
void get_info(file_info & p_info,abort_callback & p_abort);
//! See: input_info_reader::get_file_stats(). Valid after open() with any reason. \n
//! Implementation warning: this is typically also called immediately after tag update and should return new values then.
t_filestats get_file_stats(abort_callback & p_abort);
//! See: input_decoder::initialize(). Valid after open() with input_open_decode reason.
void decode_initialize(unsigned p_flags,abort_callback & p_abort);
//! See: input_decoder::run(). Valid after decode_initialize().
bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort);
//! See: input_decoder::seek(). Valid after decode_initialize().
void decode_seek(double p_seconds,abort_callback & p_abort);
//! See: input_decoder::can_seek(). Valid after decode_initialize().
bool decode_can_seek();
//! See: input_decoder::get_dynamic_info(). Valid after decode_initialize().
bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta);
//! See: input_decoder::get_dynamic_info_track(). Valid after decode_initialize().
bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta);
//! See: input_decoder::on_idle(). Valid after decode_initialize().
void decode_on_idle(abort_callback & p_abort);
//! See: input_info_writer::set_info(). Note that input_info_writer::commit() call isn't forwarded because it's useless in case of non-multitrack-enabled inputs. Valid after open() with input_open_info_write reason.
void retag(const file_info & p_info,abort_callback & p_abort);
//! See: input_entry::is_our_content_type().
static bool g_is_our_content_type(const char * p_content_type);
//! See: input_entry::is_our_path().
static bool g_is_our_path(const char * p_path,const char * p_extension);
protected:
input_singletrack_impl() {}
~input_singletrack_impl() {}
};
//! Used internally by standard input_entry implementation; do not use directly. Translates input_decoder / input_info_reader / input_info_writer calls to input_impl calls.
template<typename I, typename interface_t>
class input_impl_interface_wrapper_t : public interface_t
{
public:
template<typename ... args_t>
void open( args_t && ... args) {
m_instance.open(std::forward<args_t>(args) ... );
}
// input_info_reader methods
t_uint32 get_subsong_count() {
return m_instance.get_subsong_count();
}
t_uint32 get_subsong(t_uint32 p_index) {
return m_instance.get_subsong(p_index);
}
void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) {
m_instance.get_info(p_subsong,p_info,p_abort);
}
t_filestats get_file_stats(abort_callback & p_abort) {
return m_instance.get_file_stats(p_abort);
}
// input_decoder methods
void initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort) {
m_instance.decode_initialize(p_subsong,p_flags,p_abort);
#if PFC_DEBUG
m_eof = false;
#endif
}
bool run(audio_chunk & p_chunk,abort_callback & p_abort) {
#if PFC_DEBUG
PFC_ASSERT( !m_eof );
// Complain if run()/run_raw() gets called again after having returned EOF, this means a logic error on caller's side
#endif
bool ret = m_instance.decode_run(p_chunk,p_abort);
#if PFC_DEBUG
if ( !ret ) m_eof = true;
#endif
return ret;
}
bool run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort) {
#if PFC_DEBUG
// Complain if run()/run_raw() gets called again after having returned EOF, this means a logic error on caller's side
PFC_ASSERT(!m_eof);
#endif
bool ret = m_instance.decode_run_raw(p_chunk, p_raw, p_abort);
#if PFC_DEBUG
if ( !ret ) m_eof = true;
#endif
return ret;
}
void seek(double p_seconds,abort_callback & p_abort) {
m_instance.decode_seek(p_seconds,p_abort);
#if PFC_DEBUG
m_eof = false;
#endif
}
bool can_seek() {
return m_instance.decode_can_seek();
}
bool get_dynamic_info(file_info & p_out, double & p_timestamp_delta) {
return m_instance.decode_get_dynamic_info(p_out,p_timestamp_delta);
}
bool get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) {
return m_instance.decode_get_dynamic_info_track(p_out,p_timestamp_delta);
}
void on_idle(abort_callback & p_abort) {
m_instance.decode_on_idle(p_abort);
}
void set_logger(event_logger::ptr ptr) {
m_instance.set_logger(ptr);
}
void set_pause(bool paused) {
// obsolete
// m_instance.set_pause(paused);
}
bool flush_on_pause() {
return m_instance.flush_on_pause();
}
size_t extended_param(const GUID & type, size_t arg1, void * arg2, size_t arg2size) {
return m_instance.extended_param(type, arg1, arg2, arg2size);
}
// input_info_writer methods
void set_info(t_uint32 p_subsong,const file_info & p_info,abort_callback & p_abort) {
m_instance.retag_set_info(p_subsong,p_info,p_abort);
}
void commit(abort_callback & p_abort) {
m_instance.retag_commit(p_abort);
}
void remove_tags(abort_callback & p_abort) {
m_instance.remove_tags(p_abort);
}
private:
I m_instance;
#if PFC_DEBUG
// Report illegal API calls in debug build
bool m_eof = false;
#endif
};
template<typename input_t>
class input_forward_static_methods : public input_stubs {
public:
static bool g_is_our_content_type(const char * p_content_type) { return input_t::g_is_our_content_type(p_content_type); }
static bool g_is_our_path(const char * p_path, const char * p_extension) { return input_t::g_is_our_path(p_path, p_extension); }
static GUID g_get_preferences_guid() { return input_t::g_get_preferences_guid(); }
static GUID g_get_guid() { return input_t::g_get_guid(); }
static const char * g_get_name() { return input_t::g_get_name(); }
static bool g_is_low_merit() { return input_t::g_is_low_merit(); }
typedef typename input_t::interface_decoder_t interface_decoder_t;
typedef typename input_t::interface_info_reader_t interface_info_reader_t;
typedef typename input_t::interface_info_writer_t interface_info_writer_t;
};
//! Helper used by input_singletrack_factory_t, do not use directly. Translates input_impl calls to input_singletrack_impl calls.
template<typename I>
class input_wrapper_singletrack_t : public input_forward_static_methods<I>
{
public:
input_wrapper_singletrack_t() {}
template<typename ... args_t>
void open( args_t && ... args) {
m_instance.open(std::forward<args_t>(args) ... );
}
void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) {
if (p_subsong != 0) throw exception_io_bad_subsong_index();
m_instance.get_info(p_info,p_abort);
}
t_uint32 get_subsong_count() {
return 1;
}
t_uint32 get_subsong(t_uint32 p_index) {
PFC_ASSERT(p_index == 0);
return 0;
}
t_filestats get_file_stats(abort_callback & p_abort) {
return m_instance.get_file_stats(p_abort);
}
void decode_initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort) {
if (p_subsong != 0) throw exception_io_bad_subsong_index();
m_instance.decode_initialize(p_flags,p_abort);
}
bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort) {return m_instance.decode_run(p_chunk,p_abort);}
void decode_seek(double p_seconds,abort_callback & p_abort) {m_instance.decode_seek(p_seconds,p_abort);}
bool decode_can_seek() {return m_instance.decode_can_seek();}
bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta) {return m_instance.decode_get_dynamic_info(p_out,p_timestamp_delta);}
bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) {return m_instance.decode_get_dynamic_info_track(p_out,p_timestamp_delta);}
void decode_on_idle(abort_callback & p_abort) {m_instance.decode_on_idle(p_abort);}
void retag_set_info(t_uint32 p_subsong,const file_info & p_info,abort_callback & p_abort) {
if (p_subsong != 0) throw exception_io_bad_subsong_index();
m_instance.retag(p_info,p_abort);
}
bool decode_run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort) {
return m_instance.decode_run_raw(p_chunk, p_raw, p_abort);
}
void set_logger(event_logger::ptr ptr) {m_instance.set_logger(ptr);}
void set_pause(bool paused) {
// m_instance.set_pause(paused);
}
bool flush_on_pause() {
return m_instance.flush_on_pause();
}
size_t extended_param(const GUID & type, size_t arg1, void * arg2, size_t arg2size) {
return m_instance.extended_param(type, arg1, arg2, arg2size);
}
void retag_commit(abort_callback & p_abort) {}
void remove_tags(abort_callback & p_abort) {
m_instance.remove_tags(p_abort);
}
private:
I m_instance;
};
//! Helper; standard input_entry implementation. Do not instantiate this directly, use input_factory_t or one of other input_*_factory_t helpers instead.
template<typename I,unsigned t_flags, typename t_decoder = typename I::interface_decoder_t, typename t_inforeader = typename I::interface_info_reader_t, typename t_infowriter = typename I::interface_info_writer_t>
class input_entry_impl_t : public input_entry_v3
{
public:
bool is_our_content_type(const char * p_type) {return I::g_is_our_content_type(p_type);}
bool is_our_path(const char * p_full_path,const char * p_extension) {return I::g_is_our_path(p_full_path,p_extension);}
template<typename interface_t, typename outInterace_t, typename ... args_t>
void open_ex(service_ptr_t<outInterace_t> & p_instance,args_t && ... args)
{
auto temp = fb2k::service_new<input_impl_interface_wrapper_t<I,interface_t> >();
temp->open(std::forward<args_t>(args) ... );
p_instance = temp.get_ptr();
}
service_ptr open_v3(const GUID & whatFor, file::ptr hint, const char * path, event_logger::ptr logger, abort_callback & aborter) {
if ( whatFor == input_decoder::class_guid ) {
auto obj = fb2k::service_new< input_impl_interface_wrapper_t<I,t_decoder> > ();
if ( logger.is_valid() ) obj->set_logger(logger);
obj->open( hint, path, input_open_decode, aborter );
return obj;
}
if ( whatFor == input_info_reader::class_guid ) {
auto obj = fb2k::service_new < input_impl_interface_wrapper_t<I, t_inforeader> >();
if (logger.is_valid()) obj->set_logger(logger);
obj->open(hint, path, input_open_info_read, aborter);
return obj;
}
if ( whatFor == input_info_writer::class_guid ) {
auto obj = fb2k::service_new < input_impl_interface_wrapper_t<I, t_infowriter> >();
if (logger.is_valid()) obj->set_logger(logger);
obj->open(hint, path, input_open_info_write, aborter);
return obj;
}
throw pfc::exception_not_implemented();
}
void get_extended_data(service_ptr_t<file> p_filehint,const playable_location & p_location,const GUID & p_guid,mem_block_container & p_out,abort_callback & p_abort) {
p_out.reset();
}
unsigned get_flags() {return t_flags;}
GUID get_guid() {
return I::g_get_guid();
}
const char * get_name() {
return I::g_get_name();
}
GUID get_preferences_guid() {
return I::g_get_preferences_guid();
}
bool is_low_merit() {
return I::g_is_low_merit();
}
};
//! Stardard input factory. For reference of functions that must be supported by registered class, see input_impl.\n Usage: static input_factory_t<myinputclass> g_myinputclass_factory;\n Note that input classes can't be registered through service_factory_t template directly.
template<typename T, unsigned t_flags = 0>
class input_factory_t : public service_factory_single_t<input_entry_impl_t<T, t_flags> > {};
//! Non-multitrack-enabled input factory (helper) - hides multitrack management functions from input implementation; use this for inputs that handle file types where each physical file can contain only one user-visible playable track. For reference of functions that must be supported by registered class, see input_singletrack_impl.\n Usage: static input_singletrack_factory_t<myinputclass> g_myinputclass_factory;\n Note that input classes can't be registered through service_factory_t template directly.template<class T>
template<typename T, unsigned t_flags = 0>
class input_singletrack_factory_t : public service_factory_single_t<input_entry_impl_t<input_wrapper_singletrack_t<T>,t_flags> > {};
//! Extended version of input_factory_t, with non-default flags and supported interfaces. See: input_factory_t, input_entry::get_flags(). \n
//! This is obsolete and provided for backwards compatibility. Use interface_decoder_t + interface_info_reader_t + interface_info_writer_t typedefs in your input class to specify supported interfaces.
template<typename T, unsigned t_flags = 0, typename t_decoder = typename T::interface_decoder_t, typename t_inforeader = typename T::interface_info_reader_t, typename t_infowriter = typename T::interface_info_writer_t>
class input_factory_ex_t : public service_factory_single_t<input_entry_impl_t<T, t_flags, t_decoder, t_inforeader, t_infowriter> > {};
//! Extended version of input_singletrack_factory_t, with non-default flags and supported interfaces. See: input_singletrack_factory_t, input_entry::get_flags().
//! This is obsolete and provided for backwards compatibility. Use interface_decoder_t + interface_info_reader_t + interface_info_writer_t typedefs in your input class to specify supported interfaces.
template<typename T, unsigned t_flags = 0, typename t_decoder = typename T::interface_decoder_t, typename t_inforeader = typename T::interface_info_reader_t, typename t_infowriter = typename T::interface_info_writer_t>
class input_singletrack_factory_ex_t : public service_factory_single_t<input_entry_impl_t<input_wrapper_singletrack_t<T>, t_flags, t_decoder, t_inforeader, t_infowriter> > {};

View File

@@ -0,0 +1,206 @@
/*!
This service implements methods allowing you to interact with the Media Library.\n
All methods are valid from main thread only, unless noted otherwise.\n
Usage: Use library_manager::get() to instantiate.
*/
class NOVTABLE library_manager : public service_base {
FB2K_MAKE_SERVICE_COREAPI(library_manager);
public:
//! Interface for use with library_manager::enum_items().
class NOVTABLE enum_callback {
public:
//! Return true to continue enumeration, false to abort.
virtual bool on_item(const metadb_handle_ptr & p_item) = 0;
};
//! Returns whether the specified item is in the Media Library or not.
virtual bool is_item_in_library(const metadb_handle_ptr & p_item) = 0;
//! Returns whether current user settings allow the specified item to be added to the Media Library or not.
virtual bool is_item_addable(const metadb_handle_ptr & p_item) = 0;
//! Returns whether current user settings allow the specified item path to be added to the Media Library or not.
virtual bool is_path_addable(const char * p_path) = 0;
//! Retrieves path of the specified item relative to the Media Library folder it is in. Returns true on success, false when the item is not in the Media Library.
//! SPECIAL WARNING: to allow multi-CPU optimizations to parse relative track paths, this API works in threads other than the main app thread. Main thread MUST be blocked while working in such scenarios, it's NOT safe to call from worker threads while the Media Library content/configuration might be getting altered.
virtual bool get_relative_path(const metadb_handle_ptr & p_item,pfc::string_base & p_out) = 0;
//! Calls callback method for every item in the Media Library. Note that order of items in Media Library is undefined.
virtual void enum_items(enum_callback & p_callback) = 0;
protected:
//! OBSOLETE, do not call, does nothing.
__declspec(deprecated) virtual void add_items(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0;
//! OBSOLETE, do not call, does nothing.
__declspec(deprecated) virtual void remove_items(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0;
//! OBSOLETE, do not call, does nothing.
__declspec(deprecated) virtual void add_items_async(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0;
//! OBSOLETE, do not call, does nothing.
__declspec(deprecated) virtual void on_files_deleted_sorted(const pfc::list_base_const_t<const char *> & p_data) = 0;
public:
//! Retrieves the entire Media Library content.
virtual void get_all_items(pfc::list_base_t<metadb_handle_ptr> & p_out) = 0;
//! Returns whether Media Library functionality is enabled or not (to be exact: whether there's at least one Media Library folder present in settings), for e.g. notifying the user to change settings when trying to use a Media Library viewer without having configured the Media Library first.
virtual bool is_library_enabled() = 0;
//! Pops up the Media Library preferences page.
virtual void show_preferences() = 0;
//! OBSOLETE, do not call.
virtual void rescan() = 0;
protected:
//! OBSOLETE, do not call, does nothing.
__declspec(deprecated) virtual void check_dead_entries(const pfc::list_base_t<metadb_handle_ptr> & p_list) = 0;
public:
};
//! \since 0.9.3
class NOVTABLE library_manager_v2 : public library_manager {
FB2K_MAKE_SERVICE_COREAPI_EXTENSION(library_manager_v2,library_manager);
protected:
//! OBSOLETE, do not call, does nothing.
__declspec(deprecated) virtual bool is_rescan_running() = 0;
//! OBSOLETE, do not call, does nothing.
__declspec(deprecated) virtual void rescan_async(HWND p_parent,completion_notify_ptr p_notify) = 0;
//! OBSOLETE, do not call, does nothing.
__declspec(deprecated) virtual void check_dead_entries_async(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,HWND p_parent,completion_notify_ptr p_notify) = 0;
};
class NOVTABLE library_callback_dynamic {
public:
//! Called when new items are added to the Media Library.
virtual void on_items_added(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0;
//! Called when some items have been removed from the Media Library.
virtual void on_items_removed(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0;
//! Called when some items in the Media Library have been modified.
virtual void on_items_modified(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0;
};
//! \since 0.9.5
class NOVTABLE library_manager_v3 : public library_manager_v2 {
public:
//! Retrieves directory path and subdirectory/filename formatting scheme for newly encoded/copied/moved tracks.
//! @returns True on success, false when the feature has not been configured.
virtual bool get_new_file_pattern_tracks(pfc::string_base & p_directory,pfc::string_base & p_format) = 0;
//! Retrieves directory path and subdirectory/filename formatting scheme for newly encoded/copied/moved full album images.
//! @returns True on success, false when the feature has not been configured.
virtual bool get_new_file_pattern_images(pfc::string_base & p_directory,pfc::string_base & p_format) = 0;
virtual void register_callback(library_callback_dynamic * p_callback) = 0;
virtual void unregister_callback(library_callback_dynamic * p_callback) = 0;
FB2K_MAKE_SERVICE_COREAPI_EXTENSION(library_manager_v3,library_manager_v2);
};
class library_callback_dynamic_impl_base : public library_callback_dynamic {
public:
library_callback_dynamic_impl_base() {library_manager_v3::get()->register_callback(this);}
~library_callback_dynamic_impl_base() {library_manager_v3::get()->unregister_callback(this);}
//stub implementations - avoid pure virtual function call issues
void on_items_added(metadb_handle_list_cref p_data) {}
void on_items_removed(metadb_handle_list_cref p_data) {}
void on_items_modified(metadb_handle_list_cref p_data) {}
PFC_CLASS_NOT_COPYABLE_EX(library_callback_dynamic_impl_base);
};
//! Callback service receiving notifications about Media Library content changes. Methods called only from main thread.\n
//! Use library_callback_factory_t template to register.
class NOVTABLE library_callback : public service_base {
public:
//! Called when new items are added to the Media Library.
virtual void on_items_added(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0;
//! Called when some items have been removed from the Media Library.
virtual void on_items_removed(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0;
//! Called when some items in the Media Library have been modified.
virtual void on_items_modified(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0;
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(library_callback);
};
template<typename T>
class library_callback_factory_t : public service_factory_single_t<T> {};
//! Implement this service to appear on "library viewers" list in Media Library preferences page.\n
//! Use library_viewer_factory_t to register.
class NOVTABLE library_viewer : public service_base {
public:
//! Retrieves GUID of your preferences page (pfc::guid_null if you don't have one).
virtual GUID get_preferences_page() = 0;
//! Queries whether "activate" action is supported (relevant button will be disabled if it's not).
virtual bool have_activate() = 0;
//! Activates your Media Library viewer component (e.g. shows its window).
virtual void activate() = 0;
//! Retrieves GUID of your library_viewer implementation, for internal identification. Note that this not the same as preferences page GUID.
virtual GUID get_guid() = 0;
//! Retrieves name of your Media Library viewer, a null-terminated UTF-8 encoded string.
virtual const char * get_name() = 0;
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(library_viewer);
};
template<typename T>
class library_viewer_factory_t : public service_factory_single_t<T> {};
//! \since 0.9.5.4
//! Allows you to spawn a popup Media Library Search window with any query string that you specify. \n
//! Usage: library_search_ui::get()->show("querygoeshere");
class NOVTABLE library_search_ui : public service_base {
public:
virtual void show(const char * query) = 0;
FB2K_MAKE_SERVICE_COREAPI(library_search_ui)
};
//! \since 0.9.6
class NOVTABLE library_file_move_scope : public service_base {
FB2K_MAKE_SERVICE_INTERFACE(library_file_move_scope, service_base)
public:
};
//! \since 0.9.6
class NOVTABLE library_file_move_manager : public service_base {
FB2K_MAKE_SERVICE_COREAPI(library_file_move_manager)
public:
virtual library_file_move_scope::ptr acquire_scope() = 0;
virtual bool is_move_in_progress() = 0;
};
//! \since 0.9.6
class NOVTABLE library_file_move_notify_ {
public:
virtual void on_state_change(bool isMoving) = 0;
};
//! \since 0.9.6
class NOVTABLE library_file_move_notify : public service_base, public library_file_move_notify_ {
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(library_file_move_notify)
public:
};
//! \since 0.9.6.1
class NOVTABLE library_meta_autocomplete : public service_base {
FB2K_MAKE_SERVICE_COREAPI(library_meta_autocomplete)
public:
virtual bool get_value_list(const char * metaName, pfc::com_ptr_t<IUnknown> & out) = 0;
};
//! \since 1.6.1
//! Caching & asynchronous version. \n
//! Keep a reference to your library_meta_autocomplete_v2 object in your dialog class to cache the looked up values & speed up the operation.
class NOVTABLE library_meta_autocomplete_v2 : public service_base {
FB2K_MAKE_SERVICE_COREAPI(library_meta_autocomplete_v2)
public:
virtual bool get_value_list_async(const char* metaName, pfc::com_ptr_t<IUnknown>& out) = 0;
};

View File

@@ -0,0 +1,17 @@
#include "foobar2000.h"
bool link_resolver::g_find(service_ptr_t<link_resolver> & p_out,const char * p_path)
{
service_enum_t<link_resolver> e;
service_ptr_t<link_resolver> ptr;
pfc::string_extension ext(p_path);
while(e.next(ptr))
{
if (ptr->is_our_path(p_path,ext))
{
p_out = ptr;
return true;
}
}
return false;
}

View File

@@ -0,0 +1,33 @@
#ifndef _foobar2000_sdk_link_resolver_h_
#define _foobar2000_sdk_link_resolver_h_
//! Interface for resolving different sorts of link files.
//! Utilized by mechanisms that convert filesystem path into list of playable locations.
//! For security reasons, link may only point to playable object path, not to a playlist or another link.
class NOVTABLE link_resolver : public service_base
{
public:
//! Tests whether specified file is supported by this link_resolver service.
//! @param p_path Path of file being queried.
//! @param p_extension Extension of file being queried. This is provided for performance reasons, path already includes it.
virtual bool is_our_path(const char * p_path,const char * p_extension) = 0;
//! Resolves a link file. Before this is called, path must be accepted by is_our_path().
//! @param p_filehint Optional file interface to use. If null/empty, implementation should open file by itself.
//! @param p_path Path of link file to resolve.
//! @param p_out Receives path the link is pointing to.
//! @param p_abort abort_callback object signaling user aborting the operation.
virtual void resolve(service_ptr_t<file> p_filehint,const char * p_path,pfc::string_base & p_out,abort_callback & p_abort) = 0;
//! Helper function; finds link_resolver interface that supports specified link file.
//! @param p_out Receives link_resolver interface on success.
//! @param p_path Path of file to query.
//! @returns True on success, false on failure (no interface that supports specified path could be found).
static bool g_find(service_ptr_t<link_resolver> & p_out,const char * p_path);
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(link_resolver);
};
#endif //_foobar2000_sdk_link_resolver_h_

View File

@@ -0,0 +1,37 @@
#include "foobar2000.h"
void main_thread_callback::callback_enqueue() {
main_thread_callback_manager::get()->add_callback(this);
}
void main_thread_callback_add(main_thread_callback::ptr ptr) {
main_thread_callback_manager::get()->add_callback(ptr);
}
namespace {
typedef std::function<void ()> func_t;
class mtcallback_func : public main_thread_callback {
public:
mtcallback_func(func_t const & f) : m_f(f) {}
void callback_run() {
m_f();
}
private:
func_t m_f;
};
}
void fb2k::inMainThread( std::function<void () > f ) {
main_thread_callback_add( new service_impl_t<mtcallback_func>(f));
}
void fb2k::inMainThread2( std::function<void () > f ) {
if ( core_api::is_main_thread() ) {
f();
} else {
inMainThread(f);
}
}

View File

@@ -0,0 +1,187 @@
#pragma once
#include <functional>
// ======================================================================================================
// Most of main_thread_callback.h declares API internals and obsolete helpers.
// In modern code, simply use fb2k::inMainThread() declared below and disregard the rest.
// ======================================================================================================
namespace fb2k {
//! Queue a call in main thread. Returns immediately. \n
//! You can call this from any thread, including main thread - to execute some code outside the current call stack / global fb2k callbacks / etc.
void inMainThread(std::function<void() > f);
//! Call f synchronously if called from main thread, queue call if called from another.
void inMainThread2(std::function<void() > f);
}
// ======================================================================================================
// API declarations
// ======================================================================================================
//! Callback object class for main_thread_callback_manager service. \n
//! You do not need to implement this directly - simply use fb2k::inMainThread() with a lambda.
class NOVTABLE main_thread_callback : public service_base {
public:
//! Gets called from main app thread. See main_thread_callback_manager description for more info.
virtual void callback_run() = 0;
void callback_enqueue(); // helper
FB2K_MAKE_SERVICE_INTERFACE(main_thread_callback,service_base);
};
/*!
Allows you to queue a callback object to be called from main app thread. This is commonly used to trigger main-thread-only API calls from worker threads.\n
This can be also used from main app thread, to avoid race conditions when trying to use APIs that dispatch global callbacks from inside some other global callback, or using message loops / modal dialogs inside global callbacks. \n
There is no need to use this API directly - use fb2k::inMainThread() with a lambda.
*/
class NOVTABLE main_thread_callback_manager : public service_base {
public:
//! Queues a callback object. This can be called from any thread, implementation ensures multithread safety. Implementation will call p_callback->callback_run() once later. To get it called repeatedly, you would need to add your callback again.
virtual void add_callback(service_ptr_t<main_thread_callback> p_callback) = 0;
FB2K_MAKE_SERVICE_COREAPI(main_thread_callback_manager);
};
// ======================================================================================================
// Obsolete helpers - they still work, but it's easier to just use fb2k::inMainThread().
// ======================================================================================================
//! Helper, equivalent to main_thread_callback_manager::get()->add_callback(ptr)
void main_thread_callback_add(main_thread_callback::ptr ptr);
template<typename t_class> static void main_thread_callback_spawn() {
main_thread_callback_add(new service_impl_t<t_class>);
}
template<typename t_class, typename t_param1> static void main_thread_callback_spawn(const t_param1 & p1) {
main_thread_callback_add(new service_impl_t<t_class>(p1));
}
template<typename t_class, typename t_param1, typename t_param2> static void main_thread_callback_spawn(const t_param1 & p1, const t_param2 & p2) {
main_thread_callback_add(new service_impl_t<t_class>(p1, p2));
}
// Proxy class - friend this to allow callInMainThread to access your private methods
class callInMainThread {
public:
template<typename host_t, typename param_t>
static void callThis(host_t * host, param_t & param) {
host->inMainThread(param);
}
template<typename host_t>
static void callThis( host_t * host ) {
host->inMainThread();
}
};
// Internal class, do not use.
template<typename service_t, typename param_t>
class _callInMainThreadSvc_t : public main_thread_callback {
public:
_callInMainThreadSvc_t(service_t * host, param_t const & param) : m_host(host), m_param(param) {}
void callback_run() {
callInMainThread::callThis(m_host.get_ptr(), m_param);
}
private:
service_ptr_t<service_t> m_host;
param_t m_param;
};
// Main thread callback helper. You can use this to easily invoke inMainThread(someparam) on your class without writing any wrapper code.
// Requires myservice_t to be a fb2k service class with reference counting.
template<typename myservice_t, typename param_t>
static void callInMainThreadSvc(myservice_t * host, param_t const & param) {
typedef _callInMainThreadSvc_t<myservice_t, param_t> impl_t;
service_ptr_t<impl_t> obj = new service_impl_t<impl_t>(host, param);
main_thread_callback_manager::get()->add_callback( obj );
}
//! Helper class to call methods of your class (host class) in main thread with convenience. \n
//! Deals with the otherwise ugly scenario of your class becoming invalid while a method is queued. \n
//! Have this as a member of your class, then use m_mthelper.add( this, somearg ) ; to defer a call to this->inMainThread(somearg). \n
//! If your class becomes invalid before inMainThread is executed, the pending callback is discarded. \n
//! You can optionally call shutdown() to invalidate all pending callbacks early (in a destructor of your class - without waiting for callInMainThreadHelper destructor to do the job. \n
//! In order to let callInMainThreadHelper access your private methods, declare friend class callInMainThread. \n
//! Note that callInMainThreadHelper is expected to be created and destroyed in main thread.
class callInMainThreadHelper {
public:
typedef std::function< void () > func_t;
typedef pfc::rcptr_t< bool > killswitch_t;
class entryFunc : public main_thread_callback {
public:
entryFunc( func_t const & func, killswitch_t ks ) : m_ks(ks), m_func(func) {}
void callback_run() {
if (!*m_ks) m_func();
}
private:
killswitch_t m_ks;
func_t m_func;
};
template<typename host_t, typename arg_t>
class entry : public main_thread_callback {
public:
entry( host_t * host, arg_t const & arg, killswitch_t ks ) : m_ks(ks), m_host(host), m_arg(arg) {}
void callback_run() {
if (!*m_ks) callInMainThread::callThis( m_host, m_arg );
}
private:
killswitch_t m_ks;
host_t * m_host;
arg_t m_arg;
};
template<typename host_t>
class entryVoid : public main_thread_callback {
public:
entryVoid( host_t * host, killswitch_t ks ) : m_ks(ks), m_host(host) {}
void callback_run() {
if (!*m_ks) callInMainThread::callThis( m_host );
}
private:
killswitch_t m_ks;
host_t * m_host;
};
void add(func_t f) {
add_( new service_impl_t< entryFunc > ( f, m_ks ) );
}
template<typename host_t, typename arg_t>
void add( host_t * host, arg_t const & arg) {
add_( new service_impl_t< entry<host_t, arg_t> >( host, arg, m_ks ) );
}
template<typename host_t>
void add( host_t * host ) {
add_( new service_impl_t< entryVoid<host_t> >( host, m_ks ) );
}
void add_( main_thread_callback::ptr cb ) {
main_thread_callback_add( cb );
}
callInMainThreadHelper() {
m_ks.new_t();
* m_ks = false;
}
void shutdown() {
PFC_ASSERT( core_api::is_main_thread() );
* m_ks = true;
}
~callInMainThreadHelper() {
shutdown();
}
private:
killswitch_t m_ks;
};

View File

@@ -0,0 +1,57 @@
#include "foobar2000.h"
bool mainmenu_commands::g_execute_dynamic(const GUID & p_guid, const GUID & p_subGuid,service_ptr_t<service_base> p_callback) {
mainmenu_commands::ptr ptr; t_uint32 index;
if (!menu_item_resolver::g_resolve_main_command(p_guid, ptr, index)) return false;
mainmenu_commands_v2::ptr v2;
if (!ptr->service_query_t(v2)) return false;
if (!v2->is_command_dynamic(index)) return false;
return v2->dynamic_execute(index, p_subGuid, p_callback);
}
bool mainmenu_commands::g_execute(const GUID & p_guid,service_ptr_t<service_base> p_callback) {
mainmenu_commands::ptr ptr; t_uint32 index;
if (!menu_item_resolver::g_resolve_main_command(p_guid, ptr, index)) return false;
ptr->execute(index, p_callback);
return true;
}
bool mainmenu_commands::g_find_by_name(const char * p_name,GUID & p_guid) {
service_enum_t<mainmenu_commands> e;
service_ptr_t<mainmenu_commands> ptr;
pfc::string8_fastalloc temp;
while(e.next(ptr)) {
const t_uint32 count = ptr->get_command_count();
for(t_uint32 n=0;n<count;n++) {
ptr->get_name(n,temp);
if (stricmp_utf8(temp,p_name) == 0) {
p_guid = ptr->get_command(n);
return true;
}
}
}
return false;
}
static bool dynamic_execute_recur(mainmenu_node::ptr node, const GUID & subID, service_ptr_t<service_base> callback) {
switch(node->get_type()) {
case mainmenu_node::type_command:
if (subID == node->get_guid()) {
node->execute(callback); return true;
}
break;
case mainmenu_node::type_group:
{
const t_size total = node->get_children_count();
for(t_size walk = 0; walk < total; ++walk) {
if (dynamic_execute_recur(node->get_child(walk), subID, callback)) return true;
}
}
break;
}
return false;
}
bool mainmenu_commands_v2::dynamic_execute(t_uint32 index, const GUID & subID, service_ptr_t<service_base> callback) {
return dynamic_execute_recur(dynamic_instantiate(index), subID, callback);
}

View File

@@ -0,0 +1,12 @@
#include "foobar2000.h"
void mem_block_container::from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort) {
if (p_bytes == 0) {set_size(0);}
set_size(p_bytes);
p_stream->read_object(get_ptr(),p_bytes,p_abort);
}
void mem_block_container::set(const void * p_buffer,t_size p_size) {
set_size(p_size);
memcpy(get_ptr(),p_buffer,p_size);
}

View File

@@ -0,0 +1,96 @@
#pragma once
//! Generic interface for a memory block; used by various other interfaces to return memory blocks while allowing caller to allocate.
class NOVTABLE mem_block_container {
public:
virtual const void * get_ptr() const = 0;
virtual void * get_ptr() = 0;
virtual t_size get_size() const = 0;
virtual void set_size(t_size p_size) = 0;
void from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort);
void set(const void * p_buffer,t_size p_size);
void set(const mem_block_container & source) {copy(source);}
template<typename t_source> void set(const t_source & source) {
PFC_STATIC_ASSERT( sizeof(source[0]) == 1 );
set(source.get_ptr(), source.get_size());
}
inline void copy(const mem_block_container & p_source) {set(p_source.get_ptr(),p_source.get_size());}
inline void reset() {set_size(0);}
const mem_block_container & operator=(const mem_block_container & p_source) {copy(p_source);return *this;}
protected:
mem_block_container() {}
~mem_block_container() {}
};
//! mem_block_container implementation.
template<template<typename> class t_alloc = pfc::alloc_standard>
class mem_block_container_impl_t : public mem_block_container {
public:
const void * get_ptr() const {return m_data.get_ptr();}
void * get_ptr() {return m_data.get_ptr();}
t_size get_size() const {return m_data.get_size();}
void set_size(t_size p_size) {
m_data.set_size(p_size);
}
private:
pfc::array_t<t_uint8,t_alloc> m_data;
};
typedef mem_block_container_impl_t<> mem_block_container_impl;
template<unsigned alignBytes = 16> class mem_block_container_aligned_impl : public mem_block_container {
public:
const void * get_ptr() const {return m_data.get_ptr();}
void * get_ptr() {return m_data.get_ptr();}
t_size get_size() const {return m_data.get_size();}
void set_size(t_size p_size) {m_data.set_size(p_size);}
private:
pfc::mem_block_aligned<16> m_data;
};
template<unsigned alignBytes = 16> class mem_block_container_aligned_incremental_impl : public mem_block_container {
public:
mem_block_container_aligned_incremental_impl() : m_size() {}
const void * get_ptr() const {return m_data.get_ptr();}
void * get_ptr() {return m_data.get_ptr();}
t_size get_size() const {return m_size;}
void set_size(t_size p_size) {
if (m_data.size() < p_size) {
m_data.resize( pfc::multiply_guarded<size_t>(p_size, 3) / 2 );
}
m_size = p_size;
}
private:
pfc::mem_block_aligned<16> m_data;
size_t m_size;
};
class mem_block_container_temp_impl : public mem_block_container {
public:
mem_block_container_temp_impl(void * p_buffer,t_size p_size) : m_buffer(p_buffer), m_buffer_size(p_size), m_size(0) {}
const void * get_ptr() const {return m_buffer;}
void * get_ptr() {return m_buffer;}
t_size get_size() const {return m_size;}
void set_size(t_size p_size) {if (p_size > m_buffer_size) throw pfc::exception_overflow(); m_size = p_size;}
private:
t_size m_size,m_buffer_size;
void * m_buffer;
};
template<typename t_ref>
class mem_block_container_ref_impl : public mem_block_container {
public:
mem_block_container_ref_impl(t_ref & ref) : m_ref(ref) {
PFC_STATIC_ASSERT( sizeof(ref[0]) == 1 );
}
const void * get_ptr() const {return m_ref.get_ptr();}
void * get_ptr() {return m_ref.get_ptr();}
t_size get_size() const {return m_ref.get_size();}
void set_size(t_size p_size) {m_ref.set_size(p_size);}
private:
t_ref & m_ref;
};

225
foobar2000/SDK/menu.h Normal file
View File

@@ -0,0 +1,225 @@
#pragma once
class NOVTABLE mainmenu_group : public service_base {
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(mainmenu_group);
public:
virtual GUID get_guid() = 0;
virtual GUID get_parent() = 0;
virtual t_uint32 get_sort_priority() = 0;
};
class NOVTABLE mainmenu_group_popup : public mainmenu_group {
FB2K_MAKE_SERVICE_INTERFACE(mainmenu_group_popup, mainmenu_group);
public:
virtual void get_display_string(pfc::string_base & p_out) = 0;
void get_name(pfc::string_base & out) {get_display_string(out);}
};
//! \since 1.4
//! Allows you to control whether to render the group as a popup or inline.
class NOVTABLE mainmenu_group_popup_v2 : public mainmenu_group_popup {
FB2K_MAKE_SERVICE_INTERFACE(mainmenu_group_popup_v2, mainmenu_group_popup);
public:
virtual bool popup_condition() = 0;
};
class NOVTABLE mainmenu_commands : public service_base {
public:
enum {
flag_disabled = 1<<0,
flag_checked = 1<<1,
flag_radiochecked = 1<<2,
//! \since 1.0
//! Replaces the old return-false-from-get_display() behavior - use this to make your command hidden by default but accessible when holding shift.
flag_defaulthidden = 1<<3,
sort_priority_base = 0x10000,
sort_priority_dontcare = 0x80000000u,
sort_priority_last = ~0,
};
//! Retrieves number of implemented commands. Index parameter of other methods must be in 0....command_count-1 range.
virtual t_uint32 get_command_count() = 0;
//! Retrieves GUID of specified command.
virtual GUID get_command(t_uint32 p_index) = 0;
//! Retrieves name of item, for list of commands to assign keyboard shortcuts to etc.
virtual void get_name(t_uint32 p_index,pfc::string_base & p_out) = 0;
//! Retrieves item's description for statusbar etc.
virtual bool get_description(t_uint32 p_index,pfc::string_base & p_out) = 0;
//! Retrieves GUID of owning menu group.
virtual GUID get_parent() = 0;
//! Retrieves sorting priority of the command; the lower the number, the upper in the menu your commands will appear. Third party components should use sorting_priority_base and up (values below are reserved for internal use). In case of equal priority, order is undefined.
virtual t_uint32 get_sort_priority() {return sort_priority_dontcare;}
//! Retrieves display string and display flags to use when menu is about to be displayed. If returns false, menu item won't be displayed. You can create keyboard-shortcut-only commands by always returning false from get_display().
virtual bool get_display(t_uint32 p_index,pfc::string_base & p_text,t_uint32 & p_flags) {p_flags = 0;get_name(p_index,p_text);return true;}
//! Executes the command. p_callback parameter is reserved for future use and should be ignored / set to null pointer.
virtual void execute(t_uint32 p_index,service_ptr_t<service_base> p_callback) = 0;
static bool g_execute(const GUID & p_guid,service_ptr_t<service_base> p_callback = NULL);
static bool g_execute_dynamic(const GUID & p_guid, const GUID & p_subGuid,service_ptr_t<service_base> p_callback = NULL);
static bool g_find_by_name(const char * p_name,GUID & p_guid);
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(mainmenu_commands);
};
class NOVTABLE mainmenu_manager : public service_base {
public:
enum {
flag_show_shortcuts = 1 << 0,
flag_show_shortcuts_global = 1 << 1,
//! \since 1.0
//! To control which commands are shown, you should specify either flag_view_reduced or flag_view_full. If neither is specified, the implementation will decide automatically based on shift key being pressed, for backwards compatibility.
flag_view_reduced = 1 << 2,
//! \since 1.0
//! To control which commands are shown, you should specify either flag_view_reduced or flag_view_full. If neither is specified, the implementation will decide automatically based on shift key being pressed, for backwards compatibility.
flag_view_full = 1 << 3,
};
virtual void instantiate(const GUID & p_root) = 0;
#ifdef _WIN32
virtual void generate_menu_win32(HMENU p_menu,t_uint32 p_id_base,t_uint32 p_id_count,t_uint32 p_flags) = 0;
#else
#error portme
#endif
//@param p_id Identifier of command to execute, relative to p_id_base of generate_menu_*()
//@returns true if command was executed successfully, false if not (e.g. command with given identifier not found).
virtual bool execute_command(t_uint32 p_id,service_ptr_t<service_base> p_callback = service_ptr_t<service_base>()) = 0;
virtual bool get_description(t_uint32 p_id,pfc::string_base & p_out) = 0;
//! Safely prevent destruction from worker threads (some components attempt that).
static bool serviceRequiresMainThreadDestructor() { return true; }
FB2K_MAKE_SERVICE_COREAPI(mainmenu_manager);
};
class mainmenu_groups {
public:
static const GUID file,view,edit,playback,library,help;
static const GUID file_open,file_add,file_playlist,file_etc;
static const GUID playback_controls,playback_etc;
static const GUID view_visualisations, view_alwaysontop, view_dsp;
static const GUID edit_part1,edit_part2,edit_part3;
static const GUID edit_part2_selection,edit_part2_sort,edit_part2_selection_sort;
static const GUID file_etc_preferences, file_etc_exit;
static const GUID help_about;
static const GUID library_refresh;
enum {priority_edit_part1,priority_edit_part2,priority_edit_part3};
enum {priority_edit_part2_commands,priority_edit_part2_selection,priority_edit_part2_sort};
enum {priority_edit_part2_selection_commands,priority_edit_part2_selection_sort};
enum {priority_file_open,priority_file_add,priority_file_playlist,priority_file_etc = mainmenu_commands::sort_priority_last};
};
class mainmenu_group_impl : public mainmenu_group {
public:
mainmenu_group_impl(const GUID & p_guid,const GUID & p_parent,t_uint32 p_priority) : m_guid(p_guid), m_parent(p_parent), m_priority(p_priority) {}
GUID get_guid() {return m_guid;}
GUID get_parent() {return m_parent;}
t_uint32 get_sort_priority() {return m_priority;}
private:
GUID m_guid,m_parent; t_uint32 m_priority;
};
class mainmenu_group_popup_impl : public mainmenu_group_popup {
public:
mainmenu_group_popup_impl(const GUID & p_guid,const GUID & p_parent,t_uint32 p_priority,const char * p_name) : m_guid(p_guid), m_parent(p_parent), m_priority(p_priority), m_name(p_name) {}
GUID get_guid() {return m_guid;}
GUID get_parent() {return m_parent;}
t_uint32 get_sort_priority() {return m_priority;}
void get_display_string(pfc::string_base & p_out) {p_out = m_name;}
private:
GUID m_guid,m_parent; t_uint32 m_priority; pfc::string8 m_name;
};
typedef service_factory_single_t<mainmenu_group_impl> __mainmenu_group_factory;
typedef service_factory_single_t<mainmenu_group_popup_impl> __mainmenu_group_popup_factory;
class mainmenu_group_factory : public __mainmenu_group_factory {
public:
mainmenu_group_factory(const GUID & p_guid,const GUID & p_parent,t_uint32 p_priority) : __mainmenu_group_factory(p_guid,p_parent,p_priority) {}
};
class mainmenu_group_popup_factory : public __mainmenu_group_popup_factory {
public:
mainmenu_group_popup_factory(const GUID & p_guid,const GUID & p_parent,t_uint32 p_priority,const char * p_name) : __mainmenu_group_popup_factory(p_guid,p_parent,p_priority,p_name) {}
};
template<typename T>
class mainmenu_commands_factory_t : public service_factory_single_t<T> {};
// \since 1.0
class NOVTABLE mainmenu_node : public service_base {
FB2K_MAKE_SERVICE_INTERFACE(mainmenu_node, service_base)
public:
enum { //same as contextmenu_item_node::t_type
type_group,type_command,type_separator
};
virtual t_uint32 get_type() = 0;
virtual void get_display(pfc::string_base & text, t_uint32 & flags) = 0;
//! Valid only if type is type_group.
virtual t_size get_children_count() = 0;
//! Valid only if type is type_group.
virtual ptr get_child(t_size index) = 0;
//! Valid only if type is type_command.
virtual void execute(service_ptr_t<service_base> callback) = 0;
//! Valid only if type is type_command.
virtual GUID get_guid() = 0;
//! Valid only if type is type_command.
virtual bool get_description(pfc::string_base & out) {return false;}
};
class mainmenu_node_separator : public mainmenu_node {
public:
t_uint32 get_type() {return type_separator;}
void get_display(pfc::string_base & text, t_uint32 & flags) {text = ""; flags = 0;}
t_size get_children_count() {return 0;}
ptr get_child(t_size index) {throw pfc::exception_invalid_params();}
void execute(service_ptr_t<service_base>) {}
GUID get_guid() {return pfc::guid_null;}
};
class mainmenu_node_command : public mainmenu_node {
public:
t_uint32 get_type() {return type_command;}
t_size get_children_count() {return 0;}
ptr get_child(t_size index) {throw pfc::exception_invalid_params();}
/*
void get_display(pfc::string_base & text, t_uint32 & flags);
void execute(service_ptr_t<service_base> callback);
GUID get_guid();
bool get_description(pfc::string_base & out) {return false;}
*/
};
class mainmenu_node_group : public mainmenu_node {
public:
t_uint32 get_type() {return type_group;}
void execute(service_ptr_t<service_base> callback) {}
GUID get_guid() {return pfc::guid_null;}
/*
void get_display(pfc::string_base & text, t_uint32 & flags);
t_size get_children_count();
ptr get_child(t_size index);
*/
};
// \since 1.0
class NOVTABLE mainmenu_commands_v2 : public mainmenu_commands {
FB2K_MAKE_SERVICE_INTERFACE(mainmenu_commands_v2, mainmenu_commands)
public:
virtual bool is_command_dynamic(t_uint32 index) = 0;
//! Valid only when is_command_dynamic() returns true. Behavior undefined otherwise.
virtual mainmenu_node::ptr dynamic_instantiate(t_uint32 index) = 0;
//! Default fallback implementation provided.
virtual bool dynamic_execute(t_uint32 index, const GUID & subID, service_ptr_t<service_base> callback);
};

View File

@@ -0,0 +1,295 @@
#include "foobar2000.h"
bool menu_helpers::context_get_description(const GUID& p_guid,pfc::string_base & out) {
service_ptr_t<contextmenu_item> ptr; t_uint32 index;
if (!menu_item_resolver::g_resolve_context_command(p_guid, ptr, index)) return false;
bool rv = ptr->get_item_description(index, out);
if (!rv) out.reset();
return rv;
}
static bool run_context_command_internal(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data,const GUID & caller) {
if (data.get_count() == 0) return false;
service_ptr_t<contextmenu_item> ptr; t_uint32 index;
if (!menu_item_resolver::g_resolve_context_command(p_command, ptr, index)) return false;
{
TRACK_CALL_TEXT("menu_helpers::run_command(), by GUID");
ptr->item_execute_simple(index, p_subcommand, data, caller);
}
return true;
}
bool menu_helpers::run_command_context(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data)
{
return run_context_command_internal(p_command,p_subcommand,data,contextmenu_item::caller_undefined);
}
bool menu_helpers::run_command_context_ex(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data,const GUID & caller)
{
return run_context_command_internal(p_command,p_subcommand,data,caller);
}
bool menu_helpers::test_command_context(const GUID & p_guid)
{
service_ptr_t<contextmenu_item> ptr; t_uint32 index;
return menu_item_resolver::g_resolve_context_command(p_guid, ptr, index);
}
static bool g_is_checked(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data,const GUID & caller)
{
service_ptr_t<contextmenu_item> ptr; t_uint32 index;
if (!menu_item_resolver::g_resolve_context_command(p_command, ptr, index)) return false;
unsigned displayflags = 0;
pfc::string_formatter dummystring;
if (!ptr->item_get_display_data(dummystring,displayflags,index,p_subcommand,data,caller)) return false;
return (displayflags & contextmenu_item_node::FLAG_CHECKED) != 0;
}
bool menu_helpers::is_command_checked_context(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data)
{
return g_is_checked(p_command,p_subcommand,data,contextmenu_item::caller_undefined);
}
bool menu_helpers::is_command_checked_context_playlist(const GUID & p_command,const GUID & p_subcommand)
{
metadb_handle_list temp;
playlist_manager::get()->activeplaylist_get_selected_items(temp);
return g_is_checked(p_command,p_subcommand,temp,contextmenu_item::caller_playlist);
}
bool menu_helpers::run_command_context_playlist(const GUID & p_command,const GUID & p_subcommand)
{
metadb_handle_list temp;
playlist_manager::get()->activeplaylist_get_selected_items(temp);
return run_command_context_ex(p_command,p_subcommand,temp,contextmenu_item::caller_playlist);
}
bool menu_helpers::run_command_context_now_playing(const GUID & p_command,const GUID & p_subcommand)
{
metadb_handle_ptr item;
if (!playback_control::get()->get_now_playing(item)) return false;//not playing
return run_command_context_ex(p_command,p_subcommand,pfc::list_single_ref_t<metadb_handle_ptr>(item),contextmenu_item::caller_now_playing);
}
bool menu_helpers::guid_from_name(const char * p_name,unsigned p_name_len,GUID & p_out)
{
service_enum_t<contextmenu_item> e;
service_ptr_t<contextmenu_item> ptr;
pfc::string8_fastalloc nametemp;
while(e.next(ptr))
{
unsigned n, m = ptr->get_num_items();
for(n=0;n<m;n++)
{
ptr->get_item_name(n,nametemp);
if (!strcmp_ex(nametemp,~0,p_name,p_name_len))
{
p_out = ptr->get_item_guid(n);
return true;
}
}
}
return false;
}
bool menu_helpers::name_from_guid(const GUID & p_guid,pfc::string_base & p_out) {
service_ptr_t<contextmenu_item> ptr; t_uint32 index;
if (!menu_item_resolver::g_resolve_context_command(p_guid, ptr, index)) return false;
ptr->get_item_name(index, p_out);
return true;
}
static unsigned calc_total_action_count()
{
service_enum_t<contextmenu_item> e;
service_ptr_t<contextmenu_item> ptr;
unsigned ret = 0;
while(e.next(ptr))
ret += ptr->get_num_items();
return ret;
}
const char * menu_helpers::guid_to_name_table::search(const GUID & p_guid)
{
if (!m_inited)
{
m_data.set_size(calc_total_action_count());
t_size dataptr = 0;
pfc::string8_fastalloc nametemp;
service_enum_t<contextmenu_item> e;
service_ptr_t<contextmenu_item> ptr;
while(e.next(ptr))
{
unsigned n, m = ptr->get_num_items();
for(n=0;n<m;n++)
{
assert(dataptr < m_data.get_size());
ptr->get_item_name(n,nametemp);
m_data[dataptr].m_name = _strdup(nametemp);
m_data[dataptr].m_guid = ptr->get_item_guid(n);
dataptr++;
}
}
assert(dataptr == m_data.get_size());
pfc::sort_t(m_data,entry_compare,m_data.get_size());
m_inited = true;
}
t_size index;
if (pfc::bsearch_t(m_data.get_size(),m_data,entry_compare_search,p_guid,index))
return m_data[index].m_name;
else
return 0;
}
int menu_helpers::guid_to_name_table::entry_compare_search(const entry & entry1,const GUID & entry2)
{
return pfc::guid_compare(entry1.m_guid,entry2);
}
int menu_helpers::guid_to_name_table::entry_compare(const entry & entry1,const entry & entry2)
{
return pfc::guid_compare(entry1.m_guid,entry2.m_guid);
}
menu_helpers::guid_to_name_table::guid_to_name_table()
{
m_inited = false;
}
menu_helpers::guid_to_name_table::~guid_to_name_table()
{
t_size n, m = m_data.get_size();
for(n=0;n<m;n++) free(m_data[n].m_name);
}
int menu_helpers::name_to_guid_table::entry_compare_search(const entry & entry1,const search_entry & entry2)
{
return stricmp_utf8_ex(entry1.m_name,~0,entry2.m_name,entry2.m_name_len);
}
int menu_helpers::name_to_guid_table::entry_compare(const entry & entry1,const entry & entry2)
{
return stricmp_utf8(entry1.m_name,entry2.m_name);
}
bool menu_helpers::name_to_guid_table::search(const char * p_name,unsigned p_name_len,GUID & p_out)
{
if (!m_inited)
{
m_data.set_size(calc_total_action_count());
t_size dataptr = 0;
pfc::string8_fastalloc nametemp;
service_enum_t<contextmenu_item> e;
service_ptr_t<contextmenu_item> ptr;
while(e.next(ptr))
{
unsigned n, m = ptr->get_num_items();
for(n=0;n<m;n++)
{
assert(dataptr < m_data.get_size());
ptr->get_item_name(n,nametemp);
m_data[dataptr].m_name = _strdup(nametemp);
m_data[dataptr].m_guid = ptr->get_item_guid(n);
dataptr++;
}
}
assert(dataptr == m_data.get_size());
pfc::sort_t(m_data,entry_compare,m_data.get_size());
m_inited = true;
}
t_size index;
search_entry temp = {p_name,p_name_len};
if (pfc::bsearch_t(m_data.get_size(),m_data,entry_compare_search,temp,index))
{
p_out = m_data[index].m_guid;
return true;
}
else
return false;
}
menu_helpers::name_to_guid_table::name_to_guid_table()
{
m_inited = false;
}
menu_helpers::name_to_guid_table::~name_to_guid_table()
{
t_size n, m = m_data.get_size();
for(n=0;n<m;n++) free(m_data[n].m_name);
}
bool menu_helpers::find_command_by_name(const char * p_name,service_ptr_t<contextmenu_item> & p_item,unsigned & p_index)
{
pfc::string8_fastalloc path,name;
service_enum_t<contextmenu_item> e;
service_ptr_t<contextmenu_item> ptr;
if (e.first(ptr)) do {
// if (ptr->get_type()==type)
{
unsigned action,num_actions = ptr->get_num_items();
for(action=0;action<num_actions;action++)
{
ptr->get_item_default_path(action,path); ptr->get_item_name(action,name);
if (!path.is_empty()) path += "/";
path += name;
if (!stricmp_utf8(p_name,path))
{
p_item = ptr;
p_index = action;
return true;
}
}
}
} while(e.next(ptr));
return false;
}
bool menu_helpers::find_command_by_name(const char * p_name,GUID & p_command)
{
service_ptr_t<contextmenu_item> item;
unsigned index;
bool ret = find_command_by_name(p_name,item,index);
if (ret) p_command = item->get_item_guid(index);
return ret;
}
bool standard_commands::run_main(const GUID & p_guid) {
t_uint32 index;
mainmenu_commands::ptr ptr;
if (!menu_item_resolver::g_resolve_main_command(p_guid, ptr, index)) return false;
ptr->execute(index,service_ptr_t<service_base>());
return true;
}
bool menu_item_resolver::g_resolve_context_command(const GUID & id, contextmenu_item::ptr & out, t_uint32 & out_index) {
return menu_item_resolver::get()->resolve_context_command(id, out, out_index);
}
bool menu_item_resolver::g_resolve_main_command(const GUID & id, mainmenu_commands::ptr & out, t_uint32 & out_index) {
return menu_item_resolver::get()->resolve_main_command(id, out, out_index);
}

View File

@@ -0,0 +1,183 @@
namespace menu_helpers {
#ifdef _WIN32
void win32_auto_mnemonics(HMENU menu);
#endif
bool run_command_context(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data);
bool run_command_context_ex(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data,const GUID & caller);
bool run_command_context_playlist(const GUID & p_command,const GUID & p_subcommand);
bool run_command_context_now_playing(const GUID & p_command,const GUID & p_subcommand);
bool test_command_context(const GUID & p_guid);
bool is_command_checked_context(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data);
bool is_command_checked_context_playlist(const GUID & p_command,const GUID & p_subcommand);
bool find_command_by_name(const char * p_name,service_ptr_t<contextmenu_item> & p_item,unsigned & p_index);
bool find_command_by_name(const char * p_name,GUID & p_command);
bool context_get_description(const GUID& p_guid,pfc::string_base & out);
bool guid_from_name(const char * p_name,unsigned p_name_len,GUID & p_out);
bool name_from_guid(const GUID & p_guid,pfc::string_base & p_out);
class guid_to_name_table
{
public:
guid_to_name_table();
~guid_to_name_table();
const char * search(const GUID & p_guid);
private:
struct entry {
char* m_name;
GUID m_guid;
};
pfc::array_t<entry> m_data;
bool m_inited;
static int entry_compare_search(const entry & entry1,const GUID & entry2);
static int entry_compare(const entry & entry1,const entry & entry2);
};
class name_to_guid_table
{
public:
name_to_guid_table();
~name_to_guid_table();
bool search(const char * p_name,unsigned p_name_len,GUID & p_out);
private:
struct entry {
char* m_name;
GUID m_guid;
};
pfc::array_t<entry> m_data;
bool m_inited;
struct search_entry {
const char * m_name; unsigned m_name_len;
};
static int entry_compare_search(const entry & entry1,const search_entry & entry2);
static int entry_compare(const entry & entry1,const entry & entry2);
};
};
class standard_commands
{
public:
static const GUID
guid_context_file_properties, guid_context_file_open_directory, guid_context_copy_names,
guid_context_send_to_playlist, guid_context_reload_info, guid_context_reload_info_if_changed,
guid_context_rewrite_info, guid_context_remove_tags,
guid_context_convert_run, guid_context_convert_run_singlefile,guid_context_convert_run_withcue,
guid_context_write_cd,
guid_context_rg_scan_track, guid_context_rg_scan_album, guid_context_rg_scan_album_multi,
guid_context_rg_remove, guid_context_save_playlist, guid_context_masstag_edit,
guid_context_masstag_rename,
guid_main_always_on_top, guid_main_preferences, guid_main_about,
guid_main_exit, guid_main_restart, guid_main_activate,
guid_main_hide, guid_main_activate_or_hide, guid_main_titleformat_help,
guid_main_next, guid_main_previous,
guid_main_next_or_random, guid_main_random, guid_main_pause,
guid_main_play, guid_main_play_or_pause, guid_main_rg_set_album,
guid_main_rg_set_track, guid_main_rg_disable, guid_main_rg_byorder,
guid_main_stop,
guid_main_stop_after_current, guid_main_volume_down, guid_main_volume_up,
guid_main_volume_mute, guid_main_add_directory, guid_main_add_files,
guid_main_add_location, guid_main_add_playlist, guid_main_clear_playlist,
guid_main_create_playlist, guid_main_highlight_playing, guid_main_load_playlist,
guid_main_next_playlist, guid_main_previous_playlist, guid_main_open,
guid_main_remove_playlist, guid_main_remove_dead_entries, guid_main_remove_duplicates,
guid_main_rename_playlist, guid_main_save_all_playlists, guid_main_save_playlist,
guid_main_playlist_search, guid_main_playlist_sel_crop, guid_main_playlist_sel_remove,
guid_main_playlist_sel_invert, guid_main_playlist_undo, guid_main_show_console,
guid_main_play_cd, guid_main_restart_resetconfig, guid_main_record,
guid_main_playlist_moveback, guid_main_playlist_moveforward, guid_main_playlist_redo,
guid_main_playback_follows_cursor, guid_main_cursor_follows_playback, guid_main_saveconfig,
guid_main_playlist_select_all, guid_main_show_now_playing,
guid_seek_ahead_1s, guid_seek_ahead_5s, guid_seek_ahead_10s, guid_seek_ahead_30s,
guid_seek_ahead_1min, guid_seek_ahead_2min, guid_seek_ahead_5min, guid_seek_ahead_10min,
guid_seek_back_1s, guid_seek_back_5s, guid_seek_back_10s, guid_seek_back_30s,
guid_seek_back_1min, guid_seek_back_2min, guid_seek_back_5min, guid_seek_back_10min
;
static bool run_main(const GUID & guid);
static inline bool run_context(const GUID & guid,const pfc::list_base_const_t<metadb_handle_ptr> &data) {return menu_helpers::run_command_context(guid,pfc::guid_null,data);}
static inline bool run_context(const GUID & guid,const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller) {return menu_helpers::run_command_context_ex(guid,pfc::guid_null,data,caller);}
static inline bool context_file_properties(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_file_properties,data,caller);}
static inline bool context_file_open_directory(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_file_open_directory,data,caller);}
static inline bool context_copy_names(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_copy_names,data,caller);}
static inline bool context_send_to_playlist(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_send_to_playlist,data,caller);}
static inline bool context_reload_info(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_reload_info,data,caller);}
static inline bool context_reload_info_if_changed(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_reload_info_if_changed,data,caller);}
static inline bool context_rewrite_info(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_rewrite_info,data,caller);}
static inline bool context_remove_tags(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_remove_tags,data,caller);}
static inline bool context_convert_run(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_convert_run,data,caller);}
static inline bool context_convert_run_singlefile(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_convert_run_singlefile,data,caller);}
static inline bool context_write_cd(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_write_cd,data,caller);}
static inline bool context_rg_scan_track(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_rg_scan_track,data,caller);}
static inline bool context_rg_scan_album(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_rg_scan_album,data,caller);}
static inline bool context_rg_scan_album_multi(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_rg_scan_album_multi,data,caller);}
static inline bool context_rg_remove(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_rg_remove,data,caller);}
static inline bool context_save_playlist(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_save_playlist,data,caller);}
static inline bool context_masstag_edit(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_masstag_edit,data,caller);}
static inline bool context_masstag_rename(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_masstag_rename,data,caller);}
static inline bool main_always_on_top() {return run_main(guid_main_always_on_top);}
static inline bool main_preferences() {return run_main(guid_main_preferences);}
static inline bool main_about() {return run_main(guid_main_about);}
static inline bool main_exit() {return run_main(guid_main_exit);}
static inline bool main_restart() {return run_main(guid_main_restart);}
static inline bool main_activate() {return run_main(guid_main_activate);}
static inline bool main_hide() {return run_main(guid_main_hide);}
static inline bool main_activate_or_hide() {return run_main(guid_main_activate_or_hide);}
static inline bool main_titleformat_help() {return run_main(guid_main_titleformat_help);}
static inline bool main_playback_follows_cursor() {return run_main(guid_main_playback_follows_cursor);}
static inline bool main_next() {return run_main(guid_main_next);}
static inline bool main_previous() {return run_main(guid_main_previous);}
static inline bool main_next_or_random() {return run_main(guid_main_next_or_random);}
static inline bool main_random() {return run_main(guid_main_random);}
static inline bool main_pause() {return run_main(guid_main_pause);}
static inline bool main_play() {return run_main(guid_main_play);}
static inline bool main_play_or_pause() {return run_main(guid_main_play_or_pause);}
static inline bool main_rg_set_album() {return run_main(guid_main_rg_set_album);}
static inline bool main_rg_set_track() {return run_main(guid_main_rg_set_track);}
static inline bool main_rg_disable() {return run_main(guid_main_rg_disable);}
static inline bool main_stop() {return run_main(guid_main_stop);}
static inline bool main_stop_after_current() {return run_main(guid_main_stop_after_current);}
static inline bool main_volume_down() {return run_main(guid_main_volume_down);}
static inline bool main_volume_up() {return run_main(guid_main_volume_up);}
static inline bool main_volume_mute() {return run_main(guid_main_volume_mute);}
static inline bool main_add_directory() {return run_main(guid_main_add_directory);}
static inline bool main_add_files() {return run_main(guid_main_add_files);}
static inline bool main_add_location() {return run_main(guid_main_add_location);}
static inline bool main_add_playlist() {return run_main(guid_main_add_playlist);}
static inline bool main_clear_playlist() {return run_main(guid_main_clear_playlist);}
static inline bool main_create_playlist() {return run_main(guid_main_create_playlist);}
static inline bool main_highlight_playing() {return run_main(guid_main_highlight_playing);}
static inline bool main_load_playlist() {return run_main(guid_main_load_playlist);}
static inline bool main_next_playlist() {return run_main(guid_main_next_playlist);}
static inline bool main_previous_playlist() {return run_main(guid_main_previous_playlist);}
static inline bool main_open() {return run_main(guid_main_open);}
static inline bool main_remove_playlist() {return run_main(guid_main_remove_playlist);}
static inline bool main_remove_dead_entries() {return run_main(guid_main_remove_dead_entries);}
static inline bool main_remove_duplicates() {return run_main(guid_main_remove_duplicates);}
static inline bool main_rename_playlist() {return run_main(guid_main_rename_playlist);}
static inline bool main_save_all_playlists() {return run_main(guid_main_save_all_playlists);}
static inline bool main_save_playlist() {return run_main(guid_main_save_playlist);}
static inline bool main_playlist_search() {return run_main(guid_main_playlist_search);}
static inline bool main_playlist_sel_crop() {return run_main(guid_main_playlist_sel_crop);}
static inline bool main_playlist_sel_remove() {return run_main(guid_main_playlist_sel_remove);}
static inline bool main_playlist_sel_invert() {return run_main(guid_main_playlist_sel_invert);}
static inline bool main_playlist_undo() {return run_main(guid_main_playlist_undo);}
static inline bool main_show_console() {return run_main(guid_main_show_console);}
static inline bool main_play_cd() {return run_main(guid_main_play_cd);}
static inline bool main_restart_resetconfig() {return run_main(guid_main_restart_resetconfig);}
static inline bool main_playlist_moveback() {return run_main(guid_main_playlist_moveback);}
static inline bool main_playlist_moveforward() {return run_main(guid_main_playlist_moveforward);}
static inline bool main_saveconfig() {return run_main(guid_main_saveconfig);}
};

View File

@@ -0,0 +1,61 @@
#include "foobar2000.h"
bool contextmenu_item::item_get_display_data_root(pfc::string_base & p_out,unsigned & p_displayflags,unsigned p_index,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const GUID & p_caller)
{
bool status = false;
pfc::ptrholder_t<contextmenu_item_node_root> root ( instantiate_item(p_index,p_data,p_caller) );
if (root.is_valid()) status = root->get_display_data(p_out,p_displayflags,p_data,p_caller);
return status;
}
static contextmenu_item_node * g_find_node(const GUID & p_guid,contextmenu_item_node * p_parent)
{
if (p_parent->get_guid() == p_guid) return p_parent;
else if (p_parent->get_type() == contextmenu_item_node::TYPE_POPUP)
{
t_size n, m = p_parent->get_children_count();
for(n=0;n<m;n++)
{
contextmenu_item_node * temp = g_find_node(p_guid,p_parent->get_child(n));
if (temp) return temp;
}
return 0;
}
else return 0;
}
bool contextmenu_item::item_get_display_data(pfc::string_base & p_out,unsigned & p_displayflags,unsigned p_index,const GUID & p_node,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const GUID & p_caller)
{
bool status = false;
pfc::ptrholder_t<contextmenu_item_node_root> root ( instantiate_item(p_index,p_data,p_caller) );
if (root.is_valid())
{
contextmenu_item_node * node = g_find_node(p_node,root.get_ptr());
if (node) status = node->get_display_data(p_out,p_displayflags,p_data,p_caller);
}
return status;
}
GUID contextmenu_item::get_parent_fallback() {
unsigned total = get_num_items();
if (total < 1) return pfc::guid_null; // hide the item
pfc::string_formatter path, base; this->get_item_default_path(0, base);
for(unsigned walk = 1; walk < total; ++walk) {
this->get_item_default_path(walk, path);
if (strcmp(path, base) != 0) return contextmenu_groups::legacy;
}
return contextmenu_group_manager::get()->path_to_group(base);
}
GUID contextmenu_item::get_parent_() {
contextmenu_item_v2::ptr v2;
if (service_query_t(v2)) {
return v2->get_parent();
} else {
return get_parent_fallback();
}
}

Some files were not shown because too many files have changed in this diff Show More