latest SDK

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

View File

@@ -0,0 +1,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);
}

View 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);

View 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();}
};

View 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);

View File

@@ -0,0 +1,6 @@
#pragma once
#include <libPPUI/pp-COM-macros.h>
#define FB2K_COM_CATCH PP_COM_CATCH

View File

@@ -0,0 +1,3 @@
#pragma once
#include <libPPUI/CPropVariant.h>

View 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;
};
}

View 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);
}
}

View 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;
};
}

View 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;
};
}

View 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 >();
};
}

View 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

View 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;
}
};

View 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"

View 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_)

View 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

View 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

View 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;
}
};

View 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);
};

View 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);
}

View 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);
}

View 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();
};

View File

@@ -0,0 +1,3 @@
#pragma once
// fb2k mobile compat

View 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;

View File

@@ -0,0 +1,2 @@
#pragma once
// stub, added for compatibility with fb2k mobile source

View 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); }
};

View 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);
}
}

View 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) {}
};

View 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);
}

View 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);
};
};

View 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" );
}
}

View 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);
};

View 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);
}
}

View 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
};

View 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;
}

View 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;
}

View 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;
};

View 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

View 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

View 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

View 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

View 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;
};

View 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;
}

View 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;
};

View 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);
};

View 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();
}
}
};

View 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 );

View File

@@ -0,0 +1 @@
// obsolete, moved to SDK

View 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];
}

View 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;
};

View 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();
}
}

View 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;
};
};

View 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);
}

View 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);
};

View File

@@ -0,0 +1,4 @@
#pragma once
// fb2k mobile compat
#include "../SDK/filesystem_helper.h"

View 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

View 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

View 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;
}

View 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;
};
}

View 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>

View 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>

View 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>

View 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;
};

View 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"

View 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;
};

View 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;
}
}

View 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);
}

View 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) );
}
}

View 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);
}

View 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;
};

View 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); }

View 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;
};

View 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;
}

View 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);

View 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)

View 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

View 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;
};

View 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;
};

View 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;
};

View 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));
}

View 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;
}

View 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;
}

View 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);
};

View 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;
}

View 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);
}
};

View 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;
};

View 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;
};

View 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;
}

View 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); }
};

View 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; }
};
}

View 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();
}

View 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;
};

View 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;
}
}

View 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;
};

View 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;
};

View 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);
}
}

View 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);
};

View 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;
}
}
}

View 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;
};

View 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