latest SDK
This commit is contained in:
49
foobar2000/helpers/AutoComplete.cpp
Normal file
49
foobar2000/helpers/AutoComplete.cpp
Normal file
@@ -0,0 +1,49 @@
|
||||
#include "stdafx.h"
|
||||
#include "AutoComplete.h"
|
||||
#include <ShlGuid.h> // CLSID_AutoComplete
|
||||
#include "../helpers/COM_utils.h"
|
||||
#include "../helpers/dropdown_helper.h"
|
||||
#include <libPPUI/CEnumString.h>
|
||||
|
||||
using PP::CEnumString;
|
||||
|
||||
namespace {
|
||||
class CACList_History : public CEnumString {
|
||||
public:
|
||||
CACList_History(cfg_dropdown_history_mt * var) : m_var(var) { Reset(); }
|
||||
typedef ImplementCOMRefCounter<CACList_History> TImpl;
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Reset() {
|
||||
/*if (core_api::assert_main_thread())*/ {
|
||||
ResetStrings();
|
||||
pfc::string8 state; m_var->get_state(state);
|
||||
for (const char * walk = state;;) {
|
||||
const char * next = strchr(walk, cfg_dropdown_history_mt::separator);
|
||||
t_size len = (next != NULL) ? next - walk : ~0;
|
||||
AddStringU(walk, len);
|
||||
if (next == NULL) break;
|
||||
walk = next + 1;
|
||||
}
|
||||
}
|
||||
return __super::Reset();
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Clone(IEnumString **ppenum) {
|
||||
*ppenum = new TImpl(*this); return S_OK;
|
||||
}
|
||||
|
||||
private:
|
||||
cfg_dropdown_history_mt * const m_var;
|
||||
};
|
||||
}
|
||||
|
||||
HRESULT InitializeDropdownAC(HWND comboBox, cfg_dropdown_history_mt & var, const char * initVal) {
|
||||
var.on_init(comboBox, initVal);
|
||||
COMBOBOXINFO ci = {}; ci.cbSize = sizeof(ci);
|
||||
if (!GetComboBoxInfo(comboBox, &ci)) {
|
||||
PFC_ASSERT(!"Should not get here - GetComboBoxInfo fail!");
|
||||
return E_FAIL;
|
||||
}
|
||||
pfc::com_ptr_t<IUnknown> acl = new CACList_History::TImpl(&var);
|
||||
return InitializeSimpleAC(ci.hwndItem, acl.get_ptr(), ACO_AUTOAPPEND|ACO_AUTOSUGGEST);
|
||||
}
|
||||
7
foobar2000/helpers/AutoComplete.h
Normal file
7
foobar2000/helpers/AutoComplete.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <libPPUI/AutoComplete.h>
|
||||
|
||||
class cfg_dropdown_history_mt;
|
||||
|
||||
HRESULT InitializeDropdownAC(HWND comboBox, cfg_dropdown_history_mt & var, const char * initVal);
|
||||
73
foobar2000/helpers/BumpableElem.h
Normal file
73
foobar2000/helpers/BumpableElem.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include <libPPUI/CFlashWindow.h>
|
||||
#include "atl-misc.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
template<typename TClass>
|
||||
class ImplementBumpableElem : public TClass {
|
||||
private:
|
||||
typedef ImplementBumpableElem<TClass> TSelf;
|
||||
public:
|
||||
template<typename ... arg_t> ImplementBumpableElem( arg_t && ... arg ) : TClass(std::forward<arg_t>(arg) ... ) {_init(); }
|
||||
|
||||
BEGIN_MSG_MAP_EX(ImplementBumpableElem)
|
||||
MSG_WM_DESTROY(OnDestroy)
|
||||
CHAIN_MSG_MAP(__super)
|
||||
END_MSG_MAP_HOOK()
|
||||
|
||||
void notify(const GUID & p_what, t_size p_param1, const void * p_param2, t_size p_param2size) {
|
||||
if (p_what == ui_element_notify_visibility_changed && p_param1 == 0 && m_flash.m_hWnd != NULL) m_flash.Deactivate();
|
||||
__super::notify(p_what, p_param1, p_param2, p_param2size);
|
||||
}
|
||||
|
||||
static bool Bump() {
|
||||
for(auto walk = instances.cfirst(); walk.is_valid(); ++walk) {
|
||||
if ((*walk)->_bump()) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
~ImplementBumpableElem() throw() {
|
||||
PFC_ASSERT(core_api::is_main_thread());
|
||||
instances -= this;
|
||||
}
|
||||
private:
|
||||
void OnDestroy() throw() {
|
||||
m_selfDestruct = true;
|
||||
m_flash.CleanUp();
|
||||
SetMsgHandled(FALSE);
|
||||
}
|
||||
bool _bump() {
|
||||
if (m_selfDestruct || this->m_hWnd == NULL) return false;
|
||||
//PROBLEM: This assumes we're implementing service_base methods at this point. Explodes if called during constructors/destructors.
|
||||
if (!this->m_callback->request_activation(this)) return false;
|
||||
m_flash.Activate(*this);
|
||||
this->set_default_focus();
|
||||
return true;
|
||||
}
|
||||
void _init() {
|
||||
m_selfDestruct = false;
|
||||
PFC_ASSERT(core_api::is_main_thread());
|
||||
instances += this;
|
||||
}
|
||||
static pfc::avltree_t<TSelf*> instances;
|
||||
bool m_selfDestruct;
|
||||
CFlashWindow m_flash;
|
||||
};
|
||||
|
||||
template<typename TClass>
|
||||
pfc::avltree_t<ImplementBumpableElem<TClass> *> ImplementBumpableElem<TClass>::instances;
|
||||
|
||||
|
||||
template<typename TImpl, typename TInterface = ui_element_v2> class ui_element_impl_withpopup : public ui_element_impl<ImplementBumpableElem<TImpl>, TInterface> {
|
||||
public:
|
||||
t_uint32 get_flags() {return ui_element_v2::KFlagHavePopupCommand | ui_element_v2::KFlagSupportsBump;}
|
||||
bool bump() {return ImplementBumpableElem<TImpl>::Bump();}
|
||||
};
|
||||
|
||||
template<typename TImpl, typename TInterface = ui_element_v2> class ui_element_impl_visualisation : public ui_element_impl<ImplementBumpableElem<TImpl>, TInterface> {
|
||||
public:
|
||||
t_uint32 get_flags() {return ui_element_v2::KFlagsVisualisation | ui_element_v2::KFlagSupportsBump;}
|
||||
bool bump() {return ImplementBumpableElem<TImpl>::Bump();}
|
||||
};
|
||||
24
foobar2000/helpers/CDialogResizeHelper.h
Normal file
24
foobar2000/helpers/CDialogResizeHelper.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "WindowPositionUtils.h"
|
||||
|
||||
#include <libPPUI/CDialogResizeHelper.h>
|
||||
|
||||
template<typename TTracker> class CDialogResizeHelperTracking : public CDialogResizeHelper {
|
||||
public:
|
||||
template<typename TParam, t_size paramCount, typename TCfgVar> CDialogResizeHelperTracking(const TParam (& src)[paramCount],CRect const& minMaxRange, TCfgVar & cfgVar) : CDialogResizeHelper(src, minMaxRange), m_tracker(cfgVar) {}
|
||||
|
||||
BEGIN_MSG_MAP_EX(CDialogResizeHelperST)
|
||||
CHAIN_MSG_MAP(CDialogResizeHelper)
|
||||
CHAIN_MSG_MAP_MEMBER(m_tracker)
|
||||
END_MSG_MAP()
|
||||
|
||||
private:
|
||||
TTracker m_tracker;
|
||||
};
|
||||
|
||||
typedef CDialogResizeHelperTracking<cfgDialogSizeTracker> CDialogResizeHelperST;
|
||||
typedef CDialogResizeHelperTracking<cfgDialogPositionTracker> CDialogResizeHelperPT;
|
||||
typedef CDialogResizeHelperTracking<cfgWindowSizeTracker2> CDialogResizeHelperST2;
|
||||
|
||||
#define REDRAW_DIALOG_ON_RESIZE() if (uMsg == WM_SIZE) RedrawWindow(NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN);
|
||||
6
foobar2000/helpers/COM_utils.h
Normal file
6
foobar2000/helpers/COM_utils.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
#include <libPPUI/pp-COM-macros.h>
|
||||
|
||||
#define FB2K_COM_CATCH PP_COM_CATCH
|
||||
3
foobar2000/helpers/CPropVariant.h
Normal file
3
foobar2000/helpers/CPropVariant.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
#include <libPPUI/CPropVariant.h>
|
||||
91
foobar2000/helpers/CSingleThreadWrapper.h
Normal file
91
foobar2000/helpers/CSingleThreadWrapper.h
Normal file
@@ -0,0 +1,91 @@
|
||||
#pragma once
|
||||
|
||||
#include "ThreadUtils.h"
|
||||
#include "rethrow.h"
|
||||
|
||||
namespace ThreadUtils {
|
||||
|
||||
// OLD single thread wrapper class used only by old WMA input
|
||||
// Modern code should use cmdThread which is all kinds of prettier
|
||||
template<typename TBase, bool processMsgs = false>
|
||||
class CSingleThreadWrapper : protected CVerySimpleThread {
|
||||
private:
|
||||
protected:
|
||||
class command {
|
||||
protected:
|
||||
command() : m_abort(), m_completionEvent() {}
|
||||
virtual void executeImpl(TBase &) {}
|
||||
virtual ~command() {}
|
||||
public:
|
||||
void execute(TBase & obj) {
|
||||
m_rethrow.exec( [this, &obj] {executeImpl(obj); } );
|
||||
SetEvent(m_completionEvent);
|
||||
}
|
||||
void rethrow() const {
|
||||
m_rethrow.rethrow();
|
||||
}
|
||||
CRethrow m_rethrow;
|
||||
HANDLE m_completionEvent;
|
||||
abort_callback * m_abort;
|
||||
};
|
||||
|
||||
typedef std::function<void (TBase&) > func_t;
|
||||
|
||||
class command2 : public command {
|
||||
public:
|
||||
command2( func_t f ) : m_func(f) {}
|
||||
void executeImpl(TBase & obj) {
|
||||
m_func(obj);
|
||||
}
|
||||
private:
|
||||
|
||||
func_t m_func;
|
||||
};
|
||||
|
||||
typedef pfc::rcptr_t<command> command_ptr;
|
||||
|
||||
CSingleThreadWrapper() {
|
||||
m_completionEvent.create(true,false);
|
||||
this->StartThread();
|
||||
//start();
|
||||
}
|
||||
|
||||
~CSingleThreadWrapper() {
|
||||
m_threadAbort.abort();
|
||||
this->WaitTillThreadDone();
|
||||
}
|
||||
|
||||
void invokeCommand2( func_t f, abort_callback & abort) {
|
||||
auto c = pfc::rcnew_t<command2>(f);
|
||||
invokeCommand( c, abort );
|
||||
}
|
||||
void invokeCommand(command_ptr cmd, abort_callback & abort) {
|
||||
abort.check();
|
||||
m_completionEvent.set_state(false);
|
||||
pfc::vartoggle_t<abort_callback*> abortToggle(cmd->m_abort, &abort);
|
||||
pfc::vartoggle_t<HANDLE> eventToggle(cmd->m_completionEvent, m_completionEvent.get() );
|
||||
m_commands.Add(cmd);
|
||||
m_completionEvent.wait_for(-1);
|
||||
//WaitAbortable(m_completionEvent.get(), abort);
|
||||
cmd->rethrow();
|
||||
}
|
||||
|
||||
private:
|
||||
void ThreadProc() {
|
||||
TRACK_CALL_TEXT("CSingleThreadWrapper entry");
|
||||
try {
|
||||
TBase instance;
|
||||
for(;;) {
|
||||
command_ptr cmd;
|
||||
if (processMsgs) m_commands.Get_MsgLoop(cmd, m_threadAbort);
|
||||
else m_commands.Get(cmd, m_threadAbort);
|
||||
cmd->execute(instance);
|
||||
}
|
||||
} catch(...) {}
|
||||
if (processMsgs) ProcessPendingMessages();
|
||||
}
|
||||
win32_event m_completionEvent;
|
||||
CObjectQueue<command_ptr> m_commands;
|
||||
abort_callback_impl m_threadAbort;
|
||||
};
|
||||
}
|
||||
80
foobar2000/helpers/CTableEditHelper-Legacy.cpp
Normal file
80
foobar2000/helpers/CTableEditHelper-Legacy.cpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#include "stdafx.h"
|
||||
#include "CTableEditHelper-Legacy.h"
|
||||
#include <libPPUI/listview_helper.h>
|
||||
|
||||
namespace InPlaceEdit {
|
||||
void CTableEditHelper::TableEdit_Start(HWND p_listview, unsigned p_item, unsigned p_column, unsigned p_itemcount, unsigned p_columncount, unsigned p_basecolumn, unsigned p_flags) {
|
||||
if (m_notify.is_valid() || p_columncount == 0 || p_itemcount == 0 || p_item >= p_itemcount || p_column >= p_columncount) return;
|
||||
m_listview = p_listview;
|
||||
m_item = p_item;
|
||||
m_column = p_column;
|
||||
m_itemcount = p_itemcount;
|
||||
m_columncount = p_columncount;
|
||||
m_basecolumn = p_basecolumn;
|
||||
m_flags = p_flags;
|
||||
_Start();
|
||||
}
|
||||
|
||||
void CTableEditHelper::TableEdit_Abort(bool p_forwardcontent) {
|
||||
if (m_notify.is_valid()) {
|
||||
m_notify->orphan();
|
||||
m_notify.release();
|
||||
|
||||
if (p_forwardcontent && (m_flags & KFlagReadOnly) == 0) {
|
||||
if (m_content.is_valid()) {
|
||||
pfc::string8 temp(*m_content);
|
||||
m_content.release();
|
||||
TableEdit_SetItemText(m_item, m_column, temp);
|
||||
}
|
||||
} else {
|
||||
m_content.release();
|
||||
}
|
||||
SetFocus(NULL);
|
||||
TableEdit_Finished();
|
||||
}
|
||||
}
|
||||
|
||||
bool CTableEditHelper::TableEdit_GetItemText(unsigned p_item, unsigned p_column, pfc::string_base & p_out, unsigned & p_linecount) {
|
||||
listview_helper::get_item_text(m_listview, p_item, p_column + m_basecolumn, p_out);
|
||||
p_linecount = pfc::is_multiline(p_out) ? 5 : 1;
|
||||
return true;
|
||||
}
|
||||
void CTableEditHelper::TableEdit_SetItemText(unsigned p_item, unsigned p_column, const char * p_text) {
|
||||
listview_helper::set_item_text(m_listview, p_item, p_column + m_basecolumn, p_text);
|
||||
}
|
||||
|
||||
void CTableEditHelper::on_task_completion(unsigned p_taskid, unsigned p_state) {
|
||||
if (p_taskid == KTaskID) {
|
||||
m_notify.release();
|
||||
if (m_content.is_valid()) {
|
||||
if (p_state & InPlaceEdit::KEditFlagContentChanged) {
|
||||
TableEdit_SetItemText(m_item, m_column, *m_content);
|
||||
}
|
||||
m_content.release();
|
||||
}
|
||||
/*if (InPlaceEdit::TableEditAdvance(m_item,m_column,m_itemcount,m_columncount,p_state))*/
|
||||
if (TableEdit_OnEditCompleted(m_item, m_column, p_state) &&
|
||||
InPlaceEdit::TableEditAdvance_ListView(m_listview, m_basecolumn, m_item, m_column, m_itemcount, m_columncount, p_state)) {
|
||||
_Start();
|
||||
} else {
|
||||
TableEdit_Finished();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CTableEditHelper::~CTableEditHelper() {
|
||||
if (m_notify.is_valid()) {
|
||||
m_notify->orphan();
|
||||
m_notify.release();
|
||||
}
|
||||
}
|
||||
|
||||
void CTableEditHelper::_Start() {
|
||||
listview_helper::select_single_item(m_listview, m_item);
|
||||
m_content.new_t();
|
||||
unsigned linecount = 1;
|
||||
if (!TableEdit_GetItemText(m_item, m_column, *m_content, linecount)) return;
|
||||
m_notify = completion_notify_create(this, KTaskID);
|
||||
InPlaceEdit::Start_FromListViewEx(m_listview, m_item, m_column + m_basecolumn, linecount, m_flags, m_content, m_notify);
|
||||
}
|
||||
}
|
||||
35
foobar2000/helpers/CTableEditHelper-Legacy.h
Normal file
35
foobar2000/helpers/CTableEditHelper-Legacy.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
#include "inplace_edit.h"
|
||||
#include <libPPUI/listview_helper.h>
|
||||
|
||||
namespace InPlaceEdit {
|
||||
class CTableEditHelper {
|
||||
public:
|
||||
void TableEdit_Start(HWND p_listview, unsigned p_item, unsigned p_column, unsigned p_itemcount, unsigned p_columncount, unsigned p_basecolumn, unsigned p_flags = 0);
|
||||
void TableEdit_Abort(bool p_forwardcontent);
|
||||
bool TableEdit_IsActive() const {return m_notify.is_valid();}
|
||||
|
||||
virtual bool TableEdit_GetItemText(unsigned p_item, unsigned p_column, pfc::string_base & p_out, unsigned & p_linecount);
|
||||
virtual void TableEdit_SetItemText(unsigned p_item, unsigned p_column, const char * p_text);
|
||||
|
||||
virtual void TableEdit_Finished() {}
|
||||
|
||||
void on_task_completion(unsigned p_taskid, unsigned p_state);
|
||||
~CTableEditHelper();
|
||||
protected:
|
||||
HWND TableEdit_GetListView() const { return m_listview; }
|
||||
//return false to abort
|
||||
virtual bool TableEdit_OnEditCompleted(unsigned item, unsigned column, unsigned state) { return true; }
|
||||
private:
|
||||
void _Start();
|
||||
enum {
|
||||
KTaskID = 0xc0ffee
|
||||
};
|
||||
HWND m_listview;
|
||||
unsigned m_item, m_column;
|
||||
unsigned m_itemcount, m_columncount, m_basecolumn;
|
||||
unsigned m_flags;
|
||||
pfc::rcptr_t<pfc::string8> m_content;
|
||||
service_ptr_t<completion_notify_orphanable> m_notify;
|
||||
};
|
||||
}
|
||||
60
foobar2000/helpers/CallForwarder.h
Normal file
60
foobar2000/helpers/CallForwarder.h
Normal file
@@ -0,0 +1,60 @@
|
||||
#pragma once
|
||||
|
||||
namespace CF {
|
||||
template<typename obj_t, typename arg_t> class _inMainThread : public main_thread_callback {
|
||||
public:
|
||||
_inMainThread(obj_t const & obj, const arg_t & arg) : m_obj(obj), m_arg(arg) {}
|
||||
|
||||
void callback_run() {
|
||||
if (m_obj.IsValid()) callInMainThread::callThis(&*m_obj, m_arg);
|
||||
}
|
||||
private:
|
||||
obj_t m_obj;
|
||||
arg_t m_arg;
|
||||
};
|
||||
|
||||
template<typename TWhat> class CallForwarder {
|
||||
private:
|
||||
CallForwarder() = delete;
|
||||
protected:
|
||||
CallForwarder(TWhat * ptr) : m_ptr(pfc::rcnew_t<TWhat*>(ptr)) {}
|
||||
void Orphan() {
|
||||
*m_ptr = NULL;
|
||||
}
|
||||
public:
|
||||
bool IsValid() const {
|
||||
PFC_ASSERT( m_ptr.is_valid() );
|
||||
return m_ptr.is_valid() && *m_ptr != NULL;
|
||||
}
|
||||
bool IsEmpty() const { return !IsValid(); }
|
||||
|
||||
TWhat * operator->() const {
|
||||
PFC_ASSERT( IsValid() );
|
||||
return *m_ptr;
|
||||
}
|
||||
|
||||
TWhat & operator*() const {
|
||||
PFC_ASSERT( IsValid() );
|
||||
return **m_ptr;
|
||||
}
|
||||
|
||||
template<typename arg_t>
|
||||
void callInMainThread(const arg_t & arg) {
|
||||
main_thread_callback_add( new service_impl_t<_inMainThread< CallForwarder<TWhat>, arg_t> >(*this, arg) );
|
||||
}
|
||||
private:
|
||||
const pfc::rcptr_t<TWhat*> m_ptr;
|
||||
};
|
||||
|
||||
template<typename TWhat> class CallForwarderMaster : public CallForwarder<TWhat> {
|
||||
public:
|
||||
CallForwarderMaster(TWhat * ptr) : CallForwarder<TWhat>(ptr) {PFC_ASSERT(ptr!=NULL);}
|
||||
~CallForwarderMaster() { this->Orphan(); }
|
||||
using CallForwarder<TWhat>::Orphan;
|
||||
private:
|
||||
CallForwarderMaster() = delete;
|
||||
CallForwarderMaster( const CallForwarderMaster<TWhat> & ) = delete;
|
||||
void operator=( const CallForwarderMaster & ) = delete;
|
||||
};
|
||||
|
||||
}
|
||||
132
foobar2000/helpers/CmdThread.h
Normal file
132
foobar2000/helpers/CmdThread.h
Normal file
@@ -0,0 +1,132 @@
|
||||
#pragma once
|
||||
#include <pfc/wait_queue.h>
|
||||
#include <pfc/pool.h>
|
||||
#include <pfc/threads.h>
|
||||
#include <functional>
|
||||
#include "rethrow.h"
|
||||
#include <pfc/timers.h>
|
||||
|
||||
namespace ThreadUtils {
|
||||
|
||||
// Serialize access to some resource to a single thread
|
||||
// Execute blocking/nonabortable methods in with proper abortability (detach on abort and move on)
|
||||
class cmdThread {
|
||||
public:
|
||||
typedef std::function<void () > func_t;
|
||||
typedef pfc::waitQueue<func_t> queue_t;
|
||||
typedef std::function<void (abort_callback&) > funcAbortable_t;
|
||||
|
||||
protected:
|
||||
std::function<void () > makeWorker() {
|
||||
auto q = m_queue;
|
||||
auto x = m_atExit;
|
||||
return [q, x] {
|
||||
for ( ;; ) {
|
||||
func_t f;
|
||||
if (!q->get(f)) break;
|
||||
try { f(); } catch(...) {}
|
||||
}
|
||||
// No guard for atExit access, as nobody is supposed to be still able to call host object methods by the time we get here
|
||||
for( auto i = x->begin(); i != x->end(); ++ i ) {
|
||||
auto f = *i;
|
||||
try { f(); } catch(...) {}
|
||||
}
|
||||
};
|
||||
};
|
||||
std::function<void () > makeWorker2( std::function<void()> updater, double interval) {
|
||||
auto q = m_queue;
|
||||
auto x = m_atExit;
|
||||
return [=] {
|
||||
pfc::lores_timer t; t.start();
|
||||
for ( ;; ) {
|
||||
{
|
||||
bool bWorkReady = false;
|
||||
double left = interval - t.query();
|
||||
if ( left > 0 ) {
|
||||
if (q->wait_read( left )) bWorkReady = true;
|
||||
}
|
||||
|
||||
if (!bWorkReady) {
|
||||
updater();
|
||||
t.start();
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
func_t f;
|
||||
if (!q->get(f)) break;
|
||||
try { f(); } catch(...) {}
|
||||
}
|
||||
// No guard for atExit access, as nobody is supposed to be still able to call host object methods by the time we get here
|
||||
for( auto i = x->begin(); i != x->end(); ++ i ) {
|
||||
auto f = *i;
|
||||
try { f(); } catch(...) {}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// For derived classes: create new instance without starting thread, supply thread using by yourself
|
||||
class noCreate {};
|
||||
cmdThread( noCreate ) {}
|
||||
public:
|
||||
|
||||
cmdThread() {
|
||||
pfc::splitThread( makeWorker() );
|
||||
}
|
||||
|
||||
void atExit( func_t f ) {
|
||||
m_atExit->push_back(f);
|
||||
}
|
||||
~cmdThread() {
|
||||
m_queue->set_eof();
|
||||
}
|
||||
void runSynchronously( func_t f ) { runSynchronously_(f, nullptr); }
|
||||
void runSynchronously_( func_t f, abort_callback * abortOrNull ) {
|
||||
auto evt = m_eventPool.make();
|
||||
evt->set_state(false);
|
||||
auto rethrow = std::make_shared<ThreadUtils::CRethrow>();
|
||||
auto worker2 = [f, rethrow, evt] {
|
||||
rethrow->exec(f);
|
||||
evt->set_state( true );
|
||||
};
|
||||
|
||||
add ( worker2 );
|
||||
|
||||
if ( abortOrNull != nullptr ) {
|
||||
abortOrNull->waitForEvent( * evt, -1 );
|
||||
} else {
|
||||
evt->wait_for(-1);
|
||||
}
|
||||
|
||||
m_eventPool.put( evt );
|
||||
|
||||
rethrow->rethrow();
|
||||
}
|
||||
void runSynchronously( func_t f, abort_callback & abort ) {
|
||||
runSynchronously_(f, &abort);
|
||||
}
|
||||
void runSynchronously2( funcAbortable_t f, abort_callback & abort ) {
|
||||
auto subAbort = m_abortPool.make();
|
||||
subAbort->reset();
|
||||
auto worker = [subAbort, f] {
|
||||
f(*subAbort);
|
||||
};
|
||||
|
||||
try {
|
||||
runSynchronously( worker, abort );
|
||||
} catch(...) {
|
||||
subAbort->set(); throw;
|
||||
}
|
||||
|
||||
m_abortPool.put( subAbort );
|
||||
}
|
||||
|
||||
void add( func_t f ) { m_queue->put( f ); }
|
||||
private:
|
||||
pfc::objPool<pfc::event> m_eventPool;
|
||||
pfc::objPool<abort_callback_impl> m_abortPool;
|
||||
std::shared_ptr<queue_t> m_queue = std::make_shared<queue_t>();
|
||||
typedef std::list<func_t> atExit_t;
|
||||
std::shared_ptr<atExit_t> m_atExit = std::make_shared< atExit_t >();
|
||||
};
|
||||
}
|
||||
279
foobar2000/helpers/ProcessUtils.h
Normal file
279
foobar2000/helpers/ProcessUtils.h
Normal file
@@ -0,0 +1,279 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef FOOBAR2000_DESKTOP_WINDOWS
|
||||
|
||||
#include <libPPUI/win32_op.h>
|
||||
|
||||
namespace ProcessUtils {
|
||||
class PipeIO : public stream_reader, public stream_writer {
|
||||
public:
|
||||
PFC_DECLARE_EXCEPTION(timeout, exception_io, "Timeout");
|
||||
PipeIO(HANDLE handle, HANDLE hEvent, bool processMessages, DWORD timeOut = INFINITE) : m_handle(handle), m_event(hEvent), m_processMessages(processMessages), m_timeOut(timeOut) {
|
||||
}
|
||||
~PipeIO() {
|
||||
}
|
||||
|
||||
void write(const void * p_buffer,size_t p_bytes, abort_callback & abort) {
|
||||
if (p_bytes == 0) return;
|
||||
OVERLAPPED ol = {};
|
||||
ol.hEvent = m_event;
|
||||
ResetEvent(m_event);
|
||||
DWORD bytesWritten;
|
||||
SetLastError(NO_ERROR);
|
||||
if (WriteFile( m_handle, p_buffer, pfc::downcast_guarded<DWORD>(p_bytes), &bytesWritten, &ol)) {
|
||||
// succeeded already?
|
||||
if (bytesWritten != p_bytes) throw exception_io();
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
const DWORD code = GetLastError();
|
||||
if (code != ERROR_IO_PENDING) exception_io_from_win32(code);
|
||||
}
|
||||
const HANDLE handles[] = {m_event, abort.get_abort_event()};
|
||||
SetLastError(NO_ERROR);
|
||||
DWORD state = myWait(_countof(handles), handles);
|
||||
if (state == WAIT_OBJECT_0) {
|
||||
try {
|
||||
WIN32_IO_OP( GetOverlappedResult(m_handle,&ol,&bytesWritten,TRUE) );
|
||||
} catch(...) {
|
||||
_cancel(ol);
|
||||
throw;
|
||||
}
|
||||
if (bytesWritten != p_bytes) throw exception_io();
|
||||
return;
|
||||
}
|
||||
_cancel(ol);
|
||||
abort.check();
|
||||
throw timeout();
|
||||
}
|
||||
size_t read(void * p_buffer,size_t p_bytes, abort_callback & abort) {
|
||||
uint8_t * ptr = (uint8_t*) p_buffer;
|
||||
size_t done = 0;
|
||||
while(done < p_bytes) {
|
||||
abort.check();
|
||||
size_t delta = readPass(ptr + done, p_bytes - done, abort);
|
||||
if (delta == 0) break;
|
||||
done += delta;
|
||||
}
|
||||
return done;
|
||||
}
|
||||
size_t readPass(void * p_buffer,size_t p_bytes, abort_callback & abort) {
|
||||
if (p_bytes == 0) return 0;
|
||||
OVERLAPPED ol = {};
|
||||
ol.hEvent = m_event;
|
||||
ResetEvent(m_event);
|
||||
DWORD bytesDone;
|
||||
SetLastError(NO_ERROR);
|
||||
if (ReadFile( m_handle, p_buffer, pfc::downcast_guarded<DWORD>(p_bytes), &bytesDone, &ol)) {
|
||||
// succeeded already?
|
||||
return bytesDone;
|
||||
}
|
||||
|
||||
{
|
||||
const DWORD code = GetLastError();
|
||||
switch(code) {
|
||||
case ERROR_HANDLE_EOF:
|
||||
return 0;
|
||||
case ERROR_IO_PENDING:
|
||||
break; // continue
|
||||
default:
|
||||
exception_io_from_win32(code);
|
||||
};
|
||||
}
|
||||
|
||||
const HANDLE handles[] = {m_event, abort.get_abort_event()};
|
||||
SetLastError(NO_ERROR);
|
||||
DWORD state = myWait(_countof(handles), handles);
|
||||
if (state == WAIT_OBJECT_0) {
|
||||
SetLastError(NO_ERROR);
|
||||
if (!GetOverlappedResult(m_handle,&ol,&bytesDone,TRUE)) {
|
||||
const DWORD code = GetLastError();
|
||||
if (code == ERROR_HANDLE_EOF) bytesDone = 0;
|
||||
else {
|
||||
_cancel(ol);
|
||||
exception_io_from_win32(code);
|
||||
}
|
||||
}
|
||||
return bytesDone;
|
||||
}
|
||||
_cancel(ol);
|
||||
abort.check();
|
||||
throw timeout();
|
||||
}
|
||||
private:
|
||||
DWORD myWait(DWORD count, const HANDLE * handles) {
|
||||
if (m_processMessages) {
|
||||
for(;;) {
|
||||
DWORD state = MsgWaitForMultipleObjects(count, handles, FALSE, m_timeOut, QS_ALLINPUT);
|
||||
if (state == WAIT_OBJECT_0 + count) {
|
||||
ProcessPendingMessages();
|
||||
} else {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return WaitForMultipleObjects(count, handles, FALSE, m_timeOut);
|
||||
}
|
||||
}
|
||||
void _cancel(OVERLAPPED & ol) {
|
||||
#if _WIN32_WINNT >= 0x600
|
||||
CancelIoEx(m_handle,&ol);
|
||||
#else
|
||||
CancelIo(m_handle);
|
||||
#endif
|
||||
}
|
||||
static void ProcessPendingMessages() {
|
||||
MSG msg = {};
|
||||
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) DispatchMessage(&msg);
|
||||
}
|
||||
|
||||
HANDLE m_handle;
|
||||
HANDLE m_event;
|
||||
const DWORD m_timeOut;
|
||||
const bool m_processMessages;
|
||||
};
|
||||
|
||||
class SubProcess : public stream_reader, public stream_writer {
|
||||
public:
|
||||
PFC_DECLARE_EXCEPTION(failure, std::exception, "Unexpected failure");
|
||||
|
||||
SubProcess(const char * exePath, DWORD timeOutMS = 60*1000) : ExePath(exePath), hStdIn(), hStdOut(), hProcess(), ProcessMessages(false), TimeOutMS(timeOutMS) {
|
||||
HANDLE ev;
|
||||
WIN32_OP( (ev = CreateEvent(NULL, TRUE, FALSE, NULL)) != NULL );
|
||||
hEventRead = ev;
|
||||
WIN32_OP( (ev = CreateEvent(NULL, TRUE, FALSE, NULL)) != NULL );
|
||||
hEventWrite = ev;
|
||||
Restart();
|
||||
}
|
||||
void Restart() {
|
||||
CleanUp();
|
||||
STARTUPINFO si = {};
|
||||
try {
|
||||
si.cb = sizeof(si);
|
||||
si.dwFlags = STARTF_USESTDHANDLES | STARTF_FORCEOFFFEEDBACK;
|
||||
//si.wShowWindow = SW_HIDE;
|
||||
|
||||
myCreatePipeOut(si.hStdInput, hStdIn);
|
||||
myCreatePipeIn(hStdOut, si.hStdOutput);
|
||||
si.hStdError = GetStdHandle(STD_ERROR_HANDLE);
|
||||
|
||||
PROCESS_INFORMATION pi = {};
|
||||
try {
|
||||
WIN32_OP( CreateProcess(pfc::stringcvt::string_os_from_utf8(ExePath), NULL, NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi) );
|
||||
} catch(std::exception const & e) {
|
||||
throw failure(PFC_string_formatter() << "Could not start the worker process - " << e);
|
||||
}
|
||||
hProcess = pi.hProcess; _Close(pi.hThread);
|
||||
} catch(...) {
|
||||
_Close(si.hStdInput);
|
||||
_Close(si.hStdOutput);
|
||||
CleanUp(); throw;
|
||||
}
|
||||
_Close(si.hStdInput);
|
||||
_Close(si.hStdOutput);
|
||||
}
|
||||
~SubProcess() {
|
||||
CleanUp();
|
||||
CloseHandle(hEventRead);
|
||||
CloseHandle(hEventWrite);
|
||||
}
|
||||
|
||||
bool IsRunning() const {
|
||||
return hProcess != NULL;
|
||||
}
|
||||
void Detach() {
|
||||
CleanUp(true);
|
||||
}
|
||||
bool ProcessMessages;
|
||||
DWORD TimeOutMS;
|
||||
|
||||
|
||||
void write(const void * p_buffer,size_t p_bytes, abort_callback & abort) {
|
||||
PipeIO writer(hStdIn, hEventWrite, ProcessMessages, TimeOutMS);
|
||||
writer.write(p_buffer, p_bytes, abort);
|
||||
}
|
||||
size_t read(void * p_buffer,size_t p_bytes, abort_callback & abort) {
|
||||
PipeIO reader(hStdOut, hEventRead, ProcessMessages, TimeOutMS);
|
||||
return reader.read(p_buffer, p_bytes, abort);
|
||||
}
|
||||
void SetPriority(DWORD val) {
|
||||
SetPriorityClass(hProcess, val);
|
||||
}
|
||||
protected:
|
||||
HANDLE hStdIn, hStdOut, hProcess, hEventRead, hEventWrite;
|
||||
const pfc::string8 ExePath;
|
||||
|
||||
void CleanUp(bool bDetach = false) {
|
||||
_Close(hStdIn); _Close(hStdOut);
|
||||
if (hProcess != NULL) {
|
||||
if (!bDetach) {
|
||||
if (WaitForSingleObject(hProcess, TimeOutMS) != WAIT_OBJECT_0) {
|
||||
//PFC_ASSERT( !"Should not get here - worker stuck" );
|
||||
FB2K_console_formatter() << pfc::string_filename_ext(ExePath) << " unresponsive - terminating";
|
||||
TerminateProcess(hProcess, -1);
|
||||
}
|
||||
}
|
||||
_Close(hProcess);
|
||||
}
|
||||
}
|
||||
private:
|
||||
static void _Close(HANDLE & h) {
|
||||
if (h != NULL) {CloseHandle(h); h = NULL;}
|
||||
}
|
||||
|
||||
static void myCreatePipe(HANDLE & in, HANDLE & out) {
|
||||
SECURITY_ATTRIBUTES Attributes = { sizeof(SECURITY_ATTRIBUTES), 0, true };
|
||||
WIN32_OP( CreatePipe( &in, &out, &Attributes, 0 ) );
|
||||
}
|
||||
|
||||
static pfc::string_formatter makePipeName() {
|
||||
GUID id;
|
||||
CoCreateGuid (&id);
|
||||
return PFC_string_formatter() << "\\\\.\\pipe\\" << pfc::print_guid(id);
|
||||
}
|
||||
|
||||
static void myCreatePipeOut(HANDLE & in, HANDLE & out) {
|
||||
SECURITY_ATTRIBUTES Attributes = { sizeof(SECURITY_ATTRIBUTES), 0, true };
|
||||
const pfc::stringcvt::string_os_from_utf8 pipeName( makePipeName() );
|
||||
SetLastError(NO_ERROR);
|
||||
HANDLE pipe = CreateNamedPipe(
|
||||
pipeName,
|
||||
FILE_FLAG_FIRST_PIPE_INSTANCE | PIPE_ACCESS_OUTBOUND | FILE_FLAG_OVERLAPPED,
|
||||
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
|
||||
1,
|
||||
1024*64,
|
||||
1024*64,
|
||||
NMPWAIT_USE_DEFAULT_WAIT,&Attributes);
|
||||
if (pipe == INVALID_HANDLE_VALUE) throw exception_win32(GetLastError());
|
||||
|
||||
in = CreateFile(pipeName,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE,&Attributes,OPEN_EXISTING,0,NULL);
|
||||
DuplicateHandle ( GetCurrentProcess(), pipe, GetCurrentProcess(), &out, 0, FALSE, DUPLICATE_SAME_ACCESS );
|
||||
CloseHandle(pipe);
|
||||
}
|
||||
|
||||
static void myCreatePipeIn(HANDLE & in, HANDLE & out) {
|
||||
SECURITY_ATTRIBUTES Attributes = { sizeof(SECURITY_ATTRIBUTES), 0, true };
|
||||
const pfc::stringcvt::string_os_from_utf8 pipeName( makePipeName() );
|
||||
SetLastError(NO_ERROR);
|
||||
HANDLE pipe = CreateNamedPipe(
|
||||
pipeName,
|
||||
FILE_FLAG_FIRST_PIPE_INSTANCE | PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
|
||||
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
|
||||
1,
|
||||
1024*64,
|
||||
1024*64,
|
||||
NMPWAIT_USE_DEFAULT_WAIT,&Attributes);
|
||||
if (pipe == INVALID_HANDLE_VALUE) throw exception_win32(GetLastError());
|
||||
|
||||
out = CreateFile(pipeName,GENERIC_WRITE,FILE_SHARE_READ|FILE_SHARE_WRITE,&Attributes,OPEN_EXISTING,0,NULL);
|
||||
DuplicateHandle ( GetCurrentProcess(), pipe, GetCurrentProcess(), &in, 0, FALSE, DUPLICATE_SAME_ACCESS );
|
||||
CloseHandle(pipe);
|
||||
}
|
||||
|
||||
PFC_CLASS_NOT_COPYABLE_EX(SubProcess)
|
||||
};
|
||||
}
|
||||
|
||||
#endif // FOOBAR2000_DESKTOP_WINDOWS
|
||||
|
||||
39
foobar2000/helpers/ProfileCache.h
Normal file
39
foobar2000/helpers/ProfileCache.h
Normal file
@@ -0,0 +1,39 @@
|
||||
#pragma once
|
||||
|
||||
namespace ProfileCache {
|
||||
inline file::ptr FetchFile(const char * context, const char * name, const char * webURL, t_filetimestamp acceptableAge, abort_callback & abort) {
|
||||
const double timeoutVal = 5;
|
||||
|
||||
auto path = core_api::pathInProfile( context );
|
||||
auto fsLocal = filesystem::get(path);
|
||||
fsLocal->make_directory(path, abort);
|
||||
path.add_filename( name );
|
||||
|
||||
bool fetch = false;
|
||||
file::ptr fLocal;
|
||||
|
||||
try {
|
||||
fLocal = fsLocal->openWriteExisting(path, abort, timeoutVal );
|
||||
fetch = fLocal->get_timestamp(abort) < filetimestamp_from_system_timer() - acceptableAge;
|
||||
} catch(exception_io_not_found) {
|
||||
fLocal = fsLocal->openWriteNew(path, abort, timeoutVal);
|
||||
fetch = true;
|
||||
}
|
||||
if (fetch) {
|
||||
try {
|
||||
fLocal->resize(0, abort);
|
||||
file::ptr fRemote;
|
||||
filesystem::g_open(fRemote, webURL, filesystem::open_mode_read, abort);
|
||||
file::g_transfer_file(fRemote, fLocal, abort );
|
||||
} catch(exception_io) {
|
||||
fLocal.release();
|
||||
try {
|
||||
retryOnSharingViolation(timeoutVal, abort, [&] {fsLocal->remove(path, abort);} );
|
||||
} catch(...) {}
|
||||
throw;
|
||||
}
|
||||
fLocal->seek(0, abort);
|
||||
}
|
||||
return fLocal;
|
||||
}
|
||||
};
|
||||
6
foobar2000/helpers/StdAfx.cpp
Normal file
6
foobar2000/helpers/StdAfx.cpp
Normal file
@@ -0,0 +1,6 @@
|
||||
// stdafx.cpp : source file that includes just the standard includes
|
||||
// foobar2000_sdk_helpers.pch will be the pre-compiled header
|
||||
// stdafx.obj will contain the pre-compiled type information
|
||||
|
||||
#include "stdafx.h"
|
||||
|
||||
20
foobar2000/helpers/StdAfx.h
Normal file
20
foobar2000/helpers/StdAfx.h
Normal file
@@ -0,0 +1,20 @@
|
||||
// stdafx.h : include file for standard system include files,
|
||||
// or project specific include files that are used frequently, but
|
||||
// are changed infrequently
|
||||
//
|
||||
|
||||
#if !defined(AFX_STDAFX_H__6356EC2B_6DD1_4BE8_935C_87ECBA8697E4__INCLUDED_)
|
||||
#define AFX_STDAFX_H__6356EC2B_6DD1_4BE8_935C_87ECBA8697E4__INCLUDED_
|
||||
|
||||
#if _MSC_VER > 1000
|
||||
#pragma once
|
||||
#endif // _MSC_VER > 1000
|
||||
|
||||
#include "foobar2000+atl.h"
|
||||
|
||||
// TODO: reference additional headers your program requires here
|
||||
|
||||
//{{AFX_INSERT_LOCATION}}
|
||||
// Microsoft Visual C++ will insert additional declarations immediately before the previous line.
|
||||
|
||||
#endif // !defined(AFX_STDAFX_H__6356EC2B_6DD1_4BE8_935C_87ECBA8697E4__INCLUDED_)
|
||||
139
foobar2000/helpers/ThreadUtils.cpp
Normal file
139
foobar2000/helpers/ThreadUtils.cpp
Normal file
@@ -0,0 +1,139 @@
|
||||
#include "StdAfx.h"
|
||||
|
||||
#include "ThreadUtils.h"
|
||||
#include "rethrow.h"
|
||||
|
||||
#include <exception>
|
||||
|
||||
namespace ThreadUtils {
|
||||
bool CRethrow::exec( std::function<void () > f ) throw() {
|
||||
m_rethrow = nullptr;
|
||||
bool rv = false;
|
||||
try {
|
||||
f();
|
||||
rv = true;
|
||||
} catch( ... ) {
|
||||
auto e = std::current_exception();
|
||||
m_rethrow = [e] { std::rethrow_exception(e); };
|
||||
}
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
void CRethrow::rethrow() const {
|
||||
if ( m_rethrow ) m_rethrow();
|
||||
}
|
||||
}
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "win32_misc.h"
|
||||
|
||||
#ifdef FOOBAR2000_MOBILE
|
||||
#include <pfc/pp-winapi.h>
|
||||
#endif
|
||||
|
||||
|
||||
namespace ThreadUtils {
|
||||
bool WaitAbortable(HANDLE ev, abort_callback & abort, DWORD timeout) {
|
||||
const HANDLE handles[2] = {ev, abort.get_abort_event()};
|
||||
SetLastError(0);
|
||||
const DWORD status = WaitForMultipleObjects(2, handles, FALSE, timeout);
|
||||
switch(status) {
|
||||
case WAIT_TIMEOUT:
|
||||
PFC_ASSERT( timeout != INFINITE );
|
||||
return false;
|
||||
case WAIT_OBJECT_0:
|
||||
return true;
|
||||
case WAIT_OBJECT_0 + 1:
|
||||
throw exception_aborted();
|
||||
case WAIT_FAILED:
|
||||
WIN32_OP_FAIL();
|
||||
default:
|
||||
uBugCheck();
|
||||
}
|
||||
}
|
||||
#ifdef FOOBAR2000_DESKTOP_WINDOWS
|
||||
void ProcessPendingMessagesWithDialog(HWND hDialog) {
|
||||
MSG msg = {};
|
||||
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||
if (!IsDialogMessage(hDialog, &msg)) {
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
void ProcessPendingMessages() {
|
||||
MSG msg = {};
|
||||
while(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
}
|
||||
void WaitAbortable_MsgLoop(HANDLE ev, abort_callback & abort) {
|
||||
abort.check();
|
||||
const HANDLE handles[2] = {ev, abort.get_abort_event()};
|
||||
MultiWait_MsgLoop(handles, 2);
|
||||
abort.check();
|
||||
}
|
||||
t_size MultiWaitAbortable_MsgLoop(const HANDLE * ev, t_size evCount, abort_callback & abort) {
|
||||
abort.check();
|
||||
pfc::array_t<HANDLE> handles; handles.set_size(evCount + 1);
|
||||
handles[0] = abort.get_abort_event();
|
||||
pfc::memcpy_t(handles.get_ptr() + 1, ev, evCount);
|
||||
DWORD status = MultiWait_MsgLoop(handles.get_ptr(), handles.get_count());
|
||||
abort.check();
|
||||
return (size_t)(status - WAIT_OBJECT_0);
|
||||
}
|
||||
|
||||
void SleepAbortable_MsgLoop(abort_callback & abort, DWORD timeout) {
|
||||
HANDLE handles[] = { abort.get_abort_event() };
|
||||
MultiWait_MsgLoop(handles, 1, timeout);
|
||||
abort.check();
|
||||
}
|
||||
|
||||
bool WaitAbortable_MsgLoop(HANDLE ev, abort_callback & abort, DWORD timeout) {
|
||||
abort.check();
|
||||
HANDLE handles[2] = { abort.get_abort_event(), ev };
|
||||
DWORD status = MultiWait_MsgLoop(handles, 2, timeout);
|
||||
abort.check();
|
||||
return status != WAIT_TIMEOUT;
|
||||
}
|
||||
|
||||
DWORD MultiWait_MsgLoop(const HANDLE* ev, DWORD evCount) {
|
||||
for (;; ) {
|
||||
SetLastError(0);
|
||||
const DWORD status = MsgWaitForMultipleObjects((DWORD) evCount, ev, FALSE, INFINITE, QS_ALLINPUT);
|
||||
if (status == WAIT_FAILED) WIN32_OP_FAIL();
|
||||
if (status == WAIT_OBJECT_0 + evCount) {
|
||||
ProcessPendingMessages();
|
||||
} else if ( status >= WAIT_OBJECT_0 && status < WAIT_OBJECT_0 + evCount ) {
|
||||
return status;
|
||||
} else {
|
||||
uBugCheck();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
DWORD MultiWait_MsgLoop(const HANDLE* ev, DWORD evCount, DWORD timeout) {
|
||||
if (timeout == INFINITE) return MultiWait_MsgLoop(ev, evCount);
|
||||
const DWORD entry = GetTickCount();
|
||||
DWORD now = entry;
|
||||
for (;;) {
|
||||
const DWORD done = now - entry;
|
||||
if (done >= timeout) return WAIT_TIMEOUT;
|
||||
SetLastError(0);
|
||||
const DWORD status = MsgWaitForMultipleObjects((DWORD)evCount, ev, FALSE, timeout - done, QS_ALLINPUT);
|
||||
if (status == WAIT_FAILED) WIN32_OP_FAIL();
|
||||
if (status == WAIT_OBJECT_0 + evCount) {
|
||||
ProcessPendingMessages();
|
||||
} else if (status == WAIT_TIMEOUT || (status >= WAIT_OBJECT_0 && status < WAIT_OBJECT_0 + evCount) ) {
|
||||
return status;
|
||||
} else {
|
||||
uBugCheck();
|
||||
}
|
||||
now = GetTickCount();
|
||||
}
|
||||
}
|
||||
|
||||
#endif // FOOBAR2000_DESKTOP_WINDOWS
|
||||
|
||||
}
|
||||
#endif
|
||||
57
foobar2000/helpers/ThreadUtils.h
Normal file
57
foobar2000/helpers/ThreadUtils.h
Normal file
@@ -0,0 +1,57 @@
|
||||
#pragma once
|
||||
#include "fb2k_threads.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <functional>
|
||||
|
||||
namespace ThreadUtils {
|
||||
bool WaitAbortable(HANDLE ev, abort_callback & abort, DWORD timeout = INFINITE);
|
||||
|
||||
void ProcessPendingMessages();
|
||||
void ProcessPendingMessagesWithDialog(HWND hDialog);
|
||||
|
||||
void WaitAbortable_MsgLoop(HANDLE ev, abort_callback & abort);
|
||||
|
||||
t_size MultiWaitAbortable_MsgLoop(const HANDLE * ev, t_size evCount, abort_callback & abort);
|
||||
void SleepAbortable_MsgLoop(abort_callback & abort, DWORD timeout /*must not be INFINITE*/);
|
||||
bool WaitAbortable_MsgLoop(HANDLE ev, abort_callback & abort, DWORD timeout /*must not be INFINITE*/);
|
||||
|
||||
DWORD MultiWait_MsgLoop(const HANDLE* ev, DWORD evCount, DWORD timeout);
|
||||
DWORD MultiWait_MsgLoop(const HANDLE* ev, DWORD evCount);
|
||||
|
||||
template<typename TWhat>
|
||||
class CObjectQueue {
|
||||
public:
|
||||
CObjectQueue() { m_event.create(true,false); }
|
||||
|
||||
template<typename TSource> void Add(const TSource & source) {
|
||||
insync(m_sync);
|
||||
m_content.add_item(source);
|
||||
if (m_content.get_count() == 1) m_event.set_state(true);
|
||||
}
|
||||
template<typename TDestination> void Get(TDestination & out, abort_callback & abort) {
|
||||
WaitAbortable(m_event.get(), abort);
|
||||
_Get(out);
|
||||
}
|
||||
|
||||
template<typename TDestination> void Get_MsgLoop(TDestination & out, abort_callback & abort) {
|
||||
WaitAbortable_MsgLoop(m_event.get(), abort);
|
||||
_Get(out);
|
||||
}
|
||||
|
||||
private:
|
||||
template<typename TDestination> void _Get(TDestination & out) {
|
||||
insync(m_sync);
|
||||
auto iter = m_content.cfirst();
|
||||
FB2K_DYNAMIC_ASSERT( iter.is_valid() );
|
||||
out = *iter;
|
||||
m_content.remove(iter);
|
||||
if (m_content.get_count() == 0) m_event.set_state(false);
|
||||
}
|
||||
win32_event m_event;
|
||||
critical_section m_sync;
|
||||
pfc::chain_list_v2_t<TWhat> m_content;
|
||||
};
|
||||
|
||||
}
|
||||
#endif // _WIN32
|
||||
38
foobar2000/helpers/VisUtils.cpp
Normal file
38
foobar2000/helpers/VisUtils.cpp
Normal file
@@ -0,0 +1,38 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "VisUtils.h"
|
||||
|
||||
namespace VisUtils {
|
||||
void PrepareFFTChunk(audio_chunk const & source, audio_chunk & out, double centerOffset) {
|
||||
const t_uint32 channels = source.get_channel_count();
|
||||
const t_uint32 sampleRate = source.get_sample_rate();
|
||||
FB2K_DYNAMIC_ASSERT( sampleRate > 0 );
|
||||
out.set_channels(channels, source.get_channel_config());
|
||||
out.set_sample_rate(sampleRate);
|
||||
const t_size inSize = source.get_sample_count();
|
||||
const t_size fftSize = MatchFFTSize(inSize);
|
||||
out.set_sample_count(fftSize);
|
||||
out.set_data_size(fftSize * channels);
|
||||
if (fftSize >= inSize) { //rare case with *REALLY* small input
|
||||
pfc::memcpy_t( out.get_data(), source.get_data(), inSize * channels );
|
||||
pfc::memset_null_t( out.get_data() + inSize * channels, (fftSize - inSize) * channels );
|
||||
} else { //inSize > fftSize, we're using a subset of source chunk for the job, pick a subset around centerOffset.
|
||||
const double baseOffset = pfc::max_t<double>(0, centerOffset - 0.5 * (double)fftSize / (double)sampleRate);
|
||||
const t_size baseSample = pfc::min_t<t_size>( (t_size) audio_math::time_to_samples(baseOffset, sampleRate), inSize - fftSize);
|
||||
pfc::memcpy_t( out.get_data(), source.get_data() + baseSample * channels, fftSize * channels);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsValidFFTSize(t_size p_size) {
|
||||
return p_size >= 2 && (p_size & (p_size - 1)) == 0;
|
||||
}
|
||||
|
||||
t_size MatchFFTSize(t_size samples) {
|
||||
if (samples <= 2) return 2;
|
||||
t_size mask = 1;
|
||||
while(!IsValidFFTSize(samples)) {
|
||||
samples &= ~mask; mask <<= 1;
|
||||
}
|
||||
return samples;
|
||||
}
|
||||
};
|
||||
10
foobar2000/helpers/VisUtils.h
Normal file
10
foobar2000/helpers/VisUtils.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
namespace VisUtils {
|
||||
//! Turns an arbitrary audio_chunk into a valid chunk to run FFT on, with proper sample count etc.
|
||||
//! @param centerOffset Time offset (in seconds) inside the source chunk to center the output on, in case the FFT window is smaller than input data.
|
||||
void PrepareFFTChunk(audio_chunk const & source, audio_chunk & out, double centerOffset);
|
||||
|
||||
bool IsValidFFTSize(t_size size);
|
||||
t_size MatchFFTSize(t_size samples);
|
||||
};
|
||||
22
foobar2000/helpers/VolumeMap.cpp
Normal file
22
foobar2000/helpers/VolumeMap.cpp
Normal file
@@ -0,0 +1,22 @@
|
||||
#include "stdafx.h"
|
||||
#include "VolumeMap.h"
|
||||
|
||||
static const double powval = 2.0;
|
||||
static const double silence = -100.0;
|
||||
|
||||
double VolumeMap::SliderToDB2(double slider) {
|
||||
double v = SliderToDB(slider);
|
||||
v = floor(v * 2.0 + 0.5) * 0.5;
|
||||
return v;
|
||||
}
|
||||
|
||||
double VolumeMap::SliderToDB(double slider) {
|
||||
if (slider > 0) {
|
||||
return pfc::max_t<double>(silence,10.0 * log(slider) / log(powval));
|
||||
} else {
|
||||
return silence;
|
||||
}
|
||||
}
|
||||
double VolumeMap::DBToSlider(double volumeDB) {
|
||||
return pfc::clip_t<double>(pow(powval,volumeDB / 10.0), 0, 1);
|
||||
}
|
||||
7
foobar2000/helpers/VolumeMap.h
Normal file
7
foobar2000/helpers/VolumeMap.h
Normal file
@@ -0,0 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
namespace VolumeMap {
|
||||
double SliderToDB(double slider /*0..1 range*/);
|
||||
double SliderToDB2(double slider); // rounds to 0.5dB
|
||||
double DBToSlider(double volumeDB);
|
||||
}
|
||||
391
foobar2000/helpers/WindowPositionUtils.h
Normal file
391
foobar2000/helpers/WindowPositionUtils.h
Normal file
@@ -0,0 +1,391 @@
|
||||
#pragma once
|
||||
|
||||
#include "win32_misc.h"
|
||||
|
||||
static BOOL AdjustWindowRectHelper(CWindow wnd, CRect & rc) {
|
||||
const DWORD style = wnd.GetWindowLong(GWL_STYLE), exstyle = wnd.GetWindowLong(GWL_EXSTYLE);
|
||||
return AdjustWindowRectEx(&rc,style,(style & WS_POPUP) ? wnd.GetMenu() != NULL : FALSE, exstyle);
|
||||
}
|
||||
|
||||
static void AdjustRectToScreenArea(CRect & rc, CRect rcParent) {
|
||||
HMONITOR monitor = MonitorFromRect(rcParent,MONITOR_DEFAULTTONEAREST);
|
||||
MONITORINFO mi = {sizeof(MONITORINFO)};
|
||||
if (GetMonitorInfo(monitor,&mi)) {
|
||||
const CRect clip = mi.rcWork;
|
||||
if (rc.right > clip.right) rc.OffsetRect(clip.right - rc.right, 0);
|
||||
if (rc.bottom > clip.bottom) rc.OffsetRect(0, clip.bottom - rc.bottom);
|
||||
if (rc.left < clip.left) rc.OffsetRect(clip.left - rc.left, 0);
|
||||
if (rc.top < clip.top) rc.OffsetRect(0, clip.top - rc.top);
|
||||
}
|
||||
}
|
||||
|
||||
static BOOL GetClientRectAsSC(CWindow wnd, CRect & rc) {
|
||||
CRect temp;
|
||||
if (!wnd.GetClientRect(temp)) return FALSE;
|
||||
if (temp.IsRectNull()) return FALSE;
|
||||
if (!wnd.ClientToScreen(temp)) return FALSE;
|
||||
rc = temp;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
|
||||
static BOOL CenterWindowGetRect(CWindow wnd, CWindow wndParent, CRect & out) {
|
||||
CRect parent, child;
|
||||
if (!wndParent.GetWindowRect(&parent) || !wnd.GetWindowRect(&child)) return FALSE;
|
||||
{
|
||||
CPoint origin = parent.CenterPoint();
|
||||
origin.Offset( - child.Width() / 2, - child.Height() / 2);
|
||||
child.OffsetRect( origin - child.TopLeft() );
|
||||
}
|
||||
AdjustRectToScreenArea(child, parent);
|
||||
out = child;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
static BOOL CenterWindowAbove(CWindow wnd, CWindow wndParent) {
|
||||
CRect rc;
|
||||
if (!CenterWindowGetRect(wnd, wndParent, rc)) return FALSE;
|
||||
return wnd.SetWindowPos(NULL,rc,SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE);
|
||||
}
|
||||
|
||||
static BOOL ShowWindowCentered(CWindow wnd,CWindow wndParent) {
|
||||
CRect rc;
|
||||
if (!CenterWindowGetRect(wnd, wndParent, rc)) return FALSE;
|
||||
return wnd.SetWindowPos(HWND_TOP,rc,SWP_NOSIZE | SWP_SHOWWINDOW);
|
||||
}
|
||||
|
||||
class cfgWindowSize : public cfg_var {
|
||||
public:
|
||||
cfgWindowSize(const GUID & p_guid) : cfg_var(p_guid) {}
|
||||
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {
|
||||
stream_writer_formatter<> str(*p_stream,p_abort); str << m_width << m_height;
|
||||
}
|
||||
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {
|
||||
stream_reader_formatter<> str(*p_stream,p_abort); str >> m_width >> m_height;
|
||||
}
|
||||
|
||||
uint32_t m_width = UINT32_MAX, m_height = UINT32_MAX;
|
||||
};
|
||||
|
||||
class cfgWindowSizeTracker {
|
||||
public:
|
||||
cfgWindowSizeTracker(cfgWindowSize & p_var) : m_var(p_var) {}
|
||||
|
||||
bool Apply(HWND p_wnd) {
|
||||
bool retVal = false;
|
||||
m_applied = false;
|
||||
if (m_var.m_width != ~0 && m_var.m_height != ~0) {
|
||||
CRect rect (0,0,m_var.m_width,m_var.m_height);
|
||||
if (AdjustWindowRectHelper(p_wnd, rect)) {
|
||||
SetWindowPos(p_wnd,NULL,0,0,rect.right-rect.left,rect.bottom-rect.top,SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOZORDER);
|
||||
retVal = true;
|
||||
}
|
||||
}
|
||||
m_applied = true;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT & lResult) {
|
||||
if (uMsg == WM_SIZE && m_applied) {
|
||||
if (lParam != 0) {
|
||||
m_var.m_width = (short)LOWORD(lParam); m_var.m_height = (short)HIWORD(lParam);
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
private:
|
||||
cfgWindowSize & m_var;
|
||||
bool m_applied = false;
|
||||
};
|
||||
|
||||
class cfgDialogSizeTracker : public cfgWindowSizeTracker {
|
||||
public:
|
||||
cfgDialogSizeTracker(cfgWindowSize & p_var) : cfgWindowSizeTracker(p_var) {}
|
||||
BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT & lResult) {
|
||||
if (cfgWindowSizeTracker::ProcessWindowMessage(hWnd, uMsg, wParam, lParam, lResult)) return TRUE;
|
||||
if (uMsg == WM_INITDIALOG) Apply(hWnd);
|
||||
return FALSE;
|
||||
}
|
||||
};
|
||||
|
||||
class cfgDialogPositionData {
|
||||
public:
|
||||
cfgDialogPositionData() : m_width(sizeInvalid), m_height(sizeInvalid), m_posX(posInvalid), m_posY(posInvalid) {}
|
||||
|
||||
void OverrideDefaultSize(t_uint32 width, t_uint32 height) {
|
||||
if (m_width == sizeInvalid && m_height == sizeInvalid) {
|
||||
m_width = width; m_height = height; m_posX = m_posY = posInvalid;
|
||||
m_dpiX = m_dpiY = 96;
|
||||
}
|
||||
}
|
||||
|
||||
void AddWindow(CWindow wnd) {
|
||||
TryFetchConfig();
|
||||
m_windows += wnd;
|
||||
ApplyConfig(wnd);
|
||||
}
|
||||
void RemoveWindow(CWindow wnd) {
|
||||
if (m_windows.contains(wnd)) {
|
||||
StoreConfig(wnd); m_windows -= wnd;
|
||||
}
|
||||
}
|
||||
void FetchConfig() {TryFetchConfig();}
|
||||
|
||||
private:
|
||||
BOOL ApplyConfig(CWindow wnd) {
|
||||
ApplyDPI();
|
||||
CWindow wndParent = wnd.GetParent();
|
||||
UINT flags = SWP_NOACTIVATE | SWP_NOZORDER;
|
||||
CRect rc;
|
||||
if (!GetClientRectAsSC(wnd,rc)) return FALSE;
|
||||
if (m_width != sizeInvalid && m_height != sizeInvalid && (wnd.GetWindowLong(GWL_STYLE) & WS_SIZEBOX) != 0) {
|
||||
rc.right = rc.left + m_width;
|
||||
rc.bottom = rc.top + m_height;
|
||||
} else {
|
||||
flags |= SWP_NOSIZE;
|
||||
}
|
||||
if (wndParent != NULL) {
|
||||
CRect rcParent;
|
||||
if (GetParentWndRect(wndParent, rcParent)) {
|
||||
if (m_posX != posInvalid && m_posY != posInvalid) {
|
||||
rc.MoveToXY( rcParent.TopLeft() + CPoint(m_posX, m_posY) );
|
||||
} else {
|
||||
CPoint center = rcParent.CenterPoint();
|
||||
rc.MoveToXY( center.x - rc.Width() / 2, center.y - rc.Height() / 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!AdjustWindowRectHelper(wnd, rc)) return FALSE;
|
||||
|
||||
DeOverlap(wnd, rc);
|
||||
|
||||
{
|
||||
CRect rcAdjust(0,0,1,1);
|
||||
if (wndParent != NULL) {
|
||||
CRect temp;
|
||||
if (wndParent.GetWindowRect(temp)) rcAdjust = temp;
|
||||
}
|
||||
AdjustRectToScreenArea(rc, rcAdjust);
|
||||
}
|
||||
|
||||
|
||||
return wnd.SetWindowPos(NULL, rc, flags);
|
||||
}
|
||||
struct DeOverlapState {
|
||||
CWindow m_thisWnd;
|
||||
CPoint m_topLeft;
|
||||
bool m_match;
|
||||
};
|
||||
static BOOL CALLBACK MyEnumChildProc(HWND wnd, LPARAM param) {
|
||||
DeOverlapState * state = reinterpret_cast<DeOverlapState*>(param);
|
||||
if (wnd != state->m_thisWnd && IsWindowVisible(wnd) ) {
|
||||
CRect rc;
|
||||
if (GetWindowRect(wnd, rc)) {
|
||||
if (rc.TopLeft() == state->m_topLeft) {
|
||||
state->m_match = true; return FALSE;
|
||||
}
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
static bool DeOverlapTest(CWindow wnd, CPoint topLeft) {
|
||||
DeOverlapState state = {};
|
||||
state.m_thisWnd = wnd; state.m_topLeft = topLeft; state.m_match = false;
|
||||
EnumThreadWindows(GetCurrentThreadId(), MyEnumChildProc, reinterpret_cast<LPARAM>(&state));
|
||||
return state.m_match;
|
||||
}
|
||||
static int DeOverlapDelta() {
|
||||
return pfc::max_t<int>(GetSystemMetrics(SM_CYCAPTION),1);
|
||||
}
|
||||
static void DeOverlap(CWindow wnd, CRect & rc) {
|
||||
const int delta = DeOverlapDelta();
|
||||
for(;;) {
|
||||
if (!DeOverlapTest(wnd, rc.TopLeft())) break;
|
||||
rc.OffsetRect(delta,delta);
|
||||
}
|
||||
}
|
||||
BOOL StoreConfig(CWindow wnd) {
|
||||
CRect rc;
|
||||
if (!GetClientRectAsSC(wnd, rc)) return FALSE;
|
||||
const CSize DPI = QueryScreenDPIEx();
|
||||
m_dpiX = DPI.cx; m_dpiY = DPI.cy;
|
||||
m_width = rc.Width(); m_height = rc.Height();
|
||||
m_posX = m_posY = posInvalid;
|
||||
CWindow parent = wnd.GetParent();
|
||||
if (parent != NULL) {
|
||||
CRect rcParent;
|
||||
if (GetParentWndRect(parent, rcParent)) {
|
||||
m_posX = rc.left - rcParent.left;
|
||||
m_posY = rc.top - rcParent.top;
|
||||
}
|
||||
}
|
||||
return TRUE;
|
||||
}
|
||||
void TryFetchConfig() {
|
||||
for(auto walk = m_windows.cfirst(); walk.is_valid(); ++walk) {
|
||||
if (StoreConfig(*walk)) break;
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyDPI() {
|
||||
const CSize screenDPI = QueryScreenDPIEx();
|
||||
if (screenDPI.cx == 0 || screenDPI.cy == 0) {
|
||||
PFC_ASSERT(!"Should not get here - something seriously wrong with the OS");
|
||||
return;
|
||||
}
|
||||
if (m_dpiX != dpiInvalid && m_dpiX != screenDPI.cx) {
|
||||
if (m_width != sizeInvalid) m_width = MulDiv(m_width, screenDPI.cx, m_dpiX);
|
||||
if (m_posX != posInvalid) m_posX = MulDiv(m_posX, screenDPI.cx, m_dpiX);
|
||||
}
|
||||
if (m_dpiY != dpiInvalid && m_dpiY != screenDPI.cy) {
|
||||
if (m_height != sizeInvalid) m_height = MulDiv(m_height, screenDPI.cy, m_dpiY);
|
||||
if (m_posY != posInvalid) m_posY = MulDiv(m_posY, screenDPI.cy, m_dpiY);
|
||||
}
|
||||
m_dpiX = screenDPI.cx;
|
||||
m_dpiY = screenDPI.cy;
|
||||
}
|
||||
CSize GrabDPI() const {
|
||||
CSize DPI(96,96);
|
||||
if (m_dpiX != dpiInvalid) DPI.cx = m_dpiX;
|
||||
if (m_dpiY != dpiInvalid) DPI.cy = m_dpiY;
|
||||
return DPI;
|
||||
}
|
||||
|
||||
static BOOL GetParentWndRect(CWindow wndParent, CRect & rc) {
|
||||
if (!wndParent.IsIconic()) {
|
||||
return wndParent.GetWindowRect(rc);
|
||||
}
|
||||
WINDOWPLACEMENT pl = {sizeof(pl)};
|
||||
if (!wndParent.GetWindowPlacement(&pl)) return FALSE;
|
||||
rc = pl.rcNormalPosition;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
pfc::avltree_t<CWindow> m_windows;
|
||||
public:
|
||||
t_uint32 m_width, m_height;
|
||||
t_int32 m_posX, m_posY;
|
||||
t_uint32 m_dpiX, m_dpiY;
|
||||
enum {
|
||||
posInvalid = 0x80000000,
|
||||
sizeInvalid = 0xFFFFFFFF,
|
||||
dpiInvalid = 0,
|
||||
};
|
||||
};
|
||||
|
||||
FB2K_STREAM_READER_OVERLOAD(cfgDialogPositionData) {
|
||||
stream >> value.m_width >> value.m_height;
|
||||
try {
|
||||
stream >> value.m_posX >> value.m_posY >> value.m_dpiX >> value.m_dpiY;
|
||||
} catch(exception_io_data) {
|
||||
value.m_posX = value.m_posY = cfgDialogPositionData::posInvalid;
|
||||
value.m_dpiX = value.m_dpiY = cfgDialogPositionData::dpiInvalid;
|
||||
}
|
||||
return stream;
|
||||
}
|
||||
FB2K_STREAM_WRITER_OVERLOAD(cfgDialogPositionData) {
|
||||
return stream << value.m_width << value.m_height << value.m_posX << value.m_posY << value.m_dpiX << value.m_dpiY;
|
||||
}
|
||||
|
||||
class cfgDialogPosition : public cfgDialogPositionData, public cfg_var {
|
||||
public:
|
||||
cfgDialogPosition(const GUID & id) : cfg_var(id) {}
|
||||
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {FetchConfig(); stream_writer_formatter<> str(*p_stream, p_abort); str << *pfc::implicit_cast<cfgDialogPositionData*>(this);}
|
||||
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {stream_reader_formatter<> str(*p_stream, p_abort); str >> *pfc::implicit_cast<cfgDialogPositionData*>(this);}
|
||||
};
|
||||
|
||||
class cfgDialogPositionTracker {
|
||||
public:
|
||||
cfgDialogPositionTracker(cfgDialogPosition & p_var) : m_var(p_var) {}
|
||||
~cfgDialogPositionTracker() {Cleanup();}
|
||||
|
||||
BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT & lResult) {
|
||||
if (uMsg == WM_CREATE || uMsg == WM_INITDIALOG) {
|
||||
Cleanup();
|
||||
m_wnd = hWnd;
|
||||
m_var.AddWindow(m_wnd);
|
||||
} else if (uMsg == WM_DESTROY) {
|
||||
PFC_ASSERT( hWnd == m_wnd );
|
||||
Cleanup();
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
private:
|
||||
void Cleanup() {
|
||||
if (m_wnd != NULL) {
|
||||
m_var.RemoveWindow(m_wnd);
|
||||
m_wnd = NULL;
|
||||
}
|
||||
}
|
||||
cfgDialogPosition & m_var;
|
||||
CWindow m_wnd;
|
||||
};
|
||||
|
||||
//! DPI-safe window size var \n
|
||||
//! Stores size in pixel and original DPI\n
|
||||
//! Use with cfgWindowSizeTracker2
|
||||
class cfgWindowSize2 : public cfg_var {
|
||||
public:
|
||||
cfgWindowSize2(const GUID & p_guid) : cfg_var(p_guid) {}
|
||||
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {
|
||||
stream_writer_formatter<> str(*p_stream,p_abort); str << m_size.cx << m_size.cy << m_dpi.cx << m_dpi.cy;
|
||||
}
|
||||
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {
|
||||
stream_reader_formatter<> str(*p_stream,p_abort); str >> m_size.cx >> m_size.cy >> m_dpi.cx >> m_dpi.cy;
|
||||
}
|
||||
|
||||
bool is_valid() const {
|
||||
return m_size.cx > 0 && m_size.cy > 0;
|
||||
}
|
||||
|
||||
CSize get( CSize forDPI ) const {
|
||||
if ( forDPI == m_dpi ) return m_size;
|
||||
|
||||
CSize ret;
|
||||
ret.cx = MulDiv( m_size.cx, forDPI.cx, m_dpi.cx );
|
||||
ret.cy = MulDiv( m_size.cy, forDPI.cy, m_dpi.cy );
|
||||
return ret;
|
||||
}
|
||||
|
||||
CSize m_size = CSize(0,0), m_dpi = CSize(0,0);
|
||||
};
|
||||
|
||||
//! Forward messages to this class to utilize cfgWindowSize2
|
||||
class cfgWindowSizeTracker2 : public CMessageMap {
|
||||
public:
|
||||
cfgWindowSizeTracker2( cfgWindowSize2 & var ) : m_var(var) {}
|
||||
|
||||
BEGIN_MSG_MAP_EX(cfgWindowSizeTracker2)
|
||||
if (uMsg == WM_CREATE || uMsg == WM_INITDIALOG) {
|
||||
Apply(hWnd);
|
||||
}
|
||||
MSG_WM_SIZE( OnSize )
|
||||
END_MSG_MAP()
|
||||
|
||||
bool Apply(HWND p_wnd) {
|
||||
bool retVal = false;
|
||||
m_applied = false;
|
||||
if (m_var.is_valid()) {
|
||||
CRect rect( CPoint(0,0), m_var.get( m_DPI ) );
|
||||
if (AdjustWindowRectHelper(p_wnd, rect)) {
|
||||
SetWindowPos(p_wnd,NULL,0,0,rect.right-rect.left,rect.bottom-rect.top,SWP_NOMOVE|SWP_NOACTIVATE|SWP_NOZORDER);
|
||||
retVal = true;
|
||||
}
|
||||
}
|
||||
m_applied = true;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private:
|
||||
void OnSize(UINT nType, CSize size) {
|
||||
if ( m_applied && size.cx > 0 && size.cy > 0 ) {
|
||||
m_var.m_size = size;
|
||||
m_var.m_dpi = m_DPI;
|
||||
}
|
||||
SetMsgHandled(FALSE);
|
||||
}
|
||||
cfgWindowSize2 & m_var;
|
||||
bool m_applied = false;
|
||||
const CSize m_DPI = QueryScreenDPIEx();
|
||||
};
|
||||
3
foobar2000/helpers/advconfig_impl.h
Normal file
3
foobar2000/helpers/advconfig_impl.h
Normal file
@@ -0,0 +1,3 @@
|
||||
#pragma once
|
||||
|
||||
// fb2k mobile compat
|
||||
45
foobar2000/helpers/advconfig_runtime.h
Normal file
45
foobar2000/helpers/advconfig_runtime.h
Normal file
@@ -0,0 +1,45 @@
|
||||
#pragma once
|
||||
|
||||
// Alternate advconfig var implementations that discard their state across app restarts - use for debug options that should not stick
|
||||
|
||||
template<bool p_is_radio = false, uint32_t prefFlags = 0>
|
||||
class advconfig_entry_checkbox_runtime_impl : public advconfig_entry_checkbox_v2 {
|
||||
public:
|
||||
advconfig_entry_checkbox_runtime_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_initialstate), m_parent(p_parent), m_priority(p_priority), m_guid(p_guid) {}
|
||||
|
||||
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() {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;
|
||||
bool m_state;
|
||||
const GUID m_parent;
|
||||
const GUID m_guid;
|
||||
const double m_priority;
|
||||
};
|
||||
|
||||
template<bool p_is_radio, uint32_t prefFlags = 0>
|
||||
class advconfig_checkbox_factory_runtime_t : public service_factory_single_t<advconfig_entry_checkbox_runtime_impl<p_is_radio, prefFlags> > {
|
||||
public:
|
||||
advconfig_checkbox_factory_runtime_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_runtime_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;}
|
||||
};
|
||||
|
||||
typedef advconfig_checkbox_factory_runtime_t<false> advconfig_checkbox_factory_runtime;
|
||||
typedef advconfig_checkbox_factory_runtime_t<true> advconfig_radio_factory_runtime;
|
||||
2
foobar2000/helpers/album_art_helpers.h
Normal file
2
foobar2000/helpers/album_art_helpers.h
Normal file
@@ -0,0 +1,2 @@
|
||||
#pragma once
|
||||
// stub, added for compatibility with fb2k mobile source
|
||||
291
foobar2000/helpers/atl-misc.h
Normal file
291
foobar2000/helpers/atl-misc.h
Normal file
@@ -0,0 +1,291 @@
|
||||
#pragma once
|
||||
|
||||
#include "win32_misc.h"
|
||||
#include <libPPUI/WTL-PP.h>
|
||||
#include <utility>
|
||||
|
||||
class CMenuSelectionReceiver : public CWindowImpl<CMenuSelectionReceiver> {
|
||||
public:
|
||||
CMenuSelectionReceiver(HWND p_parent) {
|
||||
WIN32_OP( Create(p_parent) != NULL );
|
||||
}
|
||||
~CMenuSelectionReceiver() {
|
||||
if (m_hWnd != NULL) DestroyWindow();
|
||||
}
|
||||
typedef CWindowImpl<CMenuSelectionReceiver> _baseClass;
|
||||
DECLARE_WND_CLASS_EX(TEXT("{DF0087DB-E765-4283-BBAB-6AB2E8AB64A1}"),0,0);
|
||||
|
||||
BEGIN_MSG_MAP(CMenuSelectionReceiver)
|
||||
MESSAGE_HANDLER(WM_MENUSELECT,OnMenuSelect)
|
||||
END_MSG_MAP()
|
||||
protected:
|
||||
virtual bool QueryHint(unsigned p_id,pfc::string_base & p_out) {
|
||||
return false;
|
||||
}
|
||||
private:
|
||||
LRESULT OnMenuSelect(UINT,WPARAM p_wp,LPARAM p_lp,BOOL&) {
|
||||
if (p_lp != 0) {
|
||||
if (HIWORD(p_wp) & MF_POPUP) {
|
||||
m_status.release();
|
||||
} else {
|
||||
pfc::string8 msg;
|
||||
UINT cmd = LOWORD(p_wp);
|
||||
if ( cmd == 0 || !QueryHint(cmd,msg)) {
|
||||
m_status.release();
|
||||
} else {
|
||||
if (m_status.is_empty()) {
|
||||
if (!static_api_ptr_t<ui_control>()->override_status_text_create(m_status)) m_status.release();
|
||||
}
|
||||
if (m_status.is_valid()) {
|
||||
m_status->override_text(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_status.release();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
service_ptr_t<ui_status_text_override> m_status;
|
||||
|
||||
PFC_CLASS_NOT_COPYABLE(CMenuSelectionReceiver,CMenuSelectionReceiver);
|
||||
};
|
||||
|
||||
class CMenuDescriptionMap : public CMenuSelectionReceiver {
|
||||
public:
|
||||
CMenuDescriptionMap(HWND p_parent) : CMenuSelectionReceiver(p_parent) {}
|
||||
void Set(unsigned p_id,const char * p_description) {m_content.set(p_id,p_description);}
|
||||
protected:
|
||||
bool QueryHint(unsigned p_id,pfc::string_base & p_out) {
|
||||
return m_content.query(p_id,p_out);
|
||||
}
|
||||
private:
|
||||
pfc::map_t<unsigned,pfc::string8> m_content;
|
||||
};
|
||||
|
||||
class CMenuDescriptionHybrid : public CMenuSelectionReceiver {
|
||||
public:
|
||||
CMenuDescriptionHybrid(HWND parent) : CMenuSelectionReceiver(parent) {}
|
||||
void Set(unsigned id, const char * desc) {m_content.set(id, desc);}
|
||||
|
||||
void SetCM(contextmenu_manager::ptr mgr, unsigned base, unsigned max) {
|
||||
m_cmMgr = mgr; m_cmMgr_base = base; m_cmMgr_max = max;
|
||||
}
|
||||
protected:
|
||||
bool QueryHint(unsigned p_id,pfc::string_base & p_out) {
|
||||
if (m_cmMgr.is_valid() && p_id >= m_cmMgr_base && p_id < m_cmMgr_max) {
|
||||
return m_cmMgr->get_description_by_id(p_id - m_cmMgr_base,p_out);
|
||||
}
|
||||
return m_content.query(p_id,p_out);
|
||||
}
|
||||
private:
|
||||
pfc::map_t<unsigned,pfc::string8> m_content;
|
||||
contextmenu_manager::ptr m_cmMgr; unsigned m_cmMgr_base, m_cmMgr_max;
|
||||
};
|
||||
|
||||
inline pfc::string_base & operator<<(pfc::string_base & p_fmt,const CPoint & p_point) {
|
||||
return p_fmt << "(" << p_point.x << "," << p_point.y << ")";
|
||||
}
|
||||
|
||||
inline pfc::string_base & operator<<(pfc::string_base & p_fmt,const CRect & p_rect) {
|
||||
return p_fmt << "(" << p_rect.left << "," << p_rect.top << "," << p_rect.right << "," << p_rect.bottom << ")";
|
||||
}
|
||||
|
||||
template<typename TClass>
|
||||
class CAddDummyMessageMap : public TClass {
|
||||
public:
|
||||
BEGIN_MSG_MAP(CAddDummyMessageMap<TClass>)
|
||||
END_MSG_MAP()
|
||||
};
|
||||
|
||||
template<typename _parentClass> class CWindowFixSEH : public _parentClass { public:
|
||||
BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult, DWORD dwMsgMapID = 0) {
|
||||
__try {
|
||||
return _parentClass::ProcessWindowMessage(hWnd, uMsg, wParam, lParam, lResult, dwMsgMapID);
|
||||
} __except(uExceptFilterProc(GetExceptionInformation())) { return FALSE; /* should not get here */ }
|
||||
}
|
||||
template<typename ... arg_t> CWindowFixSEH( arg_t && ... arg ) : _parentClass( std::forward<arg_t>(arg) ... ) {}
|
||||
};
|
||||
|
||||
template<typename TClass>
|
||||
class CWindowAutoLifetime : public CWindowFixSEH<TClass> {
|
||||
public:
|
||||
typedef CWindowFixSEH<TClass> TBase;
|
||||
template<typename ... arg_t> CWindowAutoLifetime(HWND parent, arg_t && ... arg) : TBase( std::forward<arg_t>(arg) ... ) {Init(parent);}
|
||||
private:
|
||||
void Init(HWND parent) {WIN32_OP(this->Create(parent) != NULL);}
|
||||
void OnFinalMessage(HWND wnd) {PFC_ASSERT_NO_EXCEPTION( TBase::OnFinalMessage(wnd) ); PFC_ASSERT_NO_EXCEPTION(delete this);}
|
||||
};
|
||||
|
||||
template<typename TClass>
|
||||
class ImplementModelessTracking : public TClass {
|
||||
public:
|
||||
template<typename ... arg_t> ImplementModelessTracking(arg_t && ... arg ) : TClass(std::forward<arg_t>(arg) ... ) {}
|
||||
|
||||
BEGIN_MSG_MAP_EX(ImplementModelessTracking)
|
||||
MSG_WM_INITDIALOG(OnInitDialog)
|
||||
MSG_WM_DESTROY(OnDestroy)
|
||||
CHAIN_MSG_MAP(TClass)
|
||||
END_MSG_MAP_HOOK()
|
||||
private:
|
||||
BOOL OnInitDialog(CWindow, LPARAM) {m_modeless.Set( this->m_hWnd ); SetMsgHandled(FALSE); return FALSE; }
|
||||
void OnDestroy() {m_modeless.Set(NULL); SetMsgHandled(FALSE); }
|
||||
CModelessDialogEntry m_modeless;
|
||||
};
|
||||
|
||||
namespace fb2k {
|
||||
template<typename dialog_t, typename ... arg_t> dialog_t * newDialogEx( HWND parent, arg_t && ... arg ) {
|
||||
return new CWindowAutoLifetime<ImplementModelessTracking< dialog_t > > ( parent, std::forward<arg_t>(arg) ... );
|
||||
}
|
||||
template<typename dialog_t, typename ... arg_t> dialog_t * newDialog(arg_t && ... arg) {
|
||||
return new CWindowAutoLifetime<ImplementModelessTracking< dialog_t > > (core_api::get_main_window(), std::forward<arg_t>(arg) ...);
|
||||
}
|
||||
}
|
||||
|
||||
class CMenuSelectionReceiver_UiElement : public CMenuSelectionReceiver {
|
||||
public:
|
||||
CMenuSelectionReceiver_UiElement(service_ptr_t<ui_element_instance> p_owner,unsigned p_id_base) : CMenuSelectionReceiver(p_owner->get_wnd()), m_owner(p_owner), m_id_base(p_id_base) {}
|
||||
protected:
|
||||
bool QueryHint(unsigned p_id,pfc::string_base & p_out) {
|
||||
return m_owner->edit_mode_context_menu_get_description(p_id,m_id_base,p_out);
|
||||
}
|
||||
private:
|
||||
const unsigned m_id_base;
|
||||
const service_ptr_t<ui_element_instance> m_owner;
|
||||
};
|
||||
|
||||
static bool window_service_trait_defer_destruction(const service_base *) {return true;}
|
||||
|
||||
|
||||
//! Special service_impl_t replacement for service classes that also implement ATL/WTL windows.
|
||||
template<typename _t_base>
|
||||
class window_service_impl_t : public implement_service_query< CWindowFixSEH<_t_base> > {
|
||||
private:
|
||||
typedef window_service_impl_t<_t_base> t_self;
|
||||
typedef implement_service_query< CWindowFixSEH<_t_base> > t_base;
|
||||
public:
|
||||
BEGIN_MSG_MAP_EX(window_service_impl_t)
|
||||
MSG_WM_DESTROY(OnDestroyPassThru)
|
||||
CHAIN_MSG_MAP(__super)
|
||||
END_MSG_MAP_HOOK()
|
||||
|
||||
int FB2KAPI service_release() throw() {
|
||||
int ret = --m_counter;
|
||||
if (ret == 0) {
|
||||
if (window_service_trait_defer_destruction(this) && !InterlockedExchange(&m_delayedDestroyInProgress,1)) {
|
||||
PFC_ASSERT_NO_EXCEPTION( service_impl_helper::release_object_delayed(this); );
|
||||
} else if (this->m_hWnd != NULL) {
|
||||
if (!m_destroyWindowInProgress) { // don't double-destroy in weird scenarios
|
||||
PFC_ASSERT_NO_EXCEPTION( ::DestroyWindow(this->m_hWnd) );
|
||||
}
|
||||
} else {
|
||||
PFC_ASSERT_NO_EXCEPTION( delete this );
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
int FB2KAPI service_add_ref() throw() {return ++m_counter;}
|
||||
|
||||
template<typename ... arg_t>
|
||||
window_service_impl_t( arg_t && ... arg ) : t_base( std::forward<arg_t>(arg) ... ) {};
|
||||
private:
|
||||
void OnDestroyPassThru() {
|
||||
SetMsgHandled(FALSE); m_destroyWindowInProgress = true;
|
||||
}
|
||||
void OnFinalMessage(HWND p_wnd) {
|
||||
t_base::OnFinalMessage(p_wnd);
|
||||
service_ptr_t<service_base> bump(this);
|
||||
}
|
||||
volatile bool m_destroyWindowInProgress = false;
|
||||
volatile LONG m_delayedDestroyInProgress = 0;
|
||||
pfc::refcounter m_counter;
|
||||
};
|
||||
|
||||
namespace fb2k {
|
||||
template<typename obj_t, typename ... arg_t>
|
||||
service_ptr_t<obj_t> service_new_window(arg_t && ... arg) {
|
||||
return new window_service_impl_t< obj_t > ( std::forward<arg_t> (arg) ... );
|
||||
}
|
||||
}
|
||||
|
||||
static void AppendMenuPopup(HMENU menu, UINT flags, CMenu & popup, const TCHAR * label) {
|
||||
PFC_ASSERT( flags & MF_POPUP );
|
||||
WIN32_OP( CMenuHandle(menu).AppendMenu(flags, popup, label) );
|
||||
popup.Detach();
|
||||
}
|
||||
|
||||
class CMessageMapDummy : public CMessageMap {
|
||||
public:
|
||||
BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam,
|
||||
LRESULT& lResult, DWORD dwMsgMapID) {return FALSE;}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
template<typename TDialog> class preferences_page_instance_impl : public TDialog {
|
||||
public:
|
||||
preferences_page_instance_impl(HWND parent, preferences_page_callback::ptr callback) : TDialog(callback) {
|
||||
WIN32_OP(this->Create(parent) != NULL);
|
||||
|
||||
// complain early if what we created isn't a child window
|
||||
PFC_ASSERT( (this->GetStyle() & (WS_POPUP|WS_CHILD)) == WS_CHILD );
|
||||
}
|
||||
HWND get_wnd() {return this->m_hWnd;}
|
||||
};
|
||||
static bool window_service_trait_defer_destruction(const preferences_page_instance *) {return false;}
|
||||
template<typename TDialog> class preferences_page_impl : public preferences_page_v3 {
|
||||
public:
|
||||
preferences_page_instance::ptr instantiate(HWND parent, preferences_page_callback::ptr callback) {
|
||||
return fb2k::service_new_window<preferences_page_instance_impl<TDialog> >(parent, callback);
|
||||
}
|
||||
};
|
||||
|
||||
class CEmbeddedDialog : public CDialogImpl<CEmbeddedDialog> {
|
||||
public:
|
||||
CEmbeddedDialog(CMessageMap * owner, DWORD msgMapID, UINT dialogID) : m_owner(*owner), IDD(dialogID), m_msgMapID(msgMapID) {}
|
||||
|
||||
BEGIN_MSG_MAP(CEmbeddedDialog)
|
||||
CHAIN_MSG_MAP_ALT_MEMBER(m_owner, m_msgMapID)
|
||||
END_MSG_MAP()
|
||||
|
||||
const DWORD m_msgMapID;
|
||||
const UINT IDD;
|
||||
CMessageMap & m_owner;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
// here because of window_service_impl_t
|
||||
template<typename TImpl, typename TInterface = ui_element> class ui_element_impl : public TInterface {
|
||||
public:
|
||||
GUID get_guid() { return TImpl::g_get_guid(); }
|
||||
GUID get_subclass() { return TImpl::g_get_subclass(); }
|
||||
void get_name(pfc::string_base & out) { TImpl::g_get_name(out); }
|
||||
ui_element_instance::ptr instantiate(HWND parent, ui_element_config::ptr cfg, ui_element_instance_callback::ptr callback) {
|
||||
PFC_ASSERT(cfg->get_guid() == get_guid());
|
||||
service_nnptr_t<ui_element_instance_impl_helper> item = new window_service_impl_t<ui_element_instance_impl_helper>(cfg, callback);
|
||||
item->initialize_window(parent);
|
||||
return item;
|
||||
}
|
||||
ui_element_config::ptr get_default_configuration() { return TImpl::g_get_default_configuration(); }
|
||||
ui_element_children_enumerator_ptr enumerate_children(ui_element_config::ptr cfg) { return NULL; }
|
||||
bool get_description(pfc::string_base & out) { out = TImpl::g_get_description(); return true; }
|
||||
private:
|
||||
class ui_element_instance_impl_helper : public TImpl {
|
||||
public:
|
||||
ui_element_instance_impl_helper(ui_element_config::ptr cfg, ui_element_instance_callback::ptr callback) : TImpl(cfg, callback) {}
|
||||
|
||||
GUID get_guid() { return TImpl::g_get_guid(); }
|
||||
GUID get_subclass() { return TImpl::g_get_subclass(); }
|
||||
HWND get_wnd() { return *this; }
|
||||
};
|
||||
public:
|
||||
typedef ui_element_instance_impl_helper TInstance;
|
||||
static TInstance const & instanceGlobals() { return *reinterpret_cast<const TInstance*>(NULL); }
|
||||
};
|
||||
154
foobar2000/helpers/bitreader_helper.h
Normal file
154
foobar2000/helpers/bitreader_helper.h
Normal file
@@ -0,0 +1,154 @@
|
||||
#pragma once
|
||||
|
||||
namespace bitreader_helper {
|
||||
|
||||
inline static size_t extract_bit(const uint8_t * p_stream,size_t p_offset) {
|
||||
return (p_stream[p_offset>>3] >> (7-(p_offset&7)))&1;
|
||||
}
|
||||
|
||||
inline static size_t extract_int(const uint8_t * p_stream,size_t p_base,size_t p_width) {
|
||||
size_t ret = 0;
|
||||
size_t offset = p_base;
|
||||
for(size_t bit=0;bit<p_width;bit++) {
|
||||
ret <<= 1;
|
||||
ret |= extract_bit(p_stream,offset++);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
inline static void write_bit( uint8_t * p_stream, size_t p_offset, size_t bit ) {
|
||||
size_t bshift = 7 - (p_offset&7);
|
||||
size_t mask = 1 << bshift;
|
||||
uint8_t & b = p_stream[p_offset>>3];
|
||||
b = (b & ~mask) | ((bit&1) << bshift);
|
||||
}
|
||||
inline static void write_int( uint8_t * p_stream, size_t p_base, size_t p_width, size_t p_value) {
|
||||
size_t offset = p_base;
|
||||
size_t val = p_value;
|
||||
for( size_t bit = 0; bit < p_width; ++ bit ) {
|
||||
write_bit( p_stream, offset++, val >> (p_width - bit - 1));
|
||||
}
|
||||
}
|
||||
|
||||
class bitreader
|
||||
{
|
||||
public:
|
||||
inline bitreader(const t_uint8 * p_ptr,t_size p_base)
|
||||
: m_ptr(p_ptr), m_bitptr(p_base)
|
||||
{
|
||||
}
|
||||
|
||||
inline void skip(t_size p_bits)
|
||||
{
|
||||
m_bitptr += p_bits;
|
||||
}
|
||||
|
||||
|
||||
template<typename t_ret>
|
||||
t_ret peek_t(t_size p_bits) const {
|
||||
size_t ptr = m_bitptr;
|
||||
t_ret ret = 0;
|
||||
for(t_size bit=0;bit<p_bits;bit++)
|
||||
{
|
||||
ret <<= 1;
|
||||
ret |= (m_ptr[ptr >>3] >> (7-(ptr &7)))&1;
|
||||
ptr++;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename t_ret>
|
||||
t_ret read_t(t_size p_bits) {
|
||||
t_ret ret = peek_t<t_ret>(p_bits);
|
||||
skip(p_bits);
|
||||
return ret;
|
||||
}
|
||||
|
||||
size_t peek(size_t bits) const {
|
||||
return peek_t<size_t>(bits);
|
||||
}
|
||||
|
||||
t_size read(t_size p_bits) {return read_t<t_size>(p_bits);}
|
||||
|
||||
inline t_size get_bitptr() const {return m_bitptr;}
|
||||
|
||||
inline bool read_bit() {
|
||||
bool state = ( (m_ptr[m_bitptr>>3] >> (7-(m_bitptr&7)))&1 ) != 0;
|
||||
m_bitptr++;
|
||||
return state;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
const t_uint8 * m_ptr;
|
||||
t_size m_bitptr;
|
||||
};
|
||||
|
||||
class bitreader_fromfile
|
||||
{
|
||||
public:
|
||||
inline bitreader_fromfile(service_ptr_t<file> const& p_file) : m_file(p_file), m_buffer_ptr(0) {}
|
||||
|
||||
t_size read(t_size p_bits,abort_callback & p_abort) {
|
||||
t_size ret = 0;
|
||||
for(t_size bit=0;bit<p_bits;bit++) {
|
||||
if (m_buffer_ptr == 0)
|
||||
m_file->read_object(&m_buffer,1,p_abort);
|
||||
|
||||
ret <<= 1;
|
||||
ret |= (m_buffer >> (7-m_buffer_ptr))&1;
|
||||
m_buffer_ptr = (m_buffer_ptr+1) & 7;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void skip(t_size p_bits,abort_callback & p_abort) {
|
||||
for(t_size bit=0;bit<p_bits;bit++) {
|
||||
if (m_buffer_ptr == 0) m_file->read_object(&m_buffer,1,p_abort);
|
||||
m_buffer_ptr = (m_buffer_ptr+1) & 7;
|
||||
}
|
||||
}
|
||||
|
||||
inline void byte_align() {m_buffer_ptr = 0;}
|
||||
|
||||
private:
|
||||
service_ptr_t<file> m_file;
|
||||
t_size m_buffer_ptr;
|
||||
t_uint8 m_buffer;
|
||||
};
|
||||
|
||||
class bitreader_limited
|
||||
{
|
||||
public:
|
||||
inline bitreader_limited(const t_uint8 * p_ptr,t_size p_base,t_size p_remaining) : m_reader(p_ptr,p_base), m_remaining(p_remaining) {}
|
||||
|
||||
inline t_size get_bitptr() const {return m_reader.get_bitptr();}
|
||||
|
||||
inline t_size get_remaining() const {return m_remaining;}
|
||||
|
||||
inline void skip(t_size p_bits) {
|
||||
if (p_bits > m_remaining) throw exception_io_data_truncation();
|
||||
m_remaining -= p_bits;
|
||||
m_reader.skip(p_bits);
|
||||
}
|
||||
|
||||
size_t peek(size_t bits) {
|
||||
if (bits > m_remaining) throw exception_io_data_truncation();
|
||||
return m_reader.peek(bits);
|
||||
}
|
||||
t_size read(t_size p_bits)
|
||||
{
|
||||
if (p_bits > m_remaining) throw exception_io_data_truncation();
|
||||
m_remaining -= p_bits;
|
||||
return m_reader.read(p_bits);
|
||||
}
|
||||
|
||||
private:
|
||||
bitreader m_reader;
|
||||
t_size m_remaining;
|
||||
};
|
||||
|
||||
inline static t_size extract_bits(const t_uint8 * p_buffer,t_size p_base,t_size p_count) {
|
||||
return bitreader(p_buffer,p_base).read(p_count);
|
||||
}
|
||||
|
||||
}
|
||||
31
foobar2000/helpers/cfg_guidlist.h
Normal file
31
foobar2000/helpers/cfg_guidlist.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
class cfg_guidlist : public cfg_var, public pfc::list_t<GUID>
|
||||
{
|
||||
public:
|
||||
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) override {
|
||||
t_uint32 n, m = pfc::downcast_guarded<t_uint32>(get_count());
|
||||
p_stream->write_lendian_t(m,p_abort);
|
||||
for(n=0;n<m;n++) p_stream->write_lendian_t(get_item(n),p_abort);
|
||||
}
|
||||
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) override {
|
||||
t_uint32 n,count;
|
||||
p_stream->read_lendian_t(count,p_abort);
|
||||
m_buffer.set_size(count);
|
||||
for(n=0;n<count;n++) {
|
||||
try {
|
||||
p_stream->read_lendian_t(m_buffer[n],p_abort);
|
||||
} catch(...) {m_buffer.set_size(0); throw;}
|
||||
}
|
||||
}
|
||||
|
||||
void sort() {sort_t(pfc::guid_compare);}
|
||||
|
||||
bool have_item_bsearch(const GUID & p_item) {
|
||||
t_size dummy;
|
||||
return bsearch_t(pfc::guid_compare,p_item,dummy);
|
||||
}
|
||||
|
||||
public:
|
||||
cfg_guidlist(const GUID & p_guid) : cfg_var(p_guid) {}
|
||||
};
|
||||
194
foobar2000/helpers/create_directory_helper.cpp
Normal file
194
foobar2000/helpers/create_directory_helper.cpp
Normal file
@@ -0,0 +1,194 @@
|
||||
#include "StdAfx.h"
|
||||
#include "create_directory_helper.h"
|
||||
#include <pfc/pathUtils.h>
|
||||
|
||||
namespace create_directory_helper
|
||||
{
|
||||
static void create_path_internal(const char * p_path,t_size p_base,abort_callback & p_abort) {
|
||||
pfc::string8_fastalloc temp;
|
||||
for(t_size walk = p_base; p_path[walk]; walk++) {
|
||||
if (p_path[walk] == '\\') {
|
||||
temp.set_string(p_path,walk);
|
||||
try {filesystem::g_create_directory(temp.get_ptr(),p_abort);} catch(exception_io_already_exists) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_valid_netpath_char(char p_char) {
|
||||
return pfc::char_is_ascii_alphanumeric(p_char) || p_char == '_' || p_char == '-' || p_char == '.';
|
||||
}
|
||||
|
||||
static bool test_localpath(const char * p_path) {
|
||||
if (pfc::strcmp_partial(p_path,"file://") == 0) p_path += strlen("file://");
|
||||
return pfc::char_is_ascii_alpha(p_path[0]) &&
|
||||
p_path[1] == ':' &&
|
||||
p_path[2] == '\\';
|
||||
}
|
||||
static bool test_netpath(const char * p_path) {
|
||||
if (pfc::strcmp_partial(p_path,"file://") == 0) p_path += strlen("file://");
|
||||
if (*p_path != '\\') return false;
|
||||
p_path++;
|
||||
if (*p_path != '\\') return false;
|
||||
p_path++;
|
||||
if (!is_valid_netpath_char(*p_path)) return false;
|
||||
p_path++;
|
||||
while(is_valid_netpath_char(*p_path)) p_path++;
|
||||
if (*p_path != '\\') return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
void create_path(const char * p_path,abort_callback & p_abort) {
|
||||
if (test_localpath(p_path)) {
|
||||
t_size walk = 0;
|
||||
if (pfc::strcmp_partial(p_path,"file://") == 0) walk += strlen("file://");
|
||||
create_path_internal(p_path,walk + 3,p_abort);
|
||||
} else if (test_netpath(p_path)) {
|
||||
t_size walk = 0;
|
||||
if (pfc::strcmp_partial(p_path,"file://") == 0) walk += strlen("file://");
|
||||
while(p_path[walk] == '\\') walk++;
|
||||
while(p_path[walk] != 0 && p_path[walk] != '\\') walk++;
|
||||
while(p_path[walk] == '\\') walk++;
|
||||
while(p_path[walk] != 0 && p_path[walk] != '\\') walk++;
|
||||
while(p_path[walk] == '\\') walk++;
|
||||
create_path_internal(p_path,walk,p_abort);
|
||||
} else {
|
||||
pfc::throw_exception_with_message< exception_io > ("Could not create directory structure; unknown path format");
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
static bool is_bad_dirchar(char c)
|
||||
{
|
||||
return c==' ' || c=='.';
|
||||
}
|
||||
#endif
|
||||
|
||||
void make_path(const char * parent,const char * filename,const char * extension,bool allow_new_dirs,pfc::string8 & out,bool really_create_dirs,abort_callback & p_abort)
|
||||
{
|
||||
out.reset();
|
||||
if (parent && *parent)
|
||||
{
|
||||
out = parent;
|
||||
out.fix_dir_separator('\\');
|
||||
}
|
||||
bool last_char_is_dir_sep = true;
|
||||
while(*filename)
|
||||
{
|
||||
#ifdef WIN32
|
||||
if (allow_new_dirs && is_bad_dirchar(*filename))
|
||||
{
|
||||
const char * ptr = filename+1;
|
||||
while(is_bad_dirchar(*ptr)) ptr++;
|
||||
if (*ptr!='\\' && *ptr!='/') out.add_string(filename,ptr-filename);
|
||||
filename = ptr;
|
||||
if (*filename==0) break;
|
||||
}
|
||||
#endif
|
||||
if (pfc::is_path_bad_char(*filename))
|
||||
{
|
||||
if (allow_new_dirs && (*filename=='\\' || *filename=='/'))
|
||||
{
|
||||
if (!last_char_is_dir_sep)
|
||||
{
|
||||
if (really_create_dirs) try{filesystem::g_create_directory(out,p_abort);}catch(exception_io_already_exists){}
|
||||
out.add_char('\\');
|
||||
last_char_is_dir_sep = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
out.add_char('_');
|
||||
}
|
||||
else
|
||||
{
|
||||
out.add_byte(*filename);
|
||||
last_char_is_dir_sep = false;
|
||||
}
|
||||
filename++;
|
||||
}
|
||||
if (out.length()>0 && out[out.length()-1]=='\\')
|
||||
{
|
||||
out.add_string("noname");
|
||||
}
|
||||
if (extension && *extension)
|
||||
{
|
||||
out.add_char('.');
|
||||
out.add_string(extension);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pfc::string create_directory_helper::sanitize_formatted_path(pfc::stringp formatted, bool allowWC) {
|
||||
return sanitize_formatted_path_ex(formatted, allowWC, pfc::io::path::charReplaceDefault);
|
||||
};
|
||||
|
||||
pfc::string create_directory_helper::sanitize_formatted_path_ex(pfc::stringp formatted, bool allowWC, charReplace_t replace) {
|
||||
pfc::string out;
|
||||
t_size curSegBase = 0;
|
||||
for (t_size walk = 0; ; ++walk) {
|
||||
const char c = formatted[walk];
|
||||
const bool end = (c == 0);
|
||||
if (end || pfc::io::path::isSeparator(c)) {
|
||||
if (curSegBase < walk) {
|
||||
pfc::string seg(formatted + curSegBase, walk - curSegBase);
|
||||
out = pfc::io::path::combine(out, pfc::io::path::validateFileName(seg, allowWC, end /*preserve ext*/, replace));
|
||||
}
|
||||
if (end) break;
|
||||
curSegBase = walk + 1;
|
||||
}
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void create_directory_helper::format_filename_ex(const metadb_handle_ptr & handle, titleformat_hook * p_hook, titleformat_object::ptr spec, const char * suffix, pfc::string_base & out) {
|
||||
format_filename_ex(handle, p_hook, spec, suffix, out, pfc::io::path::charReplaceDefault);
|
||||
}
|
||||
|
||||
void create_directory_helper::format_filename_ex(const metadb_handle_ptr & handle,titleformat_hook * p_hook,titleformat_object::ptr spec,const char * suffix, pfc::string_base & out, charReplace_t replace) {
|
||||
pfc::string_formatter formatted;
|
||||
titleformat_text_filter_myimpl filter;
|
||||
filter.m_replace = replace;
|
||||
handle->format_title(p_hook,formatted,spec,&filter);
|
||||
formatted << suffix;
|
||||
out = sanitize_formatted_path_ex(formatted, false, replace).ptr();
|
||||
}
|
||||
void create_directory_helper::format_filename(const metadb_handle_ptr & handle,titleformat_hook * p_hook,titleformat_object::ptr spec,pfc::string_base & out) {
|
||||
format_filename_ex(handle, p_hook, spec, "", out);
|
||||
}
|
||||
void create_directory_helper::format_filename(const metadb_handle_ptr & handle,titleformat_hook * p_hook,const char * spec,pfc::string_base & out)
|
||||
{
|
||||
service_ptr_t<titleformat_object> script;
|
||||
if (titleformat_compiler::get()->compile(script,spec)) {
|
||||
format_filename(handle, p_hook, script, out);
|
||||
} else {
|
||||
out.reset();
|
||||
}
|
||||
}
|
||||
|
||||
static bool substSanity(const char * subst) {
|
||||
if (subst == nullptr) return false;
|
||||
for (size_t w = 0; subst[w]; ++w) {
|
||||
if (pfc::io::path::isSeparator(subst[w])) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void create_directory_helper::titleformat_text_filter_myimpl::write(const GUID & p_inputType,pfc::string_receiver & p_out,const char * p_data,t_size p_dataLength) {
|
||||
if (p_inputType == titleformat_inputtypes::meta) {
|
||||
pfc::string_formatter temp;
|
||||
for(t_size walk = 0; walk < p_dataLength; ++walk) {
|
||||
char c = p_data[walk];
|
||||
if (c == 0) break;
|
||||
const char * subst = nullptr;
|
||||
if (pfc::io::path::isSeparator(c)) {
|
||||
if (m_replace) {
|
||||
const char * proposed = m_replace(c);
|
||||
if (substSanity(proposed)) subst = proposed;
|
||||
}
|
||||
if (subst == nullptr) subst = "-";
|
||||
}
|
||||
if (subst != nullptr) temp.add_string(subst);
|
||||
else temp.add_byte(c);
|
||||
}
|
||||
p_out.add_string(temp);
|
||||
} else p_out.add_string(p_data,p_dataLength);
|
||||
}
|
||||
31
foobar2000/helpers/create_directory_helper.h
Normal file
31
foobar2000/helpers/create_directory_helper.h
Normal file
@@ -0,0 +1,31 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <pfc/stringNew.h>
|
||||
|
||||
#ifdef FOOBAR2000_MODERN
|
||||
#include "metadb_compat.h"
|
||||
#include <SDK/titleformat.h>
|
||||
#endif
|
||||
|
||||
namespace create_directory_helper {
|
||||
typedef std::function<const char* (char)> charReplace_t;
|
||||
|
||||
void create_path(const char * p_path,abort_callback & p_abort);
|
||||
void make_path(const char * parent,const char * filename,const char * extension,bool allow_new_dirs,pfc::string8 & out,bool b_really_create_dirs,abort_callback & p_dir_create_abort);
|
||||
void format_filename(const metadb_handle_ptr & handle,titleformat_hook * p_hook,const char * spec,pfc::string_base & out);
|
||||
void format_filename(const metadb_handle_ptr & handle,titleformat_hook * p_hook,titleformat_object::ptr spec,pfc::string_base & out);
|
||||
void format_filename_ex(const metadb_handle_ptr & handle,titleformat_hook * p_hook,titleformat_object::ptr spec,const char * suffix, pfc::string_base & out);
|
||||
void format_filename_ex(const metadb_handle_ptr & handle, titleformat_hook * p_hook, titleformat_object::ptr spec, const char * suffix, pfc::string_base & out, charReplace_t replace);
|
||||
|
||||
|
||||
pfc::string sanitize_formatted_path(pfc::stringp str, bool allowWC = false);
|
||||
pfc::string sanitize_formatted_path_ex(pfc::stringp str, bool allowWC, charReplace_t replace);
|
||||
|
||||
class titleformat_text_filter_myimpl : public titleformat_text_filter {
|
||||
public:
|
||||
charReplace_t m_replace;
|
||||
void write(const GUID & p_inputType,pfc::string_receiver & p_out,const char * p_data,t_size p_dataLength);
|
||||
};
|
||||
|
||||
};
|
||||
202
foobar2000/helpers/cue_creator.cpp
Normal file
202
foobar2000/helpers/cue_creator.cpp
Normal file
@@ -0,0 +1,202 @@
|
||||
#include "stdafx.h"
|
||||
#include "cue_creator.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
class format_meta
|
||||
{
|
||||
public:
|
||||
format_meta(const file_info & p_source,const char * p_name,bool p_allow_space = true)
|
||||
{
|
||||
p_source.meta_format(p_name,m_buffer);
|
||||
m_buffer.replace_byte('\"','\'');
|
||||
uReplaceString(m_buffer,pfc::string8(m_buffer),pfc_infinite,"\x0d\x0a",2,"\\",1,false);
|
||||
if (!p_allow_space) m_buffer.replace_byte(' ','_');
|
||||
m_buffer.replace_nontext_chars();
|
||||
}
|
||||
inline operator const char*() const {return m_buffer;}
|
||||
private:
|
||||
pfc::string8_fastalloc m_buffer;
|
||||
};
|
||||
}
|
||||
|
||||
static bool is_meta_same_everywhere(const cue_creator::t_entry_list & p_list,const char * p_meta)
|
||||
{
|
||||
pfc::string8_fastalloc reference,temp;
|
||||
|
||||
bool first = true;
|
||||
for(auto iter = p_list.first(); iter.is_valid(); ++ iter ) {
|
||||
if ( ! iter->isTrackAudio() ) continue;
|
||||
|
||||
if ( first ) {
|
||||
first = false;
|
||||
if (!iter->m_infos.meta_format(p_meta,reference)) return false;
|
||||
} else {
|
||||
if (!iter->m_infos.meta_format(p_meta,temp)) return false;
|
||||
if (strcmp(temp,reference)!=0) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
#define g_eol "\r\n"
|
||||
|
||||
namespace cue_creator
|
||||
{
|
||||
void create(pfc::string_formatter & p_out,const t_entry_list & p_data)
|
||||
{
|
||||
if (p_data.get_count() == 0) return;
|
||||
bool album_artist_global = is_meta_same_everywhere(p_data,"album artist"),
|
||||
artist_global = is_meta_same_everywhere(p_data,"artist"),
|
||||
album_global = is_meta_same_everywhere(p_data,"album"),
|
||||
genre_global = is_meta_same_everywhere(p_data,"genre"),
|
||||
date_global = is_meta_same_everywhere(p_data,"date"),
|
||||
discid_global = is_meta_same_everywhere(p_data,"discid"),
|
||||
comment_global = is_meta_same_everywhere(p_data,"comment"),
|
||||
catalog_global = is_meta_same_everywhere(p_data,"catalog"),
|
||||
songwriter_global = is_meta_same_everywhere(p_data,"songwriter");
|
||||
|
||||
{
|
||||
auto firstTrack = p_data.first();
|
||||
while( firstTrack.is_valid() && ! firstTrack->isTrackAudio() ) ++ firstTrack;
|
||||
if ( firstTrack.is_valid() ) {
|
||||
if (genre_global) {
|
||||
p_out << "REM GENRE " << format_meta(firstTrack->m_infos,"genre") << g_eol;
|
||||
}
|
||||
if (date_global) {
|
||||
p_out << "REM DATE " << format_meta(firstTrack->m_infos,"date") << g_eol;
|
||||
}
|
||||
if (discid_global) {
|
||||
p_out << "REM DISCID " << format_meta(firstTrack->m_infos,"discid") << g_eol;
|
||||
}
|
||||
if (comment_global) {
|
||||
p_out << "REM COMMENT " << format_meta(firstTrack->m_infos,"comment") << g_eol;
|
||||
}
|
||||
if (catalog_global) {
|
||||
p_out << "CATALOG " << format_meta(firstTrack->m_infos,"catalog") << g_eol;
|
||||
}
|
||||
if (songwriter_global) {
|
||||
p_out << "SONGWRITER \"" << format_meta(firstTrack->m_infos,"songwriter") << "\"" << g_eol;
|
||||
}
|
||||
|
||||
if (album_artist_global)
|
||||
{
|
||||
p_out << "PERFORMER \"" << format_meta(firstTrack->m_infos,"album artist") << "\"" << g_eol;
|
||||
artist_global = false;
|
||||
}
|
||||
else if (artist_global)
|
||||
{
|
||||
p_out << "PERFORMER \"" << format_meta(firstTrack->m_infos,"artist") << "\"" << g_eol;
|
||||
}
|
||||
if (album_global)
|
||||
{
|
||||
p_out << "TITLE \"" << format_meta(firstTrack->m_infos,"album") << "\"" << g_eol;
|
||||
}
|
||||
|
||||
{
|
||||
replaygain_info::t_text_buffer rgbuffer;
|
||||
replaygain_info rg = firstTrack->m_infos.get_replaygain();
|
||||
if (rg.format_album_gain(rgbuffer))
|
||||
p_out << "REM REPLAYGAIN_ALBUM_GAIN " << rgbuffer << g_eol;
|
||||
if (rg.format_album_peak(rgbuffer))
|
||||
p_out << "REM REPLAYGAIN_ALBUM_PEAK " << rgbuffer << g_eol;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
pfc::string8 last_file;
|
||||
|
||||
for(t_entry_list::const_iterator iter = p_data.first();iter.is_valid();++iter)
|
||||
{
|
||||
if (strcmp(last_file,iter->m_file) != 0)
|
||||
{
|
||||
auto fileType = iter->m_fileType;
|
||||
if ( fileType.length() == 0 ) fileType = "WAVE";
|
||||
p_out << "FILE \"" << iter->m_file << "\" " << fileType << g_eol;
|
||||
last_file = iter->m_file;
|
||||
}
|
||||
|
||||
{
|
||||
auto trackType = iter->m_trackType;
|
||||
if (trackType.length() == 0) trackType = "AUDIO";
|
||||
p_out << " TRACK " << pfc::format_int(iter->m_track_number,2) << " " << trackType << g_eol;
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (iter->m_infos.meta_find("title") != pfc_infinite)
|
||||
p_out << " TITLE \"" << format_meta(iter->m_infos,"title") << "\"" << g_eol;
|
||||
|
||||
if (!artist_global && iter->m_infos.meta_find("artist") != pfc_infinite)
|
||||
p_out << " PERFORMER \"" << format_meta(iter->m_infos,"artist") << "\"" << g_eol;
|
||||
|
||||
if (!songwriter_global && iter->m_infos.meta_find("songwriter") != pfc_infinite) {
|
||||
p_out << " SONGWRITER \"" << format_meta(iter->m_infos,"songwriter") << "\"" << g_eol;
|
||||
}
|
||||
|
||||
if (iter->m_infos.meta_find("isrc") != pfc_infinite) {
|
||||
p_out << " ISRC " << format_meta(iter->m_infos,"isrc") << g_eol;
|
||||
}
|
||||
|
||||
if (!date_global && iter->m_infos.meta_find("date") != pfc_infinite) {
|
||||
p_out << " REM DATE " << format_meta(iter->m_infos,"date") << g_eol;
|
||||
}
|
||||
|
||||
|
||||
|
||||
{
|
||||
replaygain_info::t_text_buffer rgbuffer;
|
||||
replaygain_info rg = iter->m_infos.get_replaygain();
|
||||
if (rg.format_track_gain(rgbuffer))
|
||||
p_out << " REM REPLAYGAIN_TRACK_GAIN " << rgbuffer << g_eol;
|
||||
if (rg.format_track_peak(rgbuffer))
|
||||
p_out << " REM REPLAYGAIN_TRACK_PEAK " << rgbuffer << g_eol;
|
||||
}
|
||||
|
||||
if (!iter->m_flags.is_empty()) {
|
||||
p_out << " FLAGS " << iter->m_flags << g_eol;
|
||||
}
|
||||
|
||||
if (iter->m_index_list.m_positions[0] < iter->m_index_list.m_positions[1])
|
||||
{
|
||||
if (iter->m_index_list.m_positions[0] < 0)
|
||||
p_out << " PREGAP " << cuesheet_format_index_time(iter->m_index_list.m_positions[1] - iter->m_index_list.m_positions[0]) << g_eol;
|
||||
else
|
||||
p_out << " INDEX 00 " << cuesheet_format_index_time(iter->m_index_list.m_positions[0]) << g_eol;
|
||||
}
|
||||
|
||||
p_out << " INDEX 01 " << cuesheet_format_index_time(iter->m_index_list.m_positions[1]) << g_eol;
|
||||
|
||||
for(unsigned n=2;n<t_cuesheet_index_list::count && iter->m_index_list.m_positions[n] > 0;n++)
|
||||
{
|
||||
p_out << " INDEX " << pfc::format_uint(n,2) << " " << cuesheet_format_index_time(iter->m_index_list.m_positions[n]) << g_eol;
|
||||
}
|
||||
|
||||
// p_out << " INDEX 01 " << cuesheet_format_index_time(iter->m_offset) << g_eol;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void t_entry::set_simple_index(double p_time)
|
||||
{
|
||||
m_index_list.reset();
|
||||
m_index_list.m_positions[0] = m_index_list.m_positions[1] = p_time;
|
||||
}
|
||||
void t_entry::set_index01(double index0, double index1) {
|
||||
PFC_ASSERT( index0 <= index1 );
|
||||
m_index_list.reset();
|
||||
m_index_list.m_positions[0] = index0;
|
||||
m_index_list.m_positions[1] = index1;
|
||||
}
|
||||
|
||||
bool t_entry::isTrackAudio() const {
|
||||
PFC_ASSERT( m_trackType.length() > 0 );
|
||||
return pfc::stringEqualsI_ascii( m_trackType, "AUDIO" );
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
24
foobar2000/helpers/cue_creator.h
Normal file
24
foobar2000/helpers/cue_creator.h
Normal file
@@ -0,0 +1,24 @@
|
||||
#pragma once
|
||||
|
||||
#include "cuesheet_index_list.h"
|
||||
|
||||
namespace cue_creator
|
||||
{
|
||||
struct t_entry
|
||||
{
|
||||
file_info_impl m_infos;
|
||||
pfc::string8 m_file, m_fileType, m_flags, m_trackType = "AUDIO";
|
||||
unsigned m_track_number;
|
||||
|
||||
bool isTrackAudio() const;
|
||||
|
||||
t_cuesheet_index_list m_index_list;
|
||||
|
||||
void set_simple_index(double p_time);
|
||||
void set_index01(double index0, double index1);
|
||||
};
|
||||
|
||||
typedef pfc::chain_list_v2_t<t_entry> t_entry_list;
|
||||
|
||||
void create(pfc::string_formatter & p_out,const t_entry_list & p_list);
|
||||
};
|
||||
856
foobar2000/helpers/cue_parser.cpp
Normal file
856
foobar2000/helpers/cue_parser.cpp
Normal file
@@ -0,0 +1,856 @@
|
||||
#include "stdafx.h"
|
||||
#include "cue_parser.h"
|
||||
|
||||
|
||||
#define maximumCueTrackNumber 999
|
||||
|
||||
namespace {
|
||||
PFC_DECLARE_EXCEPTION(exception_cue,pfc::exception,"Invalid cuesheet");
|
||||
PFC_DECLARE_EXCEPTION(exception_cue_tracktype, exception_cue, "Not an audio track")
|
||||
}
|
||||
|
||||
static bool is_numeric(char c) {return c>='0' && c<='9';}
|
||||
|
||||
|
||||
static bool is_spacing(char c)
|
||||
{
|
||||
return c == ' ' || c == '\t';
|
||||
}
|
||||
|
||||
static bool is_linebreak(char c)
|
||||
{
|
||||
return c == '\n' || c == '\r';
|
||||
}
|
||||
|
||||
static void validate_file_type(const char * p_type,t_size p_type_length) {
|
||||
if (
|
||||
//standard types
|
||||
stricmp_utf8_ex(p_type,p_type_length,"WAVE",pfc_infinite) != 0 &&
|
||||
stricmp_utf8_ex(p_type,p_type_length,"MP3",pfc_infinite) != 0 &&
|
||||
stricmp_utf8_ex(p_type,p_type_length,"AIFF",pfc_infinite) != 0 &&
|
||||
//common user-entered types
|
||||
stricmp_utf8_ex(p_type,p_type_length,"APE",pfc_infinite) != 0 &&
|
||||
stricmp_utf8_ex(p_type,p_type_length,"FLAC",pfc_infinite) != 0 &&
|
||||
stricmp_utf8_ex(p_type,p_type_length,"WV",pfc_infinite) != 0 &&
|
||||
stricmp_utf8_ex(p_type,p_type_length,"WAVPACK",pfc_infinite) != 0 &&
|
||||
// BINARY
|
||||
stricmp_utf8_ex(p_type,p_type_length,"BINARY",pfc_infinite) != 0
|
||||
)
|
||||
pfc::throw_exception_with_message< exception_cue >(PFC_string_formatter() << "expected WAVE, MP3 or AIFF, got : \"" << pfc::string_part(p_type,p_type_length) << "\"");
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class NOVTABLE cue_parser_callback
|
||||
{
|
||||
public:
|
||||
virtual void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) = 0;
|
||||
virtual void on_track(unsigned p_index,const char * p_type,t_size p_type_length) = 0;
|
||||
virtual void on_pregap(unsigned p_value) = 0;
|
||||
virtual void on_index(unsigned p_index,unsigned p_value) = 0;
|
||||
virtual void on_title(const char * p_title,t_size p_title_length) = 0;
|
||||
virtual void on_performer(const char * p_performer,t_size p_performer_length) = 0;
|
||||
virtual void on_songwriter(const char * p_songwriter,t_size p_songwriter_length) = 0;
|
||||
virtual void on_isrc(const char * p_isrc,t_size p_isrc_length) = 0;
|
||||
virtual void on_catalog(const char * p_catalog,t_size p_catalog_length) = 0;
|
||||
virtual void on_comment(const char * p_comment,t_size p_comment_length) = 0;
|
||||
virtual void on_flags(const char * p_flags,t_size p_flags_length) = 0;
|
||||
};
|
||||
|
||||
class NOVTABLE cue_parser_callback_meta : public cue_parser_callback
|
||||
{
|
||||
public:
|
||||
virtual void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) = 0;
|
||||
virtual void on_track(unsigned p_index,const char * p_type,t_size p_type_length) = 0;
|
||||
virtual void on_pregap(unsigned p_value) = 0;
|
||||
virtual void on_index(unsigned p_index,unsigned p_value) = 0;
|
||||
virtual void on_meta(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) = 0;
|
||||
protected:
|
||||
static bool is_known_meta(const char * p_name,t_size p_length)
|
||||
{
|
||||
static const char * metas[] = {"genre","date","discid","comment","replaygain_track_gain","replaygain_track_peak","replaygain_album_gain","replaygain_album_peak"};
|
||||
for(t_size n=0;n<PFC_TABSIZE(metas);n++) {
|
||||
if (!stricmp_utf8_ex(p_name,p_length,metas[n],pfc_infinite)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void on_comment(const char * p_comment,t_size p_comment_length)
|
||||
{
|
||||
unsigned ptr = 0;
|
||||
while(ptr < p_comment_length && !is_spacing(p_comment[ptr])) ptr++;
|
||||
if (is_known_meta(p_comment, ptr))
|
||||
{
|
||||
unsigned name_length = ptr;
|
||||
while(ptr < p_comment_length && is_spacing(p_comment[ptr])) ptr++;
|
||||
if (ptr < p_comment_length)
|
||||
{
|
||||
if (p_comment[ptr] == '\"')
|
||||
{
|
||||
ptr++;
|
||||
unsigned value_base = ptr;
|
||||
while(ptr < p_comment_length && p_comment[ptr] != '\"') ptr++;
|
||||
if (ptr == p_comment_length) pfc::throw_exception_with_message<exception_cue>("invalid REM syntax");
|
||||
if (ptr > value_base) on_meta(p_comment,name_length,p_comment + value_base,ptr - value_base);
|
||||
}
|
||||
else
|
||||
{
|
||||
unsigned value_base = ptr;
|
||||
while(ptr < p_comment_length /*&& !is_spacing(p_comment[ptr])*/) ptr++;
|
||||
if (ptr > value_base) on_meta(p_comment,name_length,p_comment + value_base,ptr - value_base);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void on_title(const char * p_title,t_size p_title_length)
|
||||
{
|
||||
on_meta("title",pfc_infinite,p_title,p_title_length);
|
||||
}
|
||||
void on_songwriter(const char * p_songwriter,t_size p_songwriter_length) {
|
||||
on_meta("songwriter",pfc_infinite,p_songwriter,p_songwriter_length);
|
||||
}
|
||||
void on_performer(const char * p_performer,t_size p_performer_length)
|
||||
{
|
||||
on_meta("artist",pfc_infinite,p_performer,p_performer_length);
|
||||
}
|
||||
|
||||
void on_isrc(const char * p_isrc,t_size p_isrc_length)
|
||||
{
|
||||
on_meta("isrc",pfc_infinite,p_isrc,p_isrc_length);
|
||||
}
|
||||
void on_catalog(const char * p_catalog,t_size p_catalog_length)
|
||||
{
|
||||
on_meta("catalog",pfc_infinite,p_catalog,p_catalog_length);
|
||||
}
|
||||
void on_flags(const char * p_flags,t_size p_flags_length) {}
|
||||
};
|
||||
|
||||
|
||||
class cue_parser_callback_retrievelist : public cue_parser_callback
|
||||
{
|
||||
public:
|
||||
cue_parser_callback_retrievelist(cue_parser::t_cue_entry_list & p_out) : m_out(p_out), m_track(0), m_pregap(0), m_index0_set(false), m_index1_set(false)
|
||||
{
|
||||
}
|
||||
|
||||
void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length)
|
||||
{
|
||||
validate_file_type(p_type,p_type_length);
|
||||
m_file.set_string(p_file,p_file_length);
|
||||
m_fileType.set_string(p_type, p_type_length);
|
||||
}
|
||||
|
||||
void on_track(unsigned p_index,const char * p_type,t_size p_type_length)
|
||||
{
|
||||
finalize_track(); // finalize previous track
|
||||
|
||||
m_trackIsAudio = stricmp_utf8_ex(p_type,p_type_length,"audio",pfc_infinite) == 0;
|
||||
if (m_file.is_empty()) pfc::throw_exception_with_message<exception_cue>("declaring a track with no file set");
|
||||
m_trackfile = m_file;
|
||||
m_trackFileType = m_fileType;
|
||||
m_track = p_index;
|
||||
}
|
||||
|
||||
void on_pregap(unsigned p_value) {m_pregap = (double) p_value / 75.0;}
|
||||
|
||||
void on_index(unsigned p_index,unsigned p_value)
|
||||
{
|
||||
if (p_index < t_cuesheet_index_list::count)
|
||||
{
|
||||
switch(p_index)
|
||||
{
|
||||
case 0: m_index0_set = true; break;
|
||||
case 1: m_index1_set = true; break;
|
||||
}
|
||||
m_index_list.m_positions[p_index] = (double) p_value / 75.0;
|
||||
}
|
||||
}
|
||||
|
||||
void on_title(const char * p_title,t_size p_title_length) {}
|
||||
void on_performer(const char * p_performer,t_size p_performer_length) {}
|
||||
void on_songwriter(const char * p_songwriter,t_size p_songwriter_length) {}
|
||||
void on_isrc(const char * p_isrc,t_size p_isrc_length) {}
|
||||
void on_catalog(const char * p_catalog,t_size p_catalog_length) {}
|
||||
void on_comment(const char * p_comment,t_size p_comment_length) {}
|
||||
void on_flags(const char * p_flags,t_size p_flags_length) {}
|
||||
|
||||
void finalize()
|
||||
{
|
||||
finalize_track(); // finalize last track
|
||||
}
|
||||
|
||||
private:
|
||||
void finalize_track()
|
||||
{
|
||||
if ( m_track != 0 && m_trackIsAudio ) {
|
||||
if (!m_index1_set) pfc::throw_exception_with_message< exception_cue > ("INDEX 01 not set");
|
||||
if (!m_index0_set) m_index_list.m_positions[0] = m_index_list.m_positions[1] - m_pregap;
|
||||
if (!m_index_list.is_valid()) pfc::throw_exception_with_message< exception_cue > ("invalid index list");
|
||||
|
||||
cue_parser::t_cue_entry_list::iterator iter;
|
||||
iter = m_out.insert_last();
|
||||
if (m_trackfile.is_empty()) pfc::throw_exception_with_message< exception_cue > ("track has no file assigned");
|
||||
iter->m_file = m_trackfile;
|
||||
iter->m_fileType = m_trackFileType;
|
||||
iter->m_track_number = m_track;
|
||||
iter->m_indexes = m_index_list;
|
||||
}
|
||||
|
||||
m_index_list.reset();
|
||||
m_index0_set = false;
|
||||
m_index1_set = false;
|
||||
m_pregap = 0;
|
||||
|
||||
m_track = 0; m_trackIsAudio = false;
|
||||
}
|
||||
|
||||
bool m_index0_set,m_index1_set;
|
||||
t_cuesheet_index_list m_index_list;
|
||||
double m_pregap;
|
||||
unsigned m_track;
|
||||
bool m_trackIsAudio = false;
|
||||
pfc::string8 m_file,m_fileType,m_trackfile,m_trackFileType;
|
||||
cue_parser::t_cue_entry_list & m_out;
|
||||
};
|
||||
|
||||
class cue_parser_callback_retrieveinfo : public cue_parser_callback_meta
|
||||
{
|
||||
public:
|
||||
cue_parser_callback_retrieveinfo(file_info & p_out,unsigned p_wanted_track) : m_out(p_out), m_wanted_track(p_wanted_track), m_track(0), m_is_va(false), m_index0_set(false), m_index1_set(false), m_pregap(0), m_totaltracks(0) {}
|
||||
|
||||
void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) {}
|
||||
|
||||
void on_track(unsigned p_index,const char * p_type,t_size p_type_length)
|
||||
{
|
||||
if (p_index == 0) pfc::throw_exception_with_message< exception_cue > ("invalid TRACK index");
|
||||
if (p_index == m_wanted_track)
|
||||
{
|
||||
if (stricmp_utf8_ex(p_type,p_type_length,"audio",pfc_infinite)) throw exception_cue_tracktype();
|
||||
}
|
||||
m_track = p_index;
|
||||
m_totaltracks++;
|
||||
}
|
||||
|
||||
void on_pregap(unsigned p_value) {if (m_track == m_wanted_track) m_pregap = (double) p_value / 75.0;}
|
||||
|
||||
void on_index(unsigned p_index,unsigned p_value)
|
||||
{
|
||||
if (m_track == m_wanted_track && p_index < t_cuesheet_index_list::count)
|
||||
{
|
||||
switch(p_index)
|
||||
{
|
||||
case 0: m_index0_set = true; break;
|
||||
case 1: m_index1_set = true; break;
|
||||
}
|
||||
m_indexes.m_positions[p_index] = (double) p_value / 75.0;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void on_meta(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length)
|
||||
{
|
||||
t_meta_list::iterator iter;
|
||||
if (m_track == 0) //globals
|
||||
{
|
||||
//convert global title to album
|
||||
if (!stricmp_utf8_ex(p_name,p_name_length,"title",pfc_infinite))
|
||||
{
|
||||
p_name = "album";
|
||||
p_name_length = 5;
|
||||
}
|
||||
else if (!stricmp_utf8_ex(p_name,p_name_length,"artist",pfc_infinite))
|
||||
{
|
||||
m_album_artist.set_string(p_value,p_value_length);
|
||||
}
|
||||
|
||||
iter = m_globals.insert_last();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!m_is_va)
|
||||
{
|
||||
if (!stricmp_utf8_ex(p_name,p_name_length,"artist",pfc_infinite))
|
||||
{
|
||||
if (!m_album_artist.is_empty())
|
||||
{
|
||||
if (stricmp_utf8_ex(p_value,p_value_length,m_album_artist,m_album_artist.length())) m_is_va = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (m_track == m_wanted_track) //locals
|
||||
{
|
||||
iter = m_locals.insert_last();
|
||||
}
|
||||
}
|
||||
if (iter.is_valid())
|
||||
{
|
||||
iter->m_name.set_string(p_name,p_name_length);
|
||||
iter->m_value.set_string(p_value,p_value_length);
|
||||
}
|
||||
}
|
||||
|
||||
void finalize()
|
||||
{
|
||||
if (!m_index1_set) pfc::throw_exception_with_message< exception_cue > ("INDEX 01 not set");
|
||||
if (!m_index0_set) m_indexes.m_positions[0] = m_indexes.m_positions[1] - m_pregap;
|
||||
m_indexes.to_infos(m_out);
|
||||
|
||||
replaygain_info rg;
|
||||
rg.reset();
|
||||
t_meta_list::const_iterator iter;
|
||||
|
||||
if (m_is_va)
|
||||
{
|
||||
//clean up VA mess
|
||||
|
||||
t_meta_list::const_iterator iter_global,iter_local;
|
||||
|
||||
iter_global = find_first_field(m_globals,"artist");
|
||||
iter_local = find_first_field(m_locals,"artist");
|
||||
if (iter_global.is_valid())
|
||||
{
|
||||
m_out.meta_set("album artist",iter_global->m_value);
|
||||
if (iter_local.is_valid()) m_out.meta_set("artist",iter_local->m_value);
|
||||
else m_out.meta_set("artist",iter_global->m_value);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (iter_local.is_valid()) m_out.meta_set("artist",iter_local->m_value);
|
||||
}
|
||||
|
||||
|
||||
wipe_field(m_globals,"artist");
|
||||
wipe_field(m_locals,"artist");
|
||||
|
||||
}
|
||||
|
||||
for(iter=m_globals.first();iter.is_valid();iter++)
|
||||
{
|
||||
if (!rg.set_from_meta(iter->m_name,iter->m_value))
|
||||
m_out.meta_set(iter->m_name,iter->m_value);
|
||||
}
|
||||
for(iter=m_locals.first();iter.is_valid();iter++)
|
||||
{
|
||||
if (!rg.set_from_meta(iter->m_name,iter->m_value))
|
||||
m_out.meta_set(iter->m_name,iter->m_value);
|
||||
}
|
||||
m_out.meta_set("tracknumber",PFC_string_formatter() << m_wanted_track);
|
||||
m_out.meta_set("totaltracks", PFC_string_formatter() << m_totaltracks);
|
||||
m_out.set_replaygain(rg);
|
||||
|
||||
}
|
||||
private:
|
||||
struct t_meta_entry {
|
||||
pfc::string8 m_name,m_value;
|
||||
};
|
||||
typedef pfc::chain_list_v2_t<t_meta_entry> t_meta_list;
|
||||
|
||||
static t_meta_list::const_iterator find_first_field(t_meta_list const & p_list,const char * p_field)
|
||||
{
|
||||
t_meta_list::const_iterator iter;
|
||||
for(iter=p_list.first();iter.is_valid();++iter)
|
||||
{
|
||||
if (!stricmp_utf8(p_field,iter->m_name)) return iter;
|
||||
}
|
||||
return t_meta_list::const_iterator();//null iterator
|
||||
}
|
||||
|
||||
static void wipe_field(t_meta_list & p_list,const char * p_field)
|
||||
{
|
||||
t_meta_list::iterator iter;
|
||||
for(iter=p_list.first();iter.is_valid();)
|
||||
{
|
||||
if (!stricmp_utf8(p_field,iter->m_name))
|
||||
{
|
||||
t_meta_list::iterator temp = iter;
|
||||
++temp;
|
||||
p_list.remove_single(iter);
|
||||
iter = temp;
|
||||
}
|
||||
else
|
||||
{
|
||||
++iter;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t_meta_list m_globals,m_locals;
|
||||
file_info & m_out;
|
||||
unsigned m_wanted_track, m_track,m_totaltracks;
|
||||
pfc::string8 m_album_artist;
|
||||
bool m_is_va;
|
||||
t_cuesheet_index_list m_indexes;
|
||||
bool m_index0_set,m_index1_set;
|
||||
double m_pregap;
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
static pfc::string_part_ref cue_line_argument( const char * base, size_t length ) {
|
||||
const char * end = base + length;
|
||||
while(base < end && is_spacing(base[0]) ) ++base;
|
||||
while(base < end && is_spacing(end[-1]) ) --end;
|
||||
if ( base + 1 < end ) {
|
||||
if ( base[0] == '\"' && end[-1] == '\"' ) {
|
||||
++base; --end;
|
||||
}
|
||||
}
|
||||
return pfc::string_part(base, end-base);
|
||||
}
|
||||
|
||||
static void g_parse_cue_line(const char * p_line,t_size p_line_length,cue_parser_callback & p_callback)
|
||||
{
|
||||
t_size ptr = 0;
|
||||
while(ptr < p_line_length && !is_spacing(p_line[ptr])) ptr++;
|
||||
if (!stricmp_utf8_ex(p_line,ptr,"file",pfc_infinite))
|
||||
{
|
||||
while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
|
||||
t_size file_base,file_length, type_base,type_length;
|
||||
|
||||
if (p_line[ptr] == '\"')
|
||||
{
|
||||
ptr++;
|
||||
file_base = ptr;
|
||||
while(ptr < p_line_length && p_line[ptr] != '\"') ptr++;
|
||||
if (ptr == p_line_length) pfc::throw_exception_with_message< exception_cue > ("invalid FILE syntax");
|
||||
file_length = ptr - file_base;
|
||||
ptr++;
|
||||
while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
|
||||
}
|
||||
else
|
||||
{
|
||||
file_base = ptr;
|
||||
while(ptr < p_line_length && !is_spacing(p_line[ptr])) ptr++;
|
||||
file_length = ptr - file_base;
|
||||
while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
|
||||
}
|
||||
|
||||
type_base = ptr;
|
||||
while(ptr < p_line_length && !is_spacing(p_line[ptr])) ptr++;
|
||||
type_length = ptr - type_base;
|
||||
while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
|
||||
|
||||
if (ptr != p_line_length || file_length == 0 || type_length == 0) pfc::throw_exception_with_message< exception_cue > ("invalid FILE syntax");
|
||||
|
||||
p_callback.on_file(p_line + file_base, file_length, p_line + type_base, type_length);
|
||||
}
|
||||
else if (!stricmp_utf8_ex(p_line,ptr,"track",pfc_infinite))
|
||||
{
|
||||
while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
|
||||
|
||||
t_size track_base = ptr, track_length;
|
||||
while(ptr < p_line_length && !is_spacing(p_line[ptr]))
|
||||
{
|
||||
if (!is_numeric(p_line[ptr])) pfc::throw_exception_with_message< exception_cue > ("invalid TRACK syntax");
|
||||
ptr++;
|
||||
}
|
||||
track_length = ptr - track_base;
|
||||
while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
|
||||
|
||||
t_size type_base = ptr, type_length;
|
||||
while(ptr < p_line_length && !is_spacing(p_line[ptr])) ptr++;
|
||||
type_length = ptr - type_base;
|
||||
|
||||
while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
|
||||
if (ptr != p_line_length || type_length == 0) pfc::throw_exception_with_message< exception_cue > ("invalid TRACK syntax");
|
||||
unsigned track = pfc::atoui_ex(p_line+track_base,track_length);
|
||||
if (track < 1 || track > maximumCueTrackNumber) pfc::throw_exception_with_message< exception_cue > ("invalid track number");
|
||||
|
||||
p_callback.on_track(track,p_line + type_base, type_length);
|
||||
}
|
||||
else if (!stricmp_utf8_ex(p_line,ptr,"index",pfc_infinite))
|
||||
{
|
||||
while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
|
||||
|
||||
t_size index_base,index_length, time_base,time_length;
|
||||
index_base = ptr;
|
||||
while(ptr < p_line_length && !is_spacing(p_line[ptr]))
|
||||
{
|
||||
if (!is_numeric(p_line[ptr])) pfc::throw_exception_with_message< exception_cue > ("invalid INDEX syntax" );
|
||||
ptr++;
|
||||
}
|
||||
index_length = ptr - index_base;
|
||||
|
||||
while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
|
||||
time_base = ptr;
|
||||
while(ptr < p_line_length && !is_spacing(p_line[ptr]))
|
||||
{
|
||||
if (!is_numeric(p_line[ptr]) && p_line[ptr] != ':')
|
||||
pfc::throw_exception_with_message< exception_cue > ("invalid INDEX syntax");
|
||||
ptr++;
|
||||
}
|
||||
time_length = ptr - time_base;
|
||||
|
||||
while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
|
||||
|
||||
if (ptr != p_line_length || index_length == 0 || time_length == 0)
|
||||
pfc::throw_exception_with_message< exception_cue > ("invalid INDEX syntax");
|
||||
|
||||
unsigned index = pfc::atoui_ex(p_line+index_base,index_length);
|
||||
if (index > maximumCueTrackNumber) pfc::throw_exception_with_message< exception_cue > ("invalid INDEX syntax");
|
||||
unsigned time = cuesheet_parse_index_time_ticks_e(p_line + time_base,time_length);
|
||||
|
||||
p_callback.on_index(index,time);
|
||||
}
|
||||
else if (!stricmp_utf8_ex(p_line,ptr,"pregap",pfc_infinite))
|
||||
{
|
||||
while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
|
||||
|
||||
t_size time_base, time_length;
|
||||
time_base = ptr;
|
||||
while(ptr < p_line_length && !is_spacing(p_line[ptr]))
|
||||
{
|
||||
if (!is_numeric(p_line[ptr]) && p_line[ptr] != ':')
|
||||
pfc::throw_exception_with_message< exception_cue > ("invalid PREGAP syntax");
|
||||
ptr++;
|
||||
}
|
||||
time_length = ptr - time_base;
|
||||
|
||||
while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
|
||||
|
||||
if (ptr != p_line_length || time_length == 0)
|
||||
pfc::throw_exception_with_message< exception_cue > ("invalid PREGAP syntax");
|
||||
|
||||
unsigned time = cuesheet_parse_index_time_ticks_e(p_line + time_base,time_length);
|
||||
|
||||
p_callback.on_pregap(time);
|
||||
}
|
||||
else if (!stricmp_utf8_ex(p_line,ptr,"title",pfc_infinite))
|
||||
{
|
||||
auto arg = cue_line_argument(p_line+ptr, p_line_length-ptr);
|
||||
if ( arg.m_len > 0 ) p_callback.on_title( arg.m_ptr, arg.m_len );
|
||||
}
|
||||
else if (!stricmp_utf8_ex(p_line,ptr,"performer",pfc_infinite))
|
||||
{
|
||||
auto arg = cue_line_argument(p_line + ptr, p_line_length - ptr);
|
||||
if (arg.m_len > 0) p_callback.on_performer(arg.m_ptr, arg.m_len);
|
||||
}
|
||||
else if (!stricmp_utf8_ex(p_line,ptr,"songwriter",pfc_infinite))
|
||||
{
|
||||
auto arg = cue_line_argument(p_line + ptr, p_line_length - ptr);
|
||||
if (arg.m_len > 0) p_callback.on_songwriter(arg.m_ptr, arg.m_len);
|
||||
}
|
||||
else if (!stricmp_utf8_ex(p_line,ptr,"isrc",pfc_infinite))
|
||||
{
|
||||
while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
|
||||
t_size length = p_line_length - ptr;
|
||||
if (length == 0) pfc::throw_exception_with_message< exception_cue > ("invalid ISRC syntax");
|
||||
p_callback.on_isrc(p_line+ptr,length);
|
||||
}
|
||||
else if (!stricmp_utf8_ex(p_line,ptr,"catalog",pfc_infinite))
|
||||
{
|
||||
while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
|
||||
t_size length = p_line_length - ptr;
|
||||
if (length == 0) pfc::throw_exception_with_message< exception_cue > ("invalid CATALOG syntax");
|
||||
p_callback.on_catalog(p_line+ptr,length);
|
||||
}
|
||||
else if (!stricmp_utf8_ex(p_line,ptr,"flags",pfc_infinite))
|
||||
{
|
||||
while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
|
||||
if (ptr < p_line_length)
|
||||
p_callback.on_flags(p_line + ptr, p_line_length - ptr);
|
||||
}
|
||||
else if (!stricmp_utf8_ex(p_line,ptr,"rem",pfc_infinite))
|
||||
{
|
||||
while(ptr < p_line_length && is_spacing(p_line[ptr])) ptr++;
|
||||
if (ptr < p_line_length)
|
||||
p_callback.on_comment(p_line + ptr, p_line_length - ptr);
|
||||
}
|
||||
else if (!stricmp_utf8_ex(p_line,ptr,"postgap",pfc_infinite)) {
|
||||
pfc::throw_exception_with_message< exception_cue > ("POSTGAP is not supported");
|
||||
} else if (!stricmp_utf8_ex(p_line,ptr,"cdtextfile",pfc_infinite)) {
|
||||
//do nothing
|
||||
}
|
||||
else pfc::throw_exception_with_message< exception_cue > ("unknown cuesheet item");
|
||||
}
|
||||
|
||||
static void g_parse_cue(const char * p_cuesheet,cue_parser_callback & p_callback)
|
||||
{
|
||||
const char * parseptr = p_cuesheet;
|
||||
t_size lineidx = 1;
|
||||
while(*parseptr)
|
||||
{
|
||||
while(is_spacing(*parseptr)) parseptr++;
|
||||
if (*parseptr)
|
||||
{
|
||||
t_size length = 0;
|
||||
while(parseptr[length] && !is_linebreak(parseptr[length])) length++;
|
||||
if (length > 0) {
|
||||
try {
|
||||
g_parse_cue_line(parseptr,length,p_callback);
|
||||
} catch(exception_cue const & e) {//rethrow with line info
|
||||
pfc::throw_exception_with_message< exception_cue > (PFC_string_formatter() << e.what() << " (line " << (unsigned)lineidx << ")");
|
||||
}
|
||||
}
|
||||
parseptr += length;
|
||||
while(is_linebreak(*parseptr)) {
|
||||
if (*parseptr == '\n') lineidx++;
|
||||
parseptr++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cue_parser::parse(const char *p_cuesheet,t_cue_entry_list & p_out) {
|
||||
try {
|
||||
cue_parser_callback_retrievelist callback(p_out);
|
||||
g_parse_cue(p_cuesheet,callback);
|
||||
callback.finalize();
|
||||
} catch(exception_cue const & e) {
|
||||
pfc::throw_exception_with_message<exception_bad_cuesheet>(PFC_string_formatter() << "Error parsing cuesheet: " << e.what());
|
||||
}
|
||||
}
|
||||
void cue_parser::parse_info(const char * p_cuesheet,file_info & p_info,unsigned p_index) {
|
||||
try {
|
||||
cue_parser_callback_retrieveinfo callback(p_info,p_index);
|
||||
g_parse_cue(p_cuesheet,callback);
|
||||
callback.finalize();
|
||||
} catch(exception_cue const & e) {
|
||||
pfc::throw_exception_with_message< exception_bad_cuesheet > (PFC_string_formatter() << "Error parsing cuesheet: " << e.what());
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class cue_parser_callback_retrievecount : public cue_parser_callback
|
||||
{
|
||||
public:
|
||||
cue_parser_callback_retrievecount() : m_count(0) {}
|
||||
unsigned get_count() const {return m_count;}
|
||||
void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) {}
|
||||
void on_track(unsigned p_index,const char * p_type,t_size p_type_length) {m_count++;}
|
||||
void on_pregap(unsigned p_value) {}
|
||||
void on_index(unsigned p_index,unsigned p_value) {}
|
||||
void on_title(const char * p_title,t_size p_title_length) {}
|
||||
void on_performer(const char * p_performer,t_size p_performer_length) {}
|
||||
void on_isrc(const char * p_isrc,t_size p_isrc_length) {}
|
||||
void on_catalog(const char * p_catalog,t_size p_catalog_length) {}
|
||||
void on_comment(const char * p_comment,t_size p_comment_length) {}
|
||||
void on_flags(const char * p_flags,t_size p_flags_length) {}
|
||||
private:
|
||||
unsigned m_count;
|
||||
};
|
||||
|
||||
class cue_parser_callback_retrievecreatorentries : public cue_parser_callback
|
||||
{
|
||||
public:
|
||||
cue_parser_callback_retrievecreatorentries(cue_creator::t_entry_list & p_out) : m_out(p_out), m_track(0), m_pregap(0), m_index0_set(false), m_index1_set(false) {}
|
||||
|
||||
void on_file(const char * p_file,t_size p_file_length,const char * p_type,t_size p_type_length) {
|
||||
validate_file_type(p_type,p_type_length);
|
||||
m_file.set_string(p_file,p_file_length);
|
||||
m_fileType.set_string(p_type, p_type_length);
|
||||
}
|
||||
|
||||
void on_track(unsigned p_index,const char * p_type,t_size p_type_length)
|
||||
{
|
||||
finalize_track();
|
||||
|
||||
m_trackType.set_string( p_type, p_type_length );
|
||||
|
||||
//if (p_index != m_track + 1) throw exception_cue("cuesheet tracks out of order",0);
|
||||
|
||||
if (m_file.is_empty()) pfc::throw_exception_with_message< exception_cue > ("declaring a track with no file set");
|
||||
m_trackfile = m_file;
|
||||
m_trackFileType = m_fileType;
|
||||
m_track = p_index;
|
||||
}
|
||||
|
||||
void on_pregap(unsigned p_value)
|
||||
{
|
||||
m_pregap = (double) p_value / 75.0;
|
||||
}
|
||||
|
||||
void on_index(unsigned p_index,unsigned p_value)
|
||||
{
|
||||
if (p_index < t_cuesheet_index_list::count)
|
||||
{
|
||||
switch(p_index)
|
||||
{
|
||||
case 0: m_index0_set = true; break;
|
||||
case 1: m_index1_set = true; break;
|
||||
}
|
||||
m_indexes.m_positions[p_index] = (double) p_value / 75.0;
|
||||
}
|
||||
}
|
||||
void on_title(const char * p_title,t_size p_title_length) {}
|
||||
void on_performer(const char * p_performer,t_size p_performer_length) {}
|
||||
void on_songwriter(const char * p_performer,t_size p_performer_length) {}
|
||||
void on_isrc(const char * p_isrc,t_size p_isrc_length) {}
|
||||
void on_catalog(const char * p_catalog,t_size p_catalog_length) {}
|
||||
void on_comment(const char * p_comment,t_size p_comment_length) {}
|
||||
void finalize()
|
||||
{
|
||||
finalize_track();
|
||||
}
|
||||
void on_flags(const char * p_flags,t_size p_flags_length) {
|
||||
m_flags.set_string(p_flags,p_flags_length);
|
||||
}
|
||||
private:
|
||||
void finalize_track()
|
||||
{
|
||||
if ( m_track != 0 ) {
|
||||
if (m_track < 1 || m_track > maximumCueTrackNumber) pfc::throw_exception_with_message< exception_cue > ("track number out of range");
|
||||
if (!m_index1_set) pfc::throw_exception_with_message< exception_cue > ("INDEX 01 not set");
|
||||
if (!m_index0_set) m_indexes.m_positions[0] = m_indexes.m_positions[1] - m_pregap;
|
||||
if (!m_indexes.is_valid()) pfc::throw_exception_with_message< exception_cue > ("invalid index list");
|
||||
|
||||
cue_creator::t_entry_list::iterator iter;
|
||||
iter = m_out.insert_last();
|
||||
iter->m_track_number = m_track;
|
||||
iter->m_file = m_trackfile;
|
||||
iter->m_fileType = m_trackFileType;
|
||||
iter->m_index_list = m_indexes;
|
||||
iter->m_flags = m_flags;
|
||||
iter->m_trackType = m_trackType;
|
||||
}
|
||||
m_pregap = 0;
|
||||
m_indexes.reset();
|
||||
m_index0_set = m_index1_set = false;
|
||||
m_flags.reset();
|
||||
m_trackType.reset();
|
||||
}
|
||||
|
||||
bool m_index0_set,m_index1_set;
|
||||
double m_pregap;
|
||||
unsigned m_track;
|
||||
bool m_trackIsAudio = false;
|
||||
cue_creator::t_entry_list & m_out;
|
||||
pfc::string8 m_file, m_fileType,m_trackfile, m_trackFileType, m_flags, m_trackType;
|
||||
t_cuesheet_index_list m_indexes;
|
||||
};
|
||||
}
|
||||
|
||||
void cue_parser::parse_full(const char * p_cuesheet,cue_creator::t_entry_list & p_out) {
|
||||
try {
|
||||
{
|
||||
cue_parser_callback_retrievecreatorentries callback(p_out);
|
||||
g_parse_cue(p_cuesheet,callback);
|
||||
callback.finalize();
|
||||
}
|
||||
|
||||
{
|
||||
cue_creator::t_entry_list::iterator iter;
|
||||
for(iter=p_out.first();iter.is_valid();++iter) {
|
||||
if ( iter->isTrackAudio() ) {
|
||||
cue_parser_callback_retrieveinfo callback(iter->m_infos,iter->m_track_number);
|
||||
g_parse_cue(p_cuesheet,callback);
|
||||
callback.finalize();
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(exception_cue const & e) {
|
||||
pfc::throw_exception_with_message< exception_bad_cuesheet > (PFC_string_formatter() << "Error parsing cuesheet: " << e.what());
|
||||
}
|
||||
}
|
||||
|
||||
namespace file_info_record_helper {
|
||||
namespace {
|
||||
class __file_info_record__info__enumerator {
|
||||
public:
|
||||
__file_info_record__info__enumerator(file_info & p_out) : m_out(p_out) {}
|
||||
void operator() (const char * p_name, const char * p_value) { m_out.__info_add_unsafe(p_name, p_value); }
|
||||
private:
|
||||
file_info & m_out;
|
||||
};
|
||||
|
||||
class __file_info_record__meta__enumerator {
|
||||
public:
|
||||
__file_info_record__meta__enumerator(file_info & p_out) : m_out(p_out) {}
|
||||
template<typename t_value> void operator() (const char * p_name, const t_value & p_value) {
|
||||
t_size index = ~0;
|
||||
for (typename t_value::const_iterator iter = p_value.first(); iter.is_valid(); ++iter) {
|
||||
if (index == ~0) index = m_out.__meta_add_unsafe(p_name, *iter);
|
||||
else m_out.meta_add_value(index, *iter);
|
||||
}
|
||||
}
|
||||
private:
|
||||
file_info & m_out;
|
||||
};
|
||||
}
|
||||
|
||||
void file_info_record::from_info(const file_info & p_info) {
|
||||
reset();
|
||||
m_length = p_info.get_length();
|
||||
m_replaygain = p_info.get_replaygain();
|
||||
from_info_overwrite_meta(p_info);
|
||||
from_info_overwrite_info(p_info);
|
||||
}
|
||||
void file_info_record::to_info(file_info & p_info) const {
|
||||
p_info.reset();
|
||||
p_info.set_length(m_length);
|
||||
p_info.set_replaygain(m_replaygain);
|
||||
|
||||
{
|
||||
__file_info_record__info__enumerator e(p_info);
|
||||
m_info.enumerate(e);
|
||||
}
|
||||
{
|
||||
__file_info_record__meta__enumerator e(p_info);
|
||||
m_meta.enumerate(e);
|
||||
}
|
||||
}
|
||||
|
||||
void file_info_record::reset() {
|
||||
m_meta.remove_all(); m_info.remove_all();
|
||||
m_length = 0;
|
||||
m_replaygain = replaygain_info_invalid;
|
||||
}
|
||||
|
||||
void file_info_record::from_info_overwrite_info(const file_info & p_info) {
|
||||
for (t_size infowalk = 0, infocount = p_info.info_get_count(); infowalk < infocount; ++infowalk) {
|
||||
m_info.set(p_info.info_enum_name(infowalk), p_info.info_enum_value(infowalk));
|
||||
}
|
||||
}
|
||||
void file_info_record::from_info_overwrite_meta(const file_info & p_info) {
|
||||
for (t_size metawalk = 0, metacount = p_info.meta_get_count(); metawalk < metacount; ++metawalk) {
|
||||
const t_size valuecount = p_info.meta_enum_value_count(metawalk);
|
||||
if (valuecount > 0) {
|
||||
t_meta_value & entry = m_meta.find_or_add(p_info.meta_enum_name(metawalk));
|
||||
entry.remove_all();
|
||||
for (t_size valuewalk = 0; valuewalk < valuecount; ++valuewalk) {
|
||||
entry.add_item(p_info.meta_enum_value(metawalk, valuewalk));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void file_info_record::from_info_overwrite_rg(const file_info & p_info) {
|
||||
m_replaygain = replaygain_info::g_merge(m_replaygain, p_info.get_replaygain());
|
||||
}
|
||||
|
||||
void file_info_record::merge_overwrite(const file_info & p_info) {
|
||||
from_info_overwrite_info(p_info);
|
||||
from_info_overwrite_meta(p_info);
|
||||
from_info_overwrite_rg(p_info);
|
||||
}
|
||||
|
||||
void file_info_record::transfer_meta_entry(const char * p_name, const file_info & p_info, t_size p_index) {
|
||||
const t_size count = p_info.meta_enum_value_count(p_index);
|
||||
if (count == 0) {
|
||||
m_meta.remove(p_name);
|
||||
} else {
|
||||
t_meta_value & val = m_meta.find_or_add(p_name);
|
||||
val.remove_all();
|
||||
for (t_size walk = 0; walk < count; ++walk) {
|
||||
val.add_item(p_info.meta_enum_value(p_index, walk));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void file_info_record::meta_set(const char * p_name, const char * p_value) {
|
||||
m_meta.find_or_add(p_name).set_single(p_value);
|
||||
}
|
||||
|
||||
const file_info_record::t_meta_value * file_info_record::meta_query_ptr(const char * p_name) const {
|
||||
return m_meta.query_ptr(p_name);
|
||||
}
|
||||
|
||||
|
||||
void file_info_record::from_info_set_meta(const file_info & p_info) {
|
||||
m_meta.remove_all();
|
||||
from_info_overwrite_meta(p_info);
|
||||
}
|
||||
|
||||
}
|
||||
360
foobar2000/helpers/cue_parser.h
Normal file
360
foobar2000/helpers/cue_parser.h
Normal file
@@ -0,0 +1,360 @@
|
||||
#pragma once
|
||||
|
||||
#include "cue_creator.h"
|
||||
|
||||
//HINT: for info on how to generate an embedded cuesheet enabled input, see the end of this header.
|
||||
|
||||
|
||||
|
||||
namespace file_info_record_helper {
|
||||
|
||||
class file_info_record {
|
||||
public:
|
||||
typedef pfc::chain_list_v2_t<pfc::string8> t_meta_value;
|
||||
typedef pfc::map_t<pfc::string8,t_meta_value,file_info::field_name_comparator> t_meta_map;
|
||||
typedef pfc::map_t<pfc::string8,pfc::string8,file_info::field_name_comparator> t_info_map;
|
||||
|
||||
file_info_record() : m_replaygain(replaygain_info_invalid), m_length(0) {}
|
||||
|
||||
replaygain_info get_replaygain() const {return m_replaygain;}
|
||||
void set_replaygain(const replaygain_info & p_replaygain) {m_replaygain = p_replaygain;}
|
||||
double get_length() const {return m_length;}
|
||||
void set_length(double p_length) {m_length = p_length;}
|
||||
|
||||
void reset();
|
||||
void from_info_overwrite_info(const file_info & p_info);
|
||||
void from_info_overwrite_meta(const file_info & p_info);
|
||||
void from_info_overwrite_rg(const file_info & p_info);
|
||||
|
||||
template<typename t_source>
|
||||
void overwrite_meta(const t_source & p_meta) {
|
||||
m_meta.overwrite(p_meta);
|
||||
}
|
||||
template<typename t_source>
|
||||
void overwrite_info(const t_source & p_info) {
|
||||
m_info.overwrite(p_info);
|
||||
}
|
||||
|
||||
void merge_overwrite(const file_info & p_info);
|
||||
void transfer_meta_entry(const char * p_name,const file_info & p_info,t_size p_index);
|
||||
|
||||
void meta_set(const char * p_name,const char * p_value);
|
||||
const t_meta_value * meta_query_ptr(const char * p_name) const;
|
||||
|
||||
void from_info_set_meta(const file_info & p_info);
|
||||
|
||||
void from_info(const file_info & p_info);
|
||||
void to_info(file_info & p_info) const;
|
||||
|
||||
template<typename t_callback> void enumerate_meta(t_callback & p_callback) const {m_meta.enumerate(p_callback);}
|
||||
template<typename t_callback> void enumerate_meta(t_callback & p_callback) {m_meta.enumerate(p_callback);}
|
||||
|
||||
//private:
|
||||
t_meta_map m_meta;
|
||||
t_info_map m_info;
|
||||
replaygain_info m_replaygain;
|
||||
double m_length;
|
||||
};
|
||||
|
||||
}//namespace file_info_record_helper
|
||||
|
||||
|
||||
namespace cue_parser
|
||||
{
|
||||
struct cue_entry {
|
||||
pfc::string8 m_file, m_fileType;
|
||||
unsigned m_track_number;
|
||||
t_cuesheet_index_list m_indexes;
|
||||
bool isFileBinary() const {return pfc::stringEqualsI_ascii(m_fileType, "BINARY");}
|
||||
};
|
||||
|
||||
typedef pfc::chain_list_v2_t<cue_entry> t_cue_entry_list;
|
||||
|
||||
|
||||
PFC_DECLARE_EXCEPTION(exception_bad_cuesheet,exception_io_data,"Invalid cuesheet");
|
||||
|
||||
//! Throws exception_bad_cuesheet on failure.
|
||||
void parse(const char *p_cuesheet,t_cue_entry_list & p_out);
|
||||
//! Throws exception_bad_cuesheet on failure.
|
||||
void parse_info(const char *p_cuesheet,file_info & p_info,unsigned p_index);
|
||||
//! Throws exception_bad_cuesheet on failure.
|
||||
void parse_full(const char * p_cuesheet,cue_creator::t_entry_list & p_out);
|
||||
|
||||
|
||||
|
||||
struct track_record {
|
||||
file_info_record_helper::file_info_record m_info;
|
||||
pfc::string8 m_file,m_flags;
|
||||
t_cuesheet_index_list m_index_list;
|
||||
};
|
||||
|
||||
typedef pfc::map_t<unsigned,track_record> track_record_list;
|
||||
|
||||
class embeddedcue_metadata_manager {
|
||||
public:
|
||||
void get_tag(file_info & p_info) const;
|
||||
void set_tag(file_info const & p_info);
|
||||
|
||||
void get_track_info(unsigned p_track,file_info & p_info) const;
|
||||
void set_track_info(unsigned p_track,file_info const & p_info);
|
||||
void query_track_offsets(unsigned p_track,double & p_begin,double & p_length) const;
|
||||
bool have_cuesheet() const;
|
||||
unsigned remap_trackno(unsigned p_index) const;
|
||||
t_size get_cue_track_count() const;
|
||||
private:
|
||||
track_record_list m_content;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
template<typename t_base>
|
||||
class input_wrapper_cue_t : public input_forward_static_methods<t_base> {
|
||||
public:
|
||||
typedef input_info_writer_v2 interface_info_writer_t; // remove_tags supplied
|
||||
void open(service_ptr_t<file> p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort) {
|
||||
m_remote = filesystem::g_is_recognized_and_remote( p_path );
|
||||
if (m_remote && p_reason == input_open_info_write) throw exception_io_object_is_remote();
|
||||
m_impl.open( p_filehint, p_path, p_reason, p_abort );
|
||||
if (!m_remote) {
|
||||
file_info_impl info;
|
||||
m_impl.get_info(info, p_abort);
|
||||
m_meta.set_tag(info);
|
||||
}
|
||||
}
|
||||
|
||||
t_uint32 get_subsong_count() {
|
||||
return this->expose_cuesheet() ? (uint32_t) m_meta.get_cue_track_count() : 1;
|
||||
}
|
||||
|
||||
t_uint32 get_subsong(t_uint32 p_index) {
|
||||
return this->expose_cuesheet() ? m_meta.remap_trackno(p_index) : 0;
|
||||
}
|
||||
|
||||
void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) {
|
||||
if (m_remote) {
|
||||
PFC_ASSERT(p_subsong == 0);
|
||||
m_impl.get_info(p_info, p_abort);
|
||||
} else if (p_subsong == 0) {
|
||||
m_meta.get_tag(p_info);
|
||||
} else {
|
||||
m_meta.get_track_info(p_subsong,p_info);
|
||||
}
|
||||
}
|
||||
|
||||
t_filestats get_file_stats(abort_callback & p_abort) {return m_impl.get_file_stats(p_abort);}
|
||||
|
||||
void decode_initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort) {
|
||||
if (p_subsong == 0) {
|
||||
m_impl.decode_initialize(p_flags, p_abort);
|
||||
m_decodeFrom = 0; m_decodeLength = -1; m_decodePos = 0;
|
||||
} else {
|
||||
double start, length;
|
||||
_query_track_offsets(p_subsong,start,length);
|
||||
unsigned flags2 = p_flags;
|
||||
if (start > 0) flags2 &= ~input_flag_no_seeking;
|
||||
flags2 &= ~input_flag_allow_inaccurate_seeking;
|
||||
m_impl.decode_initialize(flags2, p_abort);
|
||||
m_impl.decode_seek(start, p_abort);
|
||||
m_decodeFrom = start; m_decodeLength = length; m_decodePos = 0;
|
||||
}
|
||||
}
|
||||
|
||||
bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort) {
|
||||
return _run(p_chunk, NULL, p_abort);
|
||||
}
|
||||
|
||||
void decode_seek(double p_seconds,abort_callback & p_abort) {
|
||||
if (this->m_decodeLength >= 0 && p_seconds > m_decodeLength) p_seconds = m_decodeLength;
|
||||
m_impl.decode_seek(m_decodeFrom + p_seconds,p_abort);
|
||||
m_decodePos = p_seconds;
|
||||
}
|
||||
|
||||
bool decode_can_seek() {return m_impl.decode_can_seek();}
|
||||
|
||||
bool decode_run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort) {
|
||||
return _run(p_chunk, &p_raw, p_abort);
|
||||
}
|
||||
void set_logger(event_logger::ptr ptr) {
|
||||
m_impl.set_logger(ptr);
|
||||
}
|
||||
bool flush_on_pause() {
|
||||
return m_impl.flush_on_pause();
|
||||
}
|
||||
void set_pause(bool) {} // obsolete
|
||||
size_t extended_param(const GUID & type, size_t arg1, void * arg2, size_t arg2size) {
|
||||
return m_impl.extended_param(type, arg1, arg2, arg2size);
|
||||
}
|
||||
|
||||
bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta) {
|
||||
return m_impl.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_impl.decode_get_dynamic_info_track(p_out, p_timestamp_delta);
|
||||
}
|
||||
|
||||
void decode_on_idle(abort_callback & p_abort) {
|
||||
m_impl.decode_on_idle(p_abort);
|
||||
}
|
||||
|
||||
void remove_tags(abort_callback & abort) {
|
||||
PFC_ASSERT(!m_remote);
|
||||
m_impl.remove_tags( abort );
|
||||
file_info_impl info;
|
||||
m_impl.get_info(info, abort);
|
||||
m_meta.set_tag( info );
|
||||
}
|
||||
|
||||
void retag_set_info(t_uint32 p_subsong,const file_info & p_info,abort_callback & p_abort) {
|
||||
PFC_ASSERT(!m_remote);
|
||||
if (p_subsong == 0) {
|
||||
m_meta.set_tag(p_info);
|
||||
} else {
|
||||
m_meta.set_track_info(p_subsong,p_info);
|
||||
}
|
||||
}
|
||||
|
||||
void retag_commit(abort_callback & p_abort) {
|
||||
PFC_ASSERT(!m_remote);
|
||||
file_info_impl info;
|
||||
m_meta.get_tag(info);
|
||||
m_impl.retag(pfc::implicit_cast<const file_info&>(info), p_abort);
|
||||
info.reset();
|
||||
m_impl.get_info(info, p_abort);
|
||||
m_meta.set_tag( info );
|
||||
}
|
||||
void _query_track_offsets(unsigned p_subsong, double& start, double& length) const {
|
||||
m_meta.query_track_offsets(p_subsong,start,length);
|
||||
}
|
||||
bool expose_cuesheet() const {
|
||||
return !m_remote && m_meta.have_cuesheet();
|
||||
}
|
||||
private:
|
||||
bool _run(audio_chunk & chunk, mem_block_container * raw, abort_callback & aborter) {
|
||||
if (m_decodeLength >= 0 && m_decodePos >= m_decodeLength) return false;
|
||||
if (raw == NULL) {
|
||||
if (!m_impl.decode_run(chunk, aborter)) return false;
|
||||
} else {
|
||||
if (!m_impl.decode_run_raw(chunk, *raw, aborter)) return false;
|
||||
}
|
||||
|
||||
if (m_decodeLength >= 0) {
|
||||
const uint64_t remaining = audio_math::time_to_samples( m_decodeLength - m_decodePos, chunk.get_sample_rate() );
|
||||
const size_t samplesGot = chunk.get_sample_count();
|
||||
if (remaining < samplesGot) {
|
||||
m_decodePos = m_decodeLength;
|
||||
if (remaining == 0) { // rare but possible as a result of rounding SNAFU - we're EOF but we didn't notice earlier
|
||||
return false;
|
||||
}
|
||||
|
||||
chunk.set_sample_count( (size_t) remaining );
|
||||
if (raw != NULL) {
|
||||
const t_size rawSize = raw->get_size();
|
||||
PFC_ASSERT( rawSize % samplesGot == 0 );
|
||||
raw->set_size( (t_size) ( (t_uint64) rawSize * remaining / samplesGot ) );
|
||||
}
|
||||
} else {
|
||||
m_decodePos += chunk.get_duration();
|
||||
}
|
||||
} else {
|
||||
m_decodePos += chunk.get_duration();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
t_base m_impl;
|
||||
double m_decodeFrom, m_decodeLength, m_decodePos;
|
||||
bool m_remote = false;
|
||||
embeddedcue_metadata_manager m_meta;
|
||||
};
|
||||
#ifdef FOOBAR2000_HAVE_CHAPTERIZER
|
||||
template<typename I>
|
||||
class chapterizer_impl_t : public chapterizer
|
||||
{
|
||||
public:
|
||||
bool is_our_path(const char * p_path) {
|
||||
return I::g_is_our_path(p_path, pfc::string_extension(p_path));
|
||||
}
|
||||
|
||||
void set_chapters(const char * p_path,chapter_list const & p_list,abort_callback & p_abort) {
|
||||
input_wrapper_cue_t<I> instance;
|
||||
instance.open(0,p_path,input_open_info_write,p_abort);
|
||||
|
||||
//stamp the cuesheet first
|
||||
{
|
||||
file_info_impl info;
|
||||
instance.get_info(0,info,p_abort);
|
||||
|
||||
pfc::string_formatter cuesheet;
|
||||
|
||||
{
|
||||
cue_creator::t_entry_list entries;
|
||||
t_size n, m = p_list.get_chapter_count();
|
||||
const double pregap = p_list.get_pregap();
|
||||
double offset_acc = pregap;
|
||||
for(n=0;n<m;n++)
|
||||
{
|
||||
cue_creator::t_entry_list::iterator entry;
|
||||
entry = entries.insert_last();
|
||||
entry->m_infos = p_list.get_info(n);
|
||||
entry->m_file = "CDImage.wav";
|
||||
entry->m_track_number = (unsigned)(n+1);
|
||||
entry->m_index_list.from_infos(entry->m_infos,offset_acc);
|
||||
if (n == 0) entry->m_index_list.m_positions[0] = 0;
|
||||
offset_acc += entry->m_infos.get_length();
|
||||
}
|
||||
cue_creator::create(cuesheet,entries);
|
||||
}
|
||||
|
||||
info.meta_set("cuesheet",cuesheet);
|
||||
|
||||
instance.retag_set_info(0,info,p_abort);
|
||||
}
|
||||
//stamp per-chapter infos
|
||||
for(t_size walk = 0, total = p_list.get_chapter_count(); walk < total; ++walk) {
|
||||
instance.retag_set_info( (uint32_t)( walk + 1 ), p_list.get_info(walk),p_abort);
|
||||
}
|
||||
|
||||
instance.retag_commit(p_abort);
|
||||
}
|
||||
|
||||
void get_chapters(const char * p_path,chapter_list & p_list,abort_callback & p_abort) {
|
||||
|
||||
input_wrapper_cue_t<I> instance;
|
||||
instance.open(0,p_path,input_open_info_read,p_abort);
|
||||
const t_uint32 total = instance.get_subsong_count();
|
||||
|
||||
if (instance.expose_cuesheet()) {
|
||||
double start, len;
|
||||
instance._query_track_offsets(1, start, len);
|
||||
p_list.set_pregap( start );
|
||||
}
|
||||
|
||||
|
||||
p_list.set_chapter_count(total);
|
||||
for(t_uint32 walk = 0; walk < total; ++walk) {
|
||||
file_info_impl info;
|
||||
instance.get_info(instance.get_subsong(walk),info,p_abort);
|
||||
p_list.set_info(walk,info);
|
||||
}
|
||||
}
|
||||
|
||||
bool supports_pregaps() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
#endif
|
||||
};
|
||||
|
||||
//! Wrapper template for generating embedded cuesheet enabled inputs.
|
||||
//! t_input_impl is a singletrack input implementation (see input_singletrack_impl for method declarations).
|
||||
//! To declare an embedded cuesheet enabled input, change your input declaration from input_singletrack_factory_t<myinput> to input_cuesheet_factory_t<myinput>.
|
||||
template<typename t_input_impl, unsigned t_flags = 0>
|
||||
class input_cuesheet_factory_t {
|
||||
public:
|
||||
input_factory_t<cue_parser::input_wrapper_cue_t<t_input_impl>,t_flags > m_input_factory;
|
||||
#ifdef FOOBAR2000_HAVE_CHAPTERIZER
|
||||
service_factory_single_t<cue_parser::chapterizer_impl_t<t_input_impl> > m_chapterizer_factory;
|
||||
#endif
|
||||
};
|
||||
383
foobar2000/helpers/cue_parser_embedding.cpp
Normal file
383
foobar2000/helpers/cue_parser_embedding.cpp
Normal file
@@ -0,0 +1,383 @@
|
||||
#include "stdafx.h"
|
||||
#include "cue_parser.h"
|
||||
|
||||
using namespace cue_parser;
|
||||
using namespace file_info_record_helper;
|
||||
static void build_cue_meta_name(const char * p_name,unsigned p_tracknumber,pfc::string_base & p_out) {
|
||||
p_out.reset();
|
||||
p_out << "cue_track" << pfc::format_uint(p_tracknumber % 100,2) << "_" << p_name;
|
||||
}
|
||||
|
||||
static bool is_reserved_meta_entry(const char * p_name) {
|
||||
return file_info::field_name_comparator::compare(p_name,"cuesheet") == 0;
|
||||
}
|
||||
|
||||
static bool is_global_meta_entry(const char * p_name) {
|
||||
static const char header[] = "cue_track";
|
||||
return pfc::stricmp_ascii_ex(p_name,strlen(header),header,~0) != 0;
|
||||
}
|
||||
static bool is_allowed_field(const char * p_name) {
|
||||
return !is_reserved_meta_entry(p_name) && is_global_meta_entry(p_name);
|
||||
}
|
||||
namespace {
|
||||
class __get_tag_cue_track_list_builder {
|
||||
public:
|
||||
__get_tag_cue_track_list_builder(cue_creator::t_entry_list & p_entries) : m_entries(p_entries) {}
|
||||
void operator() (unsigned p_trackno,const track_record & p_record) {
|
||||
if (p_trackno > 0) {
|
||||
cue_creator::t_entry_list::iterator iter = m_entries.insert_last();
|
||||
iter->m_trackType = "AUDIO";
|
||||
iter->m_file = p_record.m_file;
|
||||
iter->m_flags = p_record.m_flags;
|
||||
iter->m_index_list = p_record.m_index_list;
|
||||
iter->m_track_number = p_trackno;
|
||||
p_record.m_info.to_info(iter->m_infos);
|
||||
}
|
||||
}
|
||||
private:
|
||||
cue_creator::t_entry_list & m_entries;
|
||||
};
|
||||
|
||||
typedef pfc::avltree_t<pfc::string8,file_info::field_name_comparator> field_name_list;
|
||||
|
||||
class __get_tag__enum_fields_enumerator {
|
||||
public:
|
||||
__get_tag__enum_fields_enumerator(field_name_list & p_out) : m_out(p_out) {}
|
||||
void operator() (unsigned p_trackno,const track_record & p_record) {
|
||||
if (p_trackno > 0) p_record.m_info.enumerate_meta(*this);
|
||||
}
|
||||
template<typename t_value> void operator() (const char * p_name,const t_value & p_value) {
|
||||
m_out.add(p_name);
|
||||
}
|
||||
private:
|
||||
field_name_list & m_out;
|
||||
};
|
||||
|
||||
|
||||
class __get_tag__is_field_global_check {
|
||||
private:
|
||||
typedef file_info_record::t_meta_value t_value;
|
||||
public:
|
||||
__get_tag__is_field_global_check(const char * p_field) : m_field(p_field), m_value(NULL), m_state(true) {}
|
||||
|
||||
void operator() (unsigned p_trackno,const track_record & p_record) {
|
||||
if (p_trackno > 0 && m_state) {
|
||||
const t_value * val = p_record.m_info.meta_query_ptr(m_field);
|
||||
if (val == NULL) {m_state = false; return;}
|
||||
if (m_value == NULL) {
|
||||
m_value = val;
|
||||
} else {
|
||||
if (pfc::comparator_list<pfc::comparator_strcmp>::compare(*m_value,*val) != 0) {
|
||||
m_state = false; return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void finalize(file_info_record::t_meta_map & p_globals) {
|
||||
if (m_state && m_value != NULL) {
|
||||
p_globals.set(m_field,*m_value);
|
||||
}
|
||||
}
|
||||
private:
|
||||
const char * const m_field;
|
||||
const t_value * m_value;
|
||||
bool m_state;
|
||||
};
|
||||
|
||||
class __get_tag__filter_globals {
|
||||
public:
|
||||
__get_tag__filter_globals(track_record_list const & p_tracks,file_info_record::t_meta_map & p_globals) : m_tracks(p_tracks), m_globals(p_globals) {}
|
||||
|
||||
void operator() (const char * p_field) {
|
||||
if (is_allowed_field(p_field)) {
|
||||
__get_tag__is_field_global_check wrapper(p_field);
|
||||
m_tracks.enumerate(wrapper);
|
||||
wrapper.finalize(m_globals);
|
||||
}
|
||||
}
|
||||
private:
|
||||
const track_record_list & m_tracks;
|
||||
file_info_record::t_meta_map & m_globals;
|
||||
};
|
||||
|
||||
class __get_tag__local_field_filter {
|
||||
public:
|
||||
__get_tag__local_field_filter(const file_info_record::t_meta_map & p_globals,file_info_record::t_meta_map & p_output) : m_globals(p_globals), m_output(p_output), m_currenttrack(0) {}
|
||||
void operator() (unsigned p_trackno,const track_record & p_track) {
|
||||
if (p_trackno > 0) {
|
||||
m_currenttrack = p_trackno;
|
||||
p_track.m_info.enumerate_meta(*this);
|
||||
}
|
||||
}
|
||||
void operator() (const char * p_name,const file_info_record::t_meta_value & p_value) {
|
||||
PFC_ASSERT(m_currenttrack > 0);
|
||||
if (!m_globals.have_item(p_name)) {
|
||||
build_cue_meta_name(p_name,m_currenttrack,m_buffer);
|
||||
m_output.set(m_buffer,p_value);
|
||||
}
|
||||
}
|
||||
private:
|
||||
unsigned m_currenttrack;
|
||||
pfc::string8_fastalloc m_buffer;
|
||||
const file_info_record::t_meta_map & m_globals;
|
||||
file_info_record::t_meta_map & m_output;
|
||||
};
|
||||
};
|
||||
|
||||
static bool meta_value_equals(const char* v1, const char* v2, bool asNumber) {
|
||||
if (asNumber) {
|
||||
// Special fix: leading zeros on track numbers
|
||||
while( *v1 == '0' ) ++ v1;
|
||||
while( *v2 == '0' ) ++ v2;
|
||||
}
|
||||
return strcmp(v1,v2) == 0;
|
||||
}
|
||||
|
||||
static void strip_redundant_track_meta(unsigned p_tracknumber,const file_info & p_cueinfo,file_info_record::t_meta_map & p_meta,const char * p_metaname, bool asNumber) {
|
||||
const size_t metaindex = p_cueinfo.meta_find(p_metaname);
|
||||
if (metaindex == SIZE_MAX) return;
|
||||
pfc::string_formatter namelocal;
|
||||
build_cue_meta_name(p_metaname,p_tracknumber,namelocal);
|
||||
{
|
||||
const file_info_record::t_meta_value * val = p_meta.query_ptr(namelocal);
|
||||
if (val == NULL) return;
|
||||
file_info_record::t_meta_value::const_iterator iter = val->first();
|
||||
for(t_size valwalk = 0, valcount = p_cueinfo.meta_enum_value_count(metaindex); valwalk < valcount; ++valwalk) {
|
||||
if (iter.is_empty()) return;
|
||||
|
||||
if (!meta_value_equals(*iter,p_cueinfo.meta_enum_value(metaindex,valwalk), asNumber)) return;
|
||||
++iter;
|
||||
}
|
||||
if (!iter.is_empty()) return;
|
||||
}
|
||||
//success
|
||||
p_meta.remove(namelocal);
|
||||
}
|
||||
|
||||
void embeddedcue_metadata_manager::get_tag(file_info & p_info) const {
|
||||
if (!have_cuesheet()) {
|
||||
m_content.query_ptr((unsigned)0)->m_info.to_info(p_info);
|
||||
p_info.meta_remove_field("cuesheet");
|
||||
} else {
|
||||
cue_creator::t_entry_list entries;
|
||||
{
|
||||
__get_tag_cue_track_list_builder e(entries);
|
||||
m_content.enumerate(e);
|
||||
}
|
||||
pfc::string_formatter cuesheet;
|
||||
cue_creator::create(cuesheet,entries);
|
||||
entries.remove_all();
|
||||
//parse it back to see what info got stored in the cuesheet and what needs to be stored outside cuesheet in the tags
|
||||
cue_parser::parse_full(cuesheet,entries);
|
||||
file_info_record output;
|
||||
|
||||
|
||||
|
||||
|
||||
{
|
||||
file_info_record::t_meta_map globals;
|
||||
//1. find global infos and forward them
|
||||
{
|
||||
field_name_list fields;
|
||||
{ __get_tag__enum_fields_enumerator e(fields); m_content.enumerate(e);}
|
||||
{ __get_tag__filter_globals e(m_content,globals); fields.enumerate(e); }
|
||||
}
|
||||
|
||||
output.overwrite_meta(globals);
|
||||
|
||||
//2. find local infos
|
||||
{__get_tag__local_field_filter e(globals,output.m_meta); m_content.enumerate(e);}
|
||||
}
|
||||
|
||||
|
||||
//strip redundant titles and tracknumbers that the cuesheet already contains
|
||||
for(cue_creator::t_entry_list::const_iterator iter = entries.first(); iter.is_valid(); ++iter) {
|
||||
strip_redundant_track_meta(iter->m_track_number,iter->m_infos,output.m_meta,"tracknumber", true);
|
||||
strip_redundant_track_meta(iter->m_track_number,iter->m_infos,output.m_meta,"title", false);
|
||||
}
|
||||
|
||||
|
||||
//add tech infos etc
|
||||
|
||||
{
|
||||
const track_record * rec = m_content.query_ptr((unsigned)0);
|
||||
if (rec != NULL) {
|
||||
output.set_length(rec->m_info.get_length());
|
||||
output.set_replaygain(rec->m_info.get_replaygain());
|
||||
output.overwrite_info(rec->m_info.m_info);
|
||||
}
|
||||
}
|
||||
output.meta_set("cuesheet",cuesheet);
|
||||
output.to_info(p_info);
|
||||
}
|
||||
}
|
||||
|
||||
static bool resolve_cue_meta_name(const char * p_name,pfc::string_base & p_outname,unsigned & p_tracknumber) {
|
||||
//"cue_trackNN_fieldname"
|
||||
static const char header[] = "cue_track";
|
||||
if (pfc::stricmp_ascii_ex(p_name,strlen(header),header,~0) != 0) return false;
|
||||
p_name += strlen(header);
|
||||
if (!pfc::char_is_numeric(p_name[0]) || !pfc::char_is_numeric(p_name[1]) || p_name[2] != '_') return false;
|
||||
unsigned tracknumber = pfc::atoui_ex(p_name,2);
|
||||
if (tracknumber == 0) return false;
|
||||
p_name += 3;
|
||||
p_tracknumber = tracknumber;
|
||||
p_outname = p_name;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
class __set_tag_global_field_relay {
|
||||
public:
|
||||
__set_tag_global_field_relay(const file_info & p_info,t_size p_index) : m_info(p_info), m_index(p_index) {}
|
||||
void operator() (unsigned p_trackno,track_record & p_record) {
|
||||
if (p_trackno > 0) {
|
||||
p_record.m_info.transfer_meta_entry(m_info.meta_enum_name(m_index),m_info,m_index);
|
||||
}
|
||||
}
|
||||
private:
|
||||
const file_info & m_info;
|
||||
const t_size m_index;
|
||||
};
|
||||
}
|
||||
|
||||
void embeddedcue_metadata_manager::set_tag(file_info const & p_info) {
|
||||
m_content.remove_all();
|
||||
|
||||
{
|
||||
track_record & track0 = m_content.find_or_add((unsigned)0);
|
||||
track0.m_info.from_info(p_info);
|
||||
track0.m_info.m_info.set("cue_embedded","no");
|
||||
}
|
||||
|
||||
|
||||
|
||||
const char * cuesheet = p_info.meta_get("cuesheet",0);
|
||||
if (cuesheet == NULL) {
|
||||
return;
|
||||
}
|
||||
|
||||
//processing order
|
||||
//1. cuesheet content
|
||||
//2. overwrite with global metadata from the tag
|
||||
//3. overwrite with local metadata from the tag
|
||||
|
||||
{
|
||||
cue_creator::t_entry_list entries;
|
||||
try {
|
||||
cue_parser::parse_full(cuesheet,entries);
|
||||
} catch(exception_io_data const & e) {
|
||||
console::complain("Attempting to embed an invalid cuesheet", e.what());
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
const double length = p_info.get_length();
|
||||
for(cue_creator::t_entry_list::const_iterator iter = entries.first(); iter.is_valid(); ++iter ) {
|
||||
if (iter->m_index_list.start() > length) {
|
||||
console::info("Invalid cuesheet - index outside allowed range");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(cue_creator::t_entry_list::const_iterator iter = entries.first(); iter.is_valid(); ) {
|
||||
cue_creator::t_entry_list::const_iterator next = iter;
|
||||
++next;
|
||||
track_record & entry = m_content.find_or_add(iter->m_track_number);
|
||||
entry.m_file = iter->m_file;
|
||||
entry.m_flags = iter->m_flags;
|
||||
entry.m_index_list = iter->m_index_list;
|
||||
entry.m_info.from_info(iter->m_infos);
|
||||
entry.m_info.from_info_overwrite_info(p_info);
|
||||
entry.m_info.m_info.set("cue_embedded","yes");
|
||||
double begin = entry.m_index_list.start(), end = next.is_valid() ? next->m_index_list.start() : p_info.get_length();
|
||||
if (end <= begin) throw exception_io_data();
|
||||
entry.m_info.set_length(end - begin);
|
||||
iter = next;
|
||||
}
|
||||
}
|
||||
|
||||
for(t_size metawalk = 0, metacount = p_info.meta_get_count(); metawalk < metacount; ++metawalk) {
|
||||
const char * name = p_info.meta_enum_name(metawalk);
|
||||
const t_size valuecount = p_info.meta_enum_value_count(metawalk);
|
||||
if (valuecount > 0 && !is_reserved_meta_entry(name) && is_global_meta_entry(name)) {
|
||||
__set_tag_global_field_relay relay(p_info,metawalk);
|
||||
m_content.enumerate(relay);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
pfc::string8_fastalloc namebuffer;
|
||||
for(t_size metawalk = 0, metacount = p_info.meta_get_count(); metawalk < metacount; ++metawalk) {
|
||||
const char * name = p_info.meta_enum_name(metawalk);
|
||||
const t_size valuecount = p_info.meta_enum_value_count(metawalk);
|
||||
unsigned trackno;
|
||||
if (valuecount > 0 && !is_reserved_meta_entry(name) && resolve_cue_meta_name(name,namebuffer,trackno)) {
|
||||
track_record * rec = m_content.query_ptr(trackno);
|
||||
if (rec != NULL) {
|
||||
rec->m_info.transfer_meta_entry(namebuffer,p_info,metawalk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void embeddedcue_metadata_manager::get_track_info(unsigned p_track,file_info & p_info) const {
|
||||
const track_record * rec = m_content.query_ptr(p_track);
|
||||
if (rec == NULL) throw exception_io_data();
|
||||
rec->m_info.to_info(p_info);
|
||||
}
|
||||
|
||||
void embeddedcue_metadata_manager::set_track_info(unsigned p_track,file_info const & p_info) {
|
||||
track_record * rec = m_content.query_ptr(p_track);
|
||||
if (rec == NULL) throw exception_io_data();
|
||||
rec->m_info.from_info_set_meta(p_info);
|
||||
rec->m_info.set_replaygain(p_info.get_replaygain());
|
||||
}
|
||||
|
||||
void embeddedcue_metadata_manager::query_track_offsets(unsigned p_track,double & p_begin,double & p_length) const {
|
||||
const track_record * rec = m_content.query_ptr(p_track);
|
||||
if (rec == NULL) throw exception_io_data();
|
||||
p_begin = rec->m_index_list.start();
|
||||
p_length = rec->m_info.get_length();
|
||||
}
|
||||
|
||||
bool embeddedcue_metadata_manager::have_cuesheet() const {
|
||||
return m_content.get_count() > 1;
|
||||
}
|
||||
|
||||
namespace {
|
||||
class __remap_trackno_enumerator {
|
||||
public:
|
||||
__remap_trackno_enumerator(unsigned p_index) : m_countdown(p_index), m_result(0) {}
|
||||
template<typename t_blah> void operator() (unsigned p_trackno,const t_blah&) {
|
||||
if (p_trackno > 0 && m_result == 0) {
|
||||
if (m_countdown == 0) {
|
||||
m_result = p_trackno;
|
||||
} else {
|
||||
--m_countdown;
|
||||
}
|
||||
}
|
||||
}
|
||||
unsigned result() const {return m_result;}
|
||||
private:
|
||||
unsigned m_countdown;
|
||||
unsigned m_result;
|
||||
};
|
||||
};
|
||||
|
||||
unsigned embeddedcue_metadata_manager::remap_trackno(unsigned p_index) const {
|
||||
if (have_cuesheet()) {
|
||||
__remap_trackno_enumerator wrapper(p_index);
|
||||
m_content.enumerate(wrapper);
|
||||
return wrapper.result();
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
t_size embeddedcue_metadata_manager::get_cue_track_count() const {
|
||||
return m_content.get_count() - 1;
|
||||
}
|
||||
145
foobar2000/helpers/cuesheet_index_list.cpp
Normal file
145
foobar2000/helpers/cuesheet_index_list.cpp
Normal file
@@ -0,0 +1,145 @@
|
||||
#include "stdafx.h"
|
||||
#include "cuesheet_index_list.h"
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#define sprintf_s sprintf
|
||||
#endif
|
||||
|
||||
bool t_cuesheet_index_list::is_valid() const {
|
||||
if (m_positions[1] < m_positions[0]) return false;
|
||||
for(t_size n = 2; n < count && m_positions[n] > 0; n++) {
|
||||
if (m_positions[n] < m_positions[n-1]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void t_cuesheet_index_list::to_infos(file_info & p_out) const
|
||||
{
|
||||
double base = m_positions[1];
|
||||
|
||||
if (base > 0) {
|
||||
p_out.info_set("referenced_offset",cuesheet_format_index_time(base));
|
||||
}
|
||||
|
||||
if (m_positions[0] < base)
|
||||
p_out.info_set("pregap",cuesheet_format_index_time(base - m_positions[0]));
|
||||
else
|
||||
p_out.info_remove("pregap");
|
||||
|
||||
p_out.info_remove("index 00");
|
||||
p_out.info_remove("index 01");
|
||||
|
||||
for(unsigned n=2;n<count;n++)
|
||||
{
|
||||
char namebuffer[16];
|
||||
sprintf_s(namebuffer,"index %02u",n);
|
||||
double position = m_positions[n] - base;
|
||||
if (position > 0)
|
||||
p_out.info_set(namebuffer,cuesheet_format_index_time(position));
|
||||
else
|
||||
p_out.info_remove(namebuffer);
|
||||
}
|
||||
}
|
||||
|
||||
static bool parse_value(const char * p_name,double & p_out)
|
||||
{
|
||||
if (p_name == NULL) return false;
|
||||
try {
|
||||
p_out = cuesheet_parse_index_time_e(p_name,strlen(p_name));
|
||||
} catch(std::exception const &) {return false;}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool t_cuesheet_index_list::from_infos(file_info const & p_in,double p_base)
|
||||
{
|
||||
double pregap;
|
||||
bool found = false;
|
||||
if (!parse_value(p_in.info_get("pregap"),pregap)) pregap = 0;
|
||||
else found = true;
|
||||
m_positions[0] = p_base - pregap;
|
||||
m_positions[1] = p_base;
|
||||
for(unsigned n=2;n<count;n++)
|
||||
{
|
||||
char namebuffer[16];
|
||||
sprintf_s(namebuffer,"index %02u",n);
|
||||
double temp;
|
||||
if (parse_value(p_in.info_get(namebuffer),temp)) {
|
||||
m_positions[n] = temp + p_base; found = true;
|
||||
} else {
|
||||
m_positions[n] = 0;
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
bool t_cuesheet_index_list::is_empty() const {
|
||||
for(unsigned n=0;n<count;n++) if (m_positions[n] != m_positions[1]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
cuesheet_format_index_time::cuesheet_format_index_time(double p_time)
|
||||
{
|
||||
t_uint64 ticks = audio_math::time_to_samples(p_time,75);
|
||||
t_uint64 seconds = ticks / 75; ticks %= 75;
|
||||
t_uint64 minutes = seconds / 60; seconds %= 60;
|
||||
m_buffer << pfc::format_uint(minutes,2) << ":" << pfc::format_uint(seconds,2) << ":" << pfc::format_uint(ticks,2);
|
||||
}
|
||||
|
||||
double cuesheet_parse_index_time_e(const char * p_string,t_size p_length)
|
||||
{
|
||||
return (double) cuesheet_parse_index_time_ticks_e(p_string,p_length) / 75.0;
|
||||
}
|
||||
|
||||
unsigned cuesheet_parse_index_time_ticks_e(const char * p_string,t_size p_length)
|
||||
{
|
||||
p_length = pfc::strlen_max(p_string,p_length);
|
||||
t_size ptr = 0;
|
||||
t_size splitmarks[2];
|
||||
t_size splitptr = 0;
|
||||
for(ptr=0;ptr<p_length;ptr++)
|
||||
{
|
||||
if (p_string[ptr] == ':')
|
||||
{
|
||||
if (splitptr >= 2) throw std::runtime_error("invalid INDEX time syntax");
|
||||
splitmarks[splitptr++] = ptr;
|
||||
}
|
||||
else if (!pfc::char_is_numeric(p_string[ptr])) throw std::runtime_error("invalid INDEX time syntax");
|
||||
}
|
||||
|
||||
t_size minutes_base = 0, minutes_length = 0, seconds_base = 0, seconds_length = 0, frames_base = 0, frames_length = 0;
|
||||
|
||||
switch(splitptr)
|
||||
{
|
||||
case 0:
|
||||
frames_base = 0;
|
||||
frames_length = p_length;
|
||||
break;
|
||||
case 1:
|
||||
seconds_base = 0;
|
||||
seconds_length = splitmarks[0];
|
||||
frames_base = splitmarks[0] + 1;
|
||||
frames_length = p_length - frames_base;
|
||||
break;
|
||||
case 2:
|
||||
minutes_base = 0;
|
||||
minutes_length = splitmarks[0];
|
||||
seconds_base = splitmarks[0] + 1;
|
||||
seconds_length = splitmarks[1] - seconds_base;
|
||||
frames_base = splitmarks[1] + 1;
|
||||
frames_length = p_length - frames_base;
|
||||
break;
|
||||
}
|
||||
|
||||
unsigned ret = 0;
|
||||
|
||||
if (frames_length > 0) ret += pfc::atoui_ex(p_string + frames_base,frames_length);
|
||||
if (seconds_length > 0) ret += 75 * pfc::atoui_ex(p_string + seconds_base,seconds_length);
|
||||
if (minutes_length > 0) ret += 60 * 75 * pfc::atoui_ex(p_string + minutes_base,minutes_length);
|
||||
|
||||
return ret;
|
||||
}
|
||||
35
foobar2000/helpers/cuesheet_index_list.h
Normal file
35
foobar2000/helpers/cuesheet_index_list.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
unsigned cuesheet_parse_index_time_ticks_e(const char * p_string,t_size p_length);
|
||||
double cuesheet_parse_index_time_e(const char * p_string,t_size p_length);
|
||||
|
||||
class cuesheet_format_index_time
|
||||
{
|
||||
public:
|
||||
cuesheet_format_index_time(double p_time);
|
||||
inline operator const char*() const {return m_buffer;}
|
||||
private:
|
||||
pfc::string_formatter m_buffer;
|
||||
};
|
||||
|
||||
|
||||
struct t_cuesheet_index_list
|
||||
{
|
||||
enum {count = 100};
|
||||
t_cuesheet_index_list() {reset();}
|
||||
void reset() {for(unsigned n=0;n<count;n++) m_positions[n]=0;}
|
||||
|
||||
void to_infos(file_info & p_out) const;
|
||||
|
||||
//returns false when there was nothing relevant in infos
|
||||
bool from_infos(file_info const & p_in,double p_base);
|
||||
|
||||
double m_positions[count];
|
||||
|
||||
inline double start() const {return m_positions[1];}
|
||||
inline double pregap() const {return m_positions[1] - m_positions[0];}
|
||||
bool is_empty() const;
|
||||
|
||||
bool is_valid() const;
|
||||
};
|
||||
|
||||
164
foobar2000/helpers/dialog_resize_helper.cpp
Normal file
164
foobar2000/helpers/dialog_resize_helper.cpp
Normal file
@@ -0,0 +1,164 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#ifdef FOOBAR2000_DESKTOP_WINDOWS
|
||||
#include "dialog_resize_helper.h"
|
||||
|
||||
static BOOL GetChildWindowRect(HWND wnd, UINT id, RECT* child)
|
||||
{
|
||||
RECT temp;
|
||||
HWND wndChild = GetDlgItem(wnd, id);
|
||||
if (wndChild == NULL) return FALSE;
|
||||
if (!GetWindowRect(wndChild, &temp)) return FALSE;
|
||||
if (!MapWindowPoints(0, wnd, (POINT*)&temp, 2)) return FALSE;
|
||||
*child = temp;
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
void dialog_resize_helper::set_parent(HWND wnd)
|
||||
{
|
||||
reset();
|
||||
parent = wnd;
|
||||
GetClientRect(parent,&orig_client);
|
||||
}
|
||||
|
||||
void dialog_resize_helper::reset()
|
||||
{
|
||||
parent = 0;
|
||||
sizegrip = 0;
|
||||
}
|
||||
|
||||
void dialog_resize_helper::on_wm_size()
|
||||
{
|
||||
if (parent)
|
||||
{
|
||||
unsigned count = m_table.get_size();
|
||||
if (sizegrip != 0) count++;
|
||||
HDWP hWinPosInfo = BeginDeferWindowPos(count);
|
||||
for(unsigned n=0;n<m_table.get_size();n++)
|
||||
{
|
||||
param & e = m_table[n];
|
||||
const RECT & orig_rect = rects[n];
|
||||
RECT cur_client;
|
||||
GetClientRect(parent,&cur_client);
|
||||
HWND wnd = GetDlgItem(parent,e.id);
|
||||
if (wnd != NULL) {
|
||||
unsigned dest_x = orig_rect.left, dest_y = orig_rect.top,
|
||||
dest_cx = orig_rect.right - orig_rect.left, dest_cy = orig_rect.bottom - orig_rect.top;
|
||||
|
||||
int delta_x = cur_client.right - orig_client.right,
|
||||
delta_y = cur_client.bottom - orig_client.bottom;
|
||||
|
||||
if (e.flags & X_MOVE)
|
||||
dest_x += delta_x;
|
||||
else if (e.flags & X_SIZE)
|
||||
dest_cx += delta_x;
|
||||
|
||||
if (e.flags & Y_MOVE)
|
||||
dest_y += delta_y;
|
||||
else if (e.flags & Y_SIZE)
|
||||
dest_cy += delta_y;
|
||||
|
||||
hWinPosInfo = DeferWindowPos(hWinPosInfo, wnd,0,dest_x,dest_y,dest_cx,dest_cy,SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE);
|
||||
}
|
||||
}
|
||||
if (sizegrip != 0)
|
||||
{
|
||||
RECT rc, rc_grip;
|
||||
GetClientRect(parent, &rc);
|
||||
GetWindowRect(sizegrip, &rc_grip);
|
||||
hWinPosInfo = DeferWindowPos(hWinPosInfo, sizegrip, NULL, rc.right - (rc_grip.right - rc_grip.left), rc.bottom - (rc_grip.bottom - rc_grip.top), 0, 0, SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOACTIVATE | SWP_NOSIZE);
|
||||
}
|
||||
EndDeferWindowPos(hWinPosInfo);
|
||||
//RedrawWindow(parent,0,0,RDW_INVALIDATE);
|
||||
}
|
||||
}
|
||||
|
||||
bool dialog_resize_helper::process_message(HWND wnd,UINT msg,WPARAM wp,LPARAM lp) {
|
||||
LRESULT result = 0;
|
||||
if (!ProcessWindowMessage(wnd,msg,wp,lp,result)) return false;
|
||||
SetWindowLongPtr(wnd,DWLP_MSGRESULT,result);
|
||||
return true;
|
||||
}
|
||||
|
||||
BOOL dialog_resize_helper::ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult) {
|
||||
switch(uMsg) {
|
||||
case WM_SIZE:
|
||||
on_wm_size();
|
||||
return FALSE;
|
||||
case WM_GETMINMAXINFO:
|
||||
{
|
||||
RECT r;
|
||||
LPMINMAXINFO info = (LPMINMAXINFO) lParam;
|
||||
DWORD dwStyle = GetWindowLong(hWnd, GWL_STYLE);
|
||||
DWORD dwExStyle = GetWindowLong(hWnd, GWL_EXSTYLE);
|
||||
if (max_x && max_y)
|
||||
{
|
||||
r.left = 0; r.right = max_x;
|
||||
r.top = 0; r.bottom = max_y;
|
||||
MapDialogRect(hWnd,&r);
|
||||
AdjustWindowRectEx(&r, dwStyle, FALSE, dwExStyle);
|
||||
info->ptMaxTrackSize.x = r.right - r.left;
|
||||
info->ptMaxTrackSize.y = r.bottom - r.top;
|
||||
}
|
||||
if (min_x && min_y)
|
||||
{
|
||||
r.left = 0; r.right = min_x;
|
||||
r.top = 0; r.bottom = min_y;
|
||||
MapDialogRect(hWnd,&r);
|
||||
AdjustWindowRectEx(&r, dwStyle, FALSE, dwExStyle);
|
||||
info->ptMinTrackSize.x = r.right - r.left;
|
||||
info->ptMinTrackSize.y = r.bottom - r.top;
|
||||
}
|
||||
}
|
||||
lResult = 0;
|
||||
return TRUE;
|
||||
case WM_INITDIALOG:
|
||||
set_parent(hWnd);
|
||||
{
|
||||
t_size n;
|
||||
for(n=0;n<m_table.get_size();n++) {
|
||||
GetChildWindowRect(parent,m_table[n].id,&rects[n]);
|
||||
}
|
||||
}
|
||||
return FALSE;
|
||||
case WM_DESTROY:
|
||||
reset();
|
||||
return FALSE;
|
||||
default:
|
||||
return FALSE;
|
||||
}
|
||||
}
|
||||
|
||||
void dialog_resize_helper::add_sizegrip()
|
||||
{
|
||||
if (parent != 0 && sizegrip == 0)
|
||||
{
|
||||
sizegrip = CreateWindowEx(0, WC_SCROLLBAR, _T(""), WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | SBS_SIZEGRIP | SBS_SIZEBOXBOTTOMRIGHTALIGN,
|
||||
0, 0, CW_USEDEFAULT, CW_USEDEFAULT,
|
||||
parent, (HMENU)0, NULL, NULL);
|
||||
if (sizegrip != 0)
|
||||
{
|
||||
RECT rc, rc_grip;
|
||||
GetClientRect(parent, &rc);
|
||||
GetWindowRect(sizegrip, &rc_grip);
|
||||
SetWindowPos(sizegrip, NULL, rc.right - (rc_grip.right - rc_grip.left), rc.bottom - (rc_grip.bottom - rc_grip.top), 0, 0, SWP_NOZORDER | SWP_NOSIZE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
dialog_resize_helper::dialog_resize_helper(const param * src,unsigned count,unsigned p_min_x,unsigned p_min_y,unsigned p_max_x,unsigned p_max_y)
|
||||
: min_x(p_min_x), min_y(p_min_y), max_x(p_max_x), max_y(p_max_y), parent(0), sizegrip(0)
|
||||
{
|
||||
m_table.set_size(count);
|
||||
unsigned n;
|
||||
for(n=0;n<count;n++)
|
||||
m_table[n] = src[n];
|
||||
rects.set_size(count);
|
||||
}
|
||||
|
||||
dialog_resize_helper::~dialog_resize_helper()
|
||||
{
|
||||
}
|
||||
|
||||
#endif // FOOBAR2000_DESKTOP_WINDOWS
|
||||
40
foobar2000/helpers/dialog_resize_helper.h
Normal file
40
foobar2000/helpers/dialog_resize_helper.h
Normal file
@@ -0,0 +1,40 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef FOOBAR2000_DESKTOP_WINDOWS
|
||||
|
||||
#include <libPPUI/CDialogResizeHelperCompat.h>
|
||||
|
||||
// Legacy class referenced by old code
|
||||
// Do not use in new code, use libPPUI instead
|
||||
class dialog_resize_helper : public CDialogResizeHelperCompat
|
||||
{
|
||||
pfc::array_t<RECT> rects;
|
||||
RECT orig_client;
|
||||
HWND parent;
|
||||
HWND sizegrip;
|
||||
unsigned min_x,min_y,max_x,max_y;
|
||||
|
||||
pfc::array_t<param> m_table;
|
||||
|
||||
void set_parent(HWND wnd);
|
||||
void reset();
|
||||
void on_wm_size();
|
||||
public:
|
||||
inline void set_min_size(unsigned x,unsigned y) {min_x = x; min_y = y;}
|
||||
inline void set_max_size(unsigned x,unsigned y) {max_x = x; max_y = y;}
|
||||
void add_sizegrip();
|
||||
|
||||
//the old way
|
||||
bool process_message(HWND wnd,UINT msg,WPARAM wp,LPARAM lp);
|
||||
|
||||
//ATL-compatible
|
||||
BOOL ProcessWindowMessage(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT& lResult);
|
||||
|
||||
dialog_resize_helper(const param * src,unsigned count,unsigned p_min_x,unsigned p_min_y,unsigned p_max_x,unsigned p_max_y);
|
||||
|
||||
~dialog_resize_helper();
|
||||
|
||||
PFC_CLASS_NOT_COPYABLE_EX(dialog_resize_helper);
|
||||
};
|
||||
|
||||
#endif // FOOBAR2000_DESKTOP_WINDOWS
|
||||
174
foobar2000/helpers/dropdown_helper.cpp
Normal file
174
foobar2000/helpers/dropdown_helper.cpp
Normal file
@@ -0,0 +1,174 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#ifdef FOOBAR2000_DESKTOP_WINDOWS
|
||||
|
||||
#include "dropdown_helper.h"
|
||||
|
||||
void _cfg_dropdown_history_base::build_list(pfc::ptr_list_t<char> & out)
|
||||
{
|
||||
pfc::string8 temp; get_state(temp);
|
||||
const char * src = temp;
|
||||
while(*src)
|
||||
{
|
||||
int ptr = 0;
|
||||
while(src[ptr] && src[ptr]!=separator) ptr++;
|
||||
if (ptr>0)
|
||||
{
|
||||
out.add_item(pfc::strdup_n(src,ptr));
|
||||
src += ptr;
|
||||
}
|
||||
while(*src==separator) src++;
|
||||
}
|
||||
}
|
||||
|
||||
void _cfg_dropdown_history_base::parse_list(const pfc::ptr_list_t<char> & src)
|
||||
{
|
||||
t_size n;
|
||||
pfc::string8_fastalloc temp;
|
||||
for(n=0;n<src.get_count();n++)
|
||||
{
|
||||
temp.add_string(src[n]);
|
||||
temp.add_char(separator);
|
||||
}
|
||||
set_state(temp);
|
||||
}
|
||||
|
||||
static void g_setup_dropdown_fromlist(HWND wnd,const pfc::ptr_list_t<char> & list)
|
||||
{
|
||||
t_size n, m = list.get_count();
|
||||
uSendMessage(wnd,CB_RESETCONTENT,0,0);
|
||||
for(n=0;n<m;n++) {
|
||||
uSendMessageText(wnd,CB_ADDSTRING,0,list[n]);
|
||||
}
|
||||
}
|
||||
|
||||
void _cfg_dropdown_history_base::setup_dropdown_set_value(HWND wnd) {
|
||||
pfc::ptr_list_t<char> list;
|
||||
build_list(list);
|
||||
g_setup_dropdown_fromlist(wnd, list);
|
||||
if ( list.get_size() > 0 ) {
|
||||
uSetWindowText(wnd, list[0] );
|
||||
}
|
||||
list.free_all();
|
||||
}
|
||||
|
||||
void _cfg_dropdown_history_base::setup_dropdown(HWND wnd)
|
||||
{
|
||||
pfc::ptr_list_t<char> list;
|
||||
build_list(list);
|
||||
g_setup_dropdown_fromlist(wnd,list);
|
||||
list.free_all();
|
||||
}
|
||||
|
||||
bool _cfg_dropdown_history_base::add_item(const char * item)
|
||||
{
|
||||
if (!item || !*item) return false;
|
||||
pfc::string8 meh;
|
||||
if (strchr(item,separator))
|
||||
{
|
||||
uReplaceChar(meh,item,-1,separator,'|',false);
|
||||
item = meh;
|
||||
}
|
||||
pfc::ptr_list_t<char> list;
|
||||
build_list(list);
|
||||
unsigned n;
|
||||
bool found = false;
|
||||
for(n=0;n<list.get_count();n++)
|
||||
{
|
||||
if (!strcmp(list[n],item))
|
||||
{
|
||||
char* temp = list.remove_by_idx(n);
|
||||
list.insert_item(temp,0);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found)
|
||||
{
|
||||
while(list.get_count() > m_max) list.delete_by_idx(list.get_count()-1);
|
||||
list.insert_item(_strdup(item),0);
|
||||
}
|
||||
parse_list(list);
|
||||
list.free_all();
|
||||
return found;
|
||||
}
|
||||
|
||||
bool _cfg_dropdown_history_base::add_item(const char *item, HWND combobox) {
|
||||
const bool state = add_item(item);
|
||||
if (state) uSendMessageText(combobox, CB_ADDSTRING, 0, item);
|
||||
return state;
|
||||
}
|
||||
|
||||
bool _cfg_dropdown_history_base::is_empty()
|
||||
{
|
||||
pfc::string8 temp; get_state(temp);
|
||||
const char * src = temp;
|
||||
while(*src)
|
||||
{
|
||||
if (*src!=separator) return false;
|
||||
src++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool _cfg_dropdown_history_base::on_context(HWND wnd,LPARAM coords) {
|
||||
try {
|
||||
int coords_x = (short)LOWORD(coords), coords_y = (short)HIWORD(coords);
|
||||
if (coords_x == -1 && coords_y == -1)
|
||||
{
|
||||
RECT asdf;
|
||||
GetWindowRect(wnd,&asdf);
|
||||
coords_x = (asdf.left + asdf.right) / 2;
|
||||
coords_y = (asdf.top + asdf.bottom) / 2;
|
||||
}
|
||||
enum {ID_ERASE_ALL = 1, ID_ERASE_ONE };
|
||||
HMENU menu = CreatePopupMenu();
|
||||
uAppendMenu(menu,MF_STRING,ID_ERASE_ALL,"Wipe history");
|
||||
{
|
||||
pfc::string8 tempvalue;
|
||||
uGetWindowText(wnd,tempvalue);
|
||||
if (!tempvalue.is_empty())
|
||||
uAppendMenu(menu,MF_STRING,ID_ERASE_ONE,"Remove this history item");
|
||||
}
|
||||
int cmd = TrackPopupMenu(menu,TPM_RIGHTBUTTON|TPM_NONOTIFY|TPM_RETURNCMD,coords_x,coords_y,0,wnd,0);
|
||||
DestroyMenu(menu);
|
||||
switch(cmd)
|
||||
{
|
||||
case ID_ERASE_ALL:
|
||||
{
|
||||
set_state("");
|
||||
pfc::string8 value;//preserve old value while wiping dropdown list
|
||||
uGetWindowText(wnd,value);
|
||||
uSendMessage(wnd,CB_RESETCONTENT,0,0);
|
||||
uSetWindowText(wnd,value);
|
||||
return true;
|
||||
}
|
||||
case ID_ERASE_ONE:
|
||||
{
|
||||
pfc::string8 value;
|
||||
uGetWindowText(wnd,value);
|
||||
|
||||
pfc::ptr_list_t<char> list;
|
||||
build_list(list);
|
||||
bool found = false;
|
||||
for(t_size n=0;n<list.get_size();n++)
|
||||
{
|
||||
if (!strcmp(value,list[n]))
|
||||
{
|
||||
free(list[n]);
|
||||
list.remove_by_idx(n);
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (found) parse_list(list);
|
||||
g_setup_dropdown_fromlist(wnd,list);
|
||||
list.free_all();
|
||||
return found;
|
||||
}
|
||||
}
|
||||
} catch(...) {}
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif // FOOBAR2000_DESKTOP_WINDOWS
|
||||
56
foobar2000/helpers/dropdown_helper.h
Normal file
56
foobar2000/helpers/dropdown_helper.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef FOOBAR2000_DESKTOP_WINDOWS
|
||||
|
||||
class _cfg_dropdown_history_base
|
||||
{
|
||||
const unsigned m_max;
|
||||
void build_list(pfc::ptr_list_t<char> & out);
|
||||
void parse_list(const pfc::ptr_list_t<char> & src);
|
||||
public:
|
||||
enum {separator = '\n'};
|
||||
virtual void set_state(const char * val) = 0;
|
||||
virtual void get_state(pfc::string_base & out) const = 0;
|
||||
_cfg_dropdown_history_base(unsigned p_max) : m_max(p_max) {}
|
||||
void on_init(HWND ctrl, const char * initVal) {
|
||||
add_item(initVal); setup_dropdown(ctrl); uSetWindowText(ctrl, initVal);
|
||||
}
|
||||
void setup_dropdown(HWND wnd);
|
||||
void setup_dropdown_set_value(HWND wnd);
|
||||
void setup_dropdown(HWND wnd,UINT id) {setup_dropdown(GetDlgItem(wnd,id));}
|
||||
bool add_item(const char * item); //returns true when the content has changed, false when not (the item was already on the list)
|
||||
bool add_item(const char * item, HWND combobox); //immediately adds the item to the combobox
|
||||
bool is_empty();
|
||||
bool on_context(HWND wnd,LPARAM coords); //returns true when the content has changed
|
||||
};
|
||||
|
||||
class cfg_dropdown_history : public _cfg_dropdown_history_base {
|
||||
public:
|
||||
cfg_dropdown_history(const GUID & p_guid,unsigned p_max = 10,const char * init_vals = "") : _cfg_dropdown_history_base(p_max), m_state(p_guid, init_vals) {}
|
||||
void set_state(const char * val) {m_state = val;}
|
||||
void get_state(pfc::string_base & out) const {out = m_state;}
|
||||
private:
|
||||
cfg_string m_state;
|
||||
};
|
||||
|
||||
class cfg_dropdown_history_mt : public _cfg_dropdown_history_base {
|
||||
public:
|
||||
cfg_dropdown_history_mt(const GUID & p_guid,unsigned p_max = 10,const char * init_vals = "") : _cfg_dropdown_history_base(p_max), m_state(p_guid, init_vals) {}
|
||||
void set_state(const char * val) {m_state.set(val);}
|
||||
void get_state(pfc::string_base & out) const {m_state.get(out);}
|
||||
private:
|
||||
cfg_string_mt m_state;
|
||||
};
|
||||
|
||||
// ATL-compatible message map entry macro for installing dropdown list context menus.
|
||||
#define DROPDOWN_HISTORY_HANDLER(ctrlID,var) \
|
||||
if(uMsg == WM_CONTEXTMENU) { \
|
||||
const HWND source = (HWND) wParam; \
|
||||
if (source != NULL && source == CWindow(hWnd).GetDlgItem(ctrlID)) { \
|
||||
var.on_context(source,lParam); \
|
||||
lResult = 0; \
|
||||
return TRUE; \
|
||||
} \
|
||||
}
|
||||
|
||||
#endif // FOOBAR2000_DESKTOP_WINDOWS
|
||||
94
foobar2000/helpers/duration_counter.h
Normal file
94
foobar2000/helpers/duration_counter.h
Normal file
@@ -0,0 +1,94 @@
|
||||
#pragma once
|
||||
|
||||
#include <SDK/dsp.h>
|
||||
#include <pfc/map.h>
|
||||
|
||||
//! Duration counter class - accumulates duration using sample values, without any kind of rounding error accumulation.
|
||||
class duration_counter {
|
||||
public:
|
||||
duration_counter() : m_offset() {
|
||||
}
|
||||
void set(double v) {
|
||||
m_sampleCounts.remove_all();
|
||||
m_offset = v;
|
||||
}
|
||||
void reset() {
|
||||
set(0);
|
||||
}
|
||||
|
||||
void add(double v) { m_offset += v; }
|
||||
void subtract(double v) { m_offset -= v; }
|
||||
|
||||
double query() const {
|
||||
double acc = m_offset;
|
||||
for (t_map::const_iterator walk = m_sampleCounts.first(); walk.is_valid(); ++walk) {
|
||||
acc += audio_math::samples_to_time(walk->m_value, walk->m_key);
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
|
||||
uint64_t queryAsSampleCount(uint32_t rate) const {
|
||||
uint64_t samples = 0;
|
||||
double acc = m_offset;
|
||||
for (t_map::const_iterator walk = m_sampleCounts.first(); walk.is_valid(); ++walk) {
|
||||
if (walk->m_key == rate) samples += walk->m_value;
|
||||
else acc += audio_math::samples_to_time(walk->m_value, walk->m_key);
|
||||
}
|
||||
return samples + audio_math::time_to_samples(acc, rate);
|
||||
}
|
||||
|
||||
void add(const audio_chunk & c) {
|
||||
add(c.get_sample_count(), c.get_sample_rate());
|
||||
}
|
||||
#ifdef FOOBAR2000_HAVE_DSP
|
||||
void add(dsp_chunk_list const & c) {
|
||||
const size_t num = c.get_count();
|
||||
for (size_t walk = 0; walk < num; ++walk) {
|
||||
add(*c.get_item(walk));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
void add(t_uint64 sampleCount, t_uint32 sampleRate) {
|
||||
PFC_ASSERT(sampleRate > 0);
|
||||
if (sampleRate > 0 && sampleCount > 0) {
|
||||
m_sampleCounts.find_or_add(sampleRate) += sampleCount;
|
||||
}
|
||||
}
|
||||
void add(const duration_counter & other) {
|
||||
add(other.m_offset);
|
||||
for (t_map::const_iterator walk = other.m_sampleCounts.first(); walk.is_valid(); ++walk) {
|
||||
add(walk->m_value, walk->m_key);
|
||||
}
|
||||
}
|
||||
void subtract(const duration_counter & other) {
|
||||
subtract(other.m_offset);
|
||||
for (t_map::const_iterator walk = other.m_sampleCounts.first(); walk.is_valid(); ++walk) {
|
||||
subtract(walk->m_value, walk->m_key);
|
||||
}
|
||||
}
|
||||
void subtract(t_uint64 sampleCount, t_uint32 sampleRate) {
|
||||
PFC_ASSERT(sampleRate > 0);
|
||||
if (sampleRate > 0 && sampleCount > 0) {
|
||||
t_uint64 * val = m_sampleCounts.query_ptr(sampleRate);
|
||||
if (val == NULL) throw pfc::exception_invalid_params();
|
||||
if (*val < sampleCount) throw pfc::exception_invalid_params();
|
||||
else if (*val == sampleCount) {
|
||||
m_sampleCounts.remove(sampleRate);
|
||||
} else {
|
||||
*val -= sampleCount;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
void subtract(const audio_chunk & c) {
|
||||
subtract(c.get_sample_count(), c.get_sample_rate());
|
||||
}
|
||||
template<typename t_source> duration_counter & operator+=(const t_source & source) { add(source); return *this; }
|
||||
template<typename t_source> duration_counter & operator-=(const t_source & source) { subtract(source); return *this; }
|
||||
template<typename t_source> duration_counter & operator=(const t_source & source) { reset(); add(source); return *this; }
|
||||
private:
|
||||
double m_offset;
|
||||
typedef pfc::map_t<t_uint32, t_uint64> t_map;
|
||||
t_map m_sampleCounts;
|
||||
};
|
||||
|
||||
76
foobar2000/helpers/dynamic_bitrate_helper.cpp
Normal file
76
foobar2000/helpers/dynamic_bitrate_helper.cpp
Normal file
@@ -0,0 +1,76 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "dynamic_bitrate_helper.h"
|
||||
|
||||
static unsigned g_query_settings()
|
||||
{
|
||||
t_int32 temp;
|
||||
try {
|
||||
config_object::g_get_data_int32(standard_config_objects::int32_dynamic_bitrate_display_rate,temp);
|
||||
} catch(std::exception const &) {return 9;}
|
||||
if (temp < 0) return 0;
|
||||
return (unsigned) temp;
|
||||
}
|
||||
|
||||
dynamic_bitrate_helper::dynamic_bitrate_helper()
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
void dynamic_bitrate_helper::init()
|
||||
{
|
||||
if (!m_inited)
|
||||
{
|
||||
m_inited = true;
|
||||
unsigned temp = g_query_settings();
|
||||
if (temp > 0) {m_enabled = true; m_update_interval = 1.0 / (double) temp; }
|
||||
else {m_enabled = false; m_update_interval = 0; }
|
||||
m_last_duration = 0;
|
||||
m_update_bits = 0;
|
||||
m_update_time = 0;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
void dynamic_bitrate_helper::on_frame(double p_duration,t_size p_bits)
|
||||
{
|
||||
init();
|
||||
m_last_duration = p_duration;
|
||||
m_update_time += p_duration;
|
||||
m_update_bits += p_bits;
|
||||
}
|
||||
|
||||
bool dynamic_bitrate_helper::on_update(file_info & p_out, double & p_timestamp_delta)
|
||||
{
|
||||
init();
|
||||
|
||||
bool ret = false;
|
||||
if (m_enabled)
|
||||
{
|
||||
if (m_update_time > m_update_interval)
|
||||
{
|
||||
int val = (int) ( ((double)m_update_bits / m_update_time + 500.0) / 1000.0 );
|
||||
if (val != p_out.info_get_bitrate_vbr())
|
||||
{
|
||||
p_timestamp_delta = - (m_update_time - m_last_duration); //relative to last frame beginning;
|
||||
p_out.info_set_bitrate_vbr(val);
|
||||
ret = true;
|
||||
}
|
||||
m_update_bits = 0;
|
||||
m_update_time = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_update_bits = 0;
|
||||
m_update_time = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
void dynamic_bitrate_helper::reset()
|
||||
{
|
||||
m_inited = false;
|
||||
}
|
||||
17
foobar2000/helpers/dynamic_bitrate_helper.h
Normal file
17
foobar2000/helpers/dynamic_bitrate_helper.h
Normal file
@@ -0,0 +1,17 @@
|
||||
#pragma once
|
||||
|
||||
class dynamic_bitrate_helper
|
||||
{
|
||||
public:
|
||||
dynamic_bitrate_helper();
|
||||
void on_frame(double p_duration,t_size p_bits);
|
||||
bool on_update(file_info & p_out, double & p_timestamp_delta);
|
||||
void reset();
|
||||
private:
|
||||
void init();
|
||||
double m_last_duration;
|
||||
t_size m_update_bits;
|
||||
double m_update_time;
|
||||
double m_update_interval;
|
||||
bool m_inited, m_enabled;
|
||||
};
|
||||
88
foobar2000/helpers/fb2k_threads.h
Normal file
88
foobar2000/helpers/fb2k_threads.h
Normal file
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
inline static t_size GetOptimalWorkerThreadCount() throw() {
|
||||
return pfc::getOptimalWorkerThreadCount();
|
||||
}
|
||||
|
||||
//! IMPORTANT: all classes derived from CVerySimpleThread must call WaitTillThreadDone() in their destructor, to avoid object destruction during a virtual function call!
|
||||
class CVerySimpleThread : private pfc::thread {
|
||||
public:
|
||||
void StartThread(int priority) {
|
||||
this->pfc::thread::startWithPriority( priority );
|
||||
}
|
||||
void StartThread() {
|
||||
this->StartThread( pfc::thread::currentPriority() );
|
||||
}
|
||||
|
||||
bool IsThreadActive() const {
|
||||
return this->pfc::thread::isActive();
|
||||
}
|
||||
void WaitTillThreadDone() {
|
||||
this->pfc::thread::waitTillDone();
|
||||
}
|
||||
protected:
|
||||
CVerySimpleThread() {}
|
||||
virtual void ThreadProc() = 0;
|
||||
private:
|
||||
|
||||
void threadProc() {
|
||||
this->ThreadProc();
|
||||
}
|
||||
|
||||
PFC_CLASS_NOT_COPYABLE_EX(CVerySimpleThread)
|
||||
};
|
||||
|
||||
//! IMPORTANT: all classes derived from CSimpleThread must call AbortThread()/WaitTillThreadDone() in their destructors, to avoid object destruction during a virtual function call!
|
||||
class CSimpleThread : private completion_notify_receiver, private pfc::thread {
|
||||
public:
|
||||
void StartThread(int priority) {
|
||||
AbortThread();
|
||||
m_abort.reset();
|
||||
m_ownNotify = create_task(0);
|
||||
this->pfc::thread::startWithPriority( priority );
|
||||
}
|
||||
void StartThread() {
|
||||
this->StartThread( pfc::thread::currentPriority () );
|
||||
}
|
||||
void AbortThread() {
|
||||
m_abort.abort();
|
||||
CloseThread();
|
||||
}
|
||||
bool IsThreadActive() const {
|
||||
return this->pfc::thread::isActive();
|
||||
}
|
||||
void WaitTillThreadDone() {
|
||||
CloseThread();
|
||||
}
|
||||
protected:
|
||||
CSimpleThread() {}
|
||||
~CSimpleThread() {AbortThread();}
|
||||
|
||||
virtual unsigned ThreadProc(abort_callback & p_abort) = 0;
|
||||
//! Called when the thread has completed normally, with p_code equal to ThreadProc retval. Not called when AbortThread() or WaitTillThreadDone() was used to abort the thread / wait for the thread to finish.
|
||||
virtual void ThreadDone(unsigned p_code) {};
|
||||
private:
|
||||
void CloseThread() {
|
||||
this->pfc::thread::waitTillDone();
|
||||
orphan_all_tasks();
|
||||
}
|
||||
|
||||
void on_task_completion(unsigned p_id,unsigned p_status) {
|
||||
if (IsThreadActive()) {
|
||||
CloseThread();
|
||||
ThreadDone(p_status);
|
||||
}
|
||||
}
|
||||
void threadProc() {
|
||||
unsigned code = ~0;
|
||||
try {
|
||||
code = ThreadProc(m_abort);
|
||||
} catch(...) {}
|
||||
if (!m_abort.is_aborting()) m_ownNotify->on_completion_async(code);
|
||||
}
|
||||
abort_callback_impl m_abort;
|
||||
completion_notify_ptr m_ownNotify;
|
||||
|
||||
PFC_CLASS_NOT_COPYABLE_EX(CSimpleThread);
|
||||
};
|
||||
|
||||
38
foobar2000/helpers/fb2k_wfx.h
Normal file
38
foobar2000/helpers/fb2k_wfx.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
struct fb2k_wfx {
|
||||
audio_chunk::spec_t spec;
|
||||
bool bFloat;
|
||||
unsigned bps;
|
||||
void parse( const WAVEFORMATEX * wfx ) {
|
||||
spec.sampleRate = wfx->nSamplesPerSec;
|
||||
spec.chanCount = wfx->nChannels;
|
||||
spec.chanMask = audio_chunk::g_guess_channel_config( spec.chanCount );
|
||||
bps = wfx->wBitsPerSample;
|
||||
switch( wfx->wFormatTag ) {
|
||||
case WAVE_FORMAT_PCM:
|
||||
bFloat = false; break;
|
||||
case WAVE_FORMAT_IEEE_FLOAT:
|
||||
bFloat = true; break;
|
||||
case WAVE_FORMAT_EXTENSIBLE:
|
||||
{
|
||||
auto wfxe = (const WAVEFORMATEXTENSIBLE*) wfx;
|
||||
auto newMask = audio_chunk::g_channel_config_from_wfx( wfxe->dwChannelMask );
|
||||
if ( audio_chunk::g_count_channels(newMask) == spec.chanCount ) {
|
||||
spec.chanMask = newMask;
|
||||
}
|
||||
if ( wfxe->SubFormat == KSDATAFORMAT_SUBTYPE_IEEE_FLOAT ) {
|
||||
bFloat = true;
|
||||
} else if ( wfxe->SubFormat == KSDATAFORMAT_SUBTYPE_PCM ) {
|
||||
bFloat = false;
|
||||
} else {
|
||||
throw exception_io_data();
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw exception_io_data();
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
4
foobar2000/helpers/fileReadAhead.h
Normal file
4
foobar2000/helpers/fileReadAhead.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
//! Creates a file with a background thread reading ahead of the current position
|
||||
file::ptr fileCreateReadAhead(file::ptr chain, size_t readAheadBytes, abort_callback & aborter );
|
||||
1
foobar2000/helpers/file_cached.h
Normal file
1
foobar2000/helpers/file_cached.h
Normal file
@@ -0,0 +1 @@
|
||||
// obsolete, moved to SDK
|
||||
287
foobar2000/helpers/file_info_const_impl.cpp
Normal file
287
foobar2000/helpers/file_info_const_impl.cpp
Normal file
@@ -0,0 +1,287 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "file_info_const_impl.h"
|
||||
|
||||
// presorted - do not change without a proper strcmp resort
|
||||
static const char * const standard_fieldnames[] = {
|
||||
"ALBUM","ALBUM ARTIST","ARTIST","Album","Album Artist","Artist","COMMENT","Comment","DATE","DISCNUMBER","Date",
|
||||
"Discnumber","GENRE","Genre","TITLE","TOTALTRACKS","TRACKNUMBER","Title","TotalTracks","Totaltracks","TrackNumber",
|
||||
"Tracknumber","album","album artist","artist","comment","date","discnumber","genre","title","totaltracks","tracknumber",
|
||||
};
|
||||
|
||||
// presorted - do not change without a proper strcmp resort
|
||||
static const char * const standard_infonames[] = {
|
||||
"bitrate","bitspersample","channels","codec","codec_profile","encoding","samplerate","tagtype","tool",
|
||||
};
|
||||
|
||||
static const char * optimize_fieldname(const char * p_string) {
|
||||
t_size index;
|
||||
if (!pfc::binarySearch<pfc::comparator_strcmp>::run(standard_fieldnames,0,PFC_TABSIZE(standard_fieldnames),p_string,index)) return NULL;
|
||||
return standard_fieldnames[index];
|
||||
}
|
||||
|
||||
static const char * optimize_infoname(const char * p_string) {
|
||||
t_size index;
|
||||
if (!pfc::binarySearch<pfc::comparator_strcmp>::run(standard_infonames,0,PFC_TABSIZE(standard_infonames),p_string,index)) return NULL;
|
||||
return standard_infonames[index];
|
||||
}
|
||||
|
||||
/*
|
||||
order of things
|
||||
|
||||
meta entries
|
||||
meta value map
|
||||
info entries
|
||||
string buffer
|
||||
|
||||
*/
|
||||
|
||||
inline static char* stringbuffer_append(char * & buffer,const char * value)
|
||||
{
|
||||
char * ret = buffer;
|
||||
while(*value) *(buffer++) = *(value++);
|
||||
*(buffer++) = 0;
|
||||
return ret;
|
||||
}
|
||||
|
||||
#ifdef __file_info_const_impl_have_hintmap__
|
||||
|
||||
namespace {
|
||||
class sort_callback_hintmap_impl : public pfc::sort_callback
|
||||
{
|
||||
public:
|
||||
sort_callback_hintmap_impl(const file_info_const_impl::meta_entry * p_meta,file_info_const_impl::t_index * p_hintmap)
|
||||
: m_meta(p_meta), m_hintmap(p_hintmap)
|
||||
{
|
||||
}
|
||||
|
||||
int compare(t_size p_index1, t_size p_index2) const
|
||||
{
|
||||
// profiler(sort_callback_hintmap_impl_compare);
|
||||
return pfc::stricmp_ascii(m_meta[m_hintmap[p_index1]].m_name,m_meta[m_hintmap[p_index2]].m_name);
|
||||
}
|
||||
|
||||
void swap(t_size p_index1, t_size p_index2)
|
||||
{
|
||||
pfc::swap_t<file_info_const_impl::t_index>(m_hintmap[p_index1],m_hintmap[p_index2]);
|
||||
}
|
||||
private:
|
||||
const file_info_const_impl::meta_entry * m_meta;
|
||||
file_info_const_impl::t_index * m_hintmap;
|
||||
};
|
||||
|
||||
class bsearch_callback_hintmap_impl// : public pfc::bsearch_callback
|
||||
{
|
||||
public:
|
||||
bsearch_callback_hintmap_impl(
|
||||
const file_info_const_impl::meta_entry * p_meta,
|
||||
const file_info_const_impl::t_index * p_hintmap,
|
||||
const char * p_name,
|
||||
t_size p_name_length)
|
||||
: m_meta(p_meta), m_hintmap(p_hintmap), m_name(p_name), m_name_length(p_name_length)
|
||||
{
|
||||
}
|
||||
|
||||
inline int test(t_size p_index) const
|
||||
{
|
||||
return pfc::stricmp_ascii_ex(m_meta[m_hintmap[p_index]].m_name,~0,m_name,m_name_length);
|
||||
}
|
||||
|
||||
private:
|
||||
const file_info_const_impl::meta_entry * m_meta;
|
||||
const file_info_const_impl::t_index * m_hintmap;
|
||||
const char * m_name;
|
||||
t_size m_name_length;
|
||||
};
|
||||
}
|
||||
|
||||
#endif//__file_info_const_impl_have_hintmap__
|
||||
|
||||
void file_info_const_impl::copy(const file_info & p_source)
|
||||
{
|
||||
// profiler(file_info_const_impl__copy);
|
||||
t_size meta_size = 0;
|
||||
t_size info_size = 0;
|
||||
t_size valuemap_size = 0;
|
||||
t_size stringbuffer_size = 0;
|
||||
#ifdef __file_info_const_impl_have_hintmap__
|
||||
t_size hintmap_size = 0;
|
||||
#endif
|
||||
|
||||
const char * optbuf[64];
|
||||
size_t optwalk = 0;
|
||||
|
||||
{
|
||||
// profiler(file_info_const_impl__copy__pass1);
|
||||
t_size index;
|
||||
m_meta_count = pfc::downcast_guarded<t_index>(p_source.meta_get_count());
|
||||
meta_size = m_meta_count * sizeof(meta_entry);
|
||||
#ifdef __file_info_const_impl_have_hintmap__
|
||||
hintmap_size = (m_meta_count > hintmap_cutoff) ? m_meta_count * sizeof(t_index) : 0;
|
||||
#endif//__file_info_const_impl_have_hintmap__
|
||||
for(index = 0; index < m_meta_count; index++ )
|
||||
{
|
||||
{
|
||||
const char * name = p_source.meta_enum_name(index);
|
||||
const char * opt = optimize_fieldname(name);
|
||||
if (optwalk < PFC_TABSIZE(optbuf)) optbuf[optwalk++] = opt;
|
||||
if (opt == NULL) stringbuffer_size += strlen(name) + 1;
|
||||
}
|
||||
|
||||
t_size val; const t_size val_max = p_source.meta_enum_value_count(index);
|
||||
|
||||
if (val_max == 1)
|
||||
{
|
||||
stringbuffer_size += strlen(p_source.meta_enum_value(index,0)) + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
valuemap_size += val_max * sizeof(char*);
|
||||
|
||||
for(val = 0; val < val_max; val++ )
|
||||
{
|
||||
stringbuffer_size += strlen(p_source.meta_enum_value(index,val)) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
m_info_count = pfc::downcast_guarded<t_index>(p_source.info_get_count());
|
||||
info_size = m_info_count * sizeof(info_entry);
|
||||
for(index = 0; index < m_info_count; index++ )
|
||||
{
|
||||
const char * name = p_source.info_enum_name(index);
|
||||
const char * opt = optimize_infoname(name);
|
||||
if (optwalk < PFC_TABSIZE(optbuf)) optbuf[optwalk++] = opt;
|
||||
if (opt == NULL) stringbuffer_size += strlen(name) + 1;
|
||||
stringbuffer_size += strlen(p_source.info_enum_value(index)) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
{
|
||||
// profiler(file_info_const_impl__copy__alloc);
|
||||
m_buffer.set_size(
|
||||
#ifdef __file_info_const_impl_have_hintmap__
|
||||
hintmap_size +
|
||||
#endif
|
||||
meta_size + info_size + valuemap_size + stringbuffer_size);
|
||||
}
|
||||
|
||||
char * walk = m_buffer.get_ptr();
|
||||
|
||||
#ifdef __file_info_const_impl_have_hintmap__
|
||||
t_index* hintmap = (hintmap_size > 0) ? (t_index*) walk : NULL;
|
||||
walk += hintmap_size;
|
||||
#endif
|
||||
meta_entry * meta = (meta_entry*) walk;
|
||||
walk += meta_size;
|
||||
char ** valuemap = (char**) walk;
|
||||
walk += valuemap_size;
|
||||
info_entry * info = (info_entry*) walk;
|
||||
walk += info_size;
|
||||
char * stringbuffer = walk;
|
||||
|
||||
m_meta = meta;
|
||||
m_info = info;
|
||||
#ifdef __file_info_const_impl_have_hintmap__
|
||||
m_hintmap = hintmap;
|
||||
#endif
|
||||
|
||||
optwalk = 0;
|
||||
{
|
||||
// profiler(file_info_const_impl__copy__pass2);
|
||||
t_size index;
|
||||
for( index = 0; index < m_meta_count; index ++ )
|
||||
{
|
||||
t_size val; const t_size val_max = p_source.meta_enum_value_count(index);
|
||||
|
||||
{
|
||||
const char * name = p_source.meta_enum_name(index);
|
||||
const char * name_opt;
|
||||
|
||||
if (optwalk < PFC_TABSIZE(optbuf)) name_opt = optbuf[optwalk++];
|
||||
else name_opt = optimize_fieldname(name);
|
||||
|
||||
if (name_opt == NULL)
|
||||
meta[index].m_name = stringbuffer_append(stringbuffer, name );
|
||||
else
|
||||
meta[index].m_name = name_opt;
|
||||
}
|
||||
|
||||
meta[index].m_valuecount = val_max;
|
||||
|
||||
if (val_max == 1)
|
||||
{
|
||||
meta[index].m_valuemap = reinterpret_cast<const char * const *>(stringbuffer_append(stringbuffer, p_source.meta_enum_value(index,0) ));
|
||||
}
|
||||
else
|
||||
{
|
||||
meta[index].m_valuemap = valuemap;
|
||||
for( val = 0; val < val_max ; val ++ )
|
||||
*(valuemap ++ ) = stringbuffer_append(stringbuffer, p_source.meta_enum_value(index,val) );
|
||||
}
|
||||
}
|
||||
|
||||
for( index = 0; index < m_info_count; index ++ )
|
||||
{
|
||||
const char * name = p_source.info_enum_name(index);
|
||||
const char * name_opt;
|
||||
|
||||
if (optwalk < PFC_TABSIZE(optbuf)) name_opt = optbuf[optwalk++];
|
||||
else name_opt = optimize_infoname(name);
|
||||
|
||||
if (name_opt == NULL)
|
||||
info[index].m_name = stringbuffer_append(stringbuffer, name );
|
||||
else
|
||||
info[index].m_name = name_opt;
|
||||
info[index].m_value = stringbuffer_append(stringbuffer, p_source.info_enum_value(index) );
|
||||
}
|
||||
}
|
||||
|
||||
m_length = p_source.get_length();
|
||||
m_replaygain = p_source.get_replaygain();
|
||||
#ifdef __file_info_const_impl_have_hintmap__
|
||||
if (hintmap != NULL) {
|
||||
// profiler(file_info_const_impl__copy__hintmap);
|
||||
for(t_size n=0;n<m_meta_count;n++) hintmap[n]= (t_index) n;
|
||||
sort_callback_hintmap_impl cb(meta,hintmap);
|
||||
pfc::sort(cb,m_meta_count);
|
||||
}
|
||||
#endif//__file_info_const_impl_have_hintmap__
|
||||
}
|
||||
|
||||
|
||||
void file_info_const_impl::reset()
|
||||
{
|
||||
m_meta_count = m_info_count = 0; m_length = 0; m_replaygain.reset();
|
||||
}
|
||||
|
||||
t_size file_info_const_impl::meta_find_ex(const char * p_name,t_size p_name_length) const
|
||||
{
|
||||
#ifdef __file_info_const_impl_have_hintmap__
|
||||
if (m_hintmap != NULL) {
|
||||
t_size result = ~0;
|
||||
if (!pfc::bsearch_inline_t(m_meta_count,bsearch_callback_hintmap_impl(m_meta,m_hintmap,p_name,p_name_length),result)) return ~0;
|
||||
else return m_hintmap[result];
|
||||
} else {
|
||||
return file_info::meta_find_ex(p_name,p_name_length);
|
||||
}
|
||||
#else
|
||||
return file_info::meta_find_ex(p_name,p_name_length);
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
t_size file_info_const_impl::meta_enum_value_count(t_size p_index) const
|
||||
{
|
||||
return m_meta[p_index].m_valuecount;
|
||||
}
|
||||
|
||||
const char* file_info_const_impl::meta_enum_value(t_size p_index,t_size p_value_number) const
|
||||
{
|
||||
const meta_entry & entry = m_meta[p_index];
|
||||
if (entry.m_valuecount == 1)
|
||||
return reinterpret_cast<const char*>(entry.m_valuemap);
|
||||
else
|
||||
return entry.m_valuemap[p_value_number];
|
||||
}
|
||||
80
foobar2000/helpers/file_info_const_impl.h
Normal file
80
foobar2000/helpers/file_info_const_impl.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#define __file_info_const_impl_have_hintmap__
|
||||
|
||||
//! Special implementation of file_info that implements only const and copy methods. The difference between this and regular file_info_impl is amount of resources used and speed of the copy operation.
|
||||
class file_info_const_impl : public file_info
|
||||
{
|
||||
public:
|
||||
file_info_const_impl(const file_info & p_source) {copy(p_source);}
|
||||
file_info_const_impl(const file_info_const_impl & p_source) {copy(p_source);}
|
||||
file_info_const_impl() {m_meta_count = m_info_count = 0; m_length = 0; m_replaygain.reset();}
|
||||
|
||||
double get_length() const {return m_length;}
|
||||
|
||||
t_size meta_get_count() const {return m_meta_count;}
|
||||
const char* meta_enum_name(t_size p_index) const {return m_meta[p_index].m_name;}
|
||||
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_find_ex(const char * p_name,t_size p_name_length) const;
|
||||
|
||||
t_size info_get_count() const {return m_info_count;}
|
||||
const char* info_enum_name(t_size p_index) const {return m_info[p_index].m_name;}
|
||||
const char* info_enum_value(t_size p_index) const {return m_info[p_index].m_value;}
|
||||
|
||||
|
||||
const file_info_const_impl & operator=(const file_info & p_source) {copy(p_source); return *this;}
|
||||
const file_info_const_impl & operator=(const file_info_const_impl & p_source) {copy(p_source); return *this;}
|
||||
void copy(const file_info & p_source);
|
||||
void reset();
|
||||
|
||||
replaygain_info get_replaygain() const {return m_replaygain;}
|
||||
|
||||
private:
|
||||
void set_length(double p_length) {uBugCheck();}
|
||||
|
||||
t_size meta_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) {uBugCheck();}
|
||||
void meta_insert_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length) {uBugCheck();}
|
||||
void meta_remove_mask(const bit_array & p_mask) {uBugCheck();}
|
||||
void meta_reorder(const t_size * p_order) {uBugCheck();}
|
||||
void meta_remove_values(t_size p_index,const bit_array & p_mask) {uBugCheck();}
|
||||
void meta_modify_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length) {uBugCheck();}
|
||||
|
||||
t_size info_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) {uBugCheck();}
|
||||
void info_remove_mask(const bit_array & p_mask) {uBugCheck();}
|
||||
|
||||
void set_replaygain(const replaygain_info & p_info) {uBugCheck();}
|
||||
|
||||
t_size meta_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) {uBugCheck();}
|
||||
t_size info_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) {uBugCheck();}
|
||||
public:
|
||||
struct meta_entry {
|
||||
const char * m_name;
|
||||
t_size m_valuecount;
|
||||
const char * const * m_valuemap;
|
||||
};
|
||||
|
||||
struct info_entry {
|
||||
const char * m_name;
|
||||
const char * m_value;
|
||||
};
|
||||
|
||||
#ifdef __file_info_const_impl_have_hintmap__
|
||||
typedef t_uint32 t_index;
|
||||
enum {hintmap_cutoff = 20};
|
||||
#endif//__file_info_const_impl_have_hintmap__
|
||||
private:
|
||||
pfc::array_t<char> m_buffer;
|
||||
t_index m_meta_count;
|
||||
t_index m_info_count;
|
||||
|
||||
const meta_entry * m_meta;
|
||||
const info_entry * m_info;
|
||||
|
||||
#ifdef __file_info_const_impl_have_hintmap__
|
||||
const t_index * m_hintmap;
|
||||
#endif
|
||||
|
||||
double m_length;
|
||||
replaygain_info m_replaygain;
|
||||
};
|
||||
79
foobar2000/helpers/file_list_helper.cpp
Normal file
79
foobar2000/helpers/file_list_helper.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "file_list_helper.h"
|
||||
|
||||
#ifndef _MSC_VER
|
||||
#define _strdup strdup
|
||||
#endif
|
||||
|
||||
static void file_list_remove_duplicates(pfc::ptr_list_t<char> & out)
|
||||
{
|
||||
t_size n, m = out.get_count();
|
||||
out.sort_t(metadb::path_compare);
|
||||
pfc::bit_array_bittable mask(m);
|
||||
t_size duplicates = 0;
|
||||
for(n=1;n<m;n++) {
|
||||
if (!metadb::path_compare(out[n-1],out[n])) {duplicates++;mask.set(n,true);}
|
||||
}
|
||||
if (duplicates>0) {
|
||||
out.free_mask(mask);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
namespace file_list_helper
|
||||
{
|
||||
t_size file_list_from_metadb_handle_list::g_get_count(metadb_handle_list_cref data, t_size max) {
|
||||
pfc::avltree_t<const char*, metadb::path_comparator> content;
|
||||
const t_size inCount = data.get_size();
|
||||
for(t_size walk = 0; walk < inCount && content.get_count() < max; ++walk) {
|
||||
content += data[walk]->get_path();
|
||||
}
|
||||
return content.get_count();
|
||||
}
|
||||
void file_list_from_metadb_handle_list::_add(const char * p_what) {
|
||||
char * temp = _strdup(p_what);
|
||||
if (temp == NULL) throw std::bad_alloc();
|
||||
try {m_data.add_item(temp); } catch(...) {free(temp); throw;}
|
||||
}
|
||||
|
||||
void file_list_from_metadb_handle_list::init_from_list(const list_base_const_t<metadb_handle_ptr> & p_list)
|
||||
{
|
||||
m_data.free_all();
|
||||
|
||||
t_size n, m = p_list.get_count();
|
||||
for(n=0;n<m;n++) {
|
||||
_add( p_list.get_item(n)->get_path() );
|
||||
}
|
||||
file_list_remove_duplicates(m_data);
|
||||
}
|
||||
|
||||
void file_list_from_metadb_handle_list::init_from_list_display(const list_base_const_t<metadb_handle_ptr> & p_list)
|
||||
{
|
||||
m_data.free_all();
|
||||
|
||||
pfc::string8_fastalloc temp;
|
||||
|
||||
t_size n, m = p_list.get_count();
|
||||
for(n=0;n<m;n++)
|
||||
{
|
||||
filesystem::g_get_display_path(p_list.get_item(n)->get_path(),temp);
|
||||
_add(temp);
|
||||
}
|
||||
file_list_remove_duplicates(m_data);
|
||||
}
|
||||
|
||||
file_list_from_metadb_handle_list::file_list_from_metadb_handle_list(metadb_handle_list_cref lst, bool bDisplayPaths) {
|
||||
if ( bDisplayPaths ) init_from_list_display(lst);
|
||||
else init_from_list( lst );
|
||||
}
|
||||
|
||||
t_size file_list_from_metadb_handle_list::get_count() const {return m_data.get_count();}
|
||||
void file_list_from_metadb_handle_list::get_item_ex(const char * & p_out,t_size n) const {p_out = m_data.get_item(n);}
|
||||
|
||||
file_list_from_metadb_handle_list::~file_list_from_metadb_handle_list()
|
||||
{
|
||||
m_data.free_all();
|
||||
}
|
||||
|
||||
}
|
||||
28
foobar2000/helpers/file_list_helper.h
Normal file
28
foobar2000/helpers/file_list_helper.h
Normal file
@@ -0,0 +1,28 @@
|
||||
#pragma once
|
||||
|
||||
namespace file_list_helper
|
||||
{
|
||||
//list guaranteed to be sorted by metadb::path_compare
|
||||
class file_list_from_metadb_handle_list : public pfc::list_base_const_t<const char*> {
|
||||
public:
|
||||
file_list_from_metadb_handle_list() {}
|
||||
file_list_from_metadb_handle_list( metadb_handle_list_cref lst, bool bDisplayPaths = false );
|
||||
|
||||
static t_size g_get_count(const list_base_const_t<metadb_handle_ptr> & p_list, t_size max = ~0);
|
||||
|
||||
void init_from_list(const list_base_const_t<metadb_handle_ptr> & p_list);
|
||||
void init_from_list_display(const list_base_const_t<metadb_handle_ptr> & p_list);
|
||||
|
||||
t_size get_count() const;
|
||||
void get_item_ex(const char * & p_out,t_size n) const;
|
||||
|
||||
~file_list_from_metadb_handle_list();
|
||||
|
||||
private:
|
||||
void _add(const char * p_what);
|
||||
pfc::ptr_list_t<char> m_data;
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
|
||||
40
foobar2000/helpers/file_move_helper.cpp
Normal file
40
foobar2000/helpers/file_move_helper.cpp
Normal file
@@ -0,0 +1,40 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "file_move_helper.h"
|
||||
|
||||
bool file_move_helper::g_on_deleted(const pfc::list_base_const_t<const char *> & p_files)
|
||||
{
|
||||
file_operation_callback::g_on_files_deleted(p_files);
|
||||
return true;
|
||||
}
|
||||
|
||||
t_size file_move_helper::g_filter_dead_files_sorted_make_mask(pfc::list_base_t<metadb_handle_ptr> & p_data,const pfc::list_base_const_t<const char*> & p_dead,bit_array_var & p_mask)
|
||||
{
|
||||
t_size n, m = p_data.get_count();
|
||||
t_size found = 0;
|
||||
for(n=0;n<m;n++)
|
||||
{
|
||||
t_size dummy;
|
||||
bool dead = p_dead.bsearch_t(metadb::path_compare,p_data.get_item(n)->get_path(),dummy);
|
||||
if (dead) found++;
|
||||
p_mask.set(n,dead);
|
||||
}
|
||||
return found;
|
||||
}
|
||||
|
||||
t_size file_move_helper::g_filter_dead_files_sorted(pfc::list_base_t<metadb_handle_ptr> & p_data,const pfc::list_base_const_t<const char*> & p_dead)
|
||||
{
|
||||
pfc::bit_array_bittable mask(p_data.get_count());
|
||||
t_size found = g_filter_dead_files_sorted_make_mask(p_data,p_dead,mask);
|
||||
if (found > 0) p_data.remove_mask(mask);
|
||||
return found;
|
||||
}
|
||||
|
||||
t_size file_move_helper::g_filter_dead_files(pfc::list_base_t<metadb_handle_ptr> & p_data,const pfc::list_base_const_t<const char*> & p_dead)
|
||||
{
|
||||
pfc::ptr_list_t<const char> temp;
|
||||
temp.add_items(p_dead);
|
||||
temp.sort_t(metadb::path_compare);
|
||||
return g_filter_dead_files_sorted(p_data,temp);
|
||||
}
|
||||
|
||||
10
foobar2000/helpers/file_move_helper.h
Normal file
10
foobar2000/helpers/file_move_helper.h
Normal file
@@ -0,0 +1,10 @@
|
||||
#pragma once
|
||||
|
||||
class file_move_helper {
|
||||
public:
|
||||
static bool g_on_deleted(const pfc::list_base_const_t<const char *> & p_files);
|
||||
|
||||
static t_size g_filter_dead_files_sorted_make_mask(pfc::list_base_t<metadb_handle_ptr> & p_data,const pfc::list_base_const_t<const char*> & p_dead,bit_array_var & p_mask);
|
||||
static t_size g_filter_dead_files_sorted(pfc::list_base_t<metadb_handle_ptr> & p_data,const pfc::list_base_const_t<const char*> & p_dead);
|
||||
static t_size g_filter_dead_files(pfc::list_base_t<metadb_handle_ptr> & p_data,const pfc::list_base_const_t<const char*> & p_dead);
|
||||
};
|
||||
4
foobar2000/helpers/file_readonly.h
Normal file
4
foobar2000/helpers/file_readonly.h
Normal file
@@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
// fb2k mobile compat
|
||||
|
||||
#include "../SDK/filesystem_helper.h"
|
||||
281
foobar2000/helpers/file_win32_wrapper.cpp
Normal file
281
foobar2000/helpers/file_win32_wrapper.cpp
Normal file
@@ -0,0 +1,281 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
#include "file_win32_wrapper.h"
|
||||
|
||||
namespace file_win32_helpers {
|
||||
t_filesize get_size(HANDLE p_handle) {
|
||||
union {
|
||||
t_uint64 val64;
|
||||
t_uint32 val32[2];
|
||||
} u;
|
||||
|
||||
u.val64 = 0;
|
||||
SetLastError(NO_ERROR);
|
||||
u.val32[0] = GetFileSize(p_handle,reinterpret_cast<DWORD*>(&u.val32[1]));
|
||||
if (GetLastError()!=NO_ERROR) exception_io_from_win32(GetLastError());
|
||||
return u.val64;
|
||||
}
|
||||
void seek(HANDLE p_handle,t_sfilesize p_position,file::t_seek_mode p_mode) {
|
||||
union {
|
||||
t_int64 temp64;
|
||||
struct {
|
||||
DWORD temp_lo;
|
||||
LONG temp_hi;
|
||||
};
|
||||
};
|
||||
|
||||
temp64 = p_position;
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
temp_lo = SetFilePointer(p_handle,temp_lo,&temp_hi,(DWORD)p_mode);
|
||||
if (GetLastError() != ERROR_SUCCESS) exception_io_from_win32(GetLastError());
|
||||
}
|
||||
|
||||
void fillOverlapped(OVERLAPPED & ol, HANDLE myEvent, t_filesize s) {
|
||||
ol.hEvent = myEvent;
|
||||
ol.Offset = (DWORD)( s & 0xFFFFFFFF );
|
||||
ol.OffsetHigh = (DWORD)(s >> 32);
|
||||
}
|
||||
|
||||
void writeOverlappedPass(HANDLE handle, HANDLE myEvent, t_filesize position, const void * in,DWORD inBytes, abort_callback & abort) {
|
||||
abort.check();
|
||||
if (inBytes == 0) return;
|
||||
OVERLAPPED ol = {};
|
||||
fillOverlapped(ol, myEvent, position);
|
||||
ResetEvent(myEvent);
|
||||
DWORD bytesWritten;
|
||||
SetLastError(NO_ERROR);
|
||||
if (WriteFile( handle, in, inBytes, &bytesWritten, &ol)) {
|
||||
// succeeded already?
|
||||
if (bytesWritten != inBytes) throw exception_io();
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
const DWORD code = GetLastError();
|
||||
if (code != ERROR_IO_PENDING) exception_io_from_win32(code);
|
||||
}
|
||||
const HANDLE handles[] = {myEvent, abort.get_abort_event()};
|
||||
SetLastError(NO_ERROR);
|
||||
DWORD state = WaitForMultipleObjects(_countof(handles), handles, FALSE, INFINITE);
|
||||
if (state == WAIT_OBJECT_0) {
|
||||
try {
|
||||
WIN32_IO_OP( GetOverlappedResult(handle,&ol,&bytesWritten,TRUE) );
|
||||
} catch(...) {
|
||||
CancelIo(handle);
|
||||
throw;
|
||||
}
|
||||
if (bytesWritten != inBytes) throw exception_io();
|
||||
return;
|
||||
}
|
||||
CancelIo(handle);
|
||||
throw exception_aborted();
|
||||
}
|
||||
|
||||
void writeOverlapped(HANDLE handle, HANDLE myEvent, t_filesize & position, const void * in, size_t inBytes, abort_callback & abort) {
|
||||
enum {writeMAX = 16*1024*1024};
|
||||
size_t done = 0;
|
||||
while(done < inBytes) {
|
||||
size_t delta = inBytes - done;
|
||||
if (delta > writeMAX) delta = writeMAX;
|
||||
writeOverlappedPass(handle, myEvent, position, (const BYTE*)in + done, (DWORD) delta, abort);
|
||||
done += delta;
|
||||
position += delta;
|
||||
}
|
||||
}
|
||||
void writeStreamOverlapped(HANDLE handle, HANDLE myEvent, const void * in, size_t inBytes, abort_callback & abort) {
|
||||
enum {writeMAX = 16*1024*1024};
|
||||
size_t done = 0;
|
||||
while(done < inBytes) {
|
||||
size_t delta = inBytes - done;
|
||||
if (delta > writeMAX) delta = writeMAX;
|
||||
writeOverlappedPass(handle, myEvent, 0, (const BYTE*)in + done, (DWORD) delta, abort);
|
||||
done += delta;
|
||||
}
|
||||
}
|
||||
|
||||
DWORD readOverlappedPass(HANDLE handle, HANDLE myEvent, t_filesize position, void * out, DWORD outBytes, abort_callback & abort) {
|
||||
abort.check();
|
||||
if (outBytes == 0) return 0;
|
||||
OVERLAPPED ol = {};
|
||||
fillOverlapped(ol, myEvent, position);
|
||||
ResetEvent(myEvent);
|
||||
DWORD bytesDone;
|
||||
SetLastError(NO_ERROR);
|
||||
if (ReadFile( handle, out, outBytes, &bytesDone, &ol)) {
|
||||
// succeeded already?
|
||||
return bytesDone;
|
||||
}
|
||||
|
||||
{
|
||||
const DWORD code = GetLastError();
|
||||
switch(code) {
|
||||
case ERROR_HANDLE_EOF:
|
||||
case ERROR_BROKEN_PIPE:
|
||||
return 0;
|
||||
case ERROR_IO_PENDING:
|
||||
break; // continue
|
||||
default:
|
||||
exception_io_from_win32(code);
|
||||
};
|
||||
}
|
||||
|
||||
const HANDLE handles[] = {myEvent, abort.get_abort_event()};
|
||||
SetLastError(NO_ERROR);
|
||||
DWORD state = WaitForMultipleObjects(_countof(handles), handles, FALSE, INFINITE);
|
||||
if (state == WAIT_OBJECT_0) {
|
||||
SetLastError(NO_ERROR);
|
||||
if (!GetOverlappedResult(handle,&ol,&bytesDone,TRUE)) {
|
||||
const DWORD code = GetLastError();
|
||||
if (code == ERROR_HANDLE_EOF || code == ERROR_BROKEN_PIPE) bytesDone = 0;
|
||||
else {
|
||||
CancelIo(handle);
|
||||
exception_io_from_win32(code);
|
||||
}
|
||||
}
|
||||
return bytesDone;
|
||||
}
|
||||
CancelIo(handle);
|
||||
throw exception_aborted();
|
||||
}
|
||||
size_t readOverlapped(HANDLE handle, HANDLE myEvent, t_filesize & position, void * out, size_t outBytes, abort_callback & abort) {
|
||||
enum {readMAX = 16*1024*1024};
|
||||
size_t done = 0;
|
||||
while(done < outBytes) {
|
||||
size_t delta = outBytes - done;
|
||||
if (delta > readMAX) delta = readMAX;
|
||||
delta = readOverlappedPass(handle, myEvent, position, (BYTE*) out + done, (DWORD) delta, abort);
|
||||
if (delta == 0) break;
|
||||
done += delta;
|
||||
position += delta;
|
||||
}
|
||||
return done;
|
||||
}
|
||||
|
||||
size_t readStreamOverlapped(HANDLE handle, HANDLE myEvent, void * out, size_t outBytes, abort_callback & abort) {
|
||||
enum {readMAX = 16*1024*1024};
|
||||
size_t done = 0;
|
||||
while(done < outBytes) {
|
||||
size_t delta = outBytes - done;
|
||||
if (delta > readMAX) delta = readMAX;
|
||||
delta = readOverlappedPass(handle, myEvent, 0, (BYTE*) out + done, (DWORD) delta, abort);
|
||||
if (delta == 0) break;
|
||||
done += delta;
|
||||
}
|
||||
return done;
|
||||
}
|
||||
|
||||
typedef BOOL (WINAPI * pCancelSynchronousIo_t)(HANDLE hThread);
|
||||
|
||||
|
||||
struct createFileData_t {
|
||||
LPCTSTR lpFileName;
|
||||
DWORD dwDesiredAccess;
|
||||
DWORD dwShareMode;
|
||||
LPSECURITY_ATTRIBUTES lpSecurityAttributes;
|
||||
DWORD dwCreationDisposition;
|
||||
DWORD dwFlagsAndAttributes;
|
||||
HANDLE hTemplateFile;
|
||||
HANDLE hResult;
|
||||
DWORD dwErrorCode;
|
||||
};
|
||||
|
||||
static unsigned CALLBACK createFileProc(void * data) {
|
||||
createFileData_t * cfd = (createFileData_t*)data;
|
||||
SetLastError(0);
|
||||
cfd->hResult = CreateFile(cfd->lpFileName, cfd->dwDesiredAccess, cfd->dwShareMode, cfd->lpSecurityAttributes, cfd->dwCreationDisposition, cfd->dwFlagsAndAttributes, cfd->hTemplateFile);
|
||||
cfd->dwErrorCode = GetLastError();
|
||||
return 0;
|
||||
}
|
||||
|
||||
HANDLE createFile(LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile, abort_callback & abort) {
|
||||
abort.check();
|
||||
|
||||
return CreateFile(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
|
||||
|
||||
// CancelSynchronousIo() doesn't fucking work. Useless.
|
||||
#if 0
|
||||
pCancelSynchronousIo_t pCancelSynchronousIo = (pCancelSynchronousIo_t) GetProcAddress(GetModuleHandle(TEXT("kernel32.dll")), "CancelSynchronousIo");
|
||||
if (pCancelSynchronousIo == NULL) {
|
||||
#ifdef _DEBUG
|
||||
uDebugLog() << "Async CreateFile unavailable - using regular";
|
||||
#endif
|
||||
return CreateFile(lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile);
|
||||
} else {
|
||||
#ifdef _DEBUG
|
||||
uDebugLog() << "Starting async CreateFile...";
|
||||
pfc::hires_timer t; t.start();
|
||||
#endif
|
||||
createFileData_t data = {lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile, NULL, 0};
|
||||
HANDLE hThread = (HANDLE) _beginthreadex(NULL, 0, createFileProc, &data, 0, NULL);
|
||||
HANDLE waitHandles[2] = {hThread, abort.get_abort_event()};
|
||||
switch(WaitForMultipleObjects(2, waitHandles, FALSE, INFINITE)) {
|
||||
case WAIT_OBJECT_0: // succeeded
|
||||
break;
|
||||
case WAIT_OBJECT_0 + 1: // abort
|
||||
#ifdef _DEBUG
|
||||
uDebugLog() << "Aborting async CreateFile...";
|
||||
#endif
|
||||
pCancelSynchronousIo(hThread);
|
||||
WaitForSingleObject(hThread, INFINITE);
|
||||
break;
|
||||
default:
|
||||
uBugCheck();
|
||||
}
|
||||
CloseHandle(hThread);
|
||||
SetLastError(data.dwErrorCode);
|
||||
#ifdef _DEBUG
|
||||
uDebugLog() << "Async CreateFile completed in " << pfc::format_time_ex(t.query(), 6) << ", status: " << (uint32_t) data.dwErrorCode;
|
||||
#endif
|
||||
if (abort.is_aborting()) {
|
||||
if (data.hResult != INVALID_HANDLE_VALUE) CloseHandle(data.hResult);
|
||||
throw exception_aborted();
|
||||
}
|
||||
return data.hResult;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
size_t lowLevelIO(HANDLE hFile, const GUID & guid, size_t arg1, void * arg2, size_t arg2size, bool canWrite, abort_callback & abort) {
|
||||
if ( guid == file_lowLevelIO::guid_flushFileBuffers ) {
|
||||
if (!canWrite) {
|
||||
PFC_ASSERT(!"File opened for reading, not writing");
|
||||
throw exception_io_denied();
|
||||
}
|
||||
WIN32_IO_OP( ::FlushFileBuffers(hFile) );
|
||||
return 1;
|
||||
} else if ( guid == file_lowLevelIO::guid_getFileTimes ) {
|
||||
if ( arg2size == sizeof(file_lowLevelIO::filetimes_t) ) {
|
||||
if (canWrite) WIN32_IO_OP(::FlushFileBuffers(hFile));
|
||||
auto ft = reinterpret_cast<file_lowLevelIO::filetimes_t *>(arg2);
|
||||
static_assert(sizeof(t_filetimestamp) == sizeof(FILETIME), "struct sanity");
|
||||
WIN32_IO_OP( GetFileTime( hFile, (FILETIME*)&ft->creation, (FILETIME*)&ft->lastAccess, (FILETIME*)&ft->lastWrite) );
|
||||
return 1;
|
||||
}
|
||||
} else if ( guid == file_lowLevelIO::guid_setFileTimes ) {
|
||||
if (arg2size == sizeof(file_lowLevelIO::filetimes_t)) {
|
||||
if (!canWrite) {
|
||||
PFC_ASSERT(!"File opened for reading, not writing");
|
||||
throw exception_io_denied();
|
||||
}
|
||||
WIN32_IO_OP(::FlushFileBuffers(hFile));
|
||||
auto ft = reinterpret_cast<file_lowLevelIO::filetimes_t *>(arg2);
|
||||
static_assert(sizeof(t_filetimestamp) == sizeof(FILETIME), "struct sanity");
|
||||
const FILETIME * pCreation = nullptr;
|
||||
const FILETIME * pLastAccess = nullptr;
|
||||
const FILETIME * pLastWrite = nullptr;
|
||||
if ( ft->creation != filetimestamp_invalid ) pCreation = (const FILETIME*)&ft->creation;
|
||||
if ( ft->lastAccess != filetimestamp_invalid ) pLastAccess = (const FILETIME*)&ft->lastAccess;
|
||||
if ( ft->lastWrite != filetimestamp_invalid ) pLastWrite = (const FILETIME*)&ft->lastWrite;
|
||||
WIN32_IO_OP( SetFileTime(hFile, pCreation, pLastAccess, pLastWrite) );
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // _WIN32
|
||||
|
||||
254
foobar2000/helpers/file_win32_wrapper.h
Normal file
254
foobar2000/helpers/file_win32_wrapper.h
Normal file
@@ -0,0 +1,254 @@
|
||||
#pragma once
|
||||
|
||||
#include <libPPUI/win32_op.h>
|
||||
|
||||
#ifdef _WIN32
|
||||
namespace file_win32_helpers {
|
||||
t_filesize get_size(HANDLE p_handle);
|
||||
void seek(HANDLE p_handle,t_sfilesize p_position,file::t_seek_mode p_mode);
|
||||
void fillOverlapped(OVERLAPPED & ol, HANDLE myEvent, t_filesize s);
|
||||
void writeOverlappedPass(HANDLE handle, HANDLE myEvent, t_filesize position, const void * in,DWORD inBytes, abort_callback & abort);
|
||||
void writeOverlapped(HANDLE handle, HANDLE myEvent, t_filesize & position, const void * in, size_t inBytes, abort_callback & abort);
|
||||
void writeStreamOverlapped(HANDLE handle, HANDLE myEvent, const void * in, size_t inBytes, abort_callback & abort);
|
||||
DWORD readOverlappedPass(HANDLE handle, HANDLE myEvent, t_filesize position, void * out, DWORD outBytes, abort_callback & abort);
|
||||
size_t readOverlapped(HANDLE handle, HANDLE myEvent, t_filesize & position, void * out, size_t outBytes, abort_callback & abort);
|
||||
size_t readStreamOverlapped(HANDLE handle, HANDLE myEvent, void * out, size_t outBytes, abort_callback & abort);
|
||||
HANDLE createFile(LPCTSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplateFile, abort_callback & abort);
|
||||
size_t lowLevelIO(HANDLE hFile, const GUID & guid, size_t arg1, void * arg2, size_t arg2size, bool canWrite, abort_callback & abort);
|
||||
};
|
||||
|
||||
template<bool p_seekable,bool p_writeable>
|
||||
class file_win32_wrapper_t : public service_multi_inherit<file, file_lowLevelIO> {
|
||||
public:
|
||||
file_win32_wrapper_t(HANDLE p_handle) : m_handle(p_handle), m_position(0)
|
||||
{
|
||||
}
|
||||
|
||||
static file::ptr g_CreateFile(const char * p_path,DWORD p_access,DWORD p_sharemode,LPSECURITY_ATTRIBUTES p_security_attributes,DWORD p_createmode,DWORD p_flags,HANDLE p_template) {
|
||||
SetLastError(NO_ERROR);
|
||||
HANDLE handle = uCreateFile(p_path,p_access,p_sharemode,p_security_attributes,p_createmode,p_flags,p_template);
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
const DWORD code = GetLastError();
|
||||
if (p_access & GENERIC_WRITE) win32_file_write_failure(code, p_path);
|
||||
else exception_io_from_win32(code);
|
||||
}
|
||||
try {
|
||||
return g_create_from_handle(handle);
|
||||
} catch(...) {CloseHandle(handle); throw;}
|
||||
}
|
||||
|
||||
static service_ptr_t<file> g_create_from_handle(HANDLE p_handle) {
|
||||
return new service_impl_t<file_win32_wrapper_t<p_seekable,p_writeable> >(p_handle);
|
||||
}
|
||||
|
||||
|
||||
void reopen(abort_callback & p_abort) {seek(0,p_abort);}
|
||||
|
||||
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
||||
if (!p_writeable) throw exception_io_denied();
|
||||
|
||||
PFC_STATIC_ASSERT(sizeof(t_size) >= sizeof(DWORD));
|
||||
|
||||
t_size bytes_written_total = 0;
|
||||
|
||||
if (sizeof(t_size) == sizeof(DWORD)) {
|
||||
p_abort.check_e();
|
||||
DWORD bytes_written = 0;
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
if (!WriteFile(m_handle,p_buffer,(DWORD)p_bytes,&bytes_written,0)) exception_io_from_win32(GetLastError());
|
||||
if (bytes_written != p_bytes) throw exception_io("Write failure");
|
||||
bytes_written_total = bytes_written;
|
||||
m_position += bytes_written;
|
||||
} else {
|
||||
while(bytes_written_total < p_bytes) {
|
||||
p_abort.check_e();
|
||||
DWORD bytes_written = 0;
|
||||
DWORD delta = (DWORD) pfc::min_t<t_size>(p_bytes - bytes_written_total, 0x80000000u);
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
if (!WriteFile(m_handle,(const t_uint8*)p_buffer + bytes_written_total,delta,&bytes_written,0)) exception_io_from_win32(GetLastError());
|
||||
if (bytes_written != delta) throw exception_io("Write failure");
|
||||
bytes_written_total += bytes_written;
|
||||
m_position += bytes_written;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
||||
PFC_STATIC_ASSERT(sizeof(t_size) >= sizeof(DWORD));
|
||||
|
||||
t_size bytes_read_total = 0;
|
||||
if (sizeof(t_size) == sizeof(DWORD)) {
|
||||
p_abort.check_e();
|
||||
DWORD bytes_read = 0;
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
if (!ReadFile(m_handle,p_buffer,pfc::downcast_guarded<DWORD>(p_bytes),&bytes_read,0)) exception_io_from_win32(GetLastError());
|
||||
bytes_read_total = bytes_read;
|
||||
m_position += bytes_read;
|
||||
} else {
|
||||
while(bytes_read_total < p_bytes) {
|
||||
p_abort.check_e();
|
||||
DWORD bytes_read = 0;
|
||||
DWORD delta = (DWORD) pfc::min_t<t_size>(p_bytes - bytes_read_total, 0x80000000u);
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
if (!ReadFile(m_handle,(t_uint8*)p_buffer + bytes_read_total,delta,&bytes_read,0)) exception_io_from_win32(GetLastError());
|
||||
bytes_read_total += bytes_read;
|
||||
m_position += bytes_read;
|
||||
if (bytes_read != delta) break;
|
||||
}
|
||||
}
|
||||
return bytes_read_total;
|
||||
}
|
||||
|
||||
|
||||
t_filesize get_size(abort_callback & p_abort) {
|
||||
p_abort.check_e();
|
||||
return file_win32_helpers::get_size(m_handle);
|
||||
}
|
||||
|
||||
t_filesize get_position(abort_callback & p_abort) {
|
||||
p_abort.check_e();
|
||||
return m_position;
|
||||
}
|
||||
|
||||
void resize(t_filesize p_size,abort_callback & p_abort) {
|
||||
if (!p_writeable) throw exception_io_denied();
|
||||
p_abort.check_e();
|
||||
if (m_position != p_size) {
|
||||
file_win32_helpers::seek(m_handle,p_size,file::seek_from_beginning);
|
||||
}
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
if (!SetEndOfFile(m_handle)) {
|
||||
DWORD code = GetLastError();
|
||||
if (m_position != p_size) try {file_win32_helpers::seek(m_handle,m_position,file::seek_from_beginning);} catch(...) {}
|
||||
exception_io_from_win32(code);
|
||||
}
|
||||
if (m_position > p_size) m_position = p_size;
|
||||
if (m_position != p_size) file_win32_helpers::seek(m_handle,m_position,file::seek_from_beginning);
|
||||
}
|
||||
|
||||
|
||||
void seek(t_filesize p_position,abort_callback & p_abort) {
|
||||
if (!p_seekable) throw exception_io_object_not_seekable();
|
||||
p_abort.check_e();
|
||||
if (p_position > file_win32_helpers::get_size(m_handle)) throw exception_io_seek_out_of_range();
|
||||
file_win32_helpers::seek(m_handle,p_position,file::seek_from_beginning);
|
||||
m_position = p_position;
|
||||
}
|
||||
|
||||
bool can_seek() {return p_seekable;}
|
||||
bool get_content_type(pfc::string_base & out) {return false;}
|
||||
bool is_in_memory() {return false;}
|
||||
void on_idle(abort_callback & p_abort) {p_abort.check_e();}
|
||||
|
||||
t_filetimestamp get_timestamp(abort_callback & p_abort) {
|
||||
p_abort.check_e();
|
||||
if (p_writeable) FlushFileBuffers(m_handle);
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
t_filetimestamp temp;
|
||||
if (!GetFileTime(m_handle,0,0,(FILETIME*)&temp)) exception_io_from_win32(GetLastError());
|
||||
return temp;
|
||||
}
|
||||
|
||||
bool is_remote() {return false;}
|
||||
~file_win32_wrapper_t() {CloseHandle(m_handle);}
|
||||
|
||||
size_t lowLevelIO(const GUID & guid, size_t arg1, void * arg2, size_t arg2size, abort_callback & abort) override {
|
||||
return file_win32_helpers::lowLevelIO(m_handle, guid, arg1, arg2, arg2size, p_writeable, abort);
|
||||
}
|
||||
protected:
|
||||
HANDLE m_handle;
|
||||
t_filesize m_position;
|
||||
};
|
||||
|
||||
template<bool p_writeable>
|
||||
class file_win32_wrapper_overlapped_t : public service_multi_inherit< file, file_lowLevelIO > {
|
||||
public:
|
||||
file_win32_wrapper_overlapped_t(HANDLE file) : m_handle(file), m_position() {
|
||||
WIN32_OP( (m_event = CreateEvent(NULL, TRUE, FALSE, NULL)) != NULL );
|
||||
}
|
||||
~file_win32_wrapper_overlapped_t() {CloseHandle(m_event); CloseHandle(m_handle);}
|
||||
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
||||
if (!p_writeable) throw exception_io_denied();
|
||||
return file_win32_helpers::writeOverlapped(m_handle, m_event, m_position, p_buffer, p_bytes, p_abort);
|
||||
}
|
||||
t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
||||
return file_win32_helpers::readOverlapped(m_handle, m_event, m_position, p_buffer, p_bytes, p_abort);
|
||||
}
|
||||
|
||||
void reopen(abort_callback & p_abort) {seek(0,p_abort);}
|
||||
|
||||
|
||||
t_filesize get_size(abort_callback & p_abort) {
|
||||
p_abort.check_e();
|
||||
return file_win32_helpers::get_size(m_handle);
|
||||
}
|
||||
|
||||
t_filesize get_position(abort_callback & p_abort) {
|
||||
p_abort.check_e();
|
||||
return m_position;
|
||||
}
|
||||
|
||||
void resize(t_filesize p_size,abort_callback & p_abort) {
|
||||
if (!p_writeable) throw exception_io_denied();
|
||||
p_abort.check_e();
|
||||
file_win32_helpers::seek(m_handle,p_size,file::seek_from_beginning);
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
if (!SetEndOfFile(m_handle)) {
|
||||
DWORD code = GetLastError();
|
||||
exception_io_from_win32(code);
|
||||
}
|
||||
if (m_position > p_size) m_position = p_size;
|
||||
}
|
||||
|
||||
|
||||
void seek(t_filesize p_position,abort_callback & p_abort) {
|
||||
p_abort.check_e();
|
||||
if (p_position > file_win32_helpers::get_size(m_handle)) throw exception_io_seek_out_of_range();
|
||||
// file_win32_helpers::seek(m_handle,p_position,file::seek_from_beginning);
|
||||
m_position = p_position;
|
||||
}
|
||||
|
||||
bool can_seek() {return true;}
|
||||
bool get_content_type(pfc::string_base & out) {return false;}
|
||||
bool is_in_memory() {return false;}
|
||||
void on_idle(abort_callback & p_abort) {p_abort.check_e();}
|
||||
|
||||
t_filetimestamp get_timestamp(abort_callback & p_abort) {
|
||||
p_abort.check_e();
|
||||
if (p_writeable) FlushFileBuffers(m_handle);
|
||||
SetLastError(ERROR_SUCCESS);
|
||||
t_filetimestamp temp;
|
||||
if (!GetFileTime(m_handle,0,0,(FILETIME*)&temp)) exception_io_from_win32(GetLastError());
|
||||
return temp;
|
||||
}
|
||||
|
||||
bool is_remote() {return false;}
|
||||
|
||||
|
||||
static file::ptr g_CreateFile(const char * p_path,DWORD p_access,DWORD p_sharemode,LPSECURITY_ATTRIBUTES p_security_attributes,DWORD p_createmode,DWORD p_flags,HANDLE p_template) {
|
||||
p_flags |= FILE_FLAG_OVERLAPPED;
|
||||
SetLastError(NO_ERROR);
|
||||
HANDLE handle = uCreateFile(p_path,p_access,p_sharemode,p_security_attributes,p_createmode,p_flags,p_template);
|
||||
if (handle == INVALID_HANDLE_VALUE) {
|
||||
const DWORD code = GetLastError();
|
||||
if (p_access & GENERIC_WRITE) win32_file_write_failure(code, p_path);
|
||||
else exception_io_from_win32(code);
|
||||
}
|
||||
try {
|
||||
return g_create_from_handle(handle);
|
||||
} catch(...) {CloseHandle(handle); throw;}
|
||||
}
|
||||
|
||||
static file::ptr g_create_from_handle(HANDLE p_handle) {
|
||||
return new service_impl_t<file_win32_wrapper_overlapped_t<p_writeable> >(p_handle);
|
||||
}
|
||||
|
||||
size_t lowLevelIO(const GUID & guid, size_t arg1, void * arg2, size_t arg2size, abort_callback & abort) override {
|
||||
return file_win32_helpers::lowLevelIO(m_handle, guid, arg1, arg2, arg2size, p_writeable, abort);
|
||||
}
|
||||
|
||||
protected:
|
||||
HANDLE m_event, m_handle;
|
||||
t_filesize m_position;
|
||||
};
|
||||
#endif // _WIN32
|
||||
103
foobar2000/helpers/filetimetools.cpp
Normal file
103
foobar2000/helpers/filetimetools.cpp
Normal file
@@ -0,0 +1,103 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
|
||||
#ifndef _WIN32
|
||||
#error PORTME
|
||||
#endif
|
||||
|
||||
#include "filetimetools.h"
|
||||
|
||||
static bool is_spacing(char c) {return c == ' ' || c==10 || c==13 || c == '\t';}
|
||||
|
||||
static bool is_spacing(const char * str, t_size len) {
|
||||
for(t_size walk = 0; walk < len; ++walk) if (!is_spacing(str[walk])) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
typedef exception_io_data exception_time_error;
|
||||
|
||||
static unsigned ParseDateElem(const char * ptr, t_size len) {
|
||||
unsigned ret = 0;
|
||||
for(t_size walk = 0; walk < len; ++walk) {
|
||||
const char c = ptr[walk];
|
||||
if (c < '0' || c > '9') throw exception_time_error();
|
||||
ret = ret * 10 + (unsigned)(c - '0');
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
t_filetimestamp foobar2000_io::filetimestamp_from_string(const char * date) {
|
||||
// Accepted format
|
||||
// YYYY-MM-DD HH:MM:SS
|
||||
try {
|
||||
t_size remaining = strlen(date);
|
||||
SYSTEMTIME st = {};
|
||||
st.wDay = 1; st.wMonth = 1;
|
||||
for(;;) {
|
||||
#define ADVANCE(n) { PFC_ASSERT( remaining >= n); date += n; remaining -= n; }
|
||||
#define ADVANCE_TEST(n) { if (remaining < n) throw exception_time_error(); }
|
||||
#define PARSE(var, digits) { ADVANCE_TEST(digits); var = (WORD) ParseDateElem(date, digits); ADVANCE(digits) ; }
|
||||
#define TEST_END( durationIncrement )
|
||||
#define SKIP(c) { ADVANCE_TEST(1); if (date[0] != c) throw exception_time_error(); ADVANCE(1); }
|
||||
#define SKIP_SPACING() {while(remaining > 0 && is_spacing(*date)) ADVANCE(1);}
|
||||
SKIP_SPACING();
|
||||
PARSE( st.wYear, 4 ); if (st.wYear < 1601) throw exception_time_error();
|
||||
TEST_END(wYear); SKIP('-');
|
||||
PARSE( st.wMonth, 2 ); if (st.wMonth < 1 || st.wMonth > 12) throw exception_time_error();
|
||||
TEST_END(wMonth); SKIP('-');
|
||||
PARSE( st.wDay, 2); if (st.wDay < 1 || st.wDay > 31) throw exception_time_error();
|
||||
TEST_END(wDay); SKIP(' ');
|
||||
PARSE( st.wHour, 2); if (st.wHour >= 24) throw exception_time_error();
|
||||
TEST_END(wHour); SKIP(':');
|
||||
PARSE( st.wMinute, 2); if (st.wMinute >= 60) throw exception_time_error();
|
||||
TEST_END(wMinute); SKIP(':');
|
||||
PARSE( st.wSecond, 2); if (st.wSecond >= 60) throw exception_time_error();
|
||||
SKIP_SPACING();
|
||||
TEST_END( wSecond );
|
||||
#undef ADVANCE
|
||||
#undef ADVANCE_TEST
|
||||
#undef PARSE
|
||||
#undef TEST_END
|
||||
#undef SKIP
|
||||
#undef SKIP_SPACING
|
||||
if (remaining > 0) throw exception_time_error();
|
||||
break;
|
||||
}
|
||||
t_filetimestamp base, out;
|
||||
if (!SystemTimeToFileTime(&st, (FILETIME*) &base)) throw exception_time_error();
|
||||
if (!LocalFileTimeToFileTime((const FILETIME*)&base, (FILETIME*)&out)) throw exception_time_error();
|
||||
return out;
|
||||
} catch(exception_time_error) {
|
||||
return filetimestamp_invalid;
|
||||
}
|
||||
}
|
||||
|
||||
static const char g_invalidMsg[] = "<invalid timestamp>";
|
||||
|
||||
format_filetimestamp::format_filetimestamp(t_filetimestamp p_timestamp) {
|
||||
try {
|
||||
SYSTEMTIME st; FILETIME ft;
|
||||
if (FileTimeToLocalFileTime((FILETIME*)&p_timestamp,&ft)) {
|
||||
if (FileTimeToSystemTime(&ft,&st)) {
|
||||
m_buffer
|
||||
<< pfc::format_uint(st.wYear,4) << "-" << pfc::format_uint(st.wMonth,2) << "-" << pfc::format_uint(st.wDay,2) << " "
|
||||
<< pfc::format_uint(st.wHour,2) << ":" << pfc::format_uint(st.wMinute,2) << ":" << pfc::format_uint(st.wSecond,2);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch(...) {}
|
||||
m_buffer = g_invalidMsg;
|
||||
}
|
||||
|
||||
format_filetimestamp_utc::format_filetimestamp_utc(t_filetimestamp p_timestamp) {
|
||||
try {
|
||||
SYSTEMTIME st;
|
||||
if (FileTimeToSystemTime((const FILETIME*)&p_timestamp,&st)) {
|
||||
m_buffer
|
||||
<< pfc::format_uint(st.wYear,4) << "-" << pfc::format_uint(st.wMonth,2) << "-" << pfc::format_uint(st.wDay,2) << " "
|
||||
<< pfc::format_uint(st.wHour,2) << ":" << pfc::format_uint(st.wMinute,2) << ":" << pfc::format_uint(st.wSecond,2);
|
||||
return;
|
||||
}
|
||||
} catch(...) {}
|
||||
m_buffer = g_invalidMsg;
|
||||
}
|
||||
25
foobar2000/helpers/filetimetools.h
Normal file
25
foobar2000/helpers/filetimetools.h
Normal file
@@ -0,0 +1,25 @@
|
||||
#pragma once
|
||||
|
||||
namespace foobar2000_io {
|
||||
t_filetimestamp filetimestamp_from_string(const char * date);
|
||||
|
||||
//! Warning: this formats according to system timezone settings, created strings should be used for display only, never for storage.
|
||||
class format_filetimestamp {
|
||||
public:
|
||||
format_filetimestamp(t_filetimestamp p_timestamp);
|
||||
operator const char*() const {return m_buffer;}
|
||||
const char * get_ptr() const {return m_buffer;}
|
||||
private:
|
||||
pfc::string_fixed_t<32> m_buffer;
|
||||
};
|
||||
|
||||
class format_filetimestamp_utc {
|
||||
public:
|
||||
format_filetimestamp_utc(t_filetimestamp p_timestamp);
|
||||
operator const char*() const {return m_buffer;}
|
||||
const char * get_ptr() const {return m_buffer;}
|
||||
private:
|
||||
pfc::string_fixed_t<32> m_buffer;
|
||||
};
|
||||
|
||||
}
|
||||
16
foobar2000/helpers/foobar2000+atl.h
Normal file
16
foobar2000/helpers/foobar2000+atl.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#pragma once
|
||||
|
||||
#include "../SDK/foobar2000-winver.h"
|
||||
|
||||
#define _SECURE_ATL 1
|
||||
|
||||
#include "../SDK/foobar2000.h"
|
||||
|
||||
#include <atlbase.h>
|
||||
#include <atltypes.h>
|
||||
#include <atlstr.h>
|
||||
#include <atlapp.h>
|
||||
#include <atlctrls.h>
|
||||
#include <atlwin.h>
|
||||
#include <atlcom.h>
|
||||
#include <atlcrack.h>
|
||||
250
foobar2000/helpers/foobar2000_sdk_helpers.vcxproj
Normal file
250
foobar2000/helpers/foobar2000_sdk_helpers.vcxproj
Normal file
@@ -0,0 +1,250 @@
|
||||
<?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>{EE47764E-A202-4F85-A767-ABDAB4AFF35F}</ProjectGuid>
|
||||
<RootNamespace>foobar2000_sdk_helpers</RootNamespace>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseOfMfc>false</UseOfMfc>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>StaticLibrary</ConfigurationType>
|
||||
<UseOfMfc>false</UseOfMfc>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</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>
|
||||
<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>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<_ProjectFileVersion>10.0.30319.1</_ProjectFileVersion>
|
||||
</PropertyGroup>
|
||||
<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>stdafx.h</PrecompiledHeaderFile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
|
||||
<AdditionalOptions>/d2notypeopt %(AdditionalOptions)</AdditionalOptions>
|
||||
<TreatSpecificWarningsAsErrors>4715</TreatSpecificWarningsAsErrors>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<OmitFramePointers>true</OmitFramePointers>
|
||||
<PreprocessorDefinitions>NDEBUG;_UNICODE;UNICODE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
</ClCompile>
|
||||
<ResourceCompile>
|
||||
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Culture>0x0409</Culture>
|
||||
</ResourceCompile>
|
||||
<Lib>
|
||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||
</Lib>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<BasicRuntimeChecks>EnableFastChecks</BasicRuntimeChecks>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<PrecompiledHeaderFile>stdafx.h</PrecompiledHeaderFile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||
<DebugInformationFormat>ProgramDatabase</DebugInformationFormat>
|
||||
<RuntimeLibrary>MultiThreadedDebugDLL</RuntimeLibrary>
|
||||
<AdditionalIncludeDirectories>../..</AdditionalIncludeDirectories>
|
||||
<TreatSpecificWarningsAsErrors>4715</TreatSpecificWarningsAsErrors>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
</ClCompile>
|
||||
<ResourceCompile>
|
||||
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<Culture>0x0409</Culture>
|
||||
</ResourceCompile>
|
||||
<Lib>
|
||||
<SuppressStartupBanner>true</SuppressStartupBanner>
|
||||
</Lib>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="AutoComplete.cpp" />
|
||||
<ClCompile Include="create_directory_helper.cpp">
|
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Disabled</Optimization>
|
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">EnableFastChecks</BasicRuntimeChecks>
|
||||
</ClCompile>
|
||||
<ClCompile Include="CTableEditHelper-Legacy.cpp" />
|
||||
<ClCompile Include="cue_creator.cpp" />
|
||||
<ClCompile Include="cue_parser.cpp">
|
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Disabled</Optimization>
|
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">EnableFastChecks</BasicRuntimeChecks>
|
||||
</ClCompile>
|
||||
<ClCompile Include="cue_parser_embedding.cpp" />
|
||||
<ClCompile Include="cuesheet_index_list.cpp" />
|
||||
<ClCompile Include="dialog_resize_helper.cpp">
|
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Disabled</Optimization>
|
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">EnableFastChecks</BasicRuntimeChecks>
|
||||
</ClCompile>
|
||||
<ClCompile Include="dropdown_helper.cpp">
|
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Disabled</Optimization>
|
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">EnableFastChecks</BasicRuntimeChecks>
|
||||
</ClCompile>
|
||||
<ClCompile Include="dynamic_bitrate_helper.cpp">
|
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Disabled</Optimization>
|
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">EnableFastChecks</BasicRuntimeChecks>
|
||||
</ClCompile>
|
||||
<ClCompile Include="file_info_const_impl.cpp">
|
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Disabled</Optimization>
|
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">EnableFastChecks</BasicRuntimeChecks>
|
||||
</ClCompile>
|
||||
<ClCompile Include="file_list_helper.cpp">
|
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Disabled</Optimization>
|
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">EnableFastChecks</BasicRuntimeChecks>
|
||||
</ClCompile>
|
||||
<ClCompile Include="file_move_helper.cpp" />
|
||||
<ClCompile Include="filetimetools.cpp" />
|
||||
<ClCompile Include="file_win32_wrapper.cpp" />
|
||||
<ClCompile Include="image_load_save.cpp" />
|
||||
<ClCompile Include="inplace_edit.cpp" />
|
||||
<ClCompile Include="input_helpers.cpp" />
|
||||
<ClCompile Include="input_helper_cue.cpp" />
|
||||
<ClCompile Include="mp3_utils.cpp" />
|
||||
<ClCompile Include="packet_decoder_aac_common.cpp" />
|
||||
<ClCompile Include="packet_decoder_mp3_common.cpp" />
|
||||
<ClCompile Include="readers.cpp" />
|
||||
<ClCompile Include="seekabilizer.cpp" />
|
||||
<ClCompile Include="StdAfx.cpp">
|
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Disabled</Optimization>
|
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">EnableFastChecks</BasicRuntimeChecks>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="stream_buffer_helper.cpp">
|
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Disabled</Optimization>
|
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">EnableFastChecks</BasicRuntimeChecks>
|
||||
</ClCompile>
|
||||
<ClCompile Include="text_file_loader.cpp">
|
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Disabled</Optimization>
|
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">EnableFastChecks</BasicRuntimeChecks>
|
||||
</ClCompile>
|
||||
<ClCompile Include="text_file_loader_v2.cpp" />
|
||||
<ClCompile Include="ThreadUtils.cpp" />
|
||||
<ClCompile Include="track_property_callback_impl.cpp" />
|
||||
<ClCompile Include="ui_element_helpers.cpp" />
|
||||
<ClCompile Include="VisUtils.cpp" />
|
||||
<ClCompile Include="VolumeMap.cpp" />
|
||||
<ClCompile Include="win32_dialog.cpp">
|
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Disabled</Optimization>
|
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">EnableFastChecks</BasicRuntimeChecks>
|
||||
</ClCompile>
|
||||
<ClCompile Include="win32_misc.cpp" />
|
||||
<ClCompile Include="window_placement_helper.cpp">
|
||||
<Optimization Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Disabled</Optimization>
|
||||
<BasicRuntimeChecks Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">EnableFastChecks</BasicRuntimeChecks>
|
||||
</ClCompile>
|
||||
<ClCompile Include="writer_wav.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="advconfig_impl.h" />
|
||||
<ClInclude Include="advconfig_runtime.h" />
|
||||
<ClInclude Include="album_art_helpers.h" />
|
||||
<ClInclude Include="atl-misc.h" />
|
||||
<ClInclude Include="AutoComplete.h" />
|
||||
<ClInclude Include="BumpableElem.h" />
|
||||
<ClInclude Include="CDialogResizeHelper.h" />
|
||||
<ClInclude Include="CmdThread.h" />
|
||||
<ClInclude Include="CPropVariant.h" />
|
||||
<ClInclude Include="CSingleThreadWrapper.h" />
|
||||
<ClInclude Include="CTableEditHelper-Legacy.h" />
|
||||
<ClInclude Include="duration_counter.h" />
|
||||
<ClInclude Include="fb2k_threads.h" />
|
||||
<ClInclude Include="fb2k_wfx.h" />
|
||||
<ClInclude Include="fileReadAhead.h" />
|
||||
<ClInclude Include="file_readonly.h" />
|
||||
<ClInclude Include="foobar2000+atl.h" />
|
||||
<ClInclude Include="fullFileBuffer.h" />
|
||||
<ClInclude Include="bitreader_helper.h" />
|
||||
<ClInclude Include="CallForwarder.h" />
|
||||
<ClInclude Include="cfg_guidlist.h" />
|
||||
<ClInclude Include="COM_utils.h" />
|
||||
<ClInclude Include="create_directory_helper.h" />
|
||||
<ClInclude Include="cue_creator.h" />
|
||||
<ClInclude Include="cue_parser.h" />
|
||||
<ClInclude Include="cuesheet_index_list.h" />
|
||||
<ClInclude Include="dialog_resize_helper.h" />
|
||||
<ClInclude Include="dropdown_helper.h" />
|
||||
<ClInclude Include="dynamic_bitrate_helper.h" />
|
||||
<ClInclude Include="file_cached.h" />
|
||||
<ClInclude Include="file_info_const_impl.h" />
|
||||
<ClInclude Include="file_list_helper.h" />
|
||||
<ClInclude Include="file_move_helper.h" />
|
||||
<ClInclude Include="file_win32_wrapper.h" />
|
||||
<ClInclude Include="filetimetools.h" />
|
||||
<ClInclude Include="helpers.h" />
|
||||
<ClInclude Include="icon_remapping_wildcard.h" />
|
||||
<ClInclude Include="image_load_save.h" />
|
||||
<ClInclude Include="inplace_edit.h" />
|
||||
<ClInclude Include="input_fix_seeking.h" />
|
||||
<ClInclude Include="input_helpers.h" />
|
||||
<ClInclude Include="input_helper_cue.h" />
|
||||
<ClInclude Include="input_logging.h" />
|
||||
<ClInclude Include="input_stream_info_reader.h" />
|
||||
<ClInclude Include="metadb_handle_set.h" />
|
||||
<ClInclude Include="meta_table_builder.h" />
|
||||
<ClInclude Include="metadb_io_hintlist.h" />
|
||||
<ClInclude Include="mp3_utils.h" />
|
||||
<ClInclude Include="packet_decoder_aac_common.h" />
|
||||
<ClInclude Include="packet_decoder_mp3_common.h" />
|
||||
<ClInclude Include="playlist_position_reference_tracker.h" />
|
||||
<ClInclude Include="ProcessUtils.h" />
|
||||
<ClInclude Include="ProfileCache.h" />
|
||||
<ClInclude Include="readers.h" />
|
||||
<ClInclude Include="reader_pretend_nonseekable.h" />
|
||||
<ClInclude Include="rethrow.h" />
|
||||
<ClInclude Include="seekabilizer.h" />
|
||||
<ClInclude Include="StdAfx.h" />
|
||||
<ClInclude Include="stream_buffer_helper.h" />
|
||||
<ClInclude Include="tag_write_callback_impl.h" />
|
||||
<ClInclude Include="text_file_loader.h" />
|
||||
<ClInclude Include="text_file_loader_v2.h" />
|
||||
<ClInclude Include="ThreadUtils.h" />
|
||||
<ClInclude Include="track_property_callback_impl.h" />
|
||||
<ClInclude Include="TypeFind.h" />
|
||||
<ClInclude Include="ui_element_helpers.h" />
|
||||
<ClInclude Include="VisUtils.h" />
|
||||
<ClInclude Include="VolumeMap.h" />
|
||||
<ClInclude Include="win32_dialog.h" />
|
||||
<ClInclude Include="win32_misc.h" />
|
||||
<ClInclude Include="WindowPositionUtils.h" />
|
||||
<ClInclude Include="window_placement_helper.h" />
|
||||
<ClInclude Include="winmm-types.h" />
|
||||
<ClInclude Include="writer_wav.h" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
350
foobar2000/helpers/foobar2000_sdk_helpers.vcxproj.filters
Normal file
350
foobar2000/helpers/foobar2000_sdk_helpers.vcxproj.filters
Normal file
@@ -0,0 +1,350 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{f9bf58c4-374f-49a5-94db-1f5ae50beca1}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cxx;rc;def;r;odl;idl;hpj;bat</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Header Files">
|
||||
<UniqueIdentifier>{07b1c50a-a3ad-4711-9ae0-d1411b80fd7a}</UniqueIdentifier>
|
||||
<Extensions>h;hpp;hxx;hm;inl</Extensions>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="create_directory_helper.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="cue_creator.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="cue_parser.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="cue_parser_embedding.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="cuesheet_index_list.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="dialog_resize_helper.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="dropdown_helper.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="dynamic_bitrate_helper.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="file_info_const_impl.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="file_list_helper.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="file_move_helper.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="filetimetools.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="input_helpers.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="mp3_utils.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="seekabilizer.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="StdAfx.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="stream_buffer_helper.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="text_file_loader.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="VisUtils.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="win32_dialog.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="win32_misc.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="window_placement_helper.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="file_win32_wrapper.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="writer_wav.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ThreadUtils.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="readers.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="packet_decoder_mp3_common.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="packet_decoder_aac_common.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="inplace_edit.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="CTableEditHelper-Legacy.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="AutoComplete.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="ui_element_helpers.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="track_property_callback_impl.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="VolumeMap.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="input_helper_cue.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="text_file_loader_v2.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="image_load_save.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="bitreader_helper.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="CallForwarder.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="cfg_guidlist.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="COM_utils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="create_directory_helper.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="cue_creator.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="cue_parser.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="cuesheet_index_list.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="dialog_resize_helper.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="dropdown_helper.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="dynamic_bitrate_helper.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="file_cached.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="file_info_const_impl.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="file_list_helper.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="file_move_helper.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="file_win32_wrapper.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="filetimetools.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="helpers.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="icon_remapping_wildcard.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="input_helpers.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="meta_table_builder.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="metadb_io_hintlist.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="mp3_utils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="playlist_position_reference_tracker.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ProfileCache.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="seekabilizer.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="StdAfx.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="stream_buffer_helper.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="text_file_loader.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ThreadUtils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="VisUtils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="win32_dialog.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="win32_misc.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="window_placement_helper.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ProcessUtils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="fullFileBuffer.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="fb2k_threads.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="writer_wav.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="TypeFind.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="readers.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="album_art_helpers.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="advconfig_runtime.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="fb2k_wfx.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="CSingleThreadWrapper.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="CmdThread.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="input_fix_seeking.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="rethrow.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="fileReadAhead.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="tag_write_callback_impl.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="input_stream_info_reader.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="duration_counter.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="reader_pretend_nonseekable.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="packet_decoder_mp3_common.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="packet_decoder_aac_common.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="inplace_edit.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="CTableEditHelper-Legacy.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="foobar2000+atl.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="atl-misc.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="AutoComplete.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="BumpableElem.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="CDialogResizeHelper.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="ui_element_helpers.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="WindowPositionUtils.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="input_logging.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="track_property_callback_impl.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="VolumeMap.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="CPropVariant.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="input_helper_cue.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="advconfig_impl.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="metadb_handle_set.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="text_file_loader_v2.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="file_readonly.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="winmm-types.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="image_load_save.h">
|
||||
<Filter>Header Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
18
foobar2000/helpers/fullFileBuffer.h
Normal file
18
foobar2000/helpers/fullFileBuffer.h
Normal file
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
|
||||
class fullFileBuffer {
|
||||
public:
|
||||
fullFileBuffer() {
|
||||
//hMutex = CreateMutex(NULL, FALSE, pfc::stringcvt::string_os_from_utf8(pfc::string_formatter() << "{3ABC4D98-2510-431C-A720-26BEB45F0278}-" << (uint32_t) GetCurrentProcessId()));
|
||||
}
|
||||
~fullFileBuffer() {
|
||||
//CloseHandle(hMutex);
|
||||
}
|
||||
file::ptr open(const char * path, abort_callback & abort, file::ptr hint, t_filesize sizeMax = 1024 * 1024 * 256);
|
||||
|
||||
private:
|
||||
fullFileBuffer(const fullFileBuffer&);
|
||||
void operator=(const fullFileBuffer&);
|
||||
|
||||
//HANDLE hMutex;
|
||||
};
|
||||
41
foobar2000/helpers/helpers.h
Normal file
41
foobar2000/helpers/helpers.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
// #pragma message("Avoid using this header. #include individual ones instead.")
|
||||
|
||||
#include "duration_counter.h"
|
||||
#include "input_helpers.h"
|
||||
#include "create_directory_helper.h"
|
||||
#include "dialog_resize_helper.h"
|
||||
#include "dropdown_helper.h"
|
||||
#include "window_placement_helper.h"
|
||||
#include "win32_dialog.h"
|
||||
#include "cuesheet_index_list.h"
|
||||
#include "cue_creator.h"
|
||||
#include "cue_parser.h"
|
||||
#include "text_file_loader.h"
|
||||
#include "file_list_helper.h"
|
||||
#include "stream_buffer_helper.h"
|
||||
#include "file_info_const_impl.h"
|
||||
#include "dynamic_bitrate_helper.h"
|
||||
#include "cfg_guidlist.h"
|
||||
#include "file_move_helper.h"
|
||||
#include "file_cached.h"
|
||||
#include "seekabilizer.h"
|
||||
#include "bitreader_helper.h"
|
||||
#include "mp3_utils.h"
|
||||
#include "win32_misc.h"
|
||||
#include "fb2k_threads.h"
|
||||
#include "COM_utils.h"
|
||||
#include "metadb_io_hintlist.h"
|
||||
#include "meta_table_builder.h"
|
||||
#include "icon_remapping_wildcard.h"
|
||||
#include "CallForwarder.h"
|
||||
#include "playlist_position_reference_tracker.h"
|
||||
#include "ThreadUtils.h"
|
||||
#include "ProcessUtils.h"
|
||||
#include "VisUtils.h"
|
||||
#include "filetimetools.h"
|
||||
#include "ProfileCache.h"
|
||||
#include "file_win32_wrapper.h"
|
||||
#include "fullFileBuffer.h"
|
||||
#include "writer_wav.h"
|
||||
#include "readers.h"
|
||||
15
foobar2000/helpers/icon_remapping_wildcard.h
Normal file
15
foobar2000/helpers/icon_remapping_wildcard.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
class icon_remapping_wildcard_impl : public icon_remapping {
|
||||
public:
|
||||
icon_remapping_wildcard_impl(const char * p_pattern,const char * p_iconname) : m_pattern(p_pattern), m_iconname(p_iconname) {}
|
||||
bool query(const char * p_extension,pfc::string_base & p_iconname) {
|
||||
if (wildcard_helper::test(p_extension,m_pattern,true)) {
|
||||
p_iconname = m_iconname; return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
private:
|
||||
pfc::string8 m_pattern,m_iconname;
|
||||
};
|
||||
88
foobar2000/helpers/image_load_save.cpp
Normal file
88
foobar2000/helpers/image_load_save.cpp
Normal file
@@ -0,0 +1,88 @@
|
||||
#include "StdAfx.h"
|
||||
#include "image_load_save.h"
|
||||
#include <memory>
|
||||
#include "../SDK/imageLoaderLite.h"
|
||||
|
||||
namespace fb2k {
|
||||
bool imageSaveDialog(album_art_data_ptr content, HWND wndParent, const char* initDir, bool bAsync) {
|
||||
pfc::string8 fileTypes = "All files|*.*";
|
||||
pfc::string8 ext;
|
||||
|
||||
try {
|
||||
auto info = fb2k::imageLoaderLite::get()->getInfo(content);
|
||||
if (info.formatName) {
|
||||
pfc::string8 nameCapitalized = pfc::stringToUpper( info.formatName );
|
||||
ext = pfc::stringToLower( info.formatName );
|
||||
if (nameCapitalized == "WEBP") nameCapitalized = "WebP";
|
||||
pfc::string8 extmask;
|
||||
if (ext == "jpeg") {
|
||||
ext = "jpg";
|
||||
extmask = "*.jpg;*.jpeg";
|
||||
} else {
|
||||
extmask << "*." << ext;
|
||||
}
|
||||
fileTypes.reset();
|
||||
fileTypes << nameCapitalized << " files|" << extmask;
|
||||
}
|
||||
} catch (...) {}
|
||||
pfc::string8 fn;
|
||||
|
||||
if (!uGetOpenFileName(wndParent, fileTypes, 0, ext.length() > 0 ? ext.c_str() : nullptr, "Export picture file", initDir, fn, TRUE)) return false;
|
||||
|
||||
auto bErrord = std::make_shared<bool>(false);
|
||||
auto work = [content, fn, bErrord] {
|
||||
try {
|
||||
auto f = fileOpenWriteNew(fn, fb2k::noAbort, 0.5);
|
||||
f->write(content->get_ptr(), content->get_size(), fb2k::noAbort);
|
||||
} catch(std::exception const & e) {
|
||||
* bErrord = true;
|
||||
pfc::string8 msg;
|
||||
msg << "Image file could not be written: " << e;
|
||||
fb2k::inMainThread([msg] {
|
||||
popup_message::g_show(msg, "Information");
|
||||
});
|
||||
}
|
||||
};
|
||||
if (bAsync) {
|
||||
fb2k::splitTask(work);
|
||||
return true;
|
||||
} else {
|
||||
work();
|
||||
return ! *bErrord;
|
||||
}
|
||||
}
|
||||
|
||||
bool imageLoadDialog(pfc::string_base& outFN, HWND wndParent, const char* initDir) {
|
||||
return !!uGetOpenFileName(wndParent, FB2K_GETOPENFILENAME_PICTUREFILES_ALL, 0, nullptr, "Import picture file", initDir, outFN, FALSE);
|
||||
}
|
||||
album_art_data::ptr imageLoadDialog(HWND wndParent, const char* initDir) {
|
||||
album_art_data::ptr ret;
|
||||
pfc::string8 fn;
|
||||
if (imageLoadDialog(fn, wndParent, initDir)) {
|
||||
try {
|
||||
ret = readPictureFile(fn, fb2k::noAbort);
|
||||
} catch (std::exception const& e) {
|
||||
popup_message::g_show(PFC_string_formatter() << "Image file could not be read: " << e, "Information");
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
album_art_data_ptr readPictureFile(const char* p_path, abort_callback& p_abort) {
|
||||
PFC_ASSERT(p_path != nullptr);
|
||||
PFC_ASSERT(*p_path != 0);
|
||||
p_abort.check();
|
||||
|
||||
// Pointless, not a media file, path often from openfiledialog and not canonical
|
||||
// file_lock_ptr lock = file_lock_manager::get()->acquire_read(p_path, p_abort);
|
||||
|
||||
file_ptr l_file;
|
||||
filesystem::g_open_timeout(l_file, p_path, filesystem::open_mode_read, 0.5, p_abort);
|
||||
service_ptr_t<album_art_data_impl> instance = new service_impl_t<album_art_data_impl>();
|
||||
t_filesize size = l_file->get_size_ex(p_abort);
|
||||
if (size > 1024 * 1024 * 64) throw std::runtime_error("File too large");
|
||||
instance->from_stream(l_file.get_ptr(), (t_size)size, p_abort);
|
||||
return instance;
|
||||
}
|
||||
|
||||
}
|
||||
13
foobar2000/helpers/image_load_save.h
Normal file
13
foobar2000/helpers/image_load_save.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
namespace fb2k {
|
||||
bool imageLoadDialog( pfc::string_base & outFN, HWND wndParent, const char * initDir );
|
||||
|
||||
album_art_data::ptr imageLoadDialog( HWND wndParent, const char * initDir );
|
||||
|
||||
//! bAllowAsync: run file writing offthread. In such case the caller will not be made aware if writing failed. \n
|
||||
//! Error popup is shown if actual file writing fails.
|
||||
bool imageSaveDialog(album_art_data_ptr content, HWND wndParent, const char * initDir , bool bAllowAsync = true );
|
||||
|
||||
album_art_data::ptr readPictureFile( const char * path, abort_callback & a);
|
||||
}
|
||||
25
foobar2000/helpers/inplace_edit.cpp
Normal file
25
foobar2000/helpers/inplace_edit.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include "stdafx.h"
|
||||
#include "inplace_edit.h"
|
||||
|
||||
// Functionality moved to libPPUI
|
||||
|
||||
namespace InPlaceEdit {
|
||||
static reply_t wrapCN( completion_notify::ptr cn ) {
|
||||
return [cn](unsigned code) { cn->on_completion(code); };
|
||||
}
|
||||
HWND Start(HWND p_parentwnd, const RECT & p_rect, bool p_multiline, pfc::rcptr_t<pfc::string_base> p_content, completion_notify_ptr p_notify) {
|
||||
return Start(p_parentwnd, p_rect, p_multiline, p_content, wrapCN(p_notify) );
|
||||
}
|
||||
|
||||
HWND StartEx(HWND p_parentwnd, const RECT & p_rect, unsigned p_flags, pfc::rcptr_t<pfc::string_base> p_content, completion_notify_ptr p_notify, IUnknown * ACData, DWORD ACOpts ) {
|
||||
return StartEx(p_parentwnd, p_rect, p_flags, p_content, wrapCN(p_notify), ACData, ACOpts );
|
||||
}
|
||||
|
||||
void Start_FromListView(HWND p_listview, unsigned p_item, unsigned p_subitem, unsigned p_linecount, pfc::rcptr_t<pfc::string_base> p_content, completion_notify_ptr p_notify) {
|
||||
Start_FromListView(p_listview,p_item, p_subitem, p_linecount, p_content, wrapCN(p_notify) );
|
||||
}
|
||||
void Start_FromListViewEx(HWND p_listview, unsigned p_item, unsigned p_subitem, unsigned p_linecount, unsigned p_flags, pfc::rcptr_t<pfc::string_base> p_content, completion_notify_ptr p_notify) {
|
||||
Start_FromListViewEx(p_listview, p_item, p_subitem, p_linecount, p_flags, p_content, wrapCN(p_notify) );
|
||||
}
|
||||
|
||||
}
|
||||
13
foobar2000/helpers/inplace_edit.h
Normal file
13
foobar2000/helpers/inplace_edit.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#pragma once
|
||||
|
||||
#include <libPPUI/InPlaceEdit.h>
|
||||
|
||||
namespace InPlaceEdit {
|
||||
|
||||
HWND Start(HWND p_parentwnd,const RECT & p_rect,bool p_multiline,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify);
|
||||
|
||||
HWND StartEx(HWND p_parentwnd,const RECT & p_rect,unsigned p_flags,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify, IUnknown * ACData = NULL, DWORD ACOpts = 0);
|
||||
|
||||
void Start_FromListView(HWND p_listview,unsigned p_item,unsigned p_subitem,unsigned p_linecount,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify);
|
||||
void Start_FromListViewEx(HWND p_listview,unsigned p_item,unsigned p_subitem,unsigned p_linecount,unsigned p_flags,pfc::rcptr_t<pfc::string_base> p_content,completion_notify_ptr p_notify);
|
||||
}
|
||||
65
foobar2000/helpers/input_fix_seeking.h
Normal file
65
foobar2000/helpers/input_fix_seeking.h
Normal file
@@ -0,0 +1,65 @@
|
||||
#pragma once
|
||||
|
||||
#include "duration_counter.h"
|
||||
|
||||
// Wrapper to implement input_flag_allow_inaccurate_seeking semantics with decoders that do not implement seeking properly.
|
||||
// If input_flag_allow_inaccurate_seeking is not specified, brute force seeking is used.
|
||||
template<typename base_t>
|
||||
class input_fix_seeking : public base_t {
|
||||
public:
|
||||
input_fix_seeking() : m_active() {}
|
||||
|
||||
void decode_initialize(unsigned p_flags,abort_callback & p_abort) {
|
||||
base_t::decode_initialize( p_flags, p_abort );
|
||||
m_active = ( p_flags & input_flag_allow_inaccurate_seeking ) == 0;
|
||||
m_position = 0;
|
||||
m_decodeFrom = 0;
|
||||
}
|
||||
|
||||
void decode_initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort) {
|
||||
base_t::decode_initialize( p_subsong, p_flags, p_abort );
|
||||
m_active = ( p_flags & input_flag_allow_inaccurate_seeking ) == 0;
|
||||
m_position = 0;
|
||||
}
|
||||
|
||||
bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort) {
|
||||
if ( ! m_active ) {
|
||||
return base_t::decode_run ( p_chunk, p_abort );
|
||||
}
|
||||
for ( ;; ) {
|
||||
p_abort.check();
|
||||
if (! base_t::decode_run( p_chunk, p_abort ) ) return false;
|
||||
|
||||
double p = m_position.query();
|
||||
m_position += p_chunk;
|
||||
if ( m_decodeFrom > p ) {
|
||||
auto skip = audio_math::time_to_samples( m_decodeFrom - p, p_chunk.get_sample_rate() );
|
||||
if ( skip >= p_chunk.get_sample_count() ) continue;
|
||||
if ( skip > 0 ) {
|
||||
p_chunk.skip_first_samples( (size_t) skip );
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void decode_seek(double p_seconds,abort_callback & p_abort) {
|
||||
if ( m_active ) {
|
||||
if ( ! this->decode_can_seek() ) throw exception_io_object_not_seekable();
|
||||
m_decodeFrom = p_seconds;
|
||||
if ( m_decodeFrom < m_position.query() ) {
|
||||
base_t::decode_seek(0, p_abort); m_position.reset();
|
||||
}
|
||||
} else {
|
||||
base_t::decode_seek(p_seconds, p_abort);
|
||||
}
|
||||
}
|
||||
|
||||
bool decode_run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort) {
|
||||
throw pfc::exception_not_implemented(); // shitformats that need this class are not likely to care
|
||||
}
|
||||
private:
|
||||
bool m_active;
|
||||
duration_counter m_position;
|
||||
double m_decodeFrom;
|
||||
};
|
||||
221
foobar2000/helpers/input_helper_cue.cpp
Normal file
221
foobar2000/helpers/input_helper_cue.cpp
Normal file
@@ -0,0 +1,221 @@
|
||||
#include "stdafx.h"
|
||||
#include "input_helper_cue.h"
|
||||
#include "../SDK/mem_block_container.h"
|
||||
|
||||
|
||||
namespace {
|
||||
class input_dec_binary : public input_decoder_v2 {
|
||||
enum {
|
||||
m_rate = 44100,
|
||||
m_bps = 16,
|
||||
m_channels = 2,
|
||||
m_channelMask = audio_chunk::channel_config_stereo,
|
||||
m_sampleBytes = (m_bps/8)*m_channels,
|
||||
m_readAtOnce = 588,
|
||||
m_readAtOnceBytes = m_readAtOnce * m_sampleBytes
|
||||
};
|
||||
public:
|
||||
input_dec_binary( file::ptr f ) : m_file(f) {}
|
||||
t_uint32 get_subsong_count() override {return 0;}
|
||||
t_uint32 get_subsong(t_uint32 p_index) override {return 0;}
|
||||
|
||||
void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) override {
|
||||
p_info.reset();
|
||||
p_info.info_set_int("samplerate", m_rate);
|
||||
p_info.info_set_int("channels", m_channels);
|
||||
p_info.info_set_int("bitspersample", m_bps);
|
||||
p_info.info_set("encoding","lossless");
|
||||
p_info.info_set_bitrate((m_bps * m_channels * m_rate + 500 /* rounding for bps to kbps*/ ) / 1000 /* bps to kbps */);
|
||||
p_info.info_set("codec", "PCM");
|
||||
|
||||
try {
|
||||
auto stats = get_file_stats(p_abort);
|
||||
if ( stats.m_size != filesize_invalid ) {
|
||||
p_info.set_length( audio_math::samples_to_time( stats.m_size / 4, 44100 ) );
|
||||
}
|
||||
} catch(exception_io) {}
|
||||
}
|
||||
|
||||
|
||||
t_filestats get_file_stats(abort_callback & p_abort) override {
|
||||
return m_file->get_stats(p_abort);
|
||||
}
|
||||
void initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort) override {
|
||||
m_file->reopen( p_abort );
|
||||
}
|
||||
bool run(audio_chunk & p_chunk,abort_callback & p_abort) override {
|
||||
mem_block_container_impl stfu;
|
||||
return run_raw(p_chunk, stfu, p_abort);
|
||||
}
|
||||
bool run_raw(audio_chunk & out, mem_block_container & outRaw, abort_callback & abort) override {
|
||||
size_t bytes = m_readAtOnceBytes;
|
||||
outRaw.set_size( bytes );
|
||||
size_t got = m_file->read(outRaw.get_ptr(), bytes, abort);
|
||||
got -= got % m_sampleBytes;
|
||||
if ( got == 0 ) return false;
|
||||
if ( got < bytes ) outRaw.set_size( got );
|
||||
out.set_data_fixedpoint_signed( outRaw.get_ptr(), got, m_rate, m_channels, m_bps, m_channelMask);
|
||||
return true;
|
||||
}
|
||||
void seek(double p_seconds,abort_callback & p_abort) override {
|
||||
m_file->seek( audio_math::time_to_samples( p_seconds, m_rate ) * m_sampleBytes, p_abort );
|
||||
}
|
||||
bool can_seek() override {
|
||||
return m_file->can_seek();
|
||||
}
|
||||
bool get_dynamic_info(file_info & p_out, double & p_timestamp_delta) override {return false;}
|
||||
bool get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) override {return false;}
|
||||
void on_idle(abort_callback & p_abort) override {}
|
||||
void set_logger(event_logger::ptr ptr) override {}
|
||||
private:
|
||||
const file::ptr m_file;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
void input_helper_cue::get_info_binary( const char * path, file_info & out, abort_callback & abort ) {
|
||||
auto f = fileOpenReadExisting( path, abort );
|
||||
auto obj = fb2k::service_new< input_dec_binary > ( f );
|
||||
obj->get_info( 0, out, abort );
|
||||
}
|
||||
|
||||
void input_helper_cue::open(service_ptr_t<file> p_filehint,const playable_location & p_location,unsigned p_flags,abort_callback & p_abort,double p_start,double p_length, bool binary) {
|
||||
p_abort.check();
|
||||
|
||||
m_start = p_start;
|
||||
m_position = 0;
|
||||
m_dynamic_info_trigger = false;
|
||||
m_dynamic_info_track_trigger = false;
|
||||
|
||||
if ( binary ) {
|
||||
{
|
||||
const char * path = p_location.get_path();
|
||||
auto f = fileOpenReadExisting( path, p_abort );
|
||||
auto obj = fb2k::service_new< input_dec_binary > ( f );
|
||||
|
||||
m_input.attach( obj, path );
|
||||
m_input.open_decoding( 0, p_flags, p_abort );
|
||||
}
|
||||
} else {
|
||||
m_input.open(p_filehint,p_location,p_flags,p_abort,true,true);
|
||||
}
|
||||
|
||||
|
||||
if (!m_input.can_seek()) throw exception_io_object_not_seekable();
|
||||
|
||||
if (m_start > 0) {
|
||||
m_input.seek(m_start,p_abort);
|
||||
}
|
||||
|
||||
if (p_length > 0) {
|
||||
m_length = p_length;
|
||||
} else {
|
||||
file_info_impl temp;
|
||||
m_input.get_info(0,temp,p_abort);
|
||||
double ref_length = temp.get_length();
|
||||
if (ref_length <= 0) throw exception_io_data();
|
||||
m_length = ref_length - m_start + p_length /* negative or zero */;
|
||||
if (m_length <= 0) throw exception_io_data();
|
||||
}
|
||||
}
|
||||
|
||||
void input_helper_cue::close() {m_input.close();}
|
||||
bool input_helper_cue::is_open() {return m_input.is_open();}
|
||||
|
||||
bool input_helper_cue::_m_input_run(audio_chunk & p_chunk, mem_block_container * p_raw, abort_callback & p_abort) {
|
||||
if (p_raw == NULL) {
|
||||
return m_input.run(p_chunk, p_abort);
|
||||
} else {
|
||||
return m_input.run_raw(p_chunk, *p_raw, p_abort);
|
||||
}
|
||||
}
|
||||
bool input_helper_cue::_run(audio_chunk & p_chunk, mem_block_container * p_raw, abort_callback & p_abort) {
|
||||
p_abort.check();
|
||||
|
||||
if (m_length > 0) {
|
||||
if (m_position >= m_length) return false;
|
||||
|
||||
if (!_m_input_run(p_chunk, p_raw, p_abort)) return false;
|
||||
|
||||
m_dynamic_info_trigger = true;
|
||||
m_dynamic_info_track_trigger = true;
|
||||
|
||||
t_uint64 max = (t_uint64)audio_math::time_to_samples(m_length - m_position, p_chunk.get_sample_rate());
|
||||
if (max == 0)
|
||||
{//handle rounding accidents, this normally shouldn't trigger
|
||||
m_position = m_length;
|
||||
return false;
|
||||
}
|
||||
|
||||
t_size samples = p_chunk.get_sample_count();
|
||||
if ((t_uint64)samples > max)
|
||||
{
|
||||
p_chunk.set_sample_count((unsigned)max);
|
||||
if (p_raw != NULL) {
|
||||
const t_size rawSize = p_raw->get_size();
|
||||
PFC_ASSERT(rawSize % samples == 0);
|
||||
p_raw->set_size((t_size)((t_uint64)rawSize * max / samples));
|
||||
}
|
||||
m_position = m_length;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_position += p_chunk.get_duration();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!_m_input_run(p_chunk, p_raw, p_abort)) return false;
|
||||
m_position += p_chunk.get_duration();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
bool input_helper_cue::run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort) {
|
||||
return _run(p_chunk, &p_raw, p_abort);
|
||||
}
|
||||
|
||||
bool input_helper_cue::run(audio_chunk & p_chunk, abort_callback & p_abort) {
|
||||
return _run(p_chunk, NULL, p_abort);
|
||||
}
|
||||
|
||||
void input_helper_cue::seek(double p_seconds, abort_callback & p_abort)
|
||||
{
|
||||
m_dynamic_info_trigger = false;
|
||||
m_dynamic_info_track_trigger = false;
|
||||
if (m_length <= 0 || p_seconds < m_length) {
|
||||
m_input.seek(p_seconds + m_start, p_abort);
|
||||
m_position = p_seconds;
|
||||
}
|
||||
else {
|
||||
m_position = m_length;
|
||||
}
|
||||
}
|
||||
|
||||
bool input_helper_cue::can_seek() { return true; }
|
||||
|
||||
void input_helper_cue::on_idle(abort_callback & p_abort) { m_input.on_idle(p_abort); }
|
||||
|
||||
bool input_helper_cue::get_dynamic_info(file_info & p_out, double & p_timestamp_delta) {
|
||||
if (m_dynamic_info_trigger) {
|
||||
m_dynamic_info_trigger = false;
|
||||
return m_input.get_dynamic_info(p_out, p_timestamp_delta);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool input_helper_cue::get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) {
|
||||
if (m_dynamic_info_track_trigger) {
|
||||
m_dynamic_info_track_trigger = false;
|
||||
return m_input.get_dynamic_info_track(p_out, p_timestamp_delta);
|
||||
}
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const char * input_helper_cue::get_path() const { return m_input.get_path(); }
|
||||
|
||||
void input_helper_cue::get_info(t_uint32 p_subsong, file_info & p_info, abort_callback & p_abort) { m_input.get_info(p_subsong, p_info, p_abort); }
|
||||
33
foobar2000/helpers/input_helper_cue.h
Normal file
33
foobar2000/helpers/input_helper_cue.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
#include "input_helpers.h"
|
||||
|
||||
|
||||
class input_helper_cue {
|
||||
public:
|
||||
void open(service_ptr_t<file> p_filehint,const playable_location & p_location,unsigned p_flags,abort_callback & p_abort,double p_start,double p_length, bool binary = false);
|
||||
static void get_info_binary( const char * path, file_info & out, abort_callback & abort );
|
||||
|
||||
void close();
|
||||
bool is_open();
|
||||
bool run(audio_chunk & p_chunk,abort_callback & p_abort);
|
||||
bool run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort);
|
||||
void seek(double seconds,abort_callback & p_abort);
|
||||
bool can_seek();
|
||||
void on_idle(abort_callback & p_abort);
|
||||
bool get_dynamic_info(file_info & p_out,double & p_timestamp_delta);
|
||||
bool get_dynamic_info_track(file_info & p_out,double & p_timestamp_delta);
|
||||
void set_logger(event_logger::ptr ptr) {m_input.set_logger(ptr);}
|
||||
|
||||
const char * get_path() const;
|
||||
|
||||
void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort);
|
||||
|
||||
private:
|
||||
bool _run(audio_chunk & p_chunk, mem_block_container * p_raw, abort_callback & p_abort);
|
||||
bool _m_input_run(audio_chunk & p_chunk, mem_block_container * p_raw, abort_callback & p_abort);
|
||||
input_helper m_input;
|
||||
double m_start,m_length,m_position;
|
||||
bool m_dynamic_info_trigger,m_dynamic_info_track_trigger;
|
||||
bool m_binary;
|
||||
};
|
||||
561
foobar2000/helpers/input_helpers.cpp
Normal file
561
foobar2000/helpers/input_helpers.cpp
Normal file
@@ -0,0 +1,561 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "input_helpers.h"
|
||||
#include "fullFileBuffer.h"
|
||||
#include "file_list_helper.h"
|
||||
#include "fileReadAhead.h"
|
||||
|
||||
|
||||
input_helper::ioFilter_t input_helper::ioFilter_full_buffer(t_filesize val ) {
|
||||
if (val == 0) return nullptr;
|
||||
return [val] ( file_ptr & f, const char * path, abort_callback & aborter) {
|
||||
if (!filesystem::g_is_remote_or_unrecognized(path)) {
|
||||
fullFileBuffer a;
|
||||
f = a.open(path, aborter, f, val);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
input_helper::ioFilter_t input_helper::ioFilter_block_buffer(size_t arg) {
|
||||
if (arg == 0) return nullptr;
|
||||
|
||||
return [arg](file_ptr & p_file, const char * p_path, abort_callback & p_abort) {
|
||||
if (!filesystem::g_is_remote_or_unrecognized(p_path)) {
|
||||
if (p_file.is_empty()) filesystem::g_open_read(p_file, p_path, p_abort);
|
||||
if (!p_file->is_in_memory() && !p_file->is_remote() && p_file->can_seek()) {
|
||||
file_cached::g_create(p_file, p_file, p_abort, (size_t)arg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
};
|
||||
}
|
||||
|
||||
static input_helper::ioFilter_t makeReadAhead(size_t arg, bool bRemote) {
|
||||
if (arg == 0) return nullptr;
|
||||
|
||||
return [arg, bRemote](file_ptr & p_file, const char * p_path, abort_callback & p_abort) {
|
||||
if (p_file.is_empty()) {
|
||||
filesystem::ptr fs;
|
||||
if (!filesystem::g_get_interface(fs, p_path)) return false;
|
||||
if (bRemote != fs->is_remote(p_path)) return false;
|
||||
fs->open(p_file, p_path, filesystem::open_mode_read, p_abort);
|
||||
} else if (bRemote != p_file->is_remote()) return false;
|
||||
if (p_file->is_in_memory()) return false;
|
||||
p_file = fileCreateReadAhead(p_file, (size_t)arg, p_abort);
|
||||
return true;
|
||||
};
|
||||
}
|
||||
|
||||
input_helper::ioFilter_t input_helper::ioFilter_remote_read_ahead(size_t arg) {
|
||||
return makeReadAhead(arg, true);
|
||||
}
|
||||
|
||||
input_helper::ioFilter_t input_helper::ioFilter_local_read_ahead(size_t arg) {
|
||||
return makeReadAhead(arg, false);
|
||||
}
|
||||
|
||||
void input_helper::open(service_ptr_t<file> p_filehint,metadb_handle_ptr p_location,unsigned p_flags,abort_callback & p_abort,bool p_from_redirect,bool p_skip_hints)
|
||||
{
|
||||
open(p_filehint,p_location->get_location(),p_flags,p_abort,p_from_redirect,p_skip_hints);
|
||||
}
|
||||
|
||||
void input_helper::fileOpenTools(service_ptr_t<file> & p_file,const char * p_path,input_helper::ioFilters_t const & filters, abort_callback & p_abort) {
|
||||
for( auto walk = filters.begin(); walk != filters.end(); ++ walk ) {
|
||||
auto f = *walk;
|
||||
if (f) {
|
||||
if (f(p_file, p_path, p_abort)) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool input_helper::need_file_reopen(const char * newPath) const {
|
||||
return m_input.is_empty() || playable_location::path_compare(m_path, newPath) != 0;
|
||||
}
|
||||
|
||||
bool input_helper::open_path(const char * path, abort_callback & abort, decodeOpen_t const & other) {
|
||||
abort.check();
|
||||
|
||||
m_logger = other.m_logger;
|
||||
|
||||
if (!need_file_reopen(path)) {
|
||||
if ( other.m_logger.is_valid() ) {
|
||||
input_decoder_v2::ptr v2;
|
||||
if (m_input->service_query_t(v2)) v2->set_logger(other.m_logger);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
m_input.release();
|
||||
|
||||
service_ptr_t<file> l_file = other.m_hint;
|
||||
fileOpenTools(l_file, path, other.m_ioFilters, abort);
|
||||
|
||||
TRACK_CODE("input_entry::g_open_for_decoding",
|
||||
m_input ^= input_entry::g_open(input_decoder::class_guid, l_file, path, m_logger, abort, other.m_from_redirect );
|
||||
);
|
||||
|
||||
#ifndef FOOBAR2000_MODERN
|
||||
if (!other.m_skip_hints) {
|
||||
try {
|
||||
metadb_io::get()->hint_reader(m_input.get_ptr(), path, abort);
|
||||
}
|
||||
catch (exception_io_data) {
|
||||
//Don't fail to decode when this barfs, might be barfing when reading info from another subsong than the one we're trying to decode etc.
|
||||
m_input.release();
|
||||
if (l_file.is_valid()) l_file->reopen(abort);
|
||||
TRACK_CODE("input_entry::g_open_for_decoding",
|
||||
m_input ^= input_entry::g_open(input_decoder::class_guid, l_file, path, m_logger, abort, other.m_from_redirect);
|
||||
);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
if (other.m_shim) m_input = other.m_shim(m_input, path, abort);
|
||||
|
||||
m_path = path;
|
||||
return true;
|
||||
}
|
||||
|
||||
void input_helper::open_decoding(t_uint32 subsong, t_uint32 flags, abort_callback & p_abort) {
|
||||
TRACK_CODE("input_decoder::initialize", m_input->initialize(subsong, flags, p_abort));
|
||||
}
|
||||
|
||||
void input_helper::open(const playable_location & location, abort_callback & abort, decodeOpen_t const & other) {
|
||||
open_path(location.get_path(), abort, other);
|
||||
|
||||
if (other.m_setSampleRate != 0) {
|
||||
this->extended_param(input_params::set_preferred_sample_rate, other.m_setSampleRate, nullptr, 0);
|
||||
}
|
||||
|
||||
open_decoding(location.get_subsong(), other.m_flags, abort);
|
||||
}
|
||||
|
||||
void input_helper::attach(input_decoder::ptr dec, const char * path) {
|
||||
m_input = dec;
|
||||
m_path = path;
|
||||
}
|
||||
|
||||
void input_helper::open(service_ptr_t<file> p_filehint, const playable_location & p_location, unsigned p_flags, abort_callback & p_abort, bool p_from_redirect, bool p_skip_hints) {
|
||||
decodeOpen_t o;
|
||||
o.m_hint = p_filehint;
|
||||
o.m_flags = p_flags;
|
||||
o.m_from_redirect = p_from_redirect;
|
||||
o.m_skip_hints = p_skip_hints;
|
||||
this->open(p_location, p_abort, o);
|
||||
}
|
||||
|
||||
|
||||
void input_helper::close() {
|
||||
m_input.release();
|
||||
}
|
||||
|
||||
bool input_helper::is_open() {
|
||||
return m_input.is_valid();
|
||||
}
|
||||
|
||||
void input_helper::set_pause(bool state) {
|
||||
input_decoder_v3::ptr v3;
|
||||
if (m_input->service_query_t(v3)) v3->set_pause(state);
|
||||
}
|
||||
bool input_helper::flush_on_pause() {
|
||||
input_decoder_v3::ptr v3;
|
||||
if (m_input->service_query_t(v3)) return v3->flush_on_pause();
|
||||
else return false;
|
||||
}
|
||||
|
||||
|
||||
void input_helper::set_logger(event_logger::ptr ptr) {
|
||||
m_logger = ptr;
|
||||
input_decoder_v2::ptr v2;
|
||||
if (m_input->service_query_t(v2)) v2->set_logger(ptr);
|
||||
}
|
||||
|
||||
bool input_helper::run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort) {
|
||||
input_decoder_v2::ptr v2;
|
||||
if (!m_input->service_query_t(v2)) throw pfc::exception_not_implemented();
|
||||
return v2->run_raw(p_chunk, p_raw, p_abort);
|
||||
}
|
||||
|
||||
bool input_helper::run(audio_chunk & p_chunk, abort_callback & p_abort) {
|
||||
TRACK_CODE("input_decoder::run", return m_input->run(p_chunk, p_abort));
|
||||
}
|
||||
|
||||
void input_helper::seek(double seconds, abort_callback & p_abort) {
|
||||
TRACK_CODE("input_decoder::seek", m_input->seek(seconds, p_abort));
|
||||
}
|
||||
|
||||
bool input_helper::can_seek() {
|
||||
return m_input->can_seek();
|
||||
}
|
||||
|
||||
bool input_helper::query_position( double & val ) {
|
||||
return extended_param(input_params::query_position, 0, &val, sizeof(val) ) != 0;
|
||||
}
|
||||
|
||||
size_t input_helper::extended_param(const GUID & type, size_t arg1, void * arg2, size_t arg2size) {
|
||||
input_decoder_v4::ptr v4;
|
||||
if (v4 &= m_input) {
|
||||
return v4->extended_param(type, arg1, arg2, arg2size);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
input_helper::decodeInfo_t input_helper::decode_info() {
|
||||
decodeInfo_t ret = {};
|
||||
if (m_input.is_valid()) {
|
||||
ret.m_can_seek = can_seek();
|
||||
ret.m_flush_on_pause = flush_on_pause();
|
||||
if (ret.m_can_seek) {
|
||||
ret.m_seeking_expensive = extended_param(input_params::seeking_expensive, 0, nullptr, 0) != 0;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void input_helper::on_idle(abort_callback & p_abort) {
|
||||
if (m_input.is_valid()) {
|
||||
TRACK_CODE("input_decoder::on_idle",m_input->on_idle(p_abort));
|
||||
}
|
||||
}
|
||||
|
||||
bool input_helper::get_dynamic_info(file_info & p_out,double & p_timestamp_delta) {
|
||||
TRACK_CODE("input_decoder::get_dynamic_info",return m_input->get_dynamic_info(p_out,p_timestamp_delta));
|
||||
}
|
||||
|
||||
bool input_helper::get_dynamic_info_track(file_info & p_out,double & p_timestamp_delta) {
|
||||
TRACK_CODE("input_decoder::get_dynamic_info_track",return m_input->get_dynamic_info_track(p_out,p_timestamp_delta));
|
||||
}
|
||||
|
||||
void input_helper::get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) {
|
||||
TRACK_CODE("input_decoder::get_info",m_input->get_info(p_subsong,p_info,p_abort));
|
||||
}
|
||||
|
||||
const char * input_helper::get_path() const {
|
||||
return m_path;
|
||||
}
|
||||
|
||||
|
||||
input_helper::input_helper()
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
void input_helper::g_get_info(const playable_location & p_location,file_info & p_info,abort_callback & p_abort,bool p_from_redirect) {
|
||||
service_ptr_t<input_info_reader> instance;
|
||||
input_entry::g_open_for_info_read(instance,0,p_location.get_path(),p_abort,p_from_redirect);
|
||||
instance->get_info(p_location.get_subsong_index(),p_info,p_abort);
|
||||
}
|
||||
|
||||
void input_helper::g_set_info(const playable_location & p_location,file_info & p_info,abort_callback & p_abort,bool p_from_redirect) {
|
||||
service_ptr_t<input_info_writer> instance;
|
||||
input_entry::g_open_for_info_write(instance,0,p_location.get_path(),p_abort,p_from_redirect);
|
||||
instance->set_info(p_location.get_subsong_index(),p_info,p_abort);
|
||||
instance->commit(p_abort);
|
||||
}
|
||||
|
||||
|
||||
bool dead_item_filter::run(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,bit_array_var & p_mask) {
|
||||
file_list_helper::file_list_from_metadb_handle_list path_list;
|
||||
path_list.init_from_list(p_list);
|
||||
metadb_handle_list valid_handles;
|
||||
auto l_metadb = metadb::get();
|
||||
for(t_size pathidx=0;pathidx<path_list.get_count();pathidx++)
|
||||
{
|
||||
if (is_aborting()) return false;
|
||||
on_progress(pathidx,path_list.get_count());
|
||||
|
||||
const char * path = path_list[pathidx];
|
||||
|
||||
if (filesystem::g_is_remote_safe(path)) {
|
||||
metadb_handle_ptr temp;
|
||||
l_metadb->handle_create(temp,make_playable_location(path,0));
|
||||
valid_handles.add_item(temp);
|
||||
} else {
|
||||
try {
|
||||
service_ptr_t<input_info_reader> reader;
|
||||
|
||||
input_entry::g_open_for_info_read(reader,0,path,*this);
|
||||
t_uint32 count = reader->get_subsong_count();
|
||||
for(t_uint32 n=0;n<count && !is_aborting();n++) {
|
||||
metadb_handle_ptr ptr;
|
||||
t_uint32 index = reader->get_subsong(n);
|
||||
l_metadb->handle_create(ptr,make_playable_location(path,index));
|
||||
valid_handles.add_item(ptr);
|
||||
}
|
||||
} catch(...) {}
|
||||
}
|
||||
}
|
||||
|
||||
if (is_aborting()) return false;
|
||||
|
||||
valid_handles.sort_by_pointer();
|
||||
for(t_size listidx=0;listidx<p_list.get_count();listidx++) {
|
||||
bool dead = valid_handles.bsearch_by_pointer(p_list[listidx]) == ~0;
|
||||
if (dead) FB2K_console_formatter() << "Dead item: " << p_list[listidx];
|
||||
p_mask.set(listidx,dead);
|
||||
}
|
||||
return !is_aborting();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class dead_item_filter_impl_simple : public dead_item_filter
|
||||
{
|
||||
public:
|
||||
inline dead_item_filter_impl_simple(abort_callback & p_abort) : m_abort(p_abort) {}
|
||||
bool is_aborting() const {return m_abort.is_aborting();}
|
||||
abort_callback_event get_abort_event() const {return m_abort.get_abort_event();}
|
||||
void on_progress(t_size p_position,t_size p_total) {}
|
||||
private:
|
||||
abort_callback & m_abort;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
bool input_helper::g_mark_dead(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,bit_array_var & p_mask,abort_callback & p_abort)
|
||||
{
|
||||
dead_item_filter_impl_simple filter(p_abort);
|
||||
return filter.run(p_list,p_mask);
|
||||
}
|
||||
|
||||
void input_info_read_helper::open(const char * p_path,abort_callback & p_abort) {
|
||||
if (m_input.is_empty() || playable_location::path_compare(m_path,p_path) != 0)
|
||||
{
|
||||
TRACK_CODE("input_entry::g_open_for_info_read",input_entry::g_open_for_info_read(m_input,0,p_path,p_abort));
|
||||
|
||||
m_path = p_path;
|
||||
}
|
||||
}
|
||||
|
||||
void input_info_read_helper::get_info(const playable_location & p_location,file_info & p_info,t_filestats & p_stats,abort_callback & p_abort) {
|
||||
open(p_location.get_path(),p_abort);
|
||||
|
||||
TRACK_CODE("input_info_reader::get_file_stats",p_stats = m_input->get_file_stats(p_abort));
|
||||
|
||||
TRACK_CODE("input_info_reader::get_info",m_input->get_info(p_location.get_subsong_index(),p_info,p_abort));
|
||||
}
|
||||
|
||||
void input_info_read_helper::get_info_check(const playable_location & p_location,file_info & p_info,t_filestats & p_stats,bool & p_reloaded,abort_callback & p_abort) {
|
||||
open(p_location.get_path(),p_abort);
|
||||
t_filestats newstats;
|
||||
TRACK_CODE("input_info_reader::get_file_stats",newstats = m_input->get_file_stats(p_abort));
|
||||
if (metadb_handle::g_should_reload(p_stats,newstats,true)) {
|
||||
p_stats = newstats;
|
||||
TRACK_CODE("input_info_reader::get_info",m_input->get_info(p_location.get_subsong_index(),p_info,p_abort));
|
||||
p_reloaded = true;
|
||||
} else {
|
||||
p_reloaded = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
// openAudioData code
|
||||
|
||||
namespace {
|
||||
|
||||
class file_decodedaudio : public file_readonly {
|
||||
public:
|
||||
void init(const playable_location & loc, input_helper::decodeOpen_t const & arg, abort_callback & aborter) {
|
||||
m_length = -1; m_lengthKnown = false;
|
||||
m_subsong = loc.get_subsong();
|
||||
m_decoder ^= input_entry::g_open( input_decoder::class_guid, arg.m_hint, loc.get_path(), arg.m_logger, aborter, arg.m_from_redirect);
|
||||
m_seekable = ( arg.m_flags & input_flag_no_seeking ) == 0 && m_decoder->can_seek();
|
||||
reopenDecoder(aborter);
|
||||
readChunk(aborter, true);
|
||||
}
|
||||
void init(const playable_location & loc, bool bSeekable, file::ptr fileHint, abort_callback & aborter) {
|
||||
m_length = -1; m_lengthKnown = false;
|
||||
m_subsong = loc.get_subsong();
|
||||
input_entry::g_open_for_decoding(m_decoder, fileHint, loc.get_path(), aborter);
|
||||
m_seekable = bSeekable && m_decoder->can_seek();
|
||||
reopenDecoder(aborter);
|
||||
readChunk(aborter, true);
|
||||
}
|
||||
|
||||
void reopen(abort_callback & aborter) {
|
||||
|
||||
// Have valid chunk and it is the first chunk in the stream? Reset read pointers and bail, no need to reopen
|
||||
if (m_chunk.get_sample_count() > 0 && m_chunkBytesPtr == m_currentPosition) {
|
||||
m_currentPosition = 0;
|
||||
m_chunkBytesPtr = 0;
|
||||
m_eof = false;
|
||||
return;
|
||||
}
|
||||
|
||||
reopenDecoder(aborter);
|
||||
readChunk(aborter);
|
||||
}
|
||||
|
||||
bool is_remote() {
|
||||
return false;
|
||||
}
|
||||
#if FOOBAR2000_TARGET_VERSION >= 2000
|
||||
t_filestats get_stats(abort_callback & aborter) {
|
||||
t_filestats fs = m_decoder->get_file_stats(aborter);
|
||||
fs.m_size = get_size(aborter);
|
||||
return fs;
|
||||
}
|
||||
#else
|
||||
t_filetimestamp get_timestamp(abort_callback & p_abort) {
|
||||
return m_decoder->get_file_stats(p_abort).m_timestamp;
|
||||
}
|
||||
#endif
|
||||
void on_idle(abort_callback & p_abort) {
|
||||
m_decoder->on_idle(p_abort);
|
||||
}
|
||||
bool get_content_type(pfc::string_base & p_out) {
|
||||
return false;
|
||||
}
|
||||
bool can_seek() {
|
||||
return m_seekable;
|
||||
}
|
||||
void seek(t_filesize p_position, abort_callback & p_abort) {
|
||||
if (!m_seekable) throw exception_io_object_not_seekable();
|
||||
|
||||
|
||||
{
|
||||
t_filesize chunkBegin = m_currentPosition - m_chunkBytesPtr;
|
||||
t_filesize chunkEnd = chunkBegin + curChunkBytes();
|
||||
if (p_position >= chunkBegin && p_position < chunkEnd) {
|
||||
m_chunkBytesPtr = (size_t)(p_position - chunkBegin);
|
||||
m_currentPosition = p_position;
|
||||
m_eof = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
t_filesize s = get_size(p_abort);
|
||||
if (s != filesize_invalid) {
|
||||
if (p_position > s) throw exception_io_seek_out_of_range();
|
||||
if (p_position == s) {
|
||||
m_chunk.reset();
|
||||
m_chunkBytesPtr = 0;
|
||||
m_currentPosition = p_position;
|
||||
m_eof = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const size_t row = m_spec.chanCount * sizeof(audio_sample);
|
||||
t_filesize samples = p_position / row;
|
||||
const double seekTime = audio_math::samples_to_time(samples, m_spec.sampleRate);
|
||||
m_decoder->seek(seekTime, p_abort);
|
||||
m_eof = false; // do this before readChunk
|
||||
if (!this->readChunk(p_abort)) {
|
||||
throw std::runtime_error( ( PFC_string_formatter() << "Premature EOF in referenced audio file at " << pfc::format_time_ex(seekTime, 6) << " out of " << pfc::format_time_ex(length(p_abort), 6) ).get_ptr() );
|
||||
}
|
||||
m_chunkBytesPtr = p_position % row;
|
||||
if (m_chunkBytesPtr > curChunkBytes()) {
|
||||
// Should not ever happen
|
||||
m_chunkBytesPtr = 0;
|
||||
throw std::runtime_error("Decoder returned invalid data");
|
||||
}
|
||||
|
||||
m_currentPosition = p_position;
|
||||
m_eof = false;
|
||||
}
|
||||
|
||||
t_filesize get_size(abort_callback & aborter) {
|
||||
const double l = length(aborter);
|
||||
if (l <= 0) return filesize_invalid;
|
||||
return audio_math::time_to_samples(l, m_spec.sampleRate) * m_spec.chanCount * sizeof(audio_sample);
|
||||
}
|
||||
t_filesize get_position(abort_callback & p_abort) {
|
||||
return m_currentPosition;
|
||||
}
|
||||
t_size read(void * p_buffer, t_size p_bytes, abort_callback & p_abort) {
|
||||
size_t done = 0;
|
||||
for (;;) {
|
||||
p_abort.check();
|
||||
{
|
||||
const size_t inChunk = curChunkBytes();
|
||||
const size_t inChunkRemaining = inChunk - m_chunkBytesPtr;
|
||||
const size_t delta = pfc::min_t<size_t>(inChunkRemaining, (p_bytes - done));
|
||||
memcpy((uint8_t*)p_buffer + done, (const uint8_t*)m_chunk.get_data() + m_chunkBytesPtr, delta);
|
||||
m_chunkBytesPtr += delta;
|
||||
done += delta;
|
||||
m_currentPosition += delta;
|
||||
|
||||
if (done == p_bytes) break;
|
||||
}
|
||||
if (!readChunk(p_abort)) break;
|
||||
}
|
||||
return done;
|
||||
}
|
||||
audio_chunk::spec_t const & get_spec() const { return m_spec; }
|
||||
private:
|
||||
void reopenDecoder(abort_callback & aborter) {
|
||||
uint32_t flags = input_flag_no_looping;
|
||||
if (!m_seekable) flags |= input_flag_no_seeking;
|
||||
m_decoder->initialize(m_subsong, flags, aborter);
|
||||
m_eof = false;
|
||||
m_currentPosition = 0;
|
||||
}
|
||||
size_t curChunkBytes() const {
|
||||
return m_chunk.get_used_size() * sizeof(audio_sample);
|
||||
}
|
||||
bool readChunk(abort_callback & aborter, bool initial = false) {
|
||||
m_chunkBytesPtr = 0;
|
||||
for (;;) {
|
||||
if (m_eof || !m_decoder->run(m_chunk, aborter)) {
|
||||
if (initial) throw std::runtime_error("Decoder produced no data");
|
||||
m_eof = true;
|
||||
m_chunk.reset();
|
||||
return false;
|
||||
}
|
||||
if (m_chunk.is_valid()) break;
|
||||
}
|
||||
audio_chunk::spec_t spec = m_chunk.get_spec();
|
||||
if (initial) m_spec = spec;
|
||||
else if (m_spec != spec) throw std::runtime_error("Sample format change in mid stream");
|
||||
return true;
|
||||
}
|
||||
double length(abort_callback & aborter) {
|
||||
if (!m_lengthKnown) {
|
||||
file_info_impl temp;
|
||||
m_decoder->get_info(m_subsong, temp, aborter);
|
||||
m_length = temp.get_length();
|
||||
m_lengthKnown = true;
|
||||
}
|
||||
return m_length;
|
||||
}
|
||||
audio_chunk_fast_impl m_chunk;
|
||||
size_t m_chunkBytesPtr;
|
||||
audio_chunk::spec_t m_spec;
|
||||
double m_length;
|
||||
bool m_lengthKnown;
|
||||
bool m_seekable;
|
||||
uint32_t m_subsong;
|
||||
input_decoder::ptr m_decoder;
|
||||
bool m_eof;
|
||||
t_filesize m_currentPosition;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
openAudioData_t openAudioData2(playable_location const & loc, input_helper::decodeOpen_t const & openArg, abort_callback & aborter) {
|
||||
service_ptr_t<file_decodedaudio> f; f = new service_impl_t < file_decodedaudio >;
|
||||
f->init(loc, openArg, aborter);
|
||||
|
||||
openAudioData_t oad = {};
|
||||
oad.audioData = f;
|
||||
oad.audioSpec = f->get_spec();
|
||||
return oad;
|
||||
}
|
||||
|
||||
openAudioData_t openAudioData(playable_location const & loc, bool bSeekable, file::ptr fileHint, abort_callback & aborter) {
|
||||
service_ptr_t<file_decodedaudio> f; f = new service_impl_t < file_decodedaudio > ;
|
||||
f->init(loc, bSeekable, fileHint, aborter);
|
||||
|
||||
openAudioData_t oad = {};
|
||||
oad.audioData = f;
|
||||
oad.audioSpec = f->get_spec();
|
||||
return oad;
|
||||
}
|
||||
130
foobar2000/helpers/input_helpers.h
Normal file
130
foobar2000/helpers/input_helpers.h
Normal file
@@ -0,0 +1,130 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <list>
|
||||
|
||||
class input_helper {
|
||||
public:
|
||||
input_helper();
|
||||
|
||||
typedef std::function<input_decoder::ptr (input_decoder::ptr, const char*, abort_callback&) > shim_t;
|
||||
|
||||
typedef std::function< bool ( file::ptr &, const char *, abort_callback & ) > ioFilter_t;
|
||||
typedef std::list<ioFilter_t> ioFilters_t;
|
||||
|
||||
struct decodeInfo_t {
|
||||
bool m_flush_on_pause;
|
||||
bool m_can_seek;
|
||||
bool m_seeking_expensive;
|
||||
};
|
||||
|
||||
struct decodeOpen_t {
|
||||
bool m_from_redirect = false;
|
||||
bool m_skip_hints = false;
|
||||
unsigned m_flags = 0;
|
||||
file::ptr m_hint;
|
||||
unsigned m_setSampleRate = 0;
|
||||
|
||||
ioFilters_t m_ioFilters;
|
||||
event_logger::ptr m_logger;
|
||||
shim_t m_shim;
|
||||
};
|
||||
|
||||
void open(service_ptr_t<file> p_filehint,metadb_handle_ptr p_location,unsigned p_flags,abort_callback & p_abort,bool p_from_redirect = false,bool p_skip_hints = false);
|
||||
void open(service_ptr_t<file> p_filehint,const playable_location & p_location,unsigned p_flags,abort_callback & p_abort,bool p_from_redirect = false,bool p_skip_hints = false);
|
||||
void attach(input_decoder::ptr dec, const char * path);
|
||||
|
||||
void open(const playable_location & location, abort_callback & abort, decodeOpen_t const & other);
|
||||
void open(metadb_handle_ptr location, abort_callback & abort, decodeOpen_t const & other) {this->open(location->get_location(), abort, other);}
|
||||
|
||||
|
||||
//! Multilevel open helpers.
|
||||
//! @returns Diagnostic/helper value: true if the decoder had to be re-opened entirely, false if the instance was reused.
|
||||
bool open_path(const char * path, abort_callback & abort, decodeOpen_t const & other);
|
||||
//! Multilevel open helpers.
|
||||
void open_decoding(t_uint32 subsong, t_uint32 flags, abort_callback & p_abort);
|
||||
|
||||
bool need_file_reopen(const char * newPath) const;
|
||||
|
||||
decodeInfo_t decode_info();
|
||||
|
||||
void close();
|
||||
bool is_open();
|
||||
bool run(audio_chunk & p_chunk,abort_callback & p_abort);
|
||||
bool run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort);
|
||||
void seek(double seconds,abort_callback & p_abort);
|
||||
bool can_seek();
|
||||
size_t extended_param( const GUID & type, size_t arg1, void * arg2, size_t arg2size);
|
||||
static ioFilter_t ioFilter_full_buffer(t_filesize val );
|
||||
static ioFilter_t ioFilter_block_buffer(size_t val);
|
||||
static ioFilter_t ioFilter_remote_read_ahead( size_t val );
|
||||
static ioFilter_t ioFilter_local_read_ahead(size_t val);
|
||||
|
||||
void on_idle(abort_callback & p_abort);
|
||||
bool get_dynamic_info(file_info & p_out,double & p_timestamp_delta);
|
||||
bool get_dynamic_info_track(file_info & p_out,double & p_timestamp_delta);
|
||||
void set_logger(event_logger::ptr ptr);
|
||||
void set_pause(bool state);
|
||||
bool flush_on_pause();
|
||||
|
||||
//! If this decoder has its own special position reporting, decoder-signaled logical decoding position will be returned. \n
|
||||
//! Otherwise, position calculated from returned audio duration should be assumed. \n
|
||||
//! Very few special-purpose decoders do this.
|
||||
bool query_position( double & val );
|
||||
|
||||
//! Retrieves path of currently open file.
|
||||
const char * get_path() const;
|
||||
|
||||
//! Retrieves info about specific subsong of currently open file.
|
||||
void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort);
|
||||
|
||||
static void g_get_info(const playable_location & p_location,file_info & p_info,abort_callback & p_abort,bool p_from_redirect = false);
|
||||
static void g_set_info(const playable_location & p_location,file_info & p_info,abort_callback & p_abort,bool p_from_redirect = false);
|
||||
|
||||
|
||||
static bool g_mark_dead(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,bit_array_var & p_mask,abort_callback & p_abort);
|
||||
|
||||
private:
|
||||
void fileOpenTools(service_ptr_t<file> & p_file,const char * p_path, ioFilters_t const & filters, abort_callback & p_abort);
|
||||
service_ptr_t<input_decoder> m_input;
|
||||
pfc::string8 m_path;
|
||||
event_logger::ptr m_logger;
|
||||
};
|
||||
|
||||
class NOVTABLE dead_item_filter : public abort_callback {
|
||||
public:
|
||||
virtual void on_progress(t_size p_position,t_size p_total) = 0;
|
||||
|
||||
bool run(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,bit_array_var & p_mask);
|
||||
};
|
||||
|
||||
class input_info_read_helper {
|
||||
public:
|
||||
input_info_read_helper() {}
|
||||
void get_info(const playable_location & p_location,file_info & p_info,t_filestats & p_stats,abort_callback & p_abort);
|
||||
void get_info_check(const playable_location & p_location,file_info & p_info,t_filestats & p_stats,bool & p_reloaded,abort_callback & p_abort);
|
||||
private:
|
||||
void open(const char * p_path,abort_callback & p_abort);
|
||||
|
||||
pfc::string8 m_path;
|
||||
service_ptr_t<input_info_reader> m_input;
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
//! openAudioData return value, see openAudioData()
|
||||
struct openAudioData_t {
|
||||
file::ptr audioData; // audio data stream
|
||||
audio_chunk::spec_t audioSpec; // format description (sample rate, channel layout).
|
||||
};
|
||||
|
||||
//! Opens the specified location as a stream of audio_samples. \n
|
||||
//! Returns a file object that allows you to read the audio data stream as if it was a physical file, together with audio stream format description (sample rate, channel layout). \n
|
||||
//! Please keep in mind that certain features of the returned file object are only as reliable as the underlying file format or decoder implementation allows them to be. \n
|
||||
//! Reported exact file size may be either unavailable or unreliable if the file format does not let us known the exact value without decoding the whole file. \n
|
||||
//! Seeking may be inaccurate with certain file formats. \n
|
||||
//! In general, all file object methods will work as intended on core-supported file formats such as FLAC or WavPack. \n
|
||||
//! However, if you want 100% functionality regardless of file format being worked with, mirror the content to a temp file and let go of the file object returned by openAudioData().
|
||||
openAudioData_t openAudioData(playable_location const & loc, bool bSeekable, file::ptr fileHint, abort_callback & aborter);
|
||||
openAudioData_t openAudioData2(playable_location const & loc, input_helper::decodeOpen_t const & openArg, abort_callback & aborter);
|
||||
35
foobar2000/helpers/input_logging.h
Normal file
35
foobar2000/helpers/input_logging.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
|
||||
class input_logging : public input_stubs {
|
||||
public:
|
||||
input_logging() {
|
||||
set_logger(nullptr);
|
||||
}
|
||||
|
||||
event_logger_recorder::ptr log_record( std::function<void () > f ) {
|
||||
auto rec = event_logger_recorder::create();
|
||||
{
|
||||
pfc::vartoggle_t< event_logger::ptr > toggle( m_logger, rec );
|
||||
f();
|
||||
}
|
||||
return rec;
|
||||
}
|
||||
|
||||
void set_logger( event_logger::ptr logger ) {
|
||||
if ( logger.is_valid() ) {
|
||||
m_haveCustomLogger = true;
|
||||
m_logger = logger;
|
||||
} else {
|
||||
m_haveCustomLogger = false;
|
||||
m_logger = new service_impl_t<event_logger_fallback>();
|
||||
}
|
||||
}
|
||||
protected:
|
||||
event_logger::ptr m_logger;
|
||||
bool m_haveCustomLogger = false;
|
||||
};
|
||||
|
||||
#define FB2K_INPUT_LOG_STATUS(X) FB2K_LOG_STATUS(m_logger, X)
|
||||
#define FB2K_INPUT_LOG_WARNING(X) FB2K_LOG_WARNING(m_logger, X)
|
||||
#define FB2K_INPUT_LOG_ERROR(X) FB2K_LOG_ERROR(m_logger, X)
|
||||
35
foobar2000/helpers/input_stream_info_reader.h
Normal file
35
foobar2000/helpers/input_stream_info_reader.h
Normal file
@@ -0,0 +1,35 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef FOOBAR2000_DESKTOP
|
||||
|
||||
template<typename input_t>
|
||||
class input_stream_info_reader_impl : public input_stream_info_reader {
|
||||
public:
|
||||
input_t theInput;
|
||||
|
||||
uint32_t get_stream_count() {
|
||||
return theInput.get_stream_count();
|
||||
}
|
||||
void get_stream_info(uint32_t index, file_info & out, abort_callback & a) {
|
||||
theInput.get_stream_info(index, out, a);
|
||||
}
|
||||
uint32_t get_default_stream() {
|
||||
return theInput.get_default_stream();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename input_t>
|
||||
class input_stream_info_reader_entry_impl : public input_stream_info_reader_entry {
|
||||
public:
|
||||
input_stream_info_reader::ptr open(const char * path, file::ptr fileHint, abort_callback & abort) {
|
||||
typedef input_stream_info_reader_impl<input_t> obj_t;
|
||||
service_ptr_t<obj_t> p = new service_impl_t<obj_t>();
|
||||
p->theInput.open(fileHint, path, input_open_info_read, abort);
|
||||
return p;
|
||||
}
|
||||
GUID get_guid() {
|
||||
return input_t::g_get_guid();
|
||||
}
|
||||
};
|
||||
|
||||
#endif // FOOBAR2000_DESKTOP
|
||||
143
foobar2000/helpers/meta_table_builder.h
Normal file
143
foobar2000/helpers/meta_table_builder.h
Normal file
@@ -0,0 +1,143 @@
|
||||
#pragma once
|
||||
|
||||
class _meta_table_enum_wrapper {
|
||||
public:
|
||||
_meta_table_enum_wrapper(file_info & p_info) : m_info(p_info) {}
|
||||
template<typename t_values>
|
||||
void operator() (const char * p_name,const t_values & p_values) {
|
||||
t_size index = ~0;
|
||||
for(typename t_values::const_iterator iter = p_values.first(); iter.is_valid(); ++iter) {
|
||||
if (index == ~0) index = m_info.__meta_add_unsafe(p_name,*iter);
|
||||
else m_info.meta_add_value(index,*iter);
|
||||
}
|
||||
}
|
||||
private:
|
||||
file_info & m_info;
|
||||
};
|
||||
|
||||
class _meta_table_enum_wrapper_RG {
|
||||
public:
|
||||
_meta_table_enum_wrapper_RG(file_info & p_info) : m_info(p_info) {}
|
||||
template<typename t_values>
|
||||
void operator() (const char * p_name,const t_values & p_values) {
|
||||
if (p_values.get_count() > 0) {
|
||||
if (!m_info.info_set_replaygain(p_name, *p_values.first())) {
|
||||
t_size index = ~0;
|
||||
for(typename t_values::const_iterator iter = p_values.first(); iter.is_valid(); ++iter) {
|
||||
if (index == ~0) index = m_info.__meta_add_unsafe(p_name,*iter);
|
||||
else m_info.meta_add_value(index,*iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
file_info & m_info;
|
||||
};
|
||||
|
||||
//! Purpose: building a file_info metadata table from loose input without search-for-existing-entry bottleneck
|
||||
class meta_table_builder {
|
||||
public:
|
||||
typedef pfc::chain_list_v2_t<pfc::string8> t_entry;
|
||||
typedef pfc::map_t<pfc::string8,t_entry,file_info::field_name_comparator> t_content;
|
||||
|
||||
t_content & content() {return m_data;}
|
||||
t_content const & content() const {return m_data;}
|
||||
|
||||
void add(const char * p_name,const char * p_value,t_size p_value_len = ~0) {
|
||||
if (file_info::g_is_valid_field_name(p_name)) {
|
||||
_add(p_name).insert_last()->set_string(p_value,p_value_len);
|
||||
}
|
||||
}
|
||||
|
||||
void remove(const char * p_name) {
|
||||
m_data.remove(p_name);
|
||||
}
|
||||
void set(const char * p_name,const char * p_value,t_size p_value_len = ~0) {
|
||||
if (file_info::g_is_valid_field_name(p_name)) {
|
||||
t_entry & entry = _add(p_name);
|
||||
entry.remove_all();
|
||||
entry.insert_last()->set_string(p_value,p_value_len);
|
||||
}
|
||||
}
|
||||
t_entry & add(const char * p_name) {
|
||||
if (!file_info::g_is_valid_field_name(p_name)) uBugCheck();//we return a reference, nothing smarter to do
|
||||
return _add(p_name);
|
||||
}
|
||||
void deduplicate(const char * name) {
|
||||
t_entry * e;
|
||||
if (!m_data.query_ptr(name, e)) return;
|
||||
pfc::avltree_t<const char*, pfc::comparator_strcmp> found;
|
||||
for(t_entry::iterator iter = e->first(); iter.is_valid(); ) {
|
||||
t_entry::iterator next = iter; ++next;
|
||||
const char * v = *iter;
|
||||
if (!found.add_item_check(v)) e->remove(iter);
|
||||
iter = next;
|
||||
}
|
||||
}
|
||||
void keep_one(const char * name) {
|
||||
t_entry * e;
|
||||
if (!m_data.query_ptr(name, e)) return;
|
||||
while(e->get_count() > 1) e->remove(e->last());
|
||||
}
|
||||
void tidy_VorbisComment() {
|
||||
deduplicate("album artist");
|
||||
deduplicate("publisher");
|
||||
keep_one("totaltracks");
|
||||
keep_one("totaldiscs");
|
||||
}
|
||||
void finalize(file_info & p_info) const {
|
||||
p_info.meta_remove_all();
|
||||
_meta_table_enum_wrapper e(p_info);
|
||||
m_data.enumerate(e);
|
||||
}
|
||||
void finalize_withRG(file_info & p_info) const {
|
||||
p_info.meta_remove_all(); p_info.set_replaygain(replaygain_info_invalid);
|
||||
_meta_table_enum_wrapper_RG e(p_info);
|
||||
m_data.enumerate(e);
|
||||
}
|
||||
|
||||
void from_info(const file_info & p_info) {
|
||||
m_data.remove_all();
|
||||
from_info_overwrite(p_info);
|
||||
}
|
||||
void from_info_withRG(const file_info & p_info) {
|
||||
m_data.remove_all();
|
||||
from_info_overwrite(p_info);
|
||||
from_RG_overwrite(p_info.get_replaygain());
|
||||
}
|
||||
void from_RG_overwrite(replaygain_info info) {
|
||||
replaygain_info::t_text_buffer buffer;
|
||||
if (info.format_album_gain(buffer)) set("replaygain_album_gain", buffer);
|
||||
if (info.format_track_gain(buffer)) set("replaygain_track_gain", buffer);
|
||||
if (info.format_album_peak(buffer)) set("replaygain_album_peak", buffer);
|
||||
if (info.format_track_peak(buffer)) set("replaygain_track_peak", buffer);
|
||||
}
|
||||
void from_info_overwrite(const file_info & p_info) {
|
||||
for(t_size metawalk = 0, metacount = p_info.meta_get_count(); metawalk < metacount; ++metawalk ) {
|
||||
const t_size valuecount = p_info.meta_enum_value_count(metawalk);
|
||||
if (valuecount > 0) {
|
||||
t_entry & entry = add(p_info.meta_enum_name(metawalk));
|
||||
entry.remove_all();
|
||||
for(t_size valuewalk = 0; valuewalk < valuecount; ++valuewalk) {
|
||||
entry.insert_last(p_info.meta_enum_value(metawalk,valuewalk));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
void reset() {m_data.remove_all();}
|
||||
|
||||
void fix_itunes_compilation() {
|
||||
static const char cmp[] = "itunescompilation";
|
||||
if (m_data.have_item(cmp)) {
|
||||
// m_data.remove(cmp);
|
||||
if (!m_data.have_item("album artist")) add("album artist", "Various Artists");
|
||||
}
|
||||
}
|
||||
private:
|
||||
|
||||
t_entry & _add(const char * p_name) {
|
||||
return m_data.find_or_add(p_name);
|
||||
}
|
||||
|
||||
t_content m_data;
|
||||
};
|
||||
71
foobar2000/helpers/metadb_handle_set.h
Normal file
71
foobar2000/helpers/metadb_handle_set.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
#include <set>
|
||||
class metadb_handle;
|
||||
|
||||
// Roughly same as pfc::avltree_t<metadb_handle_ptr> or std::set<metadb_handle_ptr>, but optimized for use with large amounts of items
|
||||
class metadb_handle_set {
|
||||
public:
|
||||
metadb_handle_set() {}
|
||||
template<typename ptr_t>
|
||||
bool add_item_check(ptr_t const & item) { return add_item_check_(&*item); }
|
||||
template<typename ptr_t>
|
||||
bool remove_item(ptr_t const & item) { return remove_item_(&*item); }
|
||||
|
||||
bool add_item_check_(metadb_handle * p) {
|
||||
bool rv = m_content.insert(p).second;
|
||||
if (rv) p->service_add_ref();
|
||||
return rv;
|
||||
}
|
||||
bool remove_item_(metadb_handle * p) {
|
||||
bool rv = m_content.erase(p) != 0;
|
||||
if (rv) p->service_release();
|
||||
return rv;
|
||||
}
|
||||
|
||||
size_t get_count() const {
|
||||
return m_content.size();
|
||||
}
|
||||
template<typename ptr_t>
|
||||
bool contains(ptr_t const & item) const {
|
||||
return m_content.count(&*item) != 0;
|
||||
}
|
||||
template<typename ptr_t>
|
||||
bool have_item(ptr_t const & item) const {
|
||||
return m_content.count(&*item) != 0;
|
||||
}
|
||||
void operator+=(metadb_handle::ptr const & item) {
|
||||
add_item_check_(item.get_ptr());
|
||||
}
|
||||
void operator-=(metadb_handle::ptr const & item) {
|
||||
remove_item_(item.get_ptr());
|
||||
}
|
||||
void operator+=(metadb_handle::ptr && item) {
|
||||
auto p = item.detach();
|
||||
bool added = m_content.insert(p).second;
|
||||
if (!added) p->service_release();
|
||||
}
|
||||
void remove_all() {
|
||||
for (auto iter = m_content.begin(); iter != m_content.end(); ++iter) {
|
||||
metadb_handle * p = (*iter);
|
||||
p->service_release();
|
||||
}
|
||||
m_content.clear();
|
||||
}
|
||||
template<typename callback_t>
|
||||
void enumerate(callback_t & cb) const {
|
||||
for (auto iter = m_content.begin(); iter != m_content.end(); ++iter) {
|
||||
cb(*iter);
|
||||
}
|
||||
}
|
||||
typedef std::set<metadb_handle*> impl_t;
|
||||
typedef impl_t::const_iterator const_iterator;
|
||||
const_iterator begin() const { return m_content.begin(); }
|
||||
const_iterator end() const { return m_content.end(); }
|
||||
private:
|
||||
|
||||
std::set<metadb_handle*> m_content;
|
||||
|
||||
private:
|
||||
metadb_handle_set(const metadb_handle_set &) = delete;
|
||||
void operator=(const metadb_handle_set&) = delete;
|
||||
};
|
||||
33
foobar2000/helpers/metadb_io_hintlist.h
Normal file
33
foobar2000/helpers/metadb_io_hintlist.h
Normal file
@@ -0,0 +1,33 @@
|
||||
#pragma once
|
||||
|
||||
// Obsolete, use metadb_hint_list instead when possible, wrapper provided for compatibility with old code
|
||||
|
||||
class metadb_io_hintlist {
|
||||
public:
|
||||
void hint_reader(service_ptr_t<input_info_reader> p_reader, const char * p_path,abort_callback & p_abort) {
|
||||
init();
|
||||
m_hints->add_hint_reader( p_path, p_reader, p_abort );
|
||||
m_pendingCount += p_reader->get_subsong_count();
|
||||
}
|
||||
void add(metadb_handle_ptr const & p_handle,const file_info & p_info,t_filestats const & p_stats,bool p_fresh) {
|
||||
init();
|
||||
m_hints->add_hint( p_handle, p_info, p_stats, p_fresh );
|
||||
++m_pendingCount;
|
||||
}
|
||||
void run() {
|
||||
if ( m_hints.is_valid() ) {
|
||||
m_hints->on_done();
|
||||
m_hints.release();
|
||||
}
|
||||
m_pendingCount = 0;
|
||||
}
|
||||
size_t get_pending_count() const { return m_pendingCount; }
|
||||
private:
|
||||
void init() {
|
||||
if ( m_hints.is_empty() ) {
|
||||
m_hints = metadb_io_v2::get()->create_hint_list();
|
||||
}
|
||||
}
|
||||
metadb_hint_list::ptr m_hints;
|
||||
size_t m_pendingCount = 0;
|
||||
};
|
||||
276
foobar2000/helpers/mp3_utils.cpp
Normal file
276
foobar2000/helpers/mp3_utils.cpp
Normal file
@@ -0,0 +1,276 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "mp3_utils.h"
|
||||
#include "bitreader_helper.h"
|
||||
|
||||
using namespace bitreader_helper;
|
||||
|
||||
static unsigned extract_header_bits(const t_uint8 p_header[4],unsigned p_base,unsigned p_bits)
|
||||
{
|
||||
PFC_ASSERT(p_base+p_bits<=32);
|
||||
return (unsigned) extract_bits(p_header,p_base,p_bits);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class header_parser
|
||||
{
|
||||
public:
|
||||
header_parser(const t_uint8 p_header[4]) : m_bitptr(0)
|
||||
{
|
||||
memcpy(m_header,p_header,4);
|
||||
}
|
||||
unsigned read(unsigned p_bits)
|
||||
{
|
||||
unsigned ret = extract_header_bits(m_header,m_bitptr,p_bits);
|
||||
m_bitptr += p_bits;
|
||||
return ret;
|
||||
}
|
||||
private:
|
||||
t_uint8 m_header[4];
|
||||
unsigned m_bitptr;
|
||||
};
|
||||
}
|
||||
|
||||
typedef t_uint16 uint16;
|
||||
|
||||
static const uint16 bitrate_table_l1v1[16] = { 0, 32, 64, 96,128,160,192,224,256,288,320,352,384,416,448, 0};
|
||||
static const uint16 bitrate_table_l2v1[16] = { 0, 32, 48, 56, 64, 80, 96,112,128,160,192,224,256,320,384, 0};
|
||||
static const uint16 bitrate_table_l3v1[16] = { 0, 32, 40, 48, 56, 64, 80, 96,112,128,160,192,224,256,320, 0};
|
||||
static const uint16 bitrate_table_l1v2[16] = { 0, 32, 48, 56, 64, 80, 96,112,128,144,160,176,192,224,256, 0};
|
||||
static const uint16 bitrate_table_l23v2[16] = { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96,112,128,144,160, 0};
|
||||
static const uint16 sample_rate_table[] = {11025,12000,8000};
|
||||
|
||||
unsigned mp3_utils::QueryMPEGFrameSize(const t_uint8 p_header[4])
|
||||
{
|
||||
TMPEGFrameInfo info;
|
||||
if (!ParseMPEGFrameHeader(info,p_header)) return 0;
|
||||
return info.m_bytes;
|
||||
}
|
||||
|
||||
bool mp3_utils::ParseMPEGFrameHeader(TMPEGFrameInfo & p_info,const t_uint8 p_header[4])
|
||||
{
|
||||
enum {MPEG_LAYER_1 = 3, MPEG_LAYER_2 = 2, MPEG_LAYER_3 = 1};
|
||||
enum {_MPEG_1 = 3, _MPEG_2 = 2, _MPEG_25 = 0};
|
||||
|
||||
header_parser parser(p_header);
|
||||
if (parser.read(11) != 0x7FF) return false;
|
||||
unsigned mpeg_version = parser.read(2);
|
||||
unsigned layer = parser.read(2);
|
||||
unsigned protection = parser.read(1);
|
||||
unsigned bitrate_index = parser.read(4);
|
||||
unsigned sample_rate_index = parser.read(2);
|
||||
if (sample_rate_index == 3) return false;//reserved
|
||||
unsigned paddingbit = parser.read(1);
|
||||
int paddingdelta = 0;
|
||||
parser.read(1);//private
|
||||
unsigned channel_mode = parser.read(2);
|
||||
unsigned channel_mode_ext = parser.read(2);//channel_mode_extension
|
||||
parser.read(1);//copyright
|
||||
parser.read(1);//original
|
||||
parser.read(2);//emphasis
|
||||
|
||||
unsigned bitrate = 0,sample_rate = 0;
|
||||
|
||||
switch(layer)
|
||||
{
|
||||
default:
|
||||
return false;
|
||||
case MPEG_LAYER_3:
|
||||
paddingdelta = paddingbit ? 1 : 0;
|
||||
|
||||
p_info.m_layer = 3;
|
||||
switch(mpeg_version)
|
||||
{
|
||||
case _MPEG_1:
|
||||
p_info.m_duration = 1152;
|
||||
bitrate = bitrate_table_l3v1[bitrate_index];
|
||||
break;
|
||||
case _MPEG_2:
|
||||
case _MPEG_25:
|
||||
p_info.m_duration = 576;
|
||||
bitrate = bitrate_table_l23v2[bitrate_index];
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
break;
|
||||
case MPEG_LAYER_2:
|
||||
paddingdelta = paddingbit ? 1 : 0;
|
||||
p_info.m_duration = 1152;
|
||||
p_info.m_layer = 2;
|
||||
switch(mpeg_version)
|
||||
{
|
||||
case _MPEG_1:
|
||||
bitrate = bitrate_table_l2v1[bitrate_index];
|
||||
break;
|
||||
case _MPEG_2:
|
||||
case _MPEG_25:
|
||||
bitrate = bitrate_table_l23v2[bitrate_index];
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case MPEG_LAYER_1:
|
||||
paddingdelta = paddingbit ? 4 : 0;
|
||||
p_info.m_duration = 384;
|
||||
p_info.m_layer = 1;
|
||||
switch(mpeg_version)
|
||||
{
|
||||
case _MPEG_1:
|
||||
bitrate = bitrate_table_l1v1[bitrate_index];
|
||||
break;
|
||||
case _MPEG_2:
|
||||
case _MPEG_25:
|
||||
bitrate = bitrate_table_l1v2[bitrate_index];
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
if (bitrate == 0) return false;
|
||||
|
||||
sample_rate = sample_rate_table[sample_rate_index];
|
||||
if (sample_rate == 0) return false;
|
||||
switch(mpeg_version)
|
||||
{
|
||||
case _MPEG_1:
|
||||
sample_rate *= 4;
|
||||
p_info.m_mpegversion = MPEG_1;
|
||||
break;
|
||||
case _MPEG_2:
|
||||
sample_rate *= 2;
|
||||
p_info.m_mpegversion = MPEG_2;
|
||||
break;
|
||||
case _MPEG_25:
|
||||
p_info.m_mpegversion = MPEG_25;
|
||||
break;
|
||||
}
|
||||
|
||||
switch(channel_mode)
|
||||
{
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
p_info.m_channels = 2;
|
||||
break;
|
||||
case 3:
|
||||
p_info.m_channels = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
|
||||
p_info.m_channel_mode = channel_mode;
|
||||
p_info.m_channel_mode_ext = channel_mode_ext;
|
||||
|
||||
p_info.m_sample_rate = sample_rate;
|
||||
p_info.m_sample_rate_idx = sample_rate_index;
|
||||
|
||||
p_info.m_bitrate = bitrate;
|
||||
p_info.m_bitrate_idx = bitrate_index;
|
||||
|
||||
p_info.m_bytes = ( bitrate /*kbps*/ * (1000/8) /* kbps-to-bytes*/ * p_info.m_duration /*samples-per-frame*/ ) / sample_rate + paddingdelta;
|
||||
|
||||
if (p_info.m_layer == 1) p_info.m_bytes &= ~3;
|
||||
|
||||
p_info.m_crc = protection == 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
unsigned mp3header::get_samples_per_frame()
|
||||
{
|
||||
mp3_utils::TMPEGFrameInfo fr;
|
||||
if (!decode(fr)) return 0;
|
||||
return fr.m_duration;
|
||||
}
|
||||
|
||||
bool mp3_utils::IsSameStream(TMPEGFrameInfo const & p_frame1,TMPEGFrameInfo const & p_frame2) {
|
||||
return
|
||||
// FFmpeg writes VBR headers with null channel mode...
|
||||
/* p_frame1.m_channel_mode == p_frame2.m_channel_mode && */
|
||||
p_frame1.m_sample_rate == p_frame2.m_sample_rate &&
|
||||
p_frame1.m_layer == p_frame2.m_layer &&
|
||||
p_frame1.m_mpegversion == p_frame2.m_mpegversion;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool mp3_utils::ValidateFrameCRC(const t_uint8 * frameData, t_size frameSize, TMPEGFrameInfo const & info) {
|
||||
if (frameSize < info.m_bytes) return false; //FAIL, incomplete data
|
||||
if (!info.m_crc) return true; //nothing to check, frame appears valid
|
||||
return ExtractFrameCRC(frameData, frameSize, info) == CalculateFrameCRC(frameData, frameSize, info);
|
||||
}
|
||||
|
||||
static t_uint32 CRC_update(unsigned value, t_uint32 crc)
|
||||
{
|
||||
enum { CRC16_POLYNOMIAL = 0x8005 };
|
||||
unsigned i;
|
||||
value <<= 8;
|
||||
for (i = 0; i < 8; i++) {
|
||||
value <<= 1;
|
||||
crc <<= 1;
|
||||
if (((crc ^ value) & 0x10000)) crc ^= CRC16_POLYNOMIAL;
|
||||
}
|
||||
return crc;
|
||||
}
|
||||
|
||||
|
||||
void mp3_utils::RecalculateFrameCRC(t_uint8 * frameData, t_size frameSize, TMPEGFrameInfo const & info) {
|
||||
PFC_ASSERT( frameSize >= info.m_bytes && info.m_crc );
|
||||
|
||||
const t_uint16 crc = CalculateFrameCRC(frameData, frameSize, info);
|
||||
frameData[4] = (t_uint8)(crc >> 8);
|
||||
frameData[5] = (t_uint8)(crc & 0xFF);
|
||||
}
|
||||
|
||||
static t_uint16 grabFrameCRC(const t_uint8 * frameData, t_size sideInfoLen) {
|
||||
t_uint32 crc = 0xffff;
|
||||
crc = CRC_update(frameData[2], crc);
|
||||
crc = CRC_update(frameData[3], crc);
|
||||
for (t_size i = 6; i < sideInfoLen; i++) {
|
||||
crc = CRC_update(frameData[i], crc);
|
||||
}
|
||||
|
||||
return (t_uint32) (crc & 0xFFFF);
|
||||
}
|
||||
|
||||
t_uint16 mp3_utils::ExtractFrameCRC(const t_uint8 * frameData, t_size frameSize, TMPEGFrameInfo const & info) {
|
||||
PFC_ASSERT( frameSize >= info.m_bytes && info.m_crc );
|
||||
|
||||
return ((t_uint16)frameData[4] << 8) | (t_uint16)frameData[5];
|
||||
|
||||
}
|
||||
t_uint16 mp3_utils::CalculateFrameCRC(const t_uint8 * frameData, t_size frameSize, TMPEGFrameInfo const & info) {
|
||||
PFC_ASSERT( frameSize >= info.m_bytes && info.m_crc );
|
||||
|
||||
t_size sideInfoLen = 0;
|
||||
if (info.m_mpegversion == MPEG_1)
|
||||
sideInfoLen = (info.m_channels == 1) ? 4 + 17 : 4 + 32;
|
||||
else
|
||||
sideInfoLen = (info.m_channels == 1) ? 4 + 9 : 4 + 17;
|
||||
|
||||
//CRC
|
||||
sideInfoLen += 2;
|
||||
|
||||
PFC_ASSERT( sideInfoLen <= frameSize );
|
||||
|
||||
return grabFrameCRC(frameData, sideInfoLen);
|
||||
}
|
||||
|
||||
|
||||
bool mp3_utils::ValidateFrameCRC(const t_uint8 * frameData, t_size frameSize) {
|
||||
if (frameSize < 4) return false; //FAIL, not a valid frame
|
||||
TMPEGFrameInfo info;
|
||||
if (!ParseMPEGFrameHeader(info, frameData)) return false; //FAIL, not a valid frame
|
||||
return ValidateFrameCRC(frameData, frameSize, info);
|
||||
}
|
||||
|
||||
|
||||
bool mp3_utils::ParseMPEGFrameHeader(TMPEGFrameInfo & p_info, const void * bytes, size_t bytesAvail) {
|
||||
if (bytesAvail < 4) return false; //FAIL, not a valid frame
|
||||
return ParseMPEGFrameHeader(p_info, reinterpret_cast<const t_uint8*>(bytes));
|
||||
}
|
||||
75
foobar2000/helpers/mp3_utils.h
Normal file
75
foobar2000/helpers/mp3_utils.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
namespace mp3_utils
|
||||
{
|
||||
|
||||
enum {
|
||||
MPG_MD_STEREO=0,
|
||||
MPG_MD_JOINT_STEREO=1,
|
||||
MPG_MD_DUAL_CHANNEL=2,
|
||||
MPG_MD_MONO=3,
|
||||
};
|
||||
|
||||
typedef t_uint8 byte;
|
||||
|
||||
enum {MPEG_1, MPEG_2, MPEG_25};
|
||||
|
||||
struct TMPEGFrameInfo
|
||||
{
|
||||
unsigned m_bytes;
|
||||
unsigned m_bitrate_idx; // original bitrate index value
|
||||
unsigned m_bitrate; // kbps
|
||||
unsigned m_sample_rate_idx; // original samples per second index value
|
||||
unsigned m_sample_rate; // samples per second
|
||||
unsigned m_layer; // 1, 2 or 3
|
||||
unsigned m_mpegversion; // MPEG_1, MPEG_2, MPEG_25
|
||||
unsigned m_channels; // 1 or 2
|
||||
unsigned m_duration; // samples
|
||||
unsigned m_channel_mode; // MPG_MD_*
|
||||
unsigned m_channel_mode_ext;
|
||||
bool m_crc;
|
||||
};
|
||||
|
||||
|
||||
bool ParseMPEGFrameHeader(TMPEGFrameInfo & p_info,const t_uint8 p_header[4]);
|
||||
bool ParseMPEGFrameHeader(TMPEGFrameInfo & p_info, const void * bytes, size_t bytesAvail);
|
||||
bool ValidateFrameCRC(const t_uint8 * frameData, t_size frameSize);
|
||||
bool ValidateFrameCRC(const t_uint8 * frameData, t_size frameSize, TMPEGFrameInfo const & frameInfo);
|
||||
|
||||
//! Assumes valid frame with CRC (frameInfo.m_crc set, frameInfo.m_bytes <= frameSize).
|
||||
t_uint16 ExtractFrameCRC(const t_uint8 * frameData, t_size frameSize, TMPEGFrameInfo const & frameInfo);
|
||||
//! Assumes valid frame with CRC (frameInfo.m_crc set, frameInfo.m_bytes <= frameSize).
|
||||
t_uint16 CalculateFrameCRC(const t_uint8 * frameData, t_size frameSize, TMPEGFrameInfo const & frameInfo);
|
||||
//! Assumes valid frame with CRC (frameInfo.m_crc set, frameInfo.m_bytes <= frameSize).
|
||||
void RecalculateFrameCRC(t_uint8 * frameData, t_size frameSize, TMPEGFrameInfo const & frameInfo);
|
||||
|
||||
unsigned QueryMPEGFrameSize(const t_uint8 p_header[4]);
|
||||
bool IsSameStream(TMPEGFrameInfo const & p_frame1,TMPEGFrameInfo const & p_frame2);
|
||||
};
|
||||
|
||||
class mp3header
|
||||
{
|
||||
t_uint8 bytes[4];
|
||||
public:
|
||||
|
||||
inline void copy(const mp3header & src) {memcpy(bytes,src.bytes,4);}
|
||||
inline void copy_raw(const void * src) {memcpy(bytes,src,4);}
|
||||
|
||||
inline mp3header(const mp3header & src) {copy(src);}
|
||||
inline mp3header() {}
|
||||
|
||||
inline const mp3header & operator=(const mp3header & src) {copy(src); return *this;}
|
||||
|
||||
inline void get_bytes(void * out) {memcpy(out,bytes,4);}
|
||||
inline unsigned get_frame_size() const {return mp3_utils::QueryMPEGFrameSize(bytes);}
|
||||
inline bool decode(mp3_utils::TMPEGFrameInfo & p_out) {return mp3_utils::ParseMPEGFrameHeader(p_out,bytes);}
|
||||
|
||||
unsigned get_samples_per_frame();
|
||||
};
|
||||
|
||||
static inline mp3header mp3header_from_buffer(const void * p_buffer)
|
||||
{
|
||||
mp3header temp;
|
||||
temp.copy_raw(p_buffer);
|
||||
return temp;
|
||||
}
|
||||
247
foobar2000/helpers/packet_decoder_aac_common.cpp
Normal file
247
foobar2000/helpers/packet_decoder_aac_common.cpp
Normal file
@@ -0,0 +1,247 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "packet_decoder_aac_common.h"
|
||||
|
||||
#include "../SDK/filesystem_helper.h"
|
||||
#include "bitreader_helper.h"
|
||||
|
||||
size_t packet_decoder_aac_common::skipADTSHeader( const uint8_t * data,size_t size ) {
|
||||
if ( size < 7 ) throw exception_io_data();
|
||||
PFC_ASSERT( bitreader_helper::extract_int(data, 0, 12) == 0xFFF);
|
||||
if (bitreader_helper::extract_bit(data, 12+1+2)) {
|
||||
return 7; // ABSENT flag
|
||||
}
|
||||
if (size < 9) throw exception_io_data();
|
||||
return 9;
|
||||
}
|
||||
|
||||
pfc::array_t<uint8_t> packet_decoder_aac_common::parseDecoderSetup( const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size) {
|
||||
|
||||
if ( p_owner == owner_ADTS ) {
|
||||
pfc::array_t<uint8_t> ret;
|
||||
ret.resize( 2 );
|
||||
ret[0] = 0; ret[1] = 0;
|
||||
// ret:
|
||||
// 5 bits AOT
|
||||
// 4 bits freqindex
|
||||
// 4 bits channelconfig
|
||||
|
||||
// source:
|
||||
// 12 bits 0xFFF
|
||||
// 4 bits disregard
|
||||
// 2 bits AOT-1 @ 16
|
||||
// 4 bits freqindex @ 18
|
||||
// 1 bit disregard
|
||||
// 3 bits channelconfig @ 23
|
||||
// 26 bits total, 4 bytes minimum
|
||||
|
||||
|
||||
if ( p_param2size < 4 ) throw exception_io_data();
|
||||
const uint8_t * source = (const uint8_t*) p_param2;
|
||||
if ( bitreader_helper::extract_int(source, 0, 12) != 0xFFF ) throw exception_io_data();
|
||||
size_t aot = bitreader_helper::extract_int(source, 16, 2) + 1;
|
||||
if ( aot >= 31 ) throw exception_io_data();
|
||||
size_t freqindex = bitreader_helper::extract_int(source, 18, 4);
|
||||
if ( freqindex > 12 ) throw exception_io_data();
|
||||
size_t channelconfig = bitreader_helper::extract_bits( source, 23, 3);
|
||||
|
||||
bitreader_helper::write_int(ret.get_ptr(), 0, 5, aot);
|
||||
bitreader_helper::write_int(ret.get_ptr(), 5, 4, freqindex);
|
||||
bitreader_helper::write_int(ret.get_ptr(), 9, 4, channelconfig);
|
||||
return ret;
|
||||
} else if ( p_owner == owner_ADIF ) {
|
||||
// bah
|
||||
} else if ( p_owner == owner_MP4 )
|
||||
{
|
||||
if ( p_param1 == 0x40 || p_param1 == 0x66 || p_param1 == 0x67 || p_param1 == 0x68 ) {
|
||||
pfc::array_t<uint8_t> ret;
|
||||
ret.set_data_fromptr( (const uint8_t*) p_param2, p_param2size);
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
else if ( p_owner == owner_matroska )
|
||||
{
|
||||
const matroska_setup * setup = ( const matroska_setup * ) p_param2;
|
||||
if ( p_param2size == sizeof(*setup) )
|
||||
{
|
||||
if ( !strcmp(setup->codec_id, "A_AAC") || !strncmp(setup->codec_id, "A_AAC/", 6) ) {
|
||||
pfc::array_t<uint8_t> ret;
|
||||
ret.set_data_fromptr( (const uint8_t*) setup->codec_private, setup->codec_private_size );
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
throw exception_io_data();
|
||||
}
|
||||
|
||||
#if 0
|
||||
bool packet_decoder_aac_common::parseDecoderSetup(const GUID &p_owner, t_size p_param1, const void *p_param2, t_size p_param2size, const void *&outCodecPrivate, size_t &outCodecPrivateSize) {
|
||||
outCodecPrivate = NULL;
|
||||
outCodecPrivateSize = 0;
|
||||
|
||||
if ( p_owner == owner_ADTS ) { return true; }
|
||||
else if ( p_owner == owner_ADIF ) { return true; }
|
||||
else if ( p_owner == owner_MP4 )
|
||||
{
|
||||
if ( p_param1 == 0x40 || p_param1 == 0x66 || p_param1 == 0x67 || p_param1 == 0x68 ) {
|
||||
outCodecPrivate = p_param2; outCodecPrivateSize = p_param2size;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if ( p_owner == owner_matroska )
|
||||
{
|
||||
const matroska_setup * setup = ( const matroska_setup * ) p_param2;
|
||||
if ( p_param2size == sizeof(*setup) )
|
||||
{
|
||||
if ( !strcmp(setup->codec_id, "A_AAC") || !strncmp(setup->codec_id, "A_AAC/", 6) ) {
|
||||
outCodecPrivate = (const uint8_t *) setup->codec_private;
|
||||
outCodecPrivateSize = setup->codec_private_size;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
||||
#endif
|
||||
|
||||
bool packet_decoder_aac_common::testDecoderSetup( const GUID & p_owner, t_size p_param1, const void * p_param2, t_size p_param2size ) {
|
||||
if ( p_owner == owner_ADTS ) { return true; }
|
||||
else if ( p_owner == owner_ADIF ) { return true; }
|
||||
else if ( p_owner == owner_MP4 )
|
||||
{
|
||||
if ( p_param1 == 0x40 || p_param1 == 0x66 || p_param1 == 0x67 || p_param1 == 0x68 ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if ( p_owner == owner_matroska )
|
||||
{
|
||||
const matroska_setup * setup = ( const matroska_setup * ) p_param2;
|
||||
if ( p_param2size == sizeof(*setup) )
|
||||
{
|
||||
if ( !strcmp(setup->codec_id, "A_AAC") || !strncmp(setup->codec_id, "A_AAC/", 6) ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
class esds_maker : public stream_writer_buffer_simple {
|
||||
public:
|
||||
void write_esds_obj( uint8_t code, const void * data, size_t size, abort_callback & aborter ) {
|
||||
if ( size >= ( 1 << 28 ) ) throw pfc::exception_overflow();
|
||||
write_byte(code, aborter);
|
||||
for ( int i = 3; i >= 0; -- i ) {
|
||||
uint8_t c = (uint8_t)( 0x7F & ( size >> 7 * i ) );
|
||||
if ( i > 0 ) c |= 0x80;
|
||||
write_byte(c, aborter);
|
||||
}
|
||||
write( data, size, aborter );
|
||||
}
|
||||
void write_esds_obj( uint8_t code, esds_maker const & other, abort_callback & aborter ) {
|
||||
write_esds_obj( code, other.m_buffer.get_ptr(), other.m_buffer.get_size(), aborter );
|
||||
}
|
||||
void write_byte( uint8_t byte , abort_callback & aborter ) {
|
||||
write( &byte, 1, aborter );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void packet_decoder_aac_common::make_ESDS( pfc::array_t<uint8_t> & outESDS, const void * inCodecPrivate, size_t inCodecPrivateSize ) {
|
||||
if (inCodecPrivateSize > 1024*1024) throw exception_io_data(); // sanity
|
||||
auto & p_abort = fb2k::noAbort;
|
||||
|
||||
esds_maker esds4;
|
||||
|
||||
const uint8_t crap[] = {0x40, 0x15, 0x00, 0x00, 0x00, 0x00, 0x05, 0x34, 0x08, 0x00, 0x02, 0x3D, 0x55};
|
||||
esds4.write( crap, sizeof(crap), p_abort );
|
||||
|
||||
{
|
||||
esds_maker esds5;
|
||||
esds5.write( inCodecPrivate, inCodecPrivateSize, p_abort );
|
||||
esds4.write_esds_obj(5, esds5, p_abort);
|
||||
}
|
||||
|
||||
|
||||
esds_maker esds3;
|
||||
|
||||
esds3.write_byte( 0, p_abort );
|
||||
esds3.write_byte( 1, p_abort );
|
||||
esds3.write_byte( 0, p_abort );
|
||||
esds3.write_esds_obj(4, esds4, p_abort);
|
||||
|
||||
// esds6 after esds4, but doesn't seem that important
|
||||
|
||||
esds_maker final;
|
||||
final.write_esds_obj(3, esds3, p_abort);
|
||||
outESDS.set_data_fromptr( final.m_buffer.get_ptr(), final.m_buffer.get_size() );
|
||||
|
||||
/*
|
||||
static const uint8_t esdsTemplate[] = {
|
||||
0x03, 0x80, 0x80, 0x80, 0x25, 0x00, 0x01, 0x00, 0x04, 0x80, 0x80, 0x80, 0x17, 0x40, 0x15, 0x00,
|
||||
0x00, 0x00, 0x00, 0x05, 0x34, 0x08, 0x00, 0x02, 0x3D, 0x55, 0x05, 0x80, 0x80, 0x80, 0x05, 0x12,
|
||||
0x30, 0x56, 0xE5, 0x00, 0x06, 0x80, 0x80, 0x80, 0x01, 0x02
|
||||
};
|
||||
*/
|
||||
|
||||
// ESDS: 03 80 80 80 25 00 01 00 04 80 80 80 17 40 15 00 00 00 00 05 34 08 00 02 3D 55 05 80 80 80 05 12 30 56 E5 00 06 80 80 80 01 02
|
||||
// For: 12 30 56 E5 00
|
||||
|
||||
}
|
||||
|
||||
const uint32_t aac_sample_rates[] = {
|
||||
96000, 88200, 64000, 48000, 44100, 32000, 24000, 22050, 16000, 12000, 11025, 8000, 7350
|
||||
};
|
||||
|
||||
static unsigned readSamplingFreq(bitreader_helper::bitreader_limited& r) {
|
||||
unsigned samplingRateIndex = (unsigned)r.read(4);
|
||||
if (samplingRateIndex == 15) {
|
||||
return (unsigned)r.read(24);
|
||||
} else {
|
||||
if (samplingRateIndex >= PFC_TABSIZE(aac_sample_rates)) throw exception_io_data();
|
||||
return aac_sample_rates[samplingRateIndex];
|
||||
}
|
||||
}
|
||||
|
||||
packet_decoder_aac_common::audioSpecificConfig_t packet_decoder_aac_common::parseASC(const void * p_, size_t s) {
|
||||
// Source: https://wiki.multimedia.cx/index.php?title=MPEG-4_Audio
|
||||
bitreader_helper::bitreader_limited r((const uint8_t*)p_, 0, s * 8);
|
||||
|
||||
audioSpecificConfig_t cfg = {};
|
||||
cfg.m_objectType = (unsigned) r.read(5);
|
||||
if (cfg.m_objectType == 31) {
|
||||
cfg.m_objectType = 32 + (unsigned) r.read(6);
|
||||
}
|
||||
|
||||
cfg.m_sampleRate = readSamplingFreq(r);
|
||||
|
||||
cfg.m_channels = (unsigned) r.read( 4 );
|
||||
|
||||
if (cfg.m_objectType == 5 || cfg.m_objectType == 29) {
|
||||
cfg.m_explicitSBR = true;
|
||||
cfg.m_explicitPS = (cfg.m_objectType == 29);
|
||||
cfg.m_sbrRate = readSamplingFreq(r);
|
||||
cfg.m_objectType = (unsigned)r.read(5);
|
||||
}
|
||||
|
||||
switch (cfg.m_objectType) {
|
||||
case 1: case 2: case 3: case 4: case 17: case 23:
|
||||
cfg.m_shortWindow = (r.read(1) != 0);
|
||||
break;
|
||||
}
|
||||
|
||||
return cfg;
|
||||
}
|
||||
|
||||
unsigned packet_decoder_aac_common::get_ASC_object_type(const void * p_, size_t s) {
|
||||
// Source: https://wiki.multimedia.cx/index.php?title=MPEG-4_Audio
|
||||
bitreader_helper::bitreader_limited r((const uint8_t*)p_, 0, s * 8);
|
||||
unsigned objectType = (unsigned) r.read(5);
|
||||
if (objectType == 31) {
|
||||
objectType = 32 + (unsigned) r.read(6);
|
||||
}
|
||||
return objectType;
|
||||
}
|
||||
37
foobar2000/helpers/packet_decoder_aac_common.h
Normal file
37
foobar2000/helpers/packet_decoder_aac_common.h
Normal file
@@ -0,0 +1,37 @@
|
||||
#pragma once
|
||||
#include "../SDK/packet_decoder.h"
|
||||
|
||||
/*
|
||||
Helper code with common AAC packet_decoder functionality. Primarily meant for foo_input_std-internal use.
|
||||
*/
|
||||
|
||||
class packet_decoder_aac_common : public packet_decoder {
|
||||
public:
|
||||
static pfc::array_t<uint8_t> parseDecoderSetup( const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size);
|
||||
static bool testDecoderSetup( const GUID & p_owner, t_size p_param1, const void * p_param2, t_size p_param2size );
|
||||
static size_t skipADTSHeader( const uint8_t * data,size_t size );
|
||||
|
||||
static unsigned get_max_frame_dependency_()
|
||||
{
|
||||
return 2;
|
||||
}
|
||||
static double get_max_frame_dependency_time_()
|
||||
{
|
||||
return 1024.0 / 8000.0;
|
||||
}
|
||||
|
||||
static void make_ESDS( pfc::array_t<uint8_t> & outESDS, const void * inCodecPrivate, size_t inCodecPrivateSize );
|
||||
|
||||
struct audioSpecificConfig_t {
|
||||
unsigned m_objectType;
|
||||
unsigned m_sampleRate;
|
||||
unsigned m_channels;
|
||||
unsigned m_sbrRate;
|
||||
bool m_shortWindow;
|
||||
bool m_explicitSBR, m_explicitPS;
|
||||
};
|
||||
|
||||
static audioSpecificConfig_t parseASC(const void *, size_t);
|
||||
|
||||
static unsigned get_ASC_object_type(const void *, size_t);
|
||||
};
|
||||
44
foobar2000/helpers/packet_decoder_mp3_common.cpp
Normal file
44
foobar2000/helpers/packet_decoder_mp3_common.cpp
Normal file
@@ -0,0 +1,44 @@
|
||||
#include "stdafx.h"
|
||||
#include "packet_decoder_mp3_common.h"
|
||||
#include "mp3_utils.h"
|
||||
|
||||
unsigned packet_decoder_mp3_common::parseDecoderSetup( const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size )
|
||||
{
|
||||
if (p_owner == owner_MP3) return 3;
|
||||
else if (p_owner == owner_MP2) return 2;
|
||||
else if (p_owner == owner_MP1) return 1;
|
||||
else if (p_owner == owner_MP4)
|
||||
{
|
||||
switch(p_param1)
|
||||
{
|
||||
case 0x6B:
|
||||
return 3;
|
||||
break;
|
||||
case 0x69:
|
||||
return 3;
|
||||
break;
|
||||
default:
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
else if (p_owner == owner_matroska)
|
||||
{
|
||||
if (p_param2size==sizeof(matroska_setup))
|
||||
{
|
||||
const matroska_setup * setup = (const matroska_setup*) p_param2;
|
||||
if (!strcmp(setup->codec_id,"A_MPEG/L3")) return 3;
|
||||
else if (!strcmp(setup->codec_id,"A_MPEG/L2")) return 2;
|
||||
else if (!strcmp(setup->codec_id,"A_MPEG/L1")) return 1;
|
||||
else return 0;
|
||||
}
|
||||
else return 0;
|
||||
}
|
||||
else return 0;
|
||||
}
|
||||
|
||||
unsigned packet_decoder_mp3_common::layer_from_frame(const void * frame, size_t size) {
|
||||
using namespace mp3_utils;
|
||||
TMPEGFrameInfo info;
|
||||
if (!ParseMPEGFrameHeader(info, frame, size)) throw exception_io_data();
|
||||
return info.m_layer;
|
||||
}
|
||||
84
foobar2000/helpers/packet_decoder_mp3_common.h
Normal file
84
foobar2000/helpers/packet_decoder_mp3_common.h
Normal file
@@ -0,0 +1,84 @@
|
||||
#pragma once
|
||||
|
||||
/*
|
||||
Helper code with common MP3 packet_decoder functionality. Primarily meant for foo_input_std-internal use.
|
||||
*/
|
||||
|
||||
class packet_decoder_mp3_common : public packet_decoder {
|
||||
public:
|
||||
static unsigned parseDecoderSetup( const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size );
|
||||
|
||||
static unsigned layer_from_frame(const void *, size_t);
|
||||
|
||||
static unsigned get_max_frame_dependency_() {return 10;}
|
||||
static double get_max_frame_dependency_time_() {return 10.0 * 1152.0 / 32000.0;}
|
||||
};
|
||||
|
||||
|
||||
template<typename impl_t>
|
||||
class packet_decoder_mp3 : public packet_decoder
|
||||
{
|
||||
impl_t m_decoder;
|
||||
unsigned m_layer;
|
||||
|
||||
void init(unsigned p_layer) {
|
||||
m_layer = p_layer;
|
||||
m_decoder.reset_after_seek();
|
||||
}
|
||||
|
||||
bool m_MP4fixes;
|
||||
|
||||
public:
|
||||
packet_decoder_mp3() : m_layer(0), m_MP4fixes()
|
||||
{
|
||||
}
|
||||
|
||||
~packet_decoder_mp3()
|
||||
{
|
||||
}
|
||||
|
||||
t_size set_stream_property(const GUID & p_type, t_size p_param1, const void * p_param2, t_size p_param2size) {
|
||||
return m_decoder.set_stream_property(p_type, p_param1, p_param2, p_param2size);
|
||||
}
|
||||
|
||||
void get_info(file_info & p_info)
|
||||
{
|
||||
switch (m_layer)
|
||||
{
|
||||
case 1: p_info.info_set("codec", "MP1"); break;
|
||||
case 2: p_info.info_set("codec", "MP2"); break;
|
||||
case 3: p_info.info_set("codec", "MP3"); break;
|
||||
default:
|
||||
throw exception_io_data();
|
||||
}
|
||||
p_info.info_set("encoding", "lossy");
|
||||
}
|
||||
|
||||
unsigned get_max_frame_dependency() { return 10; }
|
||||
double get_max_frame_dependency_time() { return 10.0 * 1152.0 / 32000.0; }
|
||||
|
||||
static bool g_is_our_setup(const GUID & p_owner, t_size p_param1, const void * p_param2, t_size p_param2size) {
|
||||
return packet_decoder_mp3_common::parseDecoderSetup(p_owner, p_param1, p_param2, p_param2size) != 0;
|
||||
}
|
||||
|
||||
void open(const GUID & p_owner, bool p_decode, t_size p_param1, const void * p_param2, t_size p_param2size, abort_callback & p_abort)
|
||||
{
|
||||
m_MP4fixes = (p_owner == owner_MP4);
|
||||
unsigned layer = packet_decoder_mp3_common::parseDecoderSetup(p_owner, p_param1, p_param2, p_param2size);
|
||||
if (layer == 0) throw exception_io_data();
|
||||
init(layer);
|
||||
}
|
||||
|
||||
void reset_after_seek() {
|
||||
m_decoder.reset_after_seek();
|
||||
}
|
||||
|
||||
void decode(const void * p_buffer, t_size p_bytes, audio_chunk & p_chunk, abort_callback & p_abort) {
|
||||
m_decoder.decode(p_buffer, p_bytes, p_chunk, p_abort);
|
||||
}
|
||||
|
||||
bool analyze_first_frame_supported() { return m_MP4fixes; }
|
||||
void analyze_first_frame(const void * p_buffer, t_size p_bytes, abort_callback & p_abort) {
|
||||
m_layer = packet_decoder_mp3_common::layer_from_frame(p_buffer, p_bytes);
|
||||
}
|
||||
};
|
||||
75
foobar2000/helpers/playlist_position_reference_tracker.h
Normal file
75
foobar2000/helpers/playlist_position_reference_tracker.h
Normal file
@@ -0,0 +1,75 @@
|
||||
#pragma once
|
||||
|
||||
class playlist_position_reference_tracker : public playlist_callback_impl_base {
|
||||
public:
|
||||
//! @param p_trackitem Specifies whether we want to track some specific item rather than just an offset in a playlist. When set to true, item index becomes invalidated when the item we're tracking is removed.
|
||||
playlist_position_reference_tracker(bool p_trackitem = true) : playlist_callback_impl_base(~0), m_trackitem(p_trackitem), m_playlist(pfc_infinite), m_item(pfc_infinite) {}
|
||||
|
||||
void on_items_added(t_size p_playlist,t_size p_start, const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const bit_array & p_selection) {
|
||||
if (p_playlist == m_playlist && m_item != pfc_infinite && p_start <= m_item) {
|
||||
m_item += p_data.get_count();
|
||||
}
|
||||
}
|
||||
void on_items_reordered(t_size p_playlist,const t_size * p_order,t_size p_count) {
|
||||
if (p_playlist == m_playlist) {
|
||||
if (m_item < p_count) {
|
||||
m_item = order_helper::g_find_reverse(p_order,m_item);
|
||||
} else {
|
||||
m_item = pfc_infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void on_items_removed(t_size p_playlist,const bit_array & p_mask,t_size p_old_count,t_size p_new_count) {
|
||||
if (p_playlist == m_playlist) {
|
||||
if (m_item < p_old_count) {
|
||||
const t_size item_before = m_item;
|
||||
for(t_size walk = p_mask.find_first(true,0,p_old_count); walk < p_old_count; walk = p_mask.find_next(true,walk,p_old_count)) {
|
||||
if (walk < item_before) {
|
||||
m_item--;
|
||||
} else if (walk == item_before) {
|
||||
if (m_trackitem) m_item = pfc_infinite;
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (m_item >= p_new_count) m_item = pfc_infinite;
|
||||
} else {
|
||||
m_item = pfc_infinite;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//todo? could be useful in some cases
|
||||
void on_items_replaced(t_size p_playlist,const bit_array & p_mask,const pfc::list_base_const_t<t_on_items_replaced_entry> & p_data) {}
|
||||
|
||||
void on_playlist_created(t_size p_index,const char * p_name,t_size p_name_len) {
|
||||
if (m_playlist != pfc_infinite && p_index <= m_playlist) m_playlist++;
|
||||
}
|
||||
void on_playlists_reorder(const t_size * p_order,t_size p_count) {
|
||||
if (m_playlist < p_count) m_playlist = order_helper::g_find_reverse(p_order,m_playlist);
|
||||
else m_playlist = pfc_infinite;
|
||||
}
|
||||
void on_playlists_removed(const bit_array & p_mask,t_size p_old_count,t_size p_new_count) {
|
||||
if (m_playlist < p_old_count) {
|
||||
const t_size playlist_before = m_playlist;
|
||||
for(t_size walk = p_mask.find_first(true,0,p_old_count); walk < p_old_count; walk = p_mask.find_next(true,walk,p_old_count)) {
|
||||
if (walk < playlist_before) {
|
||||
m_playlist--;
|
||||
} else if (walk == playlist_before) {
|
||||
m_playlist = pfc_infinite;
|
||||
break;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
m_playlist = pfc_infinite;
|
||||
}
|
||||
}
|
||||
|
||||
t_size m_playlist, m_item;
|
||||
private:
|
||||
const bool m_trackitem;
|
||||
};
|
||||
61
foobar2000/helpers/reader_pretend_nonseekable.h
Normal file
61
foobar2000/helpers/reader_pretend_nonseekable.h
Normal file
@@ -0,0 +1,61 @@
|
||||
#pragma once
|
||||
|
||||
class reader_pretend_nonseekable : public file {
|
||||
public:
|
||||
reader_pretend_nonseekable( file::ptr f, bool pretendRemote = true ) : m_file(f), m_pretendRemote(pretendRemote) {}
|
||||
|
||||
t_filesize get_size(abort_callback & p_abort) {
|
||||
return m_file->get_size(p_abort);
|
||||
}
|
||||
|
||||
|
||||
t_filesize get_position(abort_callback & p_abort) {
|
||||
return m_file->get_position(p_abort);
|
||||
}
|
||||
|
||||
void resize(t_filesize p_size, abort_callback & p_abort) {
|
||||
throw exception_io_denied();
|
||||
}
|
||||
|
||||
void seek(t_filesize p_position, abort_callback & p_abort) {
|
||||
throw exception_io_object_not_seekable();
|
||||
}
|
||||
|
||||
void seek_ex(t_sfilesize p_position, t_seek_mode p_mode, abort_callback & p_abort) {
|
||||
throw exception_io_object_not_seekable();
|
||||
}
|
||||
|
||||
bool can_seek() {return false;}
|
||||
|
||||
bool get_content_type(pfc::string_base & p_out) {
|
||||
return m_file->get_content_type(p_out);
|
||||
}
|
||||
|
||||
bool is_in_memory() { return m_file->is_in_memory(); }
|
||||
|
||||
void on_idle(abort_callback & p_abort) {m_file->on_idle(p_abort);}
|
||||
|
||||
t_filetimestamp get_timestamp(abort_callback & p_abort) { return m_file->get_timestamp(p_abort); }
|
||||
|
||||
void reopen(abort_callback & p_abort) {
|
||||
#if PFC_DEBUG
|
||||
auto pos = get_position(p_abort);
|
||||
FB2K_console_formatter() << "pretend nonseekable reader reopen @ " << pos;
|
||||
if ( pos > 0 ) {
|
||||
pfc::nop();
|
||||
}
|
||||
#endif
|
||||
m_file->reopen(p_abort);
|
||||
}
|
||||
|
||||
bool is_remote() {return m_pretendRemote;}
|
||||
|
||||
size_t read( void * ptr, size_t bytes, abort_callback & abort ) { return m_file->read(ptr, bytes, abort); }
|
||||
void read_object(void * p_buffer, t_size p_bytes, abort_callback & p_abort) {m_file->read_object(p_buffer, p_bytes, p_abort); }
|
||||
t_filesize skip(t_filesize p_bytes, abort_callback & p_abort) { return m_file->skip(p_bytes, p_abort); }
|
||||
void skip_object(t_filesize p_bytes, abort_callback & p_abort) { m_file->skip_object(p_bytes, p_abort); }
|
||||
void write( const void * ptr, size_t bytes, abort_callback & abort ) { throw exception_io_denied(); }
|
||||
private:
|
||||
const file::ptr m_file;
|
||||
const bool m_pretendRemote;
|
||||
};
|
||||
383
foobar2000/helpers/readers.cpp
Normal file
383
foobar2000/helpers/readers.cpp
Normal file
@@ -0,0 +1,383 @@
|
||||
#include "StdAfx.h"
|
||||
#include "readers.h"
|
||||
#include "fullFileBuffer.h"
|
||||
#include "fileReadAhead.h"
|
||||
|
||||
#include <list>
|
||||
|
||||
t_size reader_membuffer_base::read(void * p_buffer, t_size p_bytes, abort_callback & p_abort) {
|
||||
p_abort.check_e();
|
||||
t_size max = get_buffer_size();
|
||||
if (max < m_offset) uBugCheck();
|
||||
max -= m_offset;
|
||||
t_size delta = p_bytes;
|
||||
if (delta > max) delta = max;
|
||||
memcpy(p_buffer, (char*)get_buffer() + m_offset, delta);
|
||||
m_offset += delta;
|
||||
return delta;
|
||||
}
|
||||
|
||||
void reader_membuffer_base::seek(t_filesize position, abort_callback & p_abort) {
|
||||
p_abort.check_e();
|
||||
t_filesize max = get_buffer_size();
|
||||
if (position == filesize_invalid || position > max) throw exception_io_seek_out_of_range();
|
||||
m_offset = (t_size)position;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
file::ptr fullFileBuffer::open(const char * path, abort_callback & abort, file::ptr hint, t_filesize sizeMax) {
|
||||
//mutexScope scope(hMutex, abort);
|
||||
|
||||
file::ptr f;
|
||||
if (hint.is_valid()) f = hint;
|
||||
else filesystem::g_open_read(f, path, abort);
|
||||
|
||||
if (sizeMax != filesize_invalid) {
|
||||
t_filesize fs = f->get_size(abort);
|
||||
if (fs > sizeMax) return f;
|
||||
}
|
||||
try {
|
||||
service_ptr_t<reader_bigmem_mirror> r = new service_impl_t<reader_bigmem_mirror>();
|
||||
r->init(f, abort);
|
||||
f = r;
|
||||
}
|
||||
catch (std::bad_alloc) {}
|
||||
return f;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
#include <memory>
|
||||
#include "rethrow.h"
|
||||
#include <pfc/synchro.h>
|
||||
#include <pfc/threads.h>
|
||||
|
||||
namespace {
|
||||
struct dynInfoEntry_t {
|
||||
file_info_impl m_info;
|
||||
t_filesize m_offset;
|
||||
};
|
||||
struct readAheadInstance_t {
|
||||
file::ptr m_file;
|
||||
size_t m_readAhead, m_wakeUpThreschold;
|
||||
|
||||
pfc::array_t<uint8_t> m_buffer;
|
||||
size_t m_bufferBegin, m_bufferEnd;
|
||||
pfc::event m_canRead, m_canWrite;
|
||||
pfc::mutex m_guard;
|
||||
ThreadUtils::CRethrow m_error;
|
||||
t_filesize m_seekto;
|
||||
abort_callback_impl m_abort;
|
||||
bool m_remote;
|
||||
bool m_atEOF = false;
|
||||
|
||||
bool m_haveDynamicInfo;
|
||||
std::list<dynInfoEntry_t> m_dynamicInfo;
|
||||
};
|
||||
typedef std::shared_ptr<readAheadInstance_t> readAheadInstanceRef;
|
||||
static const t_filesize seek_reopen = (filesize_invalid-1);
|
||||
class fileReadAhead : public file_readonly_t<file_dynamicinfo_v2> {
|
||||
public:
|
||||
readAheadInstanceRef m_instance;
|
||||
~fileReadAhead() {
|
||||
if ( m_instance ) {
|
||||
auto & i = *m_instance;
|
||||
pfc::mutexScope guard( i.m_guard );
|
||||
i.m_abort.set();
|
||||
i.m_canWrite.set_state(true);
|
||||
}
|
||||
}
|
||||
void initialize( file::ptr chain, size_t readAhead, abort_callback & aborter ) {
|
||||
m_stats = chain->get_stats( aborter );
|
||||
if (!chain->get_content_type(m_contentType)) m_contentType = "";
|
||||
m_canSeek = chain->can_seek();
|
||||
m_position = chain->get_position( aborter );
|
||||
|
||||
|
||||
auto i = std::make_shared<readAheadInstance_t>();;
|
||||
i->m_file = chain;
|
||||
i->m_remote = chain->is_remote();
|
||||
i->m_readAhead = readAhead;
|
||||
i->m_wakeUpThreschold = readAhead * 3 / 4;
|
||||
i->m_buffer.set_size_discard( readAhead * 2 );
|
||||
i->m_bufferBegin = 0; i->m_bufferEnd = 0;
|
||||
i->m_canWrite.set_state(true);
|
||||
i->m_seekto = filesize_invalid;
|
||||
m_instance = i;
|
||||
|
||||
{
|
||||
file_dynamicinfo::ptr dyn;
|
||||
if (dyn &= chain) {
|
||||
m_haveStaticInfo = dyn->get_static_info(m_staticInfo);
|
||||
i->m_haveDynamicInfo = dyn->is_dynamic_info_enabled();
|
||||
}
|
||||
}
|
||||
|
||||
fb2k::splitTask( [i] {
|
||||
#ifdef PFC_SET_THREAD_DESCRIPTION
|
||||
PFC_SET_THREAD_DESCRIPTION("Fb2k Read-Ahead Thread");
|
||||
#endif
|
||||
worker(*i);
|
||||
} );
|
||||
}
|
||||
t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
||||
auto & i = * m_instance;
|
||||
size_t done = 0;
|
||||
bool initial = true;
|
||||
while( done < p_bytes ) {
|
||||
if ( !initial ) {
|
||||
// Do not invoke waiting with common case read with lots of data in the buffer
|
||||
pfc::event::g_twoEventWait( i.m_canRead.get_handle(), p_abort.get_abort_event(), -1);
|
||||
}
|
||||
p_abort.check();
|
||||
pfc::mutexScope guard ( i.m_guard );
|
||||
size_t got = i.m_bufferEnd - i.m_bufferBegin;
|
||||
if (got == 0) {
|
||||
i.m_error.rethrow();
|
||||
if ( initial && ! i.m_atEOF ) {
|
||||
initial = false; continue; // proceed to wait for more data
|
||||
}
|
||||
break; // EOF
|
||||
}
|
||||
|
||||
size_t delta = pfc::min_t<size_t>( p_bytes - done, got );
|
||||
|
||||
const bool wakeUpBefore = got < i.m_wakeUpThreschold;
|
||||
|
||||
auto bufptr = i.m_buffer.get_ptr();
|
||||
if ( p_buffer != nullptr ) memcpy( (uint8_t*) p_buffer + done, bufptr + i.m_bufferBegin, delta );
|
||||
done += delta;
|
||||
i.m_bufferBegin += delta;
|
||||
got -= delta;
|
||||
m_position += delta;
|
||||
|
||||
if (!i.m_error.didFail() && !i.m_atEOF) {
|
||||
if ( got == 0 ) i.m_canRead.set_state( false );
|
||||
const bool wakeUpNow = got < i.m_wakeUpThreschold;
|
||||
// Only set the event when *crossing* the boundary
|
||||
// we will get a lot of wakeUpNow when nearing EOF
|
||||
if ( wakeUpNow && ! wakeUpBefore ) i.m_canWrite.set_state( true );
|
||||
}
|
||||
initial = false;
|
||||
if ( i.m_atEOF ) break; // go no further
|
||||
}
|
||||
// FB2K_console_formatter() << "ReadAhead read: " << p_bytes << " => " << done;
|
||||
return done;
|
||||
}
|
||||
t_filesize get_size(abort_callback & p_abort) {
|
||||
p_abort.check();
|
||||
return m_stats.m_size;
|
||||
}
|
||||
t_filesize get_position(abort_callback & p_abort) {
|
||||
p_abort.check();
|
||||
return m_position;
|
||||
}
|
||||
|
||||
void seek(t_filesize p_position,abort_callback & p_abort) {
|
||||
p_abort.check();
|
||||
if (!m_canSeek) throw exception_io_object_not_seekable();
|
||||
if ( m_stats.m_size != filesize_invalid && p_position > m_stats.m_size ) throw exception_io_seek_out_of_range();
|
||||
|
||||
auto posNow = get_position(p_abort);
|
||||
|
||||
if ( p_position >= posNow && p_position < posNow + m_instance->m_readAhead ) {
|
||||
// FB2K_console_formatter() << "ReadAhead skip: " << posNow << " => " << p_position;
|
||||
auto toSkip = p_position - posNow;
|
||||
if ( toSkip > 0 ) read(nullptr, (size_t) toSkip, p_abort);
|
||||
return;
|
||||
}
|
||||
// FB2K_console_formatter() << "ReadAhead seek: " << posNow << " => " << p_position;
|
||||
|
||||
seekInternal( p_position );
|
||||
}
|
||||
bool can_seek() {
|
||||
return m_canSeek;
|
||||
}
|
||||
bool get_content_type(pfc::string_base & p_out) {
|
||||
if (m_contentType.length() == 0) return false;
|
||||
p_out = m_contentType; return true;
|
||||
}
|
||||
t_filestats get_stats( abort_callback & p_abort ) {
|
||||
p_abort.check();
|
||||
return m_stats;
|
||||
|
||||
}
|
||||
t_filetimestamp get_timestamp(abort_callback & p_abort) {
|
||||
p_abort.check();
|
||||
return m_stats.m_timestamp;
|
||||
}
|
||||
bool is_remote() {
|
||||
return m_instance->m_remote;
|
||||
}
|
||||
|
||||
void reopen( abort_callback & p_abort ) {
|
||||
if ( get_position( p_abort ) == 0 ) return;
|
||||
seekInternal( seek_reopen );
|
||||
}
|
||||
|
||||
bool get_static_info(class file_info & p_out) {
|
||||
if ( ! m_haveStaticInfo ) return false;
|
||||
mergeInfo(p_out, m_staticInfo);
|
||||
return true;
|
||||
}
|
||||
bool is_dynamic_info_enabled() {
|
||||
return m_instance->m_haveDynamicInfo;
|
||||
|
||||
}
|
||||
static void mergeInfo( file_info & out, const file_info & in ) {
|
||||
out.copy_meta(in);
|
||||
out.overwrite_info(in);
|
||||
}
|
||||
bool get_dynamic_info_v2(class file_info & out, t_filesize & outOffset) {
|
||||
auto & i = * m_instance;
|
||||
if ( ! i.m_haveDynamicInfo ) return false;
|
||||
|
||||
insync( i.m_guard );
|
||||
auto ptr = i.m_dynamicInfo.begin();
|
||||
for ( ;; ) {
|
||||
if ( ptr == i.m_dynamicInfo.end() ) break;
|
||||
if ( ptr->m_offset > m_position ) break;
|
||||
++ ptr;
|
||||
}
|
||||
|
||||
if ( ptr == i.m_dynamicInfo.begin() ) return false;
|
||||
|
||||
auto iter = ptr; --iter;
|
||||
mergeInfo(out, iter->m_info);
|
||||
outOffset = iter->m_offset;
|
||||
i.m_dynamicInfo.erase( i.m_dynamicInfo.begin(), ptr );
|
||||
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
void seekInternal( t_filesize p_position ) {
|
||||
auto & i = * m_instance;
|
||||
insync( i.m_guard );
|
||||
i.m_error.rethrow();
|
||||
i.m_bufferBegin = i.m_bufferEnd = 0;
|
||||
i.m_canWrite.set_state(true);
|
||||
i.m_seekto = p_position;
|
||||
i.m_atEOF = false;
|
||||
i.m_canRead.set_state(false);
|
||||
|
||||
m_position = ( p_position == seek_reopen ) ? 0 : p_position;
|
||||
}
|
||||
static void worker( readAheadInstance_t & i ) {
|
||||
ThreadUtils::CRethrow err;
|
||||
err.exec( [&i] {
|
||||
bool atEOF = false;
|
||||
uint8_t* bufptr = i.m_buffer.get_ptr();
|
||||
const size_t readAtOnceLimit = i.m_remote ? 256 : 4*1024;
|
||||
for ( ;; ) {
|
||||
i.m_canWrite.wait_for(-1);
|
||||
size_t readHowMuch = 0, readOffset = 0;
|
||||
{
|
||||
pfc::mutexScope guard(i.m_guard);
|
||||
i.m_abort.check();
|
||||
if ( i.m_seekto != filesize_invalid ) {
|
||||
if ( i.m_seekto == seek_reopen ) {
|
||||
i.m_file->reopen( i.m_abort );
|
||||
} else {
|
||||
i.m_file->seek( i.m_seekto, i.m_abort );
|
||||
}
|
||||
|
||||
i.m_seekto = filesize_invalid;
|
||||
atEOF = false;
|
||||
}
|
||||
size_t got = i.m_bufferEnd - i.m_bufferBegin;
|
||||
|
||||
if ( i.m_bufferBegin >= i.m_readAhead ) {
|
||||
memmove( bufptr, bufptr + i.m_bufferBegin, got );
|
||||
i.m_bufferBegin = 0;
|
||||
i.m_bufferEnd = got;
|
||||
}
|
||||
|
||||
if ( got < i.m_readAhead ) {
|
||||
readHowMuch = i.m_readAhead - got;
|
||||
readOffset = i.m_bufferEnd;
|
||||
}
|
||||
}
|
||||
|
||||
if ( readHowMuch > readAtOnceLimit ) {
|
||||
readHowMuch = readAtOnceLimit;
|
||||
}
|
||||
|
||||
bool dynInfoGot = false;
|
||||
dynInfoEntry_t dynInfo;
|
||||
|
||||
if ( readHowMuch > 0 ) {
|
||||
readHowMuch = i.m_file->read( bufptr + readOffset, readHowMuch, i.m_abort );
|
||||
|
||||
if ( readHowMuch == 0 ) atEOF = true;
|
||||
|
||||
if ( i.m_haveDynamicInfo ) {
|
||||
file_dynamicinfo::ptr dyn;
|
||||
if ( dyn &= i.m_file ) {
|
||||
file_dynamicinfo_v2::ptr dyn2;
|
||||
if ( dyn2 &= dyn ) {
|
||||
dynInfoGot = dyn2->get_dynamic_info_v2(dynInfo.m_info, dynInfo.m_offset);
|
||||
} else {
|
||||
dynInfoGot = dyn->get_dynamic_info( dynInfo.m_info );
|
||||
if (dynInfoGot) {
|
||||
dynInfo.m_offset = dyn->get_position( i.m_abort );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
pfc::mutexScope guard( i.m_guard );
|
||||
i.m_abort.check();
|
||||
if ( i.m_seekto != filesize_invalid ) {
|
||||
// Seek request happened while we were reading - discard and continue
|
||||
continue;
|
||||
}
|
||||
i.m_atEOF = atEOF;
|
||||
i.m_canRead.set_state( true );
|
||||
i.m_bufferEnd += readHowMuch;
|
||||
size_t got = i.m_bufferEnd - i.m_bufferBegin;
|
||||
if ( atEOF || got >= i.m_readAhead ) i.m_canWrite.set_state(false);
|
||||
|
||||
if ( dynInfoGot ) {
|
||||
i.m_dynamicInfo.push_back( std::move(dynInfo) );
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
} );
|
||||
|
||||
if ( err.didFail( ) ) {
|
||||
pfc::mutexScope guard( i.m_guard );
|
||||
i.m_error = err;
|
||||
i.m_canRead.set_state(true);
|
||||
}
|
||||
}
|
||||
|
||||
bool m_canSeek;
|
||||
t_filestats m_stats;
|
||||
pfc::string8 m_contentType;
|
||||
t_filesize m_position;
|
||||
|
||||
|
||||
bool m_haveStaticInfo;
|
||||
file_info_impl m_staticInfo;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
file::ptr fileCreateReadAhead(file::ptr chain, size_t readAheadBytes, abort_callback & aborter ) {
|
||||
auto obj = fb2k::service_new<fileReadAhead>();
|
||||
obj->initialize( chain, readAheadBytes, aborter );
|
||||
return obj;
|
||||
}
|
||||
314
foobar2000/helpers/readers.h
Normal file
314
foobar2000/helpers/readers.h
Normal file
@@ -0,0 +1,314 @@
|
||||
#pragma once
|
||||
|
||||
class NOVTABLE reader_membuffer_base : public file_readonly {
|
||||
public:
|
||||
reader_membuffer_base() : m_offset(0) {}
|
||||
|
||||
t_size read(void * p_buffer, t_size p_bytes, abort_callback & p_abort);
|
||||
|
||||
void write(const void * p_buffer, t_size p_bytes, abort_callback & p_abort) { throw exception_io_denied(); }
|
||||
|
||||
t_filesize get_size(abort_callback & p_abort) { return get_buffer_size(); }
|
||||
t_filesize get_position(abort_callback & p_abort) { return m_offset; }
|
||||
void seek(t_filesize position, abort_callback & p_abort);
|
||||
void reopen(abort_callback & p_abort) { seek(0, p_abort); }
|
||||
|
||||
bool can_seek() { return true; }
|
||||
bool is_in_memory() { return true; }
|
||||
|
||||
protected:
|
||||
virtual const void * get_buffer() = 0;
|
||||
virtual t_size get_buffer_size() = 0;
|
||||
virtual t_filetimestamp get_timestamp(abort_callback & p_abort) = 0;
|
||||
virtual bool get_content_type(pfc::string_base &) { return false; }
|
||||
inline void seek_internal(t_size p_offset) { if (p_offset > get_buffer_size()) throw exception_io_seek_out_of_range(); m_offset = p_offset; }
|
||||
private:
|
||||
t_size m_offset;
|
||||
};
|
||||
|
||||
class reader_membuffer_simple : public reader_membuffer_base {
|
||||
public:
|
||||
reader_membuffer_simple(const void * ptr, t_size size, t_filetimestamp ts = filetimestamp_invalid, bool is_remote = false) : m_isRemote(is_remote), m_ts(ts) {
|
||||
m_data.set_size_discard(size);
|
||||
memcpy(m_data.get_ptr(), ptr, size);
|
||||
}
|
||||
const void * get_buffer() { return m_data.get_ptr(); }
|
||||
t_size get_buffer_size() { return m_data.get_size(); }
|
||||
t_filetimestamp get_timestamp(abort_callback & p_abort) { return m_ts; }
|
||||
bool is_remote() { return m_isRemote; }
|
||||
private:
|
||||
pfc::array_staticsize_t<t_uint8> m_data;
|
||||
t_filetimestamp m_ts;
|
||||
bool m_isRemote;
|
||||
};
|
||||
|
||||
class reader_membuffer_mirror : public reader_membuffer_base
|
||||
{
|
||||
public:
|
||||
t_filetimestamp get_timestamp(abort_callback & p_abort) { return m_timestamp; }
|
||||
bool is_remote() { return m_remote; }
|
||||
|
||||
//! Returns false when the object could not be mirrored (too big) or did not need mirroring.
|
||||
static bool g_create(service_ptr_t<file> & p_out, const service_ptr_t<file> & p_src, abort_callback & p_abort) {
|
||||
service_ptr_t<reader_membuffer_mirror> ptr = new service_impl_t<reader_membuffer_mirror>();
|
||||
if (!ptr->init(p_src, p_abort)) return false;
|
||||
p_out = ptr.get_ptr();
|
||||
return true;
|
||||
}
|
||||
bool get_content_type(pfc::string_base & out) {
|
||||
if (m_contentType.is_empty()) return false;
|
||||
out = m_contentType; return true;
|
||||
}
|
||||
private:
|
||||
const void * get_buffer() { return m_buffer.get_ptr(); }
|
||||
t_size get_buffer_size() { return m_buffer.get_size(); }
|
||||
|
||||
bool init(const service_ptr_t<file> & p_src, abort_callback & p_abort) {
|
||||
if (p_src->is_in_memory()) return false;//already buffered
|
||||
if (!p_src->get_content_type(m_contentType)) m_contentType.reset();
|
||||
m_remote = p_src->is_remote();
|
||||
|
||||
t_size size = pfc::downcast_guarded<t_size>(p_src->get_size(p_abort));
|
||||
|
||||
m_buffer.set_size(size);
|
||||
|
||||
p_src->reopen(p_abort);
|
||||
|
||||
p_src->read_object(m_buffer.get_ptr(), size, p_abort);
|
||||
|
||||
m_timestamp = p_src->get_timestamp(p_abort);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
t_filetimestamp m_timestamp;
|
||||
pfc::array_t<char> m_buffer;
|
||||
bool m_remote;
|
||||
pfc::string8 m_contentType;
|
||||
|
||||
};
|
||||
|
||||
class reader_limited : public file_readonly {
|
||||
service_ptr_t<file> r;
|
||||
t_filesize begin;
|
||||
t_filesize end;
|
||||
|
||||
public:
|
||||
static file::ptr g_create(file::ptr base, t_filesize offset, t_filesize size, abort_callback & abort) {
|
||||
service_ptr_t<reader_limited> r = new service_impl_t<reader_limited>();
|
||||
if (offset + size < offset) throw pfc::exception_overflow();
|
||||
r->init(base, offset, offset + size, abort);
|
||||
return r;
|
||||
}
|
||||
reader_limited() { begin = 0;end = 0; }
|
||||
reader_limited(const service_ptr_t<file> & p_r, t_filesize p_begin, t_filesize p_end, abort_callback & p_abort) {
|
||||
r = p_r;
|
||||
begin = p_begin;
|
||||
end = p_end;
|
||||
reopen(p_abort);
|
||||
}
|
||||
|
||||
void init(const service_ptr_t<file> & p_r, t_filesize p_begin, t_filesize p_end, abort_callback & p_abort) {
|
||||
r = p_r;
|
||||
begin = p_begin;
|
||||
end = p_end;
|
||||
reopen(p_abort);
|
||||
}
|
||||
|
||||
t_filetimestamp get_timestamp(abort_callback & p_abort) { return r->get_timestamp(p_abort); }
|
||||
|
||||
t_size read(void *p_buffer, t_size p_bytes, abort_callback & p_abort) {
|
||||
t_filesize pos;
|
||||
pos = r->get_position(p_abort);
|
||||
if (p_bytes > end - pos) p_bytes = (t_size)(end - pos);
|
||||
return r->read(p_buffer, p_bytes, p_abort);
|
||||
}
|
||||
|
||||
t_filesize get_size(abort_callback & p_abort) { return end - begin; }
|
||||
|
||||
t_filesize get_position(abort_callback & p_abort) {
|
||||
return r->get_position(p_abort) - begin;
|
||||
}
|
||||
|
||||
void seek(t_filesize position, abort_callback & p_abort) {
|
||||
r->seek(position + begin, p_abort);
|
||||
}
|
||||
bool can_seek() { return r->can_seek(); }
|
||||
bool is_remote() { return r->is_remote(); }
|
||||
|
||||
bool get_content_type(pfc::string_base &) { return false; }
|
||||
|
||||
void reopen(abort_callback & p_abort) {
|
||||
seekInternal(begin, p_abort);
|
||||
}
|
||||
private:
|
||||
void seekInternal(t_filesize position, abort_callback & abort) {
|
||||
if (r->can_seek()) {
|
||||
r->seek(position, abort);
|
||||
}
|
||||
else {
|
||||
t_filesize positionWas = r->get_position(abort);
|
||||
if (positionWas == filesize_invalid || positionWas > position) {
|
||||
r->reopen(abort);
|
||||
try { r->skip_object(position, abort); }
|
||||
catch (exception_io_data) { throw exception_io_seek_out_of_range(); }
|
||||
}
|
||||
else {
|
||||
t_filesize skipMe = position - positionWas;
|
||||
if (skipMe > 0) {
|
||||
try { r->skip_object(skipMe, abort); }
|
||||
catch (exception_io_data) { throw exception_io_seek_out_of_range(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// A more clever version of reader_membuffer_*.
|
||||
// Behaves more nicely with large files within 32bit address space.
|
||||
class reader_bigmem : public file_readonly {
|
||||
public:
|
||||
reader_bigmem() : m_offset() {}
|
||||
t_size read(void * p_buffer, t_size p_bytes, abort_callback & p_abort) {
|
||||
pfc::min_acc(p_bytes, remaining());
|
||||
m_mem.read(p_buffer, p_bytes, m_offset);
|
||||
m_offset += p_bytes;
|
||||
return p_bytes;
|
||||
}
|
||||
void read_object(void * p_buffer, t_size p_bytes, abort_callback & p_abort) {
|
||||
if (p_bytes > remaining()) throw exception_io_data_truncation();
|
||||
m_mem.read(p_buffer, p_bytes, m_offset);
|
||||
m_offset += p_bytes;
|
||||
}
|
||||
t_filesize skip(t_filesize p_bytes, abort_callback & p_abort) {
|
||||
pfc::min_acc(p_bytes, (t_filesize)remaining());
|
||||
m_offset += (size_t)p_bytes;
|
||||
return p_bytes;
|
||||
}
|
||||
void skip_object(t_filesize p_bytes, abort_callback & p_abort) {
|
||||
if (p_bytes > remaining()) throw exception_io_data_truncation();
|
||||
m_offset += (size_t)p_bytes;
|
||||
}
|
||||
|
||||
t_filesize get_size(abort_callback & p_abort) { p_abort.check(); return m_mem.size(); }
|
||||
t_filesize get_position(abort_callback & p_abort) { p_abort.check(); return m_offset; }
|
||||
void seek(t_filesize p_position, abort_callback & p_abort) {
|
||||
if (p_position > m_mem.size()) throw exception_io_seek_out_of_range();
|
||||
m_offset = (size_t)p_position;
|
||||
}
|
||||
bool can_seek() { return true; }
|
||||
bool is_in_memory() { return true; }
|
||||
void reopen(abort_callback & p_abort) { seek(0, p_abort); }
|
||||
|
||||
// To be overridden by individual derived classes
|
||||
bool get_content_type(pfc::string_base & p_out) { return false; }
|
||||
t_filetimestamp get_timestamp(abort_callback & p_abort) { return filetimestamp_invalid; }
|
||||
bool is_remote() { return false; }
|
||||
protected:
|
||||
void resize(size_t newSize) {
|
||||
m_offset = 0;
|
||||
m_mem.resize(newSize);
|
||||
}
|
||||
size_t remaining() const { return m_mem.size() - m_offset; }
|
||||
pfc::bigmem m_mem;
|
||||
size_t m_offset;
|
||||
};
|
||||
|
||||
class reader_bigmem_mirror : public reader_bigmem {
|
||||
public:
|
||||
reader_bigmem_mirror() {}
|
||||
|
||||
void init(file::ptr source, abort_callback & abort) {
|
||||
source->reopen(abort);
|
||||
t_filesize fs = source->get_size(abort);
|
||||
if (fs > 1024 * 1024 * 1024) { // reject > 1GB
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
size_t s = (size_t)fs;
|
||||
resize(s);
|
||||
for (size_t walk = 0; walk < m_mem._sliceCount(); ++walk) {
|
||||
source->read(m_mem._slicePtr(walk), m_mem._sliceSize(walk), abort);
|
||||
}
|
||||
|
||||
if (!source->get_content_type(m_contentType)) m_contentType.reset();
|
||||
m_isRemote = source->is_remote();
|
||||
m_ts = source->get_timestamp(abort);
|
||||
}
|
||||
|
||||
bool get_content_type(pfc::string_base & p_out) {
|
||||
if (m_contentType.is_empty()) return false;
|
||||
p_out = m_contentType; return true;
|
||||
}
|
||||
t_filetimestamp get_timestamp(abort_callback & p_abort) { return m_ts; }
|
||||
bool is_remote() { return m_isRemote; }
|
||||
private:
|
||||
t_filetimestamp m_ts;
|
||||
pfc::string8 m_contentType;
|
||||
bool m_isRemote;
|
||||
};
|
||||
|
||||
class file_chain : public file {
|
||||
public:
|
||||
t_size read(void * p_buffer, t_size p_bytes, abort_callback & p_abort) {
|
||||
return m_file->read(p_buffer, p_bytes, p_abort);
|
||||
}
|
||||
void read_object(void * p_buffer, t_size p_bytes, abort_callback & p_abort) {
|
||||
m_file->read_object(p_buffer, p_bytes, p_abort);
|
||||
}
|
||||
t_filesize skip(t_filesize p_bytes, abort_callback & p_abort) {
|
||||
return m_file->skip(p_bytes, p_abort);
|
||||
}
|
||||
void skip_object(t_filesize p_bytes, abort_callback & p_abort) {
|
||||
m_file->skip_object(p_bytes, p_abort);
|
||||
}
|
||||
void write(const void * p_buffer, t_size p_bytes, abort_callback & p_abort) {
|
||||
m_file->write(p_buffer, p_bytes, p_abort);
|
||||
}
|
||||
|
||||
t_filesize get_size(abort_callback & p_abort) {
|
||||
return m_file->get_size(p_abort);
|
||||
}
|
||||
|
||||
t_filesize get_position(abort_callback & p_abort) {
|
||||
return m_file->get_position(p_abort);
|
||||
}
|
||||
|
||||
void resize(t_filesize p_size, abort_callback & p_abort) {
|
||||
m_file->resize(p_size, p_abort);
|
||||
}
|
||||
|
||||
void seek(t_filesize p_position, abort_callback & p_abort) {
|
||||
m_file->seek(p_position, p_abort);
|
||||
}
|
||||
|
||||
void seek_ex(t_sfilesize p_position, t_seek_mode p_mode, abort_callback & p_abort) {
|
||||
m_file->seek_ex(p_position, p_mode, p_abort);
|
||||
}
|
||||
|
||||
bool can_seek() { return m_file->can_seek(); }
|
||||
bool get_content_type(pfc::string_base & p_out) { return m_file->get_content_type(p_out); }
|
||||
bool is_in_memory() { return m_file->is_in_memory(); }
|
||||
void on_idle(abort_callback & p_abort) { m_file->on_idle(p_abort); }
|
||||
#if FOOBAR2000_TARGET_VERSION >= 2000
|
||||
t_filestats get_stats(abort_callback & abort) { return m_file->get_stats(abort); }
|
||||
#else
|
||||
t_filetimestamp get_timestamp(abort_callback & p_abort) { return m_file->get_timestamp(p_abort); }
|
||||
#endif
|
||||
void reopen(abort_callback & p_abort) { m_file->reopen(p_abort); }
|
||||
bool is_remote() { return m_file->is_remote(); }
|
||||
|
||||
file_chain(file::ptr chain) : m_file(chain) {}
|
||||
private:
|
||||
file::ptr m_file;
|
||||
};
|
||||
|
||||
class file_chain_readonly : public file_chain {
|
||||
public:
|
||||
void write(const void * p_buffer, t_size p_bytes, abort_callback & p_abort) { throw exception_io_denied(); }
|
||||
void resize(t_filesize p_size, abort_callback & p_abort) { throw exception_io_denied(); }
|
||||
file_chain_readonly(file::ptr chain) : file_chain(chain) {}
|
||||
static file::ptr create(file::ptr chain) { return new service_impl_t< file_chain_readonly >(chain); }
|
||||
};
|
||||
15
foobar2000/helpers/rethrow.h
Normal file
15
foobar2000/helpers/rethrow.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
|
||||
namespace ThreadUtils {
|
||||
class CRethrow {
|
||||
private:
|
||||
std::function<void () > m_rethrow;
|
||||
public:
|
||||
bool exec( std::function<void () > f ) throw();
|
||||
void rethrow() const;
|
||||
bool didFail() const { return !! m_rethrow; }
|
||||
void clear() { m_rethrow = nullptr; }
|
||||
};
|
||||
}
|
||||
221
foobar2000/helpers/seekabilizer.cpp
Normal file
221
foobar2000/helpers/seekabilizer.cpp
Normal file
@@ -0,0 +1,221 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "seekabilizer.h"
|
||||
|
||||
enum {backread_on_seek = 1024};
|
||||
|
||||
void seekabilizer_backbuffer::initialize(t_size p_size)
|
||||
{
|
||||
m_depth = m_cursor = 0;
|
||||
|
||||
m_buffer.set_size(p_size);
|
||||
}
|
||||
|
||||
void seekabilizer_backbuffer::write(const void * p_buffer,t_size p_bytes)
|
||||
{
|
||||
if (p_bytes >= m_buffer.get_size())
|
||||
{
|
||||
memcpy(m_buffer.get_ptr(),(const t_uint8*)p_buffer + p_bytes - m_buffer.get_size(),m_buffer.get_size());
|
||||
m_cursor = 0;
|
||||
m_depth = m_buffer.get_size();
|
||||
}
|
||||
else
|
||||
{
|
||||
const t_uint8* sourceptr = (const t_uint8*) p_buffer;
|
||||
t_size remaining = p_bytes;
|
||||
while(remaining > 0)
|
||||
{
|
||||
t_size delta = m_buffer.get_size() - m_cursor;
|
||||
if (delta > remaining) delta = remaining;
|
||||
|
||||
memcpy(m_buffer.get_ptr() + m_cursor,sourceptr,delta);
|
||||
|
||||
sourceptr += delta;
|
||||
remaining -= delta;
|
||||
m_cursor = (m_cursor + delta) % m_buffer.get_size();
|
||||
|
||||
m_depth = pfc::min_t<t_size>(m_buffer.get_size(),m_depth + delta);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void seekabilizer_backbuffer::read(t_size p_backlogdepth,void * p_buffer,t_size p_bytes) const
|
||||
{
|
||||
PFC_ASSERT(p_backlogdepth <= m_depth);
|
||||
PFC_ASSERT(p_backlogdepth >= p_bytes);
|
||||
|
||||
|
||||
t_uint8* targetptr = (t_uint8*) p_buffer;
|
||||
t_size remaining = p_bytes;
|
||||
t_size cursor = (m_cursor + m_buffer.get_size() - p_backlogdepth) % m_buffer.get_size();
|
||||
|
||||
while(remaining > 0)
|
||||
{
|
||||
t_size delta = m_buffer.get_size() - cursor;
|
||||
if (delta > remaining) delta = remaining;
|
||||
|
||||
memcpy(targetptr,m_buffer.get_ptr() + cursor,delta);
|
||||
|
||||
targetptr += delta;
|
||||
remaining -= delta;
|
||||
cursor = (cursor + delta) % m_buffer.get_size();
|
||||
}
|
||||
}
|
||||
|
||||
t_size seekabilizer_backbuffer::get_depth() const
|
||||
{
|
||||
return m_depth;
|
||||
}
|
||||
|
||||
t_size seekabilizer_backbuffer::get_max_depth() const
|
||||
{
|
||||
return m_buffer.get_size();
|
||||
}
|
||||
|
||||
void seekabilizer_backbuffer::reset()
|
||||
{
|
||||
m_depth = m_cursor = 0;
|
||||
}
|
||||
|
||||
|
||||
void seekabilizer::initialize(service_ptr_t<file> p_base,t_size p_buffer_size,abort_callback & p_abort) {
|
||||
m_buffer.initialize(p_buffer_size);
|
||||
m_file = p_base;
|
||||
m_position = m_position_base = 0;
|
||||
m_size = m_file->get_size(p_abort);
|
||||
}
|
||||
|
||||
void seekabilizer::g_seekabilize(service_ptr_t<file> & p_reader,t_size p_buffer_size,abort_callback & p_abort) {
|
||||
if (p_reader.is_valid() && p_reader->is_remote() && p_buffer_size > 0) {
|
||||
service_ptr_t<seekabilizer> instance = new service_impl_t<seekabilizer>();
|
||||
instance->initialize(p_reader,p_buffer_size,p_abort);
|
||||
p_reader = instance.get_ptr();
|
||||
}
|
||||
}
|
||||
|
||||
t_size seekabilizer::read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
||||
p_abort.check_e();
|
||||
|
||||
if (m_position > m_position_base + pfc::max_t<t_size>(m_buffer.get_max_depth(),backread_on_seek) && m_file->can_seek()) {
|
||||
m_buffer.reset();
|
||||
t_filesize target = m_position;
|
||||
if (target < backread_on_seek) target = 0;
|
||||
else target -= backread_on_seek;
|
||||
m_file->seek(target,p_abort);
|
||||
m_position_base = target;
|
||||
}
|
||||
|
||||
//seek ahead
|
||||
while(m_position > m_position_base) {
|
||||
enum {tempsize = 1024};
|
||||
t_uint8 temp[tempsize];
|
||||
t_size delta = (t_size) pfc::min_t<t_filesize>(tempsize,m_position - m_position_base);
|
||||
t_size bytes_read = 0;
|
||||
bytes_read = m_file->read(temp,delta,p_abort);
|
||||
m_buffer.write(temp,bytes_read);
|
||||
m_position_base += bytes_read;
|
||||
|
||||
if (bytes_read < delta) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
t_size done = 0;
|
||||
t_uint8 * targetptr = (t_uint8*) p_buffer;
|
||||
|
||||
//try to read backbuffer
|
||||
if (m_position < m_position_base) {
|
||||
if (m_position_base - m_position > (t_filesize)m_buffer.get_depth()) throw exception_io_seek_out_of_range();
|
||||
t_size backread_depth = (t_size) (m_position_base - m_position);
|
||||
t_size delta = pfc::min_t<t_size>(backread_depth,p_bytes-done);
|
||||
m_buffer.read(backread_depth,targetptr,delta);
|
||||
done += delta;
|
||||
m_position += delta;
|
||||
}
|
||||
|
||||
//regular read
|
||||
if (done < p_bytes)
|
||||
{
|
||||
t_size bytes_read;
|
||||
bytes_read = m_file->read(targetptr+done,p_bytes-done,p_abort);
|
||||
|
||||
m_buffer.write(targetptr+done,bytes_read);
|
||||
|
||||
done += bytes_read;
|
||||
m_position += bytes_read;
|
||||
m_position_base += bytes_read;
|
||||
}
|
||||
|
||||
return done;
|
||||
}
|
||||
|
||||
t_filesize seekabilizer::get_size(abort_callback & p_abort) {
|
||||
p_abort.check_e();
|
||||
return m_size;
|
||||
}
|
||||
|
||||
t_filesize seekabilizer::get_position(abort_callback & p_abort) {
|
||||
p_abort.check_e();
|
||||
return m_position;
|
||||
}
|
||||
|
||||
void seekabilizer::seek(t_filesize p_position,abort_callback & p_abort) {
|
||||
PFC_ASSERT(m_position_base >= m_buffer.get_depth());
|
||||
p_abort.check_e();
|
||||
|
||||
if (m_size != filesize_invalid && p_position > m_size) throw exception_io_seek_out_of_range();
|
||||
|
||||
t_filesize lowest = m_position_base - m_buffer.get_depth();
|
||||
|
||||
if (p_position < lowest) {
|
||||
if (m_file->can_seek()) {
|
||||
m_buffer.reset();
|
||||
t_filesize target = p_position;
|
||||
t_size delta = m_buffer.get_max_depth();
|
||||
if (delta > backread_on_seek) delta = backread_on_seek;
|
||||
if (target > delta) target -= delta;
|
||||
else target = 0;
|
||||
m_file->seek(target,p_abort);
|
||||
m_position_base = target;
|
||||
}
|
||||
else {
|
||||
m_buffer.reset();
|
||||
m_file->reopen(p_abort);
|
||||
m_position_base = 0;
|
||||
}
|
||||
}
|
||||
|
||||
m_position = p_position;
|
||||
}
|
||||
|
||||
bool seekabilizer::can_seek()
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
bool seekabilizer::get_content_type(pfc::string_base & p_out) {return m_file->get_content_type(p_out);}
|
||||
|
||||
bool seekabilizer::is_in_memory() {return false;}
|
||||
|
||||
void seekabilizer::on_idle(abort_callback & p_abort) {return m_file->on_idle(p_abort);}
|
||||
|
||||
t_filetimestamp seekabilizer::get_timestamp(abort_callback & p_abort) {
|
||||
p_abort.check_e();
|
||||
return m_file->get_timestamp(p_abort);
|
||||
}
|
||||
|
||||
void seekabilizer::reopen(abort_callback & p_abort) {
|
||||
if (m_position_base - m_buffer.get_depth() == 0) {
|
||||
seek(0,p_abort);
|
||||
} else {
|
||||
m_position = m_position_base = 0;
|
||||
m_buffer.reset();
|
||||
m_file->reopen(p_abort);
|
||||
}
|
||||
}
|
||||
|
||||
bool seekabilizer::is_remote()
|
||||
{
|
||||
return m_file->is_remote();
|
||||
}
|
||||
38
foobar2000/helpers/seekabilizer.h
Normal file
38
foobar2000/helpers/seekabilizer.h
Normal file
@@ -0,0 +1,38 @@
|
||||
#pragma once
|
||||
|
||||
class seekabilizer_backbuffer
|
||||
{
|
||||
public:
|
||||
void initialize(t_size p_size);
|
||||
void write(const void * p_buffer,t_size p_bytes);
|
||||
void read(t_size p_backlogdepth,void * p_buffer,t_size p_bytes) const;
|
||||
t_size get_depth() const;
|
||||
void reset();
|
||||
t_size get_max_depth() const;
|
||||
private:
|
||||
pfc::array_t<t_uint8> m_buffer;
|
||||
t_size m_depth,m_cursor;
|
||||
};
|
||||
|
||||
class seekabilizer : public file_readonly {
|
||||
public:
|
||||
void initialize(service_ptr_t<file> p_base,t_size p_buffer_size,abort_callback & p_abort);
|
||||
|
||||
static void g_seekabilize(service_ptr_t<file> & p_reader,t_size p_buffer_size,abort_callback & p_abort);
|
||||
|
||||
t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort);
|
||||
t_filesize get_size(abort_callback & p_abort);
|
||||
t_filesize get_position(abort_callback & p_abort);
|
||||
void seek(t_filesize p_position,abort_callback & p_abort);
|
||||
bool can_seek();
|
||||
bool get_content_type(pfc::string_base & p_out);
|
||||
bool is_in_memory();
|
||||
void on_idle(abort_callback & p_abort);
|
||||
t_filetimestamp get_timestamp(abort_callback & p_abort);
|
||||
void reopen(abort_callback & p_abort);
|
||||
bool is_remote();
|
||||
private:
|
||||
service_ptr_t<file> m_file;
|
||||
seekabilizer_backbuffer m_buffer;
|
||||
t_filesize m_size,m_position,m_position_base;
|
||||
};
|
||||
89
foobar2000/helpers/stream_buffer_helper.cpp
Normal file
89
foobar2000/helpers/stream_buffer_helper.cpp
Normal file
@@ -0,0 +1,89 @@
|
||||
#include "stdafx.h"
|
||||
|
||||
#include "stream_buffer_helper.h"
|
||||
|
||||
stream_reader_buffered::stream_reader_buffered(stream_reader * p_base,t_size p_buffer) : m_base(p_base)
|
||||
{
|
||||
m_buffer.set_size_in_range(pfc::min_t<size_t>(1024, p_buffer), p_buffer);
|
||||
m_bufferRemaining = 0;
|
||||
}
|
||||
|
||||
t_size stream_reader_buffered::read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
||||
if (p_bytes <= m_bufferRemaining) {
|
||||
memcpy( p_buffer, m_bufferPtr, p_bytes );
|
||||
m_bufferRemaining -= p_bytes;
|
||||
m_bufferPtr += p_bytes;
|
||||
return p_bytes;
|
||||
}
|
||||
|
||||
p_abort.check();
|
||||
char * output = (char*) p_buffer;
|
||||
t_size output_ptr = 0;
|
||||
|
||||
while(output_ptr < p_bytes) {
|
||||
{
|
||||
t_size delta = pfc::min_t(p_bytes - output_ptr, m_bufferRemaining);
|
||||
if (delta > 0)
|
||||
{
|
||||
memcpy(output + output_ptr, m_bufferPtr, delta);
|
||||
output_ptr += delta;
|
||||
m_bufferPtr += delta;
|
||||
m_bufferRemaining -= delta;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_bufferRemaining == 0)
|
||||
{
|
||||
t_size bytes_read;
|
||||
bytes_read = m_base->read(m_buffer.get_ptr(), m_buffer.get_size(), p_abort);
|
||||
m_bufferPtr = m_buffer.get_ptr();
|
||||
m_bufferRemaining = bytes_read;
|
||||
|
||||
if (m_bufferRemaining == 0) break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return output_ptr;
|
||||
}
|
||||
|
||||
stream_writer_buffered::stream_writer_buffered(stream_writer * p_base,t_size p_buffer)
|
||||
: m_base(p_base)
|
||||
{
|
||||
m_buffer.set_size_in_range(pfc::min_t<size_t>(1024, p_buffer), p_buffer);
|
||||
m_buffer_ptr = 0;
|
||||
}
|
||||
|
||||
void stream_writer_buffered::write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
||||
p_abort.check_e();
|
||||
const char * source = (const char*)p_buffer;
|
||||
t_size source_remaining = p_bytes;
|
||||
const t_size buffer_size = m_buffer.get_size();
|
||||
if (source_remaining >= buffer_size)
|
||||
{
|
||||
flush(p_abort);
|
||||
m_base->write_object(source,source_remaining,p_abort);
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_buffer_ptr + source_remaining >= buffer_size)
|
||||
{
|
||||
t_size delta = buffer_size - m_buffer_ptr;
|
||||
memcpy(m_buffer.get_ptr() + m_buffer_ptr, source,delta);
|
||||
source += delta;
|
||||
source_remaining -= delta;
|
||||
m_buffer_ptr += delta;
|
||||
flush(p_abort);
|
||||
}
|
||||
|
||||
memcpy(m_buffer.get_ptr() + m_buffer_ptr, source,source_remaining);
|
||||
m_buffer_ptr += source_remaining;
|
||||
}
|
||||
|
||||
|
||||
void stream_writer_buffered::flush(abort_callback & p_abort) {
|
||||
if (m_buffer_ptr > 0) {
|
||||
m_base->write_object(m_buffer.get_ptr(),m_buffer_ptr,p_abort);
|
||||
m_buffer_ptr = 0;
|
||||
}
|
||||
}
|
||||
29
foobar2000/helpers/stream_buffer_helper.h
Normal file
29
foobar2000/helpers/stream_buffer_helper.h
Normal file
@@ -0,0 +1,29 @@
|
||||
#pragma once
|
||||
|
||||
class stream_reader_buffered : public stream_reader
|
||||
{
|
||||
public:
|
||||
stream_reader_buffered(stream_reader * p_base,t_size p_buffer);
|
||||
t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort);
|
||||
private:
|
||||
stream_reader * m_base;
|
||||
pfc::array_t<char> m_buffer;
|
||||
const char * m_bufferPtr;
|
||||
size_t m_bufferRemaining;
|
||||
};
|
||||
|
||||
class stream_writer_buffered : public stream_writer
|
||||
{
|
||||
public:
|
||||
stream_writer_buffered(stream_writer * p_base,t_size p_buffer);
|
||||
|
||||
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort);
|
||||
|
||||
void flush(abort_callback & p_abort);
|
||||
|
||||
private:
|
||||
stream_writer * m_base;
|
||||
pfc::array_t<char> m_buffer;
|
||||
t_size m_buffer_ptr;
|
||||
};
|
||||
|
||||
79
foobar2000/helpers/tag_write_callback_impl.h
Normal file
79
foobar2000/helpers/tag_write_callback_impl.h
Normal file
@@ -0,0 +1,79 @@
|
||||
#pragma once
|
||||
|
||||
#ifdef PFC_WINDOWS_STORE_APP
|
||||
#include <pfc/pp-winapi.h>
|
||||
#endif
|
||||
|
||||
#if defined(_WIN32) && defined(FOOBAR2000_MOBILE)
|
||||
#include <pfc/timers.h>
|
||||
#include <pfc/string_conv.h>
|
||||
#endif
|
||||
|
||||
|
||||
class tag_write_callback_impl : public tag_write_callback {
|
||||
public:
|
||||
tag_write_callback_impl(const char * p_origpath) : m_origpath(p_origpath) {
|
||||
m_fs = filesystem::get(p_origpath);
|
||||
}
|
||||
bool open_temp_file(service_ptr_t<file> & p_out,abort_callback & p_abort) {
|
||||
pfc::dynamic_assert(m_tempfile.is_empty());
|
||||
generate_temp_location_for_file(m_temppath,m_origpath,/*pfc::string_extension(m_origpath)*/ "tmp","retagging temporary file");
|
||||
service_ptr_t<file> l_tempfile;
|
||||
try {
|
||||
openTempFile(l_tempfile, p_abort);
|
||||
} catch(exception_io) {return false;}
|
||||
p_out = m_tempfile = l_tempfile;
|
||||
return true;
|
||||
}
|
||||
bool got_temp_file() const {return m_tempfile.is_valid();}
|
||||
|
||||
// p_owner must be the only reference to open source file, it will be closed + reopened
|
||||
//WARNING: if this errors out, it may leave caller with null file pointer; take appropriate measures not to crash in such cases
|
||||
void finalize(service_ptr_t<file> & p_owner,abort_callback & p_abort) {
|
||||
if (m_tempfile.is_valid()) {
|
||||
m_tempfile->flushFileBuffers_(p_abort);
|
||||
|
||||
if (p_owner.is_valid()) {
|
||||
try {
|
||||
file::g_copy_creation_time(p_owner, m_tempfile, p_abort);
|
||||
} catch (exception_io) {}
|
||||
}
|
||||
|
||||
m_tempfile.release();
|
||||
p_owner.release();
|
||||
handleFileMove(m_temppath, m_origpath, p_abort);
|
||||
input_open_file_helper(p_owner,m_origpath,input_open_info_write,p_abort);
|
||||
}
|
||||
}
|
||||
// Alternate finalizer without owner file object, caller takes responsibility for closing the source file before calling
|
||||
void finalize_no_reopen( abort_callback & p_abort ) {
|
||||
if (m_tempfile.is_valid()) {
|
||||
m_tempfile->flushFileBuffers_(p_abort);
|
||||
m_tempfile.release();
|
||||
handleFileMove(m_temppath, m_origpath, p_abort);
|
||||
}
|
||||
}
|
||||
void handle_failure() throw() {
|
||||
if (m_tempfile.is_valid()) {
|
||||
m_tempfile.release();
|
||||
try {
|
||||
retryOnSharingViolation( 1, fb2k::noAbort, [&] {
|
||||
m_fs->remove(m_temppath, fb2k::noAbort);
|
||||
} );
|
||||
} catch(...) {}
|
||||
}
|
||||
}
|
||||
private:
|
||||
void openTempFile(file::ptr & out, abort_callback & abort) {
|
||||
out = m_fs->openWriteNew(m_temppath, abort, 1 );
|
||||
}
|
||||
void handleFileMove(const char * from, const char * to, abort_callback & abort) {
|
||||
PFC_ASSERT(m_fs->is_our_path(from));
|
||||
PFC_ASSERT(m_fs->is_our_path(to));
|
||||
FB2K_RETRY_FILE_MOVE(m_fs->replace_file(from, to, abort), abort, 10 );
|
||||
}
|
||||
pfc::string8 m_origpath;
|
||||
pfc::string8 m_temppath;
|
||||
service_ptr_t<file> m_tempfile;
|
||||
filesystem::ptr m_fs;
|
||||
};
|
||||
124
foobar2000/helpers/text_file_loader.cpp
Normal file
124
foobar2000/helpers/text_file_loader.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
#include "StdAfx.h"
|
||||
|
||||
#include "text_file_loader.h"
|
||||
|
||||
#include <pfc/string_conv.h>
|
||||
|
||||
static const unsigned char utf8_header[3] = {0xEF,0xBB,0xBF};
|
||||
|
||||
namespace text_file_loader
|
||||
{
|
||||
void write(const service_ptr_t<file> & p_file,abort_callback & p_abort,const char * p_string,bool is_utf8)
|
||||
{
|
||||
p_file->seek(0,p_abort);
|
||||
p_file->set_eof(p_abort);
|
||||
if (is_utf8)
|
||||
{
|
||||
p_file->write_object(utf8_header,sizeof(utf8_header),p_abort);
|
||||
p_file->write_object(p_string,strlen(p_string),p_abort);
|
||||
}
|
||||
else
|
||||
{
|
||||
#ifdef _WIN32
|
||||
pfc::stringcvt::string_ansi_from_utf8 bah(p_string);
|
||||
p_file->write_object(bah,bah.length(),p_abort);
|
||||
#else
|
||||
throw exception_io_data();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
void read(const service_ptr_t<file> & p_file, abort_callback & p_abort, pfc::string_base & p_out, bool & is_utf8) {
|
||||
read_v2( p_file, p_abort, p_out, is_utf8, false );
|
||||
}
|
||||
void read_v2(const service_ptr_t<file> & p_file,abort_callback & p_abort,pfc::string_base & p_out,bool & is_utf8, bool forceUTF8) {
|
||||
p_out.reset();
|
||||
p_file->reopen( p_abort );
|
||||
|
||||
pfc::array_t<char> mem;
|
||||
t_filesize size64;
|
||||
size64 = p_file->get_size(p_abort);
|
||||
if (size64 == filesize_invalid)//typically HTTP
|
||||
{
|
||||
pfc::string8 ansitemp;
|
||||
t_size done;
|
||||
enum { delta = 1024 * 64, max = 1024 * 512 };
|
||||
|
||||
is_utf8 = forceUTF8;
|
||||
char temp[3];
|
||||
done = p_file->read(temp, 3, p_abort);
|
||||
if (done != 3)
|
||||
{
|
||||
if (done > 0) {
|
||||
if ( is_utf8 ) {
|
||||
p_out.set_string( temp, done );
|
||||
} else {
|
||||
p_out = pfc::stringcvt::string_utf8_from_ansi(temp, done);
|
||||
}
|
||||
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!memcmp(utf8_header, temp, 3)) is_utf8 = true;
|
||||
else if (is_utf8) p_out.add_string(temp,3);
|
||||
else ansitemp.add_string(temp, 3);
|
||||
|
||||
mem.set_size(delta);
|
||||
|
||||
for(;;)
|
||||
{
|
||||
done = p_file->read(mem.get_ptr(),delta,p_abort);
|
||||
if (done > 0)
|
||||
{
|
||||
if (is_utf8) p_out.add_string(mem.get_ptr(),done);
|
||||
else ansitemp.add_string(mem.get_ptr(),done);
|
||||
}
|
||||
if (done < delta) break;
|
||||
}
|
||||
|
||||
if (!is_utf8)
|
||||
{
|
||||
p_out = pfc::stringcvt::string_utf8_from_ansi(ansitemp);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (size64>1024*1024*128) throw exception_io_data();//hard limit
|
||||
t_size size = pfc::downcast_guarded<t_size>(size64);
|
||||
mem.set_size(size+1);
|
||||
char * asdf = mem.get_ptr();
|
||||
p_file->read_object(asdf,size,p_abort);
|
||||
asdf[size]=0;
|
||||
if (size>=3 && !memcmp(utf8_header,asdf,3)) {
|
||||
is_utf8 = true;
|
||||
p_out.add_string(asdf+3);
|
||||
} else if (forceUTF8) {
|
||||
is_utf8 = true;
|
||||
p_out = asdf;
|
||||
} else {
|
||||
is_utf8 = false;
|
||||
p_out = pfc::stringcvt::string_utf8_from_ansi(asdf);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void write(const char * p_path,abort_callback & p_abort,const char * p_string,bool is_utf8)
|
||||
{
|
||||
service_ptr_t<file> f;
|
||||
filesystem::g_open_write_new(f,p_path,p_abort);
|
||||
write(f,p_abort,p_string,is_utf8);
|
||||
}
|
||||
|
||||
void read(const char * p_path, abort_callback & p_abort, pfc::string_base & p_out, bool & is_utf8) {
|
||||
read_v2( p_path, p_abort, p_out, is_utf8, false );
|
||||
}
|
||||
void read_v2(const char * p_path,abort_callback & p_abort,pfc::string_base & p_out,bool & is_utf8, bool forceUTF8)
|
||||
{
|
||||
service_ptr_t<file> f;
|
||||
filesystem::g_open_read(f,p_path,p_abort);
|
||||
read_v2(f,p_abort,p_out,is_utf8,forceUTF8);
|
||||
}
|
||||
|
||||
}
|
||||
14
foobar2000/helpers/text_file_loader.h
Normal file
14
foobar2000/helpers/text_file_loader.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
namespace text_file_loader
|
||||
{
|
||||
void write(const service_ptr_t<file> & p_file,abort_callback & p_abort,const char * p_string,bool is_utf8);
|
||||
void read(const service_ptr_t<file> & p_file,abort_callback & p_abort,pfc::string_base & p_out,bool & is_utf8);
|
||||
void read_v2(const service_ptr_t<file> & p_file, abort_callback & p_abort, pfc::string_base & p_out, bool & is_utf8, bool forceUTF8);
|
||||
|
||||
|
||||
void write(const char * p_path,abort_callback & p_abort,const char * p_string,bool is_utf8);
|
||||
void read(const char * p_path,abort_callback & p_abort,pfc::string_base & p_out,bool & is_utf8);
|
||||
void read_v2(const char * p_path, abort_callback & p_abort, pfc::string_base & p_out, bool & is_utf8, bool forceUTF8);
|
||||
|
||||
};
|
||||
25
foobar2000/helpers/text_file_loader_v2.cpp
Normal file
25
foobar2000/helpers/text_file_loader_v2.cpp
Normal file
@@ -0,0 +1,25 @@
|
||||
#include "StdAfx.h"
|
||||
#include "text_file_loader_v2.h"
|
||||
#include "text_file_loader.h"
|
||||
|
||||
void text_file_loader_v2::load(file::ptr f, abort_callback & abort) {
|
||||
m_lines.clear();
|
||||
bool dummy;
|
||||
text_file_loader::read_v2(f, abort, m_data, dummy, m_forceUTF8);
|
||||
|
||||
m_lines.reserve(128);
|
||||
|
||||
char * p = const_cast<char*>(m_data.get_ptr());
|
||||
bool line = false;
|
||||
const size_t len = m_data.length();
|
||||
for (size_t walk = 0; walk < len; ++walk) {
|
||||
char & c = p[walk];
|
||||
if (c == '\n' || c == '\r') {
|
||||
c = 0;
|
||||
line = false;
|
||||
} else if (!line) {
|
||||
m_lines.push_back(&c);
|
||||
line = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
14
foobar2000/helpers/text_file_loader_v2.h
Normal file
14
foobar2000/helpers/text_file_loader_v2.h
Normal file
@@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
#include <vector>
|
||||
#include <list>
|
||||
|
||||
class text_file_loader_v2 {
|
||||
public:
|
||||
bool m_forceUTF8 = false;
|
||||
|
||||
void load(file::ptr f, abort_callback & abort);
|
||||
|
||||
std::vector< const char * > m_lines;
|
||||
|
||||
pfc::string8 m_data;
|
||||
};
|
||||
124
foobar2000/helpers/track_property_callback_impl.cpp
Normal file
124
foobar2000/helpers/track_property_callback_impl.cpp
Normal file
@@ -0,0 +1,124 @@
|
||||
#include "StdAfx.h"
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
|
||||
#include "track_property_callback_impl.h"
|
||||
|
||||
|
||||
void track_property_callback_impl::set_property(const char * p_group, double p_sortpriority, const char * p_name, const char * p_value) {
|
||||
propertyname_container temp;
|
||||
temp.m_name = p_name;
|
||||
temp.m_priority = p_sortpriority;
|
||||
|
||||
pfc::string8 fixEOL;
|
||||
if (m_cutMultiLine && strchr(p_value, '\n') != nullptr) {
|
||||
fixEOL = p_value; fixEOL.fix_eol(); p_value = fixEOL;
|
||||
}
|
||||
|
||||
m_entries.find_or_add(p_group).set(temp, p_value);
|
||||
}
|
||||
|
||||
bool track_property_callback_impl::is_group_wanted(const char * p_group) {
|
||||
if (m_groupFilter) return m_groupFilter(p_group);
|
||||
return true;
|
||||
}
|
||||
|
||||
void track_property_callback_impl::merge(track_property_callback_impl const & other) {
|
||||
for (auto iterGroup = other.m_entries.first(); iterGroup.is_valid(); ++iterGroup) {
|
||||
auto & in = iterGroup->m_value;
|
||||
auto & out = m_entries[iterGroup->m_key];
|
||||
for (auto iterEntry = in.first(); iterEntry.is_valid(); ++iterEntry) {
|
||||
out.set(iterEntry->m_key, iterEntry->m_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool is_filtered_info_field(const char * p_name) {
|
||||
service_ptr_t<track_property_provider> ptr;
|
||||
service_enum_t<track_property_provider> e;
|
||||
while (e.next(ptr)) {
|
||||
if (ptr->is_our_tech_info(p_name)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static const char strGroupOther[] = "Other";
|
||||
|
||||
static void enumOtherHere(track_property_callback_impl & callback, metadb_info_container::ptr info_) {
|
||||
const file_info * infoptr = &info_->info();
|
||||
for (t_size n = 0, m = infoptr->info_get_count(); n < m; n++) {
|
||||
const char * name = infoptr->info_enum_name(n);
|
||||
if (!is_filtered_info_field(name)) {
|
||||
pfc::string_formatter temp;
|
||||
temp << "<";
|
||||
uAddStringUpper(temp, name);
|
||||
temp << ">";
|
||||
callback.set_property("Other", 0, temp, infoptr->info_enum_value(n));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void enumOther( track_property_callback_impl & callback, metadb_handle_list_cref items, track_property_provider_v3_info_source * infoSource ) {
|
||||
if (items.get_count() == 1 ) {
|
||||
enumOtherHere(callback, infoSource->get_info(0) );
|
||||
}
|
||||
}
|
||||
|
||||
void enumerateTrackProperties( track_property_callback_impl & callback, std::function< metadb_handle_list_cref () > itemsSource, std::function<track_property_provider_v3_info_source*()> infoSource, std::function<abort_callback& () > abortSource) {
|
||||
|
||||
|
||||
if ( core_api::is_main_thread() ) {
|
||||
// should not get here like this
|
||||
// but that does make our job easier
|
||||
auto & items = itemsSource();
|
||||
auto info = infoSource();
|
||||
track_property_provider::ptr ptr;
|
||||
service_enum_t<track_property_provider> e;
|
||||
while (e.next(ptr)) {
|
||||
ptr->enumerate_properties_helper(items, info, callback, abortSource() );
|
||||
}
|
||||
if ( callback.is_group_wanted( strGroupOther ) ) {
|
||||
enumOther(callback, items, info );
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
std::list<std::shared_ptr<pfc::event> > lstWaitFor;
|
||||
std::list<std::shared_ptr< track_property_callback_impl > > lstMerge;
|
||||
track_property_provider::ptr ptr;
|
||||
service_enum_t<track_property_provider> e;
|
||||
while (e.next(ptr)) {
|
||||
auto evt = std::make_shared<pfc::event>();
|
||||
auto cb = std::make_shared< track_property_callback_impl >(callback); // clone watched group info
|
||||
auto work = [ptr, itemsSource, evt, cb, infoSource, abortSource] {
|
||||
try {
|
||||
ptr->enumerate_properties_helper(itemsSource(), infoSource(), *cb, abortSource());
|
||||
} catch (...) {}
|
||||
evt->set_state(true);
|
||||
};
|
||||
|
||||
track_property_provider_v4::ptr v4;
|
||||
if (v4 &= ptr) {
|
||||
// Supports v4 = split a worker thread, work in parallel
|
||||
pfc::splitThread(work);
|
||||
} else {
|
||||
// No v4 = delegate to main thread. Ugly but gets the job done.
|
||||
fb2k::inMainThread(work);
|
||||
}
|
||||
|
||||
lstWaitFor.push_back(std::move(evt));
|
||||
lstMerge.push_back(std::move(cb));
|
||||
}
|
||||
|
||||
if (callback.is_group_wanted(strGroupOther)) {
|
||||
enumOther(callback, itemsSource(), infoSource());
|
||||
}
|
||||
|
||||
for (auto i = lstWaitFor.begin(); i != lstWaitFor.end(); ++i) {
|
||||
abortSource().waitForEvent(**i, -1);
|
||||
}
|
||||
for (auto i = lstMerge.begin(); i != lstMerge.end(); ++i) {
|
||||
callback.merge(** i);
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user