Fixed vio2sf interpolation setting, imported foobar2000 0.9 SDK, imported missing files from foobar2000 0.8.3 SDK
This commit is contained in:
parent
16c57263de
commit
024e8b9707
|
@ -1,18 +1,70 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 9.00
|
||||
# Visual C++ Express 2005
|
||||
Microsoft Visual Studio Solution File, Format Version 10.00
|
||||
# Visual Studio 2008
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "foo_input_vio2sf", "foo_input_vio2sf.vcproj", "{22752D82-8F89-4F87-B07E-D254D7BF675A}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{E8091321-D79D-4575-86EF-064EA1A4A20D} = {E8091321-D79D-4575-86EF-064EA1A4A20D}
|
||||
{EE47764E-A202-4F85-A767-ABDAB4AFF35F} = {EE47764E-A202-4F85-A767-ABDAB4AFF35F}
|
||||
{71AD2674-065B-48F5-B8B0-E1F9D3892081} = {71AD2674-065B-48F5-B8B0-E1F9D3892081}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pfc", "src\foobar\pfc\pfc.vcproj", "{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "foobar2000_component_client", "src\foobar\foobar2000\foobar2000_component_client\foobar2000_component_client.vcproj", "{71AD2674-065B-48F5-B8B0-E1F9D3892081}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "foobar2000_sdk_helpers", "src\foobar\foobar2000\helpers\foobar2000_sdk_helpers.vcproj", "{EE47764E-A202-4F85-A767-ABDAB4AFF35F}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "foobar2000_SDK", "src\foobar\foobar2000\SDK\foobar2000_SDK.vcproj", "{E8091321-D79D-4575-86EF-064EA1A4A20D}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C} = {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Win32 = Debug|Win32
|
||||
Debug|x64 = Debug|x64
|
||||
Release|Win32 = Release|Win32
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{22752D82-8F89-4F87-B07E-D254D7BF675A}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{22752D82-8F89-4F87-B07E-D254D7BF675A}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{22752D82-8F89-4F87-B07E-D254D7BF675A}.Debug|x64.ActiveCfg = Debug|Win32
|
||||
{22752D82-8F89-4F87-B07E-D254D7BF675A}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{22752D82-8F89-4F87-B07E-D254D7BF675A}.Release|Win32.Build.0 = Release|Win32
|
||||
{22752D82-8F89-4F87-B07E-D254D7BF675A}.Release|x64.ActiveCfg = Release|Win32
|
||||
{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug|x64.Build.0 = Debug|x64
|
||||
{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release|Win32.Build.0 = Release|Win32
|
||||
{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release|x64.ActiveCfg = Release|x64
|
||||
{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release|x64.Build.0 = Release|x64
|
||||
{71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug|x64.Build.0 = Debug|x64
|
||||
{71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release|Win32.Build.0 = Release|Win32
|
||||
{71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release|x64.ActiveCfg = Release|x64
|
||||
{71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release|x64.Build.0 = Release|x64
|
||||
{EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Debug|x64.Build.0 = Debug|x64
|
||||
{EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Release|Win32.Build.0 = Release|Win32
|
||||
{EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Release|x64.ActiveCfg = Release|x64
|
||||
{EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Release|x64.Build.0 = Release|x64
|
||||
{E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug|x64.Build.0 = Debug|x64
|
||||
{E8091321-D79D-4575-86EF-064EA1A4A20D}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{E8091321-D79D-4575-86EF-064EA1A4A20D}.Release|Win32.Build.0 = Release|Win32
|
||||
{E8091321-D79D-4575-86EF-064EA1A4A20D}.Release|x64.ActiveCfg = Release|x64
|
||||
{E8091321-D79D-4575-86EF-064EA1A4A20D}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
@ -1,11 +1,12 @@
|
|||
<?xml version="1.0" encoding="shift_jis"?>
|
||||
<VisualStudioProject
|
||||
ProjectType="Visual C++"
|
||||
Version="8.00"
|
||||
Version="9.00"
|
||||
Name="foo_input_vio2sf"
|
||||
ProjectGUID="{22752D82-8F89-4F87-B07E-D254D7BF675A}"
|
||||
RootNamespace="foo_input_vio2sf"
|
||||
Keyword="Win32Proj"
|
||||
TargetFrameworkVersion="131072"
|
||||
>
|
||||
<Platforms>
|
||||
<Platform
|
||||
|
@ -40,6 +41,7 @@
|
|||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
Optimization="0"
|
||||
AdditionalIncludeDirectories="src/foobar;src"
|
||||
PreprocessorDefinitions="WIN32;_DEBUG;_WINDOWS;_USRDLL;FOO_INPUT_VIO2SF_EXPORTS"
|
||||
MinimalRebuild="true"
|
||||
BasicRuntimeChecks="3"
|
||||
|
@ -60,11 +62,12 @@
|
|||
/>
|
||||
<Tool
|
||||
Name="VCLinkerTool"
|
||||
AdditionalDependencies="pfcD.lib foobar2000_SDKD.lib foobar2000_component_clientD.lib shared.lib"
|
||||
OutputFile="C:\Program Files\foobar2000\components\$(ProjectName).dll"
|
||||
OutputFile="$(SolutionDir)$(ProjectName).dll"
|
||||
LinkIncremental="2"
|
||||
GenerateDebugInformation="true"
|
||||
SubSystem="2"
|
||||
RandomizedBaseAddress="1"
|
||||
DataExecutionPrevention="0"
|
||||
TargetMachine="1"
|
||||
/>
|
||||
<Tool
|
||||
|
@ -85,9 +88,6 @@
|
|||
<Tool
|
||||
Name="VCAppVerifierTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCWebDeploymentTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPostBuildEventTool"
|
||||
/>
|
||||
|
@ -118,9 +118,11 @@
|
|||
<Tool
|
||||
Name="VCCLCompilerTool"
|
||||
EnableIntrinsicFunctions="false"
|
||||
AdditionalIncludeDirectories="src/foobar;src"
|
||||
PreprocessorDefinitions="WIN32;NDEBUG;_WINDOWS;_USRDLL;FOO_INPUT_VIO2SF_EXPORTS"
|
||||
StringPooling="true"
|
||||
RuntimeLibrary="0"
|
||||
FloatingPointModel="2"
|
||||
UsePrecompiledHeader="0"
|
||||
WarningLevel="3"
|
||||
Detect64BitPortabilityProblems="true"
|
||||
|
@ -137,7 +139,6 @@
|
|||
/>
|
||||
<Tool
|
||||
Name="VCLinkerTool"
|
||||
AdditionalDependencies="pfc.lib foobar2000_SDK.lib foobar2000_component_client.lib shared.lib"
|
||||
OutputFile="$(SolutionDir)$(ProjectName).dll"
|
||||
LinkIncremental="1"
|
||||
GenerateManifest="true"
|
||||
|
@ -146,6 +147,8 @@
|
|||
OptimizeReferences="2"
|
||||
EnableCOMDATFolding="2"
|
||||
SetChecksum="true"
|
||||
RandomizedBaseAddress="1"
|
||||
DataExecutionPrevention="0"
|
||||
TargetMachine="1"
|
||||
/>
|
||||
<Tool
|
||||
|
@ -167,9 +170,6 @@
|
|||
<Tool
|
||||
Name="VCAppVerifierTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCWebDeploymentTool"
|
||||
/>
|
||||
<Tool
|
||||
Name="VCPostBuildEventTool"
|
||||
/>
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 10.00
|
||||
# Visual Studio 2008
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "foobar2000_SDK", "foobar2000\SDK\foobar2000_SDK.vcproj", "{E8091321-D79D-4575-86EF-064EA1A4A20D}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C} = {EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "foobar2000_ATL_helpers", "foobar2000\ATLHelpers\foobar2000_ATL_helpers.vcproj", "{622E8B19-8109-4717-BD4D-9657AA78363E}"
|
||||
ProjectSection(ProjectDependencies) = postProject
|
||||
{E8091321-D79D-4575-86EF-064EA1A4A20D} = {E8091321-D79D-4575-86EF-064EA1A4A20D}
|
||||
{EE47764E-A202-4F85-A767-ABDAB4AFF35F} = {EE47764E-A202-4F85-A767-ABDAB4AFF35F}
|
||||
EndProjectSection
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "foobar2000_component_client", "foobar2000\foobar2000_component_client\foobar2000_component_client.vcproj", "{71AD2674-065B-48F5-B8B0-E1F9D3892081}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "foobar2000_sdk_helpers", "foobar2000\helpers\foobar2000_sdk_helpers.vcproj", "{EE47764E-A202-4F85-A767-ABDAB4AFF35F}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pfc", "pfc\pfc.vcproj", "{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Win32 = Debug|Win32
|
||||
Debug|x64 = Debug|x64
|
||||
Release|Win32 = Release|Win32
|
||||
Release|x64 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{E8091321-D79D-4575-86EF-064EA1A4A20D}.Debug|x64.Build.0 = Debug|x64
|
||||
{E8091321-D79D-4575-86EF-064EA1A4A20D}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{E8091321-D79D-4575-86EF-064EA1A4A20D}.Release|Win32.Build.0 = Release|Win32
|
||||
{E8091321-D79D-4575-86EF-064EA1A4A20D}.Release|x64.ActiveCfg = Release|x64
|
||||
{E8091321-D79D-4575-86EF-064EA1A4A20D}.Release|x64.Build.0 = Release|x64
|
||||
{622E8B19-8109-4717-BD4D-9657AA78363E}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{622E8B19-8109-4717-BD4D-9657AA78363E}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{622E8B19-8109-4717-BD4D-9657AA78363E}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{622E8B19-8109-4717-BD4D-9657AA78363E}.Debug|x64.Build.0 = Debug|x64
|
||||
{622E8B19-8109-4717-BD4D-9657AA78363E}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{622E8B19-8109-4717-BD4D-9657AA78363E}.Release|Win32.Build.0 = Release|Win32
|
||||
{622E8B19-8109-4717-BD4D-9657AA78363E}.Release|x64.ActiveCfg = Release|x64
|
||||
{622E8B19-8109-4717-BD4D-9657AA78363E}.Release|x64.Build.0 = Release|x64
|
||||
{71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{71AD2674-065B-48F5-B8B0-E1F9D3892081}.Debug|x64.Build.0 = Debug|x64
|
||||
{71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release|Win32.Build.0 = Release|Win32
|
||||
{71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release|x64.ActiveCfg = Release|x64
|
||||
{71AD2674-065B-48F5-B8B0-E1F9D3892081}.Release|x64.Build.0 = Release|x64
|
||||
{EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Debug|x64.Build.0 = Debug|x64
|
||||
{EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Release|Win32.Build.0 = Release|Win32
|
||||
{EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Release|x64.ActiveCfg = Release|x64
|
||||
{EE47764E-A202-4F85-A767-ABDAB4AFF35F}.Release|x64.Build.0 = Release|x64
|
||||
{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug|Win32.ActiveCfg = Debug|Win32
|
||||
{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug|Win32.Build.0 = Debug|Win32
|
||||
{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Debug|x64.Build.0 = Debug|x64
|
||||
{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release|Win32.ActiveCfg = Release|Win32
|
||||
{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release|Win32.Build.0 = Release|Win32
|
||||
{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release|x64.ActiveCfg = Release|x64
|
||||
{EBFFFB4E-261D-44D3-B89C-957B31A0BF9C}.Release|x64.Build.0 = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,17 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
void abort_callback::check() const {
|
||||
if (is_aborting()) throw exception_aborted();
|
||||
}
|
||||
|
||||
void abort_callback::sleep(double p_timeout_seconds) const {
|
||||
if (!sleep_ex(p_timeout_seconds)) throw exception_aborted();
|
||||
}
|
||||
|
||||
bool abort_callback::sleep_ex(double p_timeout_seconds) const {
|
||||
#ifdef _WIN32
|
||||
return !win32_event::g_wait_for(get_abort_event(),p_timeout_seconds);
|
||||
#else
|
||||
#error PORTME
|
||||
#endif
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
#ifndef _foobar2000_sdk_abort_callback_h_
|
||||
#define _foobar2000_sdk_abort_callback_h_
|
||||
|
||||
namespace foobar2000_io {
|
||||
|
||||
PFC_DECLARE_EXCEPTION(exception_aborted,pfc::exception,"User abort");
|
||||
|
||||
#ifdef _WIN32
|
||||
typedef HANDLE abort_callback_event;
|
||||
#else
|
||||
#error PORTME
|
||||
#endif
|
||||
|
||||
//! This class is used to signal underlying worker code whether user has decided to abort a potentially time-consuming operation. It is commonly required by all file related operations. Code that receives an abort_callback object should periodically check it and abort any operations being performed if it is signaled, typically throwing exception_aborted. \n
|
||||
//! See abort_callback_impl for an implementation.
|
||||
class NOVTABLE abort_callback
|
||||
{
|
||||
public:
|
||||
//! Returns whether user has requested the operation to be aborted.
|
||||
virtual bool is_aborting() const = 0;
|
||||
|
||||
//! Retrieves event object that can be used with some OS calls. The even object becomes signaled when abort is triggered. On win32, this is equivalent to win32 event handle (see: CreateEvent). \n
|
||||
//! You must not close this handle or call any methods that change this handle's state (SetEvent() or ResetEvent()), you can only wait for it.
|
||||
virtual abort_callback_event get_abort_event() const = 0;
|
||||
|
||||
//! Checks if user has requested the operation to be aborted, and throws exception_aborted if so.
|
||||
void check() const;
|
||||
|
||||
//! For compatibility with old code. Do not call.
|
||||
inline void check_e() const {check();}
|
||||
|
||||
|
||||
//! Sleeps p_timeout_seconds or less when aborted, throws exception_aborted on abort.
|
||||
void sleep(double p_timeout_seconds) const;
|
||||
//! Sleeps p_timeout_seconds or less when aborted, returns true when execution should continue, false when not.
|
||||
bool sleep_ex(double p_timeout_seconds) const;
|
||||
protected:
|
||||
abort_callback() {}
|
||||
~abort_callback() {}
|
||||
};
|
||||
|
||||
|
||||
|
||||
//! Implementation of abort_callback interface.
|
||||
class abort_callback_impl : public abort_callback {
|
||||
public:
|
||||
abort_callback_impl() : m_aborting(false) {
|
||||
m_event.create(true,false);
|
||||
}
|
||||
inline void abort() {set_state(true);}
|
||||
inline void reset() {set_state(false);}
|
||||
|
||||
void set_state(bool p_state) {m_aborting = p_state; m_event.set_state(p_state);}
|
||||
|
||||
bool is_aborting() const {return m_aborting;}
|
||||
|
||||
abort_callback_event get_abort_event() const {return m_event.get();}
|
||||
|
||||
private:
|
||||
abort_callback_impl(const abort_callback_impl &) {throw pfc::exception_not_implemented();}
|
||||
const abort_callback_impl & operator=(const abort_callback_impl&) {throw pfc::exception_not_implemented();}
|
||||
|
||||
volatile bool m_aborting;
|
||||
#ifdef WIN32
|
||||
win32_event m_event;
|
||||
#endif
|
||||
};
|
||||
|
||||
//! Dummy abort_callback that never gets aborted. To be possibly optimized in the future.
|
||||
typedef abort_callback_impl abort_callback_dummy;
|
||||
|
||||
}
|
||||
|
||||
using namespace foobar2000_io;
|
||||
|
||||
#endif //_foobar2000_sdk_abort_callback_h_
|
|
@ -0,0 +1,266 @@
|
|||
//! Entrypoint class for adding items to Advanced Preferences page. \n
|
||||
//! Implementations must derive from one of subclasses: advconfig_branch, advconfig_entry_checkbox, advconfig_entry_string. \n
|
||||
//! Implementations are typically registered using static service_factory_single_t<myclass>, or using provided helper classes in case of standard implementations declared in this header.
|
||||
class NOVTABLE advconfig_entry : public service_base {
|
||||
public:
|
||||
virtual void get_name(pfc::string_base & p_out) = 0;
|
||||
virtual GUID get_guid() = 0;
|
||||
virtual GUID get_parent() = 0;
|
||||
virtual void reset() = 0;
|
||||
virtual double get_sort_priority() = 0;
|
||||
|
||||
static bool g_find(service_ptr_t<advconfig_entry>& out, const GUID & id) {
|
||||
service_enum_t<advconfig_entry> e; service_ptr_t<advconfig_entry> ptr; while(e.next(ptr)) { if (ptr->get_guid() == id) {out = ptr; return true;} } return false;
|
||||
}
|
||||
|
||||
static const GUID guid_root;
|
||||
static const GUID guid_branch_tagging,guid_branch_decoding,guid_branch_tools,guid_branch_playback,guid_branch_display;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(advconfig_entry);
|
||||
};
|
||||
|
||||
//! Creates a new branch in Advanced Preferences. \n
|
||||
//! Implementation: see advconfig_branch_impl / advconfig_branch_factory.
|
||||
class NOVTABLE advconfig_branch : public advconfig_entry {
|
||||
public:
|
||||
FB2K_MAKE_SERVICE_INTERFACE(advconfig_branch,advconfig_entry);
|
||||
};
|
||||
|
||||
//! Creates a checkbox/radiocheckbox entry in Advanced Preferences. \n
|
||||
//! The difference between checkboxes and radiocheckboxes is different icon (obviously) and that checking a radiocheckbox unchecks all other radiocheckboxes in the same branch. \n
|
||||
//! Implementation: see advconfig_entry_checkbox_impl / advconfig_checkbox_factory_t.
|
||||
class NOVTABLE advconfig_entry_checkbox : public advconfig_entry {
|
||||
public:
|
||||
virtual bool get_state() = 0;
|
||||
virtual void set_state(bool p_state) = 0;
|
||||
virtual bool is_radio() = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(advconfig_entry_checkbox,advconfig_entry);
|
||||
};
|
||||
|
||||
//! Creates a string/integer editbox entry in Advanced Preferences.\n
|
||||
//! Implementation: see advconfig_entry_string_impl / advconfig_string_factory.
|
||||
class NOVTABLE advconfig_entry_string : public advconfig_entry {
|
||||
public:
|
||||
virtual void get_state(pfc::string_base & p_out) = 0;
|
||||
virtual void set_state(const char * p_string,t_size p_length = infinite) = 0;
|
||||
virtual t_uint32 get_flags() = 0;
|
||||
|
||||
enum {
|
||||
flag_is_integer = 1 << 0,
|
||||
flag_is_signed = 1 << 1,
|
||||
};
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(advconfig_entry_string,advconfig_entry);
|
||||
};
|
||||
|
||||
|
||||
//! Standard implementation of advconfig_branch. \n
|
||||
//! Usage: no need to use this class directly - use advconfig_branch_factory instead.
|
||||
class advconfig_branch_impl : public advconfig_branch {
|
||||
public:
|
||||
advconfig_branch_impl(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority) : m_name(p_name), m_guid(p_guid), m_parent(p_parent), m_priority(p_priority) {}
|
||||
void get_name(pfc::string_base & p_out) {p_out = m_name;}
|
||||
GUID get_guid() {return m_guid;}
|
||||
GUID get_parent() {return m_parent;}
|
||||
void reset() {}
|
||||
double get_sort_priority() {return m_priority;}
|
||||
private:
|
||||
pfc::string8 m_name;
|
||||
GUID m_guid,m_parent;
|
||||
const double m_priority;
|
||||
};
|
||||
|
||||
//! Standard implementation of advconfig_entry_checkbox. \n
|
||||
//! p_is_radio parameter controls whether we're implementing a checkbox or a radiocheckbox (see advconfig_entry_checkbox description for more details).
|
||||
template<bool p_is_radio = false>
|
||||
class advconfig_entry_checkbox_impl : public advconfig_entry_checkbox {
|
||||
public:
|
||||
advconfig_entry_checkbox_impl(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,bool p_initialstate)
|
||||
: m_name(p_name), m_initialstate(p_initialstate), m_state(p_guid,p_initialstate), m_parent(p_parent), m_priority(p_priority) {}
|
||||
|
||||
void get_name(pfc::string_base & p_out) {p_out = m_name;}
|
||||
GUID get_guid() {return m_state.get_guid();}
|
||||
GUID get_parent() {return m_parent;}
|
||||
void reset() {m_state = m_initialstate;}
|
||||
bool get_state() {return m_state;}
|
||||
void set_state(bool p_state) {m_state = p_state;}
|
||||
bool is_radio() {return p_is_radio;}
|
||||
double get_sort_priority() {return m_priority;}
|
||||
bool get_state_() const {return m_state;}
|
||||
private:
|
||||
pfc::string8 m_name;
|
||||
const bool m_initialstate;
|
||||
cfg_bool m_state;
|
||||
GUID m_parent;
|
||||
const double m_priority;
|
||||
};
|
||||
|
||||
//! Service factory helper around standard advconfig_branch implementation. Use this class to register your own Advanced Preferences branches. \n
|
||||
//! Usage: static advconfig_branch_factory mybranch(name, branchID, parentBranchID, priority);
|
||||
class advconfig_branch_factory : public service_factory_single_t<advconfig_branch_impl> {
|
||||
public:
|
||||
advconfig_branch_factory(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority)
|
||||
: service_factory_single_t<advconfig_branch_impl>(p_name,p_guid,p_parent,p_priority) {}
|
||||
};
|
||||
|
||||
//! Service factory helper around standard advconfig_entry_checkbox implementation. Use this class to register your own Advanced Preferences checkbox/radiocheckbox entries. \n
|
||||
//! Usage: static advconfig_entry_checkbox<isRadioBox> mybox(name, itemID, parentID, priority, initialstate);
|
||||
template<bool p_is_radio>
|
||||
class advconfig_checkbox_factory_t : public service_factory_single_t<advconfig_entry_checkbox_impl<p_is_radio> > {
|
||||
public:
|
||||
advconfig_checkbox_factory_t(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,bool p_initialstate)
|
||||
: service_factory_single_t<advconfig_entry_checkbox_impl<p_is_radio> >(p_name,p_guid,p_parent,p_priority,p_initialstate) {}
|
||||
|
||||
bool get() const {return get_static_instance().get_state_();}
|
||||
void set(bool val) {get_static_instance().set_state(val);}
|
||||
operator bool() const {return get();}
|
||||
bool operator=(bool val) {set(val); return val;}
|
||||
};
|
||||
|
||||
//! Service factory helper around standard advconfig_entry_checkbox implementation, specialized for checkboxes (rather than radiocheckboxes). See advconfig_checkbox_factory_t<> for more details.
|
||||
typedef advconfig_checkbox_factory_t<false> advconfig_checkbox_factory;
|
||||
//! Service factory helper around standard advconfig_entry_checkbox implementation, specialized for radiocheckboxes (rather than standard checkboxes). See advconfig_checkbox_factory_t<> for more details.
|
||||
typedef advconfig_checkbox_factory_t<true> advconfig_radio_factory;
|
||||
|
||||
|
||||
//! Standard advconfig_entry_string implementation. Use advconfig_string_factory to register your own string entries in Advanced Preferences instead of using this class directly.
|
||||
class advconfig_entry_string_impl : public advconfig_entry_string {
|
||||
public:
|
||||
advconfig_entry_string_impl(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,const char * p_initialstate)
|
||||
: m_name(p_name), m_parent(p_parent), m_priority(p_priority), m_initialstate(p_initialstate), m_state(p_guid,p_initialstate) {}
|
||||
void get_name(pfc::string_base & p_out) {p_out = m_name;}
|
||||
GUID get_guid() {return m_state.get_guid();}
|
||||
GUID get_parent() {return m_parent;}
|
||||
void reset() {core_api::ensure_main_thread();m_state = m_initialstate;}
|
||||
double get_sort_priority() {return m_priority;}
|
||||
void get_state(pfc::string_base & p_out) {core_api::ensure_main_thread();p_out = m_state;}
|
||||
void set_state(const char * p_string,t_size p_length = infinite) {core_api::ensure_main_thread();m_state.set_string(p_string,p_length);}
|
||||
t_uint32 get_flags() {return 0;}
|
||||
private:
|
||||
const pfc::string8 m_initialstate, m_name;
|
||||
cfg_string m_state;
|
||||
const double m_priority;
|
||||
const GUID m_parent;
|
||||
};
|
||||
|
||||
//! Service factory helper around standard advconfig_entry_string implementation. Use this class to register your own string entries in Advanced Preferences. \n
|
||||
//! Usage: static advconfig_string_factory mystring(name, itemID, branchID, priority, initialValue);
|
||||
class advconfig_string_factory : public service_factory_single_t<advconfig_entry_string_impl> {
|
||||
public:
|
||||
advconfig_string_factory(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,const char * p_initialstate)
|
||||
: service_factory_single_t<advconfig_entry_string_impl>(p_name,p_guid,p_parent,p_priority,p_initialstate) {}
|
||||
|
||||
void get(pfc::string_base & out) {get_static_instance().get_state(out);}
|
||||
void set(const char * in) {get_static_instance().set_state(in);}
|
||||
};
|
||||
|
||||
|
||||
//! Special advconfig_entry_string implementation - implements integer entries. Use advconfig_integer_factory to register your own integer entries in Advanced Preferences instead of using this class directly.
|
||||
class advconfig_entry_integer_impl : public advconfig_entry_string {
|
||||
public:
|
||||
advconfig_entry_integer_impl(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,t_uint64 p_initialstate,t_uint64 p_min,t_uint64 p_max)
|
||||
: m_name(p_name), m_parent(p_parent), m_priority(p_priority), m_initval(p_initialstate), m_min(p_min), m_max(p_max), m_state(p_guid,p_initialstate) {}
|
||||
void get_name(pfc::string_base & p_out) {p_out = m_name;}
|
||||
GUID get_guid() {return m_state.get_guid();}
|
||||
GUID get_parent() {return m_parent;}
|
||||
void reset() {m_state = m_initval;}
|
||||
double get_sort_priority() {return m_priority;}
|
||||
void get_state(pfc::string_base & p_out) {p_out = pfc::format_uint(m_state.get_value());}
|
||||
void set_state(const char * p_string,t_size p_length) {set_state_int(pfc::atoui64_ex(p_string,p_length));}
|
||||
t_uint32 get_flags() {return advconfig_entry_string::flag_is_integer;}
|
||||
|
||||
t_uint64 get_state_int() const {return m_state;}
|
||||
void set_state_int(t_uint64 val) {m_state = pfc::clip_t<t_uint64>(val,m_min,m_max);}
|
||||
private:
|
||||
cfg_int_t<t_uint64> m_state;
|
||||
const double m_priority;
|
||||
const t_uint64 m_initval, m_min, m_max;
|
||||
const GUID m_parent;
|
||||
const pfc::string8 m_name;
|
||||
};
|
||||
|
||||
//! Service factory helper around integer-specialized advconfig_entry_string implementation. Use this class to register your own integer entries in Advanced Preferences. \n
|
||||
//! Usage: static advconfig_integer_factory myint(name, itemID, parentID, priority, initialValue, minValue, maxValue);
|
||||
class advconfig_integer_factory : public service_factory_single_t<advconfig_entry_integer_impl> {
|
||||
public:
|
||||
advconfig_integer_factory(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,t_uint64 p_initialstate,t_uint64 p_min,t_uint64 p_max)
|
||||
: service_factory_single_t<advconfig_entry_integer_impl>(p_name,p_guid,p_parent,p_priority,p_initialstate,p_min,p_max) {}
|
||||
|
||||
t_uint64 get() const {return get_static_instance().get_state_int();}
|
||||
void set(t_uint64 val) {get_static_instance().set_state_int(val);}
|
||||
|
||||
operator t_uint64() const {return get();}
|
||||
t_uint64 operator=(t_uint64 val) {set(val); return val;}
|
||||
};
|
||||
|
||||
|
||||
//! Not currently used, reserved for future use.
|
||||
class NOVTABLE advconfig_entry_enum : public advconfig_entry {
|
||||
public:
|
||||
virtual t_size get_value_count() = 0;
|
||||
virtual void enum_value(pfc::string_base & p_out,t_size p_index) = 0;
|
||||
virtual t_size get_state() = 0;
|
||||
virtual void set_state(t_size p_value) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(advconfig_entry_enum,advconfig_entry);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
//! Special version if advconfig_entry_string_impl that allows the value to be retrieved from worker threads.
|
||||
class advconfig_entry_string_impl_MT : public advconfig_entry_string {
|
||||
public:
|
||||
advconfig_entry_string_impl_MT(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,const char * p_initialstate)
|
||||
: m_name(p_name), m_parent(p_parent), m_priority(p_priority), m_initialstate(p_initialstate), m_state(p_guid,p_initialstate) {}
|
||||
void get_name(pfc::string_base & p_out) {p_out = m_name;}
|
||||
GUID get_guid() {return m_state.get_guid();}
|
||||
GUID get_parent() {return m_parent;}
|
||||
void reset() {
|
||||
insync(m_sync);
|
||||
m_state = m_initialstate;
|
||||
}
|
||||
double get_sort_priority() {return m_priority;}
|
||||
void get_state(pfc::string_base & p_out) {
|
||||
insync(m_sync);
|
||||
p_out = m_state;
|
||||
}
|
||||
void set_state(const char * p_string,t_size p_length = infinite) {
|
||||
insync(m_sync);
|
||||
m_state.set_string(p_string,p_length);
|
||||
}
|
||||
t_uint32 get_flags() {return 0;}
|
||||
private:
|
||||
const pfc::string8 m_initialstate, m_name;
|
||||
cfg_string m_state;
|
||||
critical_section m_sync;
|
||||
const double m_priority;
|
||||
const GUID m_parent;
|
||||
};
|
||||
|
||||
//! Special version if advconfig_string_factory that allows the value to be retrieved from worker threads.
|
||||
class advconfig_string_factory_MT : public service_factory_single_t<advconfig_entry_string_impl_MT> {
|
||||
public:
|
||||
advconfig_string_factory_MT(const char * p_name,const GUID & p_guid,const GUID & p_parent,double p_priority,const char * p_initialstate)
|
||||
: service_factory_single_t<advconfig_entry_string_impl_MT>(p_name,p_guid,p_parent,p_priority,p_initialstate) {}
|
||||
|
||||
void get(pfc::string_base & out) {get_static_instance().get_state(out);}
|
||||
void set(const char * in) {get_static_instance().set_state(in);}
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
/*
|
||||
Advanced Preferences variable declaration examples
|
||||
|
||||
static advconfig_string_factory mystring("name goes here",myguid,parentguid,0,"asdf");
|
||||
to retrieve state: pfc::string8 val; mystring.get(val);
|
||||
|
||||
static advconfig_checkbox_factory mycheckbox("name goes here",myguid,parentguid,0,false);
|
||||
to retrieve state: mycheckbox.get();
|
||||
|
||||
static advconfig_integer_factory myint("name goes here",myguid,parentguid,0,initialValue,minimumValue,maximumValue);
|
||||
to retrieve state: myint.get();
|
||||
*/
|
|
@ -0,0 +1,17 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
bool album_art_editor::g_get_interface(service_ptr_t<album_art_editor> & out,const char * path) {
|
||||
service_enum_t<album_art_editor> e; ptr ptr;
|
||||
pfc::string_extension ext(path);
|
||||
while(e.next(ptr)) {
|
||||
if (ptr->is_our_path(path,ext)) {
|
||||
out = ptr; return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool album_art_editor::g_is_supported_path(const char * path) {
|
||||
ptr ptr;
|
||||
return g_get_interface(ptr,path);
|
||||
}
|
|
@ -0,0 +1,222 @@
|
|||
//new in 0.9.5
|
||||
|
||||
//! Common class for handling picture data. \n
|
||||
//! Type of contained picture data is unknown and to be determined according to memory block contents by code parsing/rendering the picture. Commonly encountered types are: BMP, PNG, JPEG and GIF. \n
|
||||
//! Implementation: use album_art_data_impl.
|
||||
class NOVTABLE album_art_data : public service_base {
|
||||
public:
|
||||
//! Retrieves a pointer to a memory block containing the picture.
|
||||
virtual const void * get_ptr() const = 0;
|
||||
//! Retrieves size of the memory block containing the picture.
|
||||
virtual t_size get_size() const = 0;
|
||||
|
||||
//! Determine whether two album_art_data objects store the same picture data.
|
||||
static bool equals(album_art_data const & v1, album_art_data const & v2) {
|
||||
const t_size s = v1.get_size();
|
||||
if (s != v2.get_size()) return false;
|
||||
return memcmp(v1.get_ptr(), v2.get_ptr(),s) == 0;
|
||||
}
|
||||
bool operator==(const album_art_data & other) const {return equals(*this,other);}
|
||||
bool operator!=(const album_art_data & other) const {return !equals(*this,other);}
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(album_art_data,service_base);
|
||||
};
|
||||
|
||||
typedef service_ptr_t<album_art_data> album_art_data_ptr;
|
||||
|
||||
//! Implements album_art_data.
|
||||
class album_art_data_impl : public album_art_data {
|
||||
public:
|
||||
const void * get_ptr() const {return m_content.get_ptr();}
|
||||
t_size get_size() const {return m_content.get_size();}
|
||||
|
||||
void * get_ptr() {return m_content.get_ptr();}
|
||||
void set_size(t_size p_size) {m_content.set_size(p_size);}
|
||||
|
||||
//! Reads picture data from the specified stream object.
|
||||
void from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort) {
|
||||
set_size(p_bytes); p_stream->read_object(get_ptr(),p_bytes,p_abort);
|
||||
}
|
||||
|
||||
//! Creates an album_art_data object from picture data contained in a memory buffer.
|
||||
static album_art_data_ptr g_create(const void * p_buffer,t_size p_bytes) {
|
||||
service_ptr_t<album_art_data_impl> instance = new service_impl_t<album_art_data_impl>();
|
||||
instance->set_size(p_bytes);
|
||||
memcpy(instance->get_ptr(),p_buffer,p_bytes);
|
||||
return instance;
|
||||
}
|
||||
//! Creates an album_art_data object from picture data contained in a stream.
|
||||
static album_art_data_ptr g_create(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort) {
|
||||
service_ptr_t<album_art_data_impl> instance = new service_impl_t<album_art_data_impl>();
|
||||
instance->from_stream(p_stream,p_bytes,p_abort);
|
||||
return instance;
|
||||
}
|
||||
|
||||
private:
|
||||
pfc::array_t<t_uint8> m_content;
|
||||
};
|
||||
|
||||
//! Namespace containing identifiers of album art types.
|
||||
namespace album_art_ids {
|
||||
//! Front cover.
|
||||
static const GUID cover_front = { 0xf1e66f4e, 0xfe09, 0x4b94, { 0x91, 0xa3, 0x67, 0xc2, 0x3e, 0xd1, 0x44, 0x5e } };
|
||||
//! Back cover.
|
||||
static const GUID cover_back = { 0xcb552d19, 0x86d5, 0x434c, { 0xac, 0x77, 0xbb, 0x24, 0xed, 0x56, 0x7e, 0xe4 } };
|
||||
//! Picture of a disc or other storage media.
|
||||
static const GUID disc = { 0x3dba9f36, 0xf928, 0x4fa4, { 0x87, 0x9c, 0xd3, 0x40, 0x47, 0x59, 0x58, 0x7e } };
|
||||
//! Album-specific icon (NOT a file type icon).
|
||||
static const GUID icon = { 0x74cdf5b4, 0x7053, 0x4b3d, { 0x9a, 0x3c, 0x54, 0x69, 0xf5, 0x82, 0x6e, 0xec } };
|
||||
};
|
||||
|
||||
PFC_DECLARE_EXCEPTION(exception_album_art_not_found,exception_io_not_found,"Album Art Not Found");
|
||||
PFC_DECLARE_EXCEPTION(exception_album_art_unsupported_entry,exception_io_data,"Unsupported Album Art Entry");
|
||||
|
||||
//! Class encapsulating access to album art stored in a media file. Use album_art_extractor class obtain album_art_extractor_instance referring to specified media file.
|
||||
class NOVTABLE album_art_extractor_instance : public service_base {
|
||||
public:
|
||||
//! Throws exception_album_art_not_found when the requested album art entry could not be found in the referenced media file.
|
||||
virtual album_art_data_ptr query(const GUID & p_what,abort_callback & p_abort) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(album_art_extractor_instance,service_base);
|
||||
};
|
||||
|
||||
//! Class encapsulating access to album art stored in a media file. Use album_art_editor class to obtain album_art_editor_instance referring to specified media file.
|
||||
class NOVTABLE album_art_editor_instance : public album_art_extractor_instance {
|
||||
public:
|
||||
//! Throws exception_album_art_unsupported_entry when the file format we're dealing with does not support specific entry.
|
||||
virtual void set(const GUID & p_what,album_art_data_ptr p_data,abort_callback & p_abort) = 0;
|
||||
|
||||
//! Removes the requested entry. Fails silently when the entry doesn't exist.
|
||||
virtual void remove(const GUID & p_what) = 0;
|
||||
|
||||
//! Finalizes file tag update operation.
|
||||
virtual void commit(abort_callback & p_abort) = 0;
|
||||
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(album_art_editor_instance,album_art_extractor_instance);
|
||||
};
|
||||
|
||||
typedef service_ptr_t<album_art_extractor_instance> album_art_extractor_instance_ptr;
|
||||
typedef service_ptr_t<album_art_editor_instance> album_art_editor_instance_ptr;
|
||||
|
||||
//! Entrypoint class for accessing album art extraction functionality. Register your own implementation to allow album art extraction from your media file format. \n
|
||||
//! If you want to extract album art from a media file, it's recommended that you use album_art_manager API instead of calling album_art_extractor directly.
|
||||
class NOVTABLE album_art_extractor : public service_base {
|
||||
public:
|
||||
//! Returns whether the specified file is one of formats supported by our album_art_extractor implementation.
|
||||
//! @param p_path Path to file being queried.
|
||||
//! @param p_extension Extension of file being queried (also present in p_path parameter) - provided as a separate parameter for performance reasons.
|
||||
virtual bool is_our_path(const char * p_path,const char * p_extension) = 0;
|
||||
|
||||
//! Instantiates album_art_extractor_instance providing access to album art stored in a specified media file. \n
|
||||
//! Throws one of I/O exceptions on failure; exception_album_art_not_found when the file has no album art record at all.
|
||||
//! @param p_filehint Optional; specifies a file interface to use for accessing the specified file; can be null - in that case, the implementation will open and close the file internally.
|
||||
virtual album_art_extractor_instance_ptr open(file_ptr p_filehint,const char * p_path,abort_callback & p_abort) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(album_art_extractor);
|
||||
};
|
||||
|
||||
//! Entrypoint class for accessing album art editing functionality. Register your own implementation to allow album art editing on your media file format.
|
||||
class NOVTABLE album_art_editor : public service_base {
|
||||
public:
|
||||
//! Returns whether the specified file is one of formats supported by our album_art_editor implementation.
|
||||
//! @param p_path Path to file being queried.
|
||||
//! @param p_extension Extension of file being queried (also present in p_path parameter) - provided as a separate parameter for performance reasons.
|
||||
virtual bool is_our_path(const char * p_path,const char * p_extension) = 0;
|
||||
|
||||
//! Instantiates album_art_editor_instance providing access to album art stored in a specified media file. \n
|
||||
//! @param p_filehint Optional; specifies a file interface to use for accessing the specified file; can be null - in that case, the implementation will open and close the file internally.
|
||||
virtual album_art_editor_instance_ptr open(file_ptr p_filehint,const char * p_path,abort_callback & p_abort) = 0;
|
||||
|
||||
//! Helper; attempts to retrieve an album_art_editor service pointer that supports the specified file.
|
||||
//! @returns True on success, false on failure (no registered album_art_editor supports this file type).
|
||||
static bool g_get_interface(service_ptr_t<album_art_editor> & out,const char * path);
|
||||
//! Helper; returns whether one of registered album_art_editor implementations is capable of opening the specified file.
|
||||
static bool g_is_supported_path(const char * path);
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(album_art_editor);
|
||||
};
|
||||
|
||||
//! Primary API for interfacing with foobar2000 core's album art extraction functionality. \n
|
||||
//! Use static_api_ptr_t<album_art_manager>()->instantiate() to obtain a pointer to album_art_manager_instance. \n
|
||||
//! The main difference between using album_art_manager_instance and calling album_art_extractor methods directly is that
|
||||
//! album_art_manager_instance will fall back to returning pictures found in the folder containing the specified media file
|
||||
//! in case requested album art entries can't be extracted from the media file itself.
|
||||
class NOVTABLE album_art_manager_instance : public service_base {
|
||||
public:
|
||||
//! @returns True when the newly requested file has different album art than the old one, false when album art we're referencing is the same as before.
|
||||
virtual bool open(const char * p_file,abort_callback & p_abort) = 0;
|
||||
//! Resets internal data.
|
||||
virtual void close() = 0;
|
||||
|
||||
//! Queries album art data for currently open media file. Throws exception_album_art_not_found when the requested album art entry isn't present.
|
||||
virtual album_art_data_ptr query(const GUID & p_what,abort_callback & p_abort) = 0;
|
||||
|
||||
//! Queries for stub image to display when there's no album art to show. \n
|
||||
//! May fail with exception_album_art_not_found as well when we have no stub image configured.
|
||||
virtual album_art_data_ptr query_stub_image(abort_callback & p_abort) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(album_art_manager_instance,service_base);
|
||||
};
|
||||
|
||||
typedef service_ptr_t<album_art_manager_instance> album_art_manager_instance_ptr;
|
||||
|
||||
//! Entrypoint API for accessing album art loading functionality provided by foobar2000 core. Usage: static_api_ptr_t<album_art_manager>. \n
|
||||
//! This API was introduced in 0.9.5.
|
||||
class NOVTABLE album_art_manager : public service_base {
|
||||
public:
|
||||
virtual album_art_manager_instance_ptr instantiate() = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(album_art_manager);
|
||||
};
|
||||
|
||||
|
||||
//! Helper - simple implementation of album_art_extractor_instance.
|
||||
class album_art_extractor_instance_simple : public album_art_extractor_instance {
|
||||
public:
|
||||
void set(const GUID & p_what,album_art_data_ptr p_content) {m_content.set(p_what,p_content);}
|
||||
bool have_item(const GUID & p_what) {return m_content.have_item(p_what);}
|
||||
album_art_data_ptr query(const GUID & p_what,abort_callback & p_abort) {
|
||||
album_art_data_ptr temp;
|
||||
if (!m_content.query(p_what,temp)) throw exception_album_art_not_found();
|
||||
return temp;
|
||||
}
|
||||
bool is_empty() const {return m_content.get_count() == 0;}
|
||||
private:
|
||||
pfc::map_t<GUID,album_art_data_ptr> m_content;
|
||||
};
|
||||
|
||||
//! Helper API for extracting album art from APEv2 tags - introduced in 0.9.5.
|
||||
class NOVTABLE tag_processor_album_art_utils : public service_base {
|
||||
public:
|
||||
|
||||
//! Throws one of I/O exceptions on failure; exception_album_art_not_found when the file has no album art record at all.
|
||||
virtual album_art_extractor_instance_ptr open(file_ptr p_file,abort_callback & p_abort) = 0;
|
||||
|
||||
//! Currently not implemented. Reserved for future use.
|
||||
virtual album_art_editor_instance_ptr edit(file_ptr p_file,abort_callback & p_abort) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(tag_processor_album_art_utils)
|
||||
};
|
||||
|
||||
//! Helper implementation of album_art_extractor - reads album art from arbitrary file formats that comply with APEv2 tagging specification.
|
||||
class album_art_extractor_impl_stdtags : public album_art_extractor {
|
||||
public:
|
||||
//! @param exts Semicolon-separated list of file format extensions to support.
|
||||
album_art_extractor_impl_stdtags(const char * exts) {
|
||||
pfc::splitStringSimple_toList(m_extensions,";",exts);
|
||||
}
|
||||
|
||||
bool is_our_path(const char * p_path,const char * p_extension) {
|
||||
return m_extensions.have_item(p_extension);
|
||||
}
|
||||
|
||||
album_art_extractor_instance_ptr open(file_ptr p_filehint,const char * p_path,abort_callback & p_abort) {
|
||||
PFC_ASSERT( is_our_path(p_path, pfc::string_extension(p_path) ) );
|
||||
file_ptr l_file ( p_filehint );
|
||||
if (l_file.is_empty()) filesystem::g_open_read(l_file, p_path, p_abort);
|
||||
return static_api_ptr_t<tag_processor_album_art_utils>()->open( l_file, p_abort );
|
||||
}
|
||||
private:
|
||||
pfc::avltree_t<pfc::string,pfc::string::comparatorCaseInsensitiveASCII> m_extensions;
|
||||
};
|
|
@ -0,0 +1,12 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
bool app_close_blocker::g_query()
|
||||
{
|
||||
service_ptr_t<app_close_blocker> ptr;
|
||||
service_enum_t<app_close_blocker> e;
|
||||
while(e.next(ptr))
|
||||
{
|
||||
if (!ptr->query()) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
//! (DEPRECATED) This service is used to signal whether something is currently preventing main window from being closed and app from being shut down.
|
||||
class NOVTABLE app_close_blocker : public service_base
|
||||
{
|
||||
public:
|
||||
//! Checks whether this service is currently preventing main window from being closed and app from being shut down.
|
||||
virtual bool query() = 0;
|
||||
|
||||
//! Static helper function, checks whether any of registered app_close_blocker services is currently preventing main window from being closed and app from being shut down.
|
||||
static bool g_query();
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(app_close_blocker);
|
||||
};
|
||||
|
||||
//! An interface encapsulating a task preventing the foobar2000 application from being closed. Instances of this class need to be registered using app_close_blocking_task_manager methods. \n
|
||||
//! Implementation: it's recommended that you derive from app_close_blocking_task_impl class instead of deriving from app_close_blocking_task directly, it manages registration/unregistration behind-the-scenes.
|
||||
class NOVTABLE app_close_blocking_task {
|
||||
public:
|
||||
virtual void query_task_name(pfc::string_base & out) = 0;
|
||||
|
||||
protected:
|
||||
app_close_blocking_task() {}
|
||||
~app_close_blocking_task() {}
|
||||
|
||||
PFC_CLASS_NOT_COPYABLE_EX(app_close_blocking_task);
|
||||
};
|
||||
|
||||
//! Entrypoint class for registering app_close_blocking_task instances. Introduced in 0.9.5.1. \n
|
||||
//! Usage: static_api_ptr_t<app_close_blocking_task_manager>(). May fail if user runs pre-0.9.5.1. It's recommended that you use app_close_blocking_task_impl class instead of calling app_close_blocking_task_manager directly.
|
||||
class NOVTABLE app_close_blocking_task_manager : public service_base {
|
||||
public:
|
||||
virtual void register_task(app_close_blocking_task * task) = 0;
|
||||
virtual void unregister_task(app_close_blocking_task * task) = 0;
|
||||
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(app_close_blocking_task_manager);
|
||||
};
|
||||
|
||||
//! Helper; implements standard functionality required by app_close_blocking_task implementations - registers/unregisters the task on construction/destruction.
|
||||
class app_close_blocking_task_impl : public app_close_blocking_task {
|
||||
public:
|
||||
app_close_blocking_task_impl() { try { static_api_ptr_t<app_close_blocking_task_manager>()->register_task(this); } catch(exception_service_not_found) {/*user runs pre-0.9.5.1*/}}
|
||||
~app_close_blocking_task_impl() { try { static_api_ptr_t<app_close_blocking_task_manager>()->unregister_task(this); } catch(exception_service_not_found) {/*user runs pre-0.9.5.1*/}}
|
||||
|
||||
void query_task_name(pfc::string_base & out) { out = "<unnamed task>"; }
|
||||
};
|
|
@ -0,0 +1,335 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
void audio_chunk::set_data(const audio_sample * src,t_size samples,unsigned nch,unsigned srate,unsigned channel_config)
|
||||
{
|
||||
t_size size = samples * nch;
|
||||
set_data_size(size);
|
||||
if (src)
|
||||
pfc::memcpy_t(get_data(),src,size);
|
||||
else
|
||||
pfc::memset_t(get_data(),(audio_sample)0,size);
|
||||
set_sample_count(samples);
|
||||
set_channels(nch,channel_config);
|
||||
set_srate(srate);
|
||||
}
|
||||
|
||||
static bool check_exclusive(unsigned val, unsigned mask)
|
||||
{
|
||||
return (val&mask)!=0 && (val&mask)!=mask;
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
template<class T,bool b_swap,bool b_signed,bool b_pad> class msvc6_sucks_v2 { public:
|
||||
inline static void do_fixedpoint_convert(const void * source,unsigned bps,t_size count,audio_sample* buffer)
|
||||
{
|
||||
const char * src = (const char *) source;
|
||||
unsigned bytes = bps>>3;
|
||||
t_size n;
|
||||
T max = ((T)1)<<(bps-1);
|
||||
|
||||
T negmask = - max;
|
||||
|
||||
ASSUME(bytes<=sizeof(T));
|
||||
|
||||
const double div = 1.0 / (double)(1<<(bps-1));
|
||||
for(n=0;n<count;n++) {
|
||||
T temp;
|
||||
if (b_pad)
|
||||
{
|
||||
temp = 0;
|
||||
memcpy(&temp,src,bytes);
|
||||
if (b_swap) pfc::byteswap_raw(&temp,bytes);
|
||||
}
|
||||
else
|
||||
{
|
||||
temp = * reinterpret_cast<const T*>(src);
|
||||
if (b_swap) temp = pfc::byteswap_t(temp);
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!b_signed) temp ^= max;
|
||||
|
||||
if (b_pad)
|
||||
{
|
||||
if (temp & max) temp |= negmask;
|
||||
}
|
||||
|
||||
if (b_pad)
|
||||
src += bytes;
|
||||
else
|
||||
src += sizeof(T);
|
||||
|
||||
|
||||
buffer[n] = (audio_sample) ( (double)temp * div );
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template <class T,bool b_pad> class msvc6_sucks { public:
|
||||
inline static void do_fixedpoint_convert(bool b_swap,bool b_signed,const void * source,unsigned bps,t_size count,audio_sample* buffer)
|
||||
{
|
||||
if (sizeof(T)==1)
|
||||
{
|
||||
if (b_signed)
|
||||
{
|
||||
msvc6_sucks_v2<T,false,true,b_pad>::do_fixedpoint_convert(source,bps,count,buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
msvc6_sucks_v2<T,false,false,b_pad>::do_fixedpoint_convert(source,bps,count,buffer);
|
||||
}
|
||||
}
|
||||
else if (b_swap)
|
||||
{
|
||||
if (b_signed)
|
||||
{
|
||||
msvc6_sucks_v2<T,true,true,b_pad>::do_fixedpoint_convert(source,bps,count,buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
msvc6_sucks_v2<T,true,false,b_pad>::do_fixedpoint_convert(source,bps,count,buffer);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (b_signed)
|
||||
{
|
||||
msvc6_sucks_v2<T,false,true,b_pad>::do_fixedpoint_convert(source,bps,count,buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
msvc6_sucks_v2<T,false,false,b_pad>::do_fixedpoint_convert(source,bps,count,buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
};
|
||||
|
||||
|
||||
void audio_chunk::set_data_fixedpoint_ex(const void * source,t_size size,unsigned srate,unsigned nch,unsigned bps,unsigned flags,unsigned p_channel_config)
|
||||
{
|
||||
assert( check_exclusive(flags,FLAG_SIGNED|FLAG_UNSIGNED) );
|
||||
assert( check_exclusive(flags,FLAG_LITTLE_ENDIAN|FLAG_BIG_ENDIAN) );
|
||||
|
||||
bool need_swap = !!(flags & FLAG_BIG_ENDIAN);
|
||||
if (pfc::byte_order_is_big_endian) need_swap = !need_swap;
|
||||
|
||||
t_size count = size / (bps/8);
|
||||
set_data_size(count);
|
||||
audio_sample * buffer = get_data();
|
||||
bool b_signed = !!(flags & FLAG_SIGNED);
|
||||
|
||||
switch(bps)
|
||||
{
|
||||
case 8:
|
||||
msvc6_sucks<t_int8,false>::do_fixedpoint_convert(need_swap,b_signed,source,bps,count,buffer);
|
||||
break;
|
||||
case 16:
|
||||
if (!need_swap && b_signed) audio_math::convert_from_int16((const t_int16*)source,count,buffer,1.0);
|
||||
else msvc6_sucks<t_int16,false>::do_fixedpoint_convert(need_swap,b_signed,source,bps,count,buffer);
|
||||
break;
|
||||
case 24:
|
||||
msvc6_sucks<t_int32,true>::do_fixedpoint_convert(need_swap,b_signed,source,bps,count,buffer);
|
||||
break;
|
||||
case 32:
|
||||
if (!need_swap && b_signed) audio_math::convert_from_int32((const t_int32*)source,count,buffer,1.0);
|
||||
else msvc6_sucks<t_int32,false>::do_fixedpoint_convert(need_swap,b_signed,source,bps,count,buffer);
|
||||
break;
|
||||
default:
|
||||
//unknown size, cant convert
|
||||
pfc::memset_t(buffer,(audio_sample)0,count);
|
||||
break;
|
||||
}
|
||||
set_sample_count(count/nch);
|
||||
set_srate(srate);
|
||||
set_channels(nch,p_channel_config);
|
||||
}
|
||||
|
||||
template<class t_float>
|
||||
static void process_float_multi(audio_sample * p_out,const t_float * p_in,const t_size p_count)
|
||||
{
|
||||
t_size n;
|
||||
for(n=0;n<p_count;n++)
|
||||
p_out[n] = (audio_sample)p_in[n];
|
||||
}
|
||||
|
||||
template<class t_float>
|
||||
static void process_float_multi_swap(audio_sample * p_out,const t_float * p_in,const t_size p_count)
|
||||
{
|
||||
t_size n;
|
||||
for(n=0;n<p_count;n++) {
|
||||
p_out[n] = (audio_sample) pfc::byteswap_t(p_in[n]);
|
||||
}
|
||||
}
|
||||
|
||||
void audio_chunk::set_data_floatingpoint_ex(const void * ptr,t_size size,unsigned srate,unsigned nch,unsigned bps,unsigned flags,unsigned p_channel_config)
|
||||
{
|
||||
assert(bps==32 || bps==64);
|
||||
assert( check_exclusive(flags,FLAG_LITTLE_ENDIAN|FLAG_BIG_ENDIAN) );
|
||||
assert( ! (flags & (FLAG_SIGNED|FLAG_UNSIGNED) ) );
|
||||
|
||||
bool use_swap = pfc::byte_order_is_big_endian ? !!(flags & FLAG_LITTLE_ENDIAN) : !!(flags & FLAG_BIG_ENDIAN);
|
||||
|
||||
const t_size count = size / (bps/8);
|
||||
set_data_size(count);
|
||||
audio_sample * out = get_data();
|
||||
|
||||
if (bps == 32)
|
||||
{
|
||||
if (use_swap)
|
||||
process_float_multi_swap(out,reinterpret_cast<const float*>(ptr),count);
|
||||
else
|
||||
process_float_multi(out,reinterpret_cast<const float*>(ptr),count);
|
||||
}
|
||||
else if (bps == 64)
|
||||
{
|
||||
if (use_swap)
|
||||
process_float_multi_swap(out,reinterpret_cast<const double*>(ptr),count);
|
||||
else
|
||||
process_float_multi(out,reinterpret_cast<const double*>(ptr),count);
|
||||
}
|
||||
else throw exception_io_data("invalid bit depth");
|
||||
|
||||
set_sample_count(count/nch);
|
||||
set_srate(srate);
|
||||
set_channels(nch,p_channel_config);
|
||||
}
|
||||
|
||||
bool audio_chunk::is_valid() const
|
||||
{
|
||||
unsigned nch = get_channels();
|
||||
if (nch==0 || nch>256) return false;
|
||||
if (!g_is_valid_sample_rate(get_srate())) return false;
|
||||
t_size samples = get_sample_count();
|
||||
if (samples==0 || samples >= 0x80000000 / (sizeof(audio_sample) * nch) ) return false;
|
||||
t_size size = get_data_size();
|
||||
if (samples * nch > size) return false;
|
||||
if (!get_data()) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void audio_chunk::pad_with_silence_ex(t_size samples,unsigned hint_nch,unsigned hint_srate) {
|
||||
if (is_empty())
|
||||
{
|
||||
if (hint_srate && hint_nch) {
|
||||
return set_data(0,samples,hint_nch,hint_srate);
|
||||
} else throw exception_io_data();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hint_srate && hint_srate != get_srate()) samples = MulDiv_Size(samples,get_srate(),hint_srate);
|
||||
if (samples > get_sample_count())
|
||||
{
|
||||
t_size old_size = get_sample_count() * get_channels();
|
||||
t_size new_size = samples * get_channels();
|
||||
set_data_size(new_size);
|
||||
pfc::memset_t(get_data() + old_size,(audio_sample)0,new_size - old_size);
|
||||
set_sample_count(samples);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void audio_chunk::pad_with_silence(t_size samples) {
|
||||
if (samples > get_sample_count())
|
||||
{
|
||||
t_size old_size = get_sample_count() * get_channels();
|
||||
t_size new_size = pfc::multiply_guarded(samples,get_channels());
|
||||
set_data_size(new_size);
|
||||
pfc::memset_t(get_data() + old_size,(audio_sample)0,new_size - old_size);
|
||||
set_sample_count(samples);
|
||||
}
|
||||
}
|
||||
|
||||
void audio_chunk::insert_silence_fromstart(t_size samples) {
|
||||
t_size old_size = get_sample_count() * get_channels();
|
||||
t_size delta = samples * get_channels();
|
||||
t_size new_size = old_size + delta;
|
||||
set_data_size(new_size);
|
||||
audio_sample * ptr = get_data();
|
||||
pfc::memmove_t(ptr+delta,ptr,old_size);
|
||||
pfc::memset_t(ptr,(audio_sample)0,delta);
|
||||
set_sample_count(get_sample_count() + samples);
|
||||
}
|
||||
|
||||
t_size audio_chunk::skip_first_samples(t_size samples_delta)
|
||||
{
|
||||
t_size samples_old = get_sample_count();
|
||||
if (samples_delta >= samples_old)
|
||||
{
|
||||
set_sample_count(0);
|
||||
set_data_size(0);
|
||||
return samples_old;
|
||||
}
|
||||
else
|
||||
{
|
||||
t_size samples_new = samples_old - samples_delta;
|
||||
unsigned nch = get_channels();
|
||||
audio_sample * ptr = get_data();
|
||||
pfc::memmove_t(ptr,ptr+nch*samples_delta,nch*samples_new);
|
||||
set_sample_count(samples_new);
|
||||
set_data_size(nch*samples_new);
|
||||
return samples_delta;
|
||||
}
|
||||
}
|
||||
|
||||
audio_sample audio_chunk::get_peak(audio_sample p_peak) const {
|
||||
return pfc::max_t(p_peak, get_peak());
|
||||
}
|
||||
|
||||
audio_sample audio_chunk::get_peak() const {
|
||||
return audio_math::calculate_peak(get_data(),get_sample_count() * get_channels());
|
||||
}
|
||||
|
||||
void audio_chunk::scale(audio_sample p_value)
|
||||
{
|
||||
audio_sample * ptr = get_data();
|
||||
audio_math::scale(ptr,get_sample_count() * get_channels(),ptr,p_value);
|
||||
}
|
||||
|
||||
|
||||
static void render_8bit(const audio_sample * in, t_size inLen, void * out) {
|
||||
t_int8 * outWalk = reinterpret_cast<t_int8*>(out);
|
||||
for(t_size walk = 0; walk < inLen; ++walk) {
|
||||
*outWalk++ = (t_int8)pfc::clip_t<t_int32>(audio_math::rint32( in[walk] * 0x80 ), -128, 127);
|
||||
}
|
||||
}
|
||||
static void render_24bit(const audio_sample * in, t_size inLen, void * out) {
|
||||
t_uint8 * outWalk = reinterpret_cast<t_uint8*>(out);
|
||||
for(t_size walk = 0; walk < inLen; ++walk) {
|
||||
const t_int32 v = pfc::clip_t<t_int32>(audio_math::rint32( in[walk] * 0x800000 ), -128 * 256 * 256, 128 * 256 * 256 - 1);
|
||||
*(outWalk ++) = (t_uint8) (v & 0xFF);
|
||||
*(outWalk ++) = (t_uint8) ((v >> 8) & 0xFF);
|
||||
*(outWalk ++) = (t_uint8) ((v >> 16) & 0xFF);
|
||||
}
|
||||
}
|
||||
|
||||
bool audio_chunk::to_raw_data(mem_block_container & out, t_uint32 bps) const {
|
||||
const t_size samples = get_sample_count();
|
||||
const t_size dataLen = pfc::multiply_guarded(samples, (t_size)get_channel_count());
|
||||
switch(bps) {
|
||||
case 8:
|
||||
out.set_size(dataLen);
|
||||
render_8bit(get_data(), dataLen, out.get_ptr());
|
||||
break;
|
||||
case 16:
|
||||
out.set_size(dataLen * 2);
|
||||
audio_math::convert_to_int16(get_data(), dataLen, reinterpret_cast<t_int16*>(out.get_ptr()), 1.0);
|
||||
break;
|
||||
case 24:
|
||||
out.set_size(dataLen * 3);
|
||||
render_24bit(get_data(), dataLen, out.get_ptr());
|
||||
break;
|
||||
case 32:
|
||||
pfc::static_assert<sizeof(audio_sample) == 4>();
|
||||
out.set(get_data(), dataLen * sizeof(audio_sample));
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,342 @@
|
|||
//! Thrown when audio_chunk sample rate or channel mapping changes in mid-stream and the code receiving audio_chunks can't deal with that scenario.
|
||||
PFC_DECLARE_EXCEPTION(exception_unexpected_audio_format_change, exception_io_data, "Unexpected audio format change" );
|
||||
|
||||
//! Interface to container of a chunk of audio data. See audio_chunk_impl for an implementation.
|
||||
class NOVTABLE audio_chunk {
|
||||
public:
|
||||
|
||||
enum {
|
||||
sample_rate_min = 1000, sample_rate_max = 1000000
|
||||
};
|
||||
static bool g_is_valid_sample_rate(t_uint32 p_val) {return p_val >= sample_rate_min && p_val <= sample_rate_max;}
|
||||
|
||||
//! Channel map flag declarations. Note that order of interleaved channel data in the stream is same as order of these flags.
|
||||
enum
|
||||
{
|
||||
channel_front_left = 1<<0,
|
||||
channel_front_right = 1<<1,
|
||||
channel_front_center = 1<<2,
|
||||
channel_lfe = 1<<3,
|
||||
channel_back_left = 1<<4,
|
||||
channel_back_right = 1<<5,
|
||||
channel_front_center_left = 1<<6,
|
||||
channel_front_center_right = 1<<7,
|
||||
channel_back_center = 1<<8,
|
||||
channel_side_left = 1<<9,
|
||||
channel_side_right = 1<<10,
|
||||
channel_top_center = 1<<11,
|
||||
channel_top_front_left = 1<<12,
|
||||
channel_top_front_center = 1<<13,
|
||||
channel_top_front_right = 1<<14,
|
||||
channel_top_back_left = 1<<15,
|
||||
channel_top_back_center = 1<<16,
|
||||
channel_top_back_right = 1<<17,
|
||||
|
||||
channel_config_mono = channel_front_center,
|
||||
channel_config_stereo = channel_front_left | channel_front_right,
|
||||
channel_config_5point1 = channel_front_left | channel_front_right | channel_back_left | channel_back_right | channel_front_center | channel_lfe,
|
||||
|
||||
defined_channel_count = 18,
|
||||
};
|
||||
|
||||
//! Helper function; guesses default channel map for specified channel count.
|
||||
static unsigned g_guess_channel_config(unsigned count);
|
||||
|
||||
#ifdef _WIN32
|
||||
//! Helper function; translates audio_chunk channel map to WAVEFORMATEXTENSIBLE channel map.
|
||||
static DWORD g_channel_config_to_wfx(unsigned p_config);
|
||||
//! Helper function; translates WAVEFORMATEXTENSIBLE channel map to audio_chunk channel map.
|
||||
static unsigned g_channel_config_from_wfx(DWORD p_wfx);
|
||||
#endif
|
||||
|
||||
//! Extracts flag describing Nth channel from specified map. Usable to figure what specific channel in a stream means.
|
||||
static unsigned g_extract_channel_flag(unsigned p_config,unsigned p_index);
|
||||
//! Counts channels specified by channel map.
|
||||
static unsigned g_count_channels(unsigned p_config);
|
||||
//! Calculates index of a channel specified by p_flag in a stream where channel map is described by p_config.
|
||||
static unsigned g_channel_index_from_flag(unsigned p_config,unsigned p_flag);
|
||||
|
||||
|
||||
|
||||
//! Retrieves audio data buffer pointer (non-const version). Returned pointer is for temporary use only; it is valid until next set_data_size call, or until the object is destroyed. \n
|
||||
//! Size of returned buffer is equal to get_data_size() return value (in audio_samples). Amount of actual data may be smaller, depending on sample count and channel count. Conditions where sample count * channel count are greater than data size should not be possible.
|
||||
virtual audio_sample * get_data() = 0;
|
||||
//! Retrieves audio data buffer pointer (const version). Returned pointer is for temporary use only; it is valid until next set_data_size call, or until the object is destroyed. \n
|
||||
//! Size of returned buffer is equal to get_data_size() return value (in audio_samples). Amount of actual data may be smaller, depending on sample count and channel count. Conditions where sample count * channel count are greater than data size should not be possible.
|
||||
virtual const audio_sample * get_data() const = 0;
|
||||
//! Retrieves size of allocated buffer space, in audio_samples.
|
||||
virtual t_size get_data_size() const = 0;
|
||||
//! Resizes audio data buffer to specified size. Throws std::bad_alloc on failure.
|
||||
virtual void set_data_size(t_size p_new_size) = 0;
|
||||
|
||||
//! Retrieves sample rate of contained audio data.
|
||||
virtual unsigned get_srate() const = 0;
|
||||
//! Sets sample rate of contained audio data.
|
||||
virtual void set_srate(unsigned val) = 0;
|
||||
//! Retrieves channel count of contained audio data.
|
||||
virtual unsigned get_channels() const = 0;
|
||||
//! Helper - for consistency - same as get_channels().
|
||||
inline unsigned get_channel_count() const {return get_channels();}
|
||||
//! Retrieves channel map of contained audio data. Conditions where number of channels specified by channel map don't match get_channels() return value should not be possible.
|
||||
virtual unsigned get_channel_config() const = 0;
|
||||
//! Sets channel count / channel map.
|
||||
virtual void set_channels(unsigned p_count,unsigned p_config) = 0;
|
||||
|
||||
//! Retrieves number of valid samples in the buffer. \n
|
||||
//! Note that a "sample" means a unit of interleaved PCM data representing states of each channel at given point of time, not a single PCM value. \n
|
||||
//! For an example, duration of contained audio data is equal to sample count / sample rate, while actual size of contained data is equal to sample count * channel count.
|
||||
virtual t_size get_sample_count() const = 0;
|
||||
|
||||
//! Sets number of valid samples in the buffer. WARNING: sample count * channel count should never be above allocated buffer size.
|
||||
virtual void set_sample_count(t_size val) = 0;
|
||||
|
||||
//! Helper, same as get_srate().
|
||||
inline unsigned get_sample_rate() const {return get_srate();}
|
||||
//! Helper, same as set_srate().
|
||||
inline void set_sample_rate(unsigned val) {set_srate(val);}
|
||||
|
||||
//! Helper; sets channel count to specified value and uses default channel map for this channel count.
|
||||
void set_channels(unsigned val) {set_channels(val,g_guess_channel_config(val));}
|
||||
|
||||
|
||||
//! Helper; resizes audio data buffer when it's current size is smaller than requested.
|
||||
inline void grow_data_size(t_size p_requested) {if (p_requested > get_data_size()) set_data_size(p_requested);}
|
||||
|
||||
|
||||
//! Retrieves duration of contained audio data, in seconds.
|
||||
inline double get_duration() const
|
||||
{
|
||||
double rv = 0;
|
||||
t_size srate = get_srate (), samples = get_sample_count();
|
||||
if (srate>0 && samples>0) rv = (double)samples/(double)srate;
|
||||
return rv;
|
||||
}
|
||||
|
||||
//! Returns whether the chunk is empty (contains no audio data).
|
||||
inline bool is_empty() const {return get_channels()==0 || get_srate()==0 || get_sample_count()==0;}
|
||||
|
||||
//! Returns whether the chunk contents are valid (for bug check purposes).
|
||||
bool is_valid() const;
|
||||
|
||||
//! Returns actual amount of audio data contained in the buffer (sample count * channel count). Must not be greater than data size (see get_data_size()).
|
||||
inline t_size get_data_length() const {return get_sample_count() * get_channels();}
|
||||
|
||||
//! Resets all audio_chunk data.
|
||||
inline void reset() {
|
||||
set_sample_count(0);
|
||||
set_srate(0);
|
||||
set_channels(0);
|
||||
set_data_size(0);
|
||||
}
|
||||
|
||||
//! Helper, sets chunk data to contents of specified buffer, with specified number of channels / sample rate / channel map.
|
||||
void set_data(const audio_sample * src,t_size samples,unsigned nch,unsigned srate,unsigned channel_config);
|
||||
|
||||
//! Helper, sets chunk data to contents of specified buffer, with specified number of channels / sample rate, using default channel map for specified channel count.
|
||||
inline void set_data(const audio_sample * src,t_size samples,unsigned nch,unsigned srate) {set_data(src,samples,nch,srate,g_guess_channel_config(nch));}
|
||||
|
||||
//! Helper, sets chunk data to contents of specified buffer, using default win32/wav conventions for signed/unsigned switch.
|
||||
inline void set_data_fixedpoint(const void * ptr,t_size bytes,unsigned srate,unsigned nch,unsigned bps,unsigned channel_config) {
|
||||
set_data_fixedpoint_ex(ptr,bytes,srate,nch,bps,(bps==8 ? FLAG_UNSIGNED : FLAG_SIGNED) | flags_autoendian(), channel_config);
|
||||
}
|
||||
|
||||
inline void set_data_fixedpoint_unsigned(const void * ptr,t_size bytes,unsigned srate,unsigned nch,unsigned bps,unsigned channel_config) {
|
||||
return set_data_fixedpoint_ex(ptr,bytes,srate,nch,bps,FLAG_UNSIGNED | flags_autoendian(), channel_config);
|
||||
}
|
||||
|
||||
inline void set_data_fixedpoint_signed(const void * ptr,t_size bytes,unsigned srate,unsigned nch,unsigned bps,unsigned channel_config) {
|
||||
return set_data_fixedpoint_ex(ptr,bytes,srate,nch,bps,FLAG_SIGNED | flags_autoendian(), channel_config);
|
||||
}
|
||||
|
||||
enum
|
||||
{
|
||||
FLAG_LITTLE_ENDIAN = 1,
|
||||
FLAG_BIG_ENDIAN = 2,
|
||||
FLAG_SIGNED = 4,
|
||||
FLAG_UNSIGNED = 8,
|
||||
};
|
||||
|
||||
inline static unsigned flags_autoendian() {
|
||||
return pfc::byte_order_is_big_endian ? FLAG_BIG_ENDIAN : FLAG_LITTLE_ENDIAN;
|
||||
}
|
||||
|
||||
void set_data_fixedpoint_ex(const void * ptr,t_size bytes,unsigned p_sample_rate,unsigned p_channels,unsigned p_bits_per_sample,unsigned p_flags,unsigned p_channel_config);//p_flags - see FLAG_* above
|
||||
|
||||
void set_data_floatingpoint_ex(const void * ptr,t_size bytes,unsigned p_sample_rate,unsigned p_channels,unsigned p_bits_per_sample,unsigned p_flags,unsigned p_channel_config);//signed/unsigned flags dont apply
|
||||
|
||||
inline void set_data_32(const float * src,t_size samples,unsigned nch,unsigned srate) {return set_data(src,samples,nch,srate);}
|
||||
|
||||
void pad_with_silence_ex(t_size samples,unsigned hint_nch,unsigned hint_srate);
|
||||
void pad_with_silence(t_size samples);
|
||||
void insert_silence_fromstart(t_size samples);
|
||||
t_size skip_first_samples(t_size samples);
|
||||
|
||||
//! Simple function to get original PCM stream back. Assumes host's endianness, integers are signed - including the 8bit mode; 32bit mode assumed to be float.
|
||||
//! @returns false when the conversion could not be performed because of unsupported bit depth etc.
|
||||
bool to_raw_data(class mem_block_container & out, t_uint32 bps) const;
|
||||
|
||||
|
||||
//! Helper, calculates peak value of data in the chunk. The optional parameter specifies initial peak value, to simplify calling code.
|
||||
audio_sample get_peak(audio_sample p_peak) const;
|
||||
audio_sample get_peak() const;
|
||||
|
||||
//! Helper function; scales entire chunk content by specified value.
|
||||
void scale(audio_sample p_value);
|
||||
|
||||
//! Helper; copies content of another audio chunk to this chunk.
|
||||
void copy(const audio_chunk & p_source) {
|
||||
set_data(p_source.get_data(),p_source.get_sample_count(),p_source.get_channels(),p_source.get_srate(),p_source.get_channel_config());
|
||||
}
|
||||
|
||||
const audio_chunk & operator=(const audio_chunk & p_source) {
|
||||
copy(p_source);
|
||||
return *this;
|
||||
}
|
||||
protected:
|
||||
audio_chunk() {}
|
||||
~audio_chunk() {}
|
||||
};
|
||||
|
||||
//! Implementation of audio_chunk. Takes pfc allocator template as template parameter.
|
||||
template<template<typename> class t_alloc = pfc::alloc_standard>
|
||||
class audio_chunk_impl_t : public audio_chunk {
|
||||
typedef audio_chunk_impl_t<t_alloc> t_self;
|
||||
pfc::array_t<audio_sample,t_alloc> m_data;
|
||||
unsigned m_srate,m_nch,m_setup;
|
||||
t_size m_samples;
|
||||
public:
|
||||
audio_chunk_impl_t() : m_srate(0), m_nch(0), m_samples(0), m_setup(0) {}
|
||||
audio_chunk_impl_t(const audio_sample * src,unsigned samples,unsigned nch,unsigned srate) : m_srate(0), m_nch(0), m_samples(0)
|
||||
{set_data(src,samples,nch,srate);}
|
||||
audio_chunk_impl_t(const audio_chunk & p_source) : m_srate(0), m_nch(0), m_samples(0), m_setup(0) {copy(p_source);}
|
||||
audio_chunk_impl_t(const t_self & p_source) : m_srate(0), m_nch(0), m_samples(0), m_setup(0) {copy(p_source);}
|
||||
|
||||
virtual audio_sample * get_data() {return m_data.get_ptr();}
|
||||
virtual const audio_sample * get_data() const {return m_data.get_ptr();}
|
||||
virtual t_size get_data_size() const {return m_data.get_size();}
|
||||
virtual void set_data_size(t_size new_size) {m_data.set_size(new_size);}
|
||||
|
||||
virtual unsigned get_srate() const {return m_srate;}
|
||||
virtual void set_srate(unsigned val) {m_srate=val;}
|
||||
virtual unsigned get_channels() const {return m_nch;}
|
||||
virtual unsigned get_channel_config() const {return m_setup;}
|
||||
virtual void set_channels(unsigned val,unsigned setup) {m_nch = val;m_setup = setup;}
|
||||
void set_channels(unsigned val) {set_channels(val,g_guess_channel_config(val));}
|
||||
|
||||
virtual t_size get_sample_count() const {return m_samples;}
|
||||
virtual void set_sample_count(t_size val) {m_samples = val;}
|
||||
|
||||
const t_self & operator=(const audio_chunk & p_source) {copy(p_source);return *this;}
|
||||
const t_self & operator=(const t_self & p_source) {copy(p_source);return *this;}
|
||||
};
|
||||
|
||||
typedef audio_chunk_impl_t<> audio_chunk_impl;
|
||||
typedef audio_chunk_impl_t<pfc::alloc_fast_aggressive> audio_chunk_impl_temporary;
|
||||
typedef audio_chunk_impl audio_chunk_i;//for compatibility
|
||||
|
||||
//! Implements const methods of audio_chunk only, referring to an external buffer. For temporary use only (does not maintain own storage), e.g.: somefunc( audio_chunk_temp_impl(mybuffer,....) );
|
||||
class audio_chunk_temp_impl : public audio_chunk {
|
||||
public:
|
||||
audio_chunk_temp_impl(const audio_sample * p_data,t_size p_samples,t_uint32 p_sample_rate,t_uint32 p_channels,t_uint32 p_channel_config) :
|
||||
m_data(p_data), m_samples(p_samples), m_sample_rate(p_sample_rate), m_channels(p_channels), m_channel_config(p_channel_config)
|
||||
{
|
||||
PFC_ASSERT(is_valid());
|
||||
}
|
||||
|
||||
audio_sample * get_data() {throw pfc::exception_not_implemented();}
|
||||
const audio_sample * get_data() const {return m_data;}
|
||||
t_size get_data_size() const {return m_samples * m_channels;}
|
||||
void set_data_size(t_size p_new_size) {throw pfc::exception_not_implemented();}
|
||||
|
||||
unsigned get_srate() const {return m_sample_rate;}
|
||||
void set_srate(unsigned val) {throw pfc::exception_not_implemented();}
|
||||
unsigned get_channels() const {return m_channels;}
|
||||
unsigned get_channel_config() const {return m_channel_config;}
|
||||
void set_channels(unsigned p_count,unsigned p_config) {throw pfc::exception_not_implemented();}
|
||||
|
||||
t_size get_sample_count() const {return m_samples;}
|
||||
|
||||
void set_sample_count(t_size val) {throw pfc::exception_not_implemented();}
|
||||
|
||||
private:
|
||||
t_size m_samples;
|
||||
t_uint32 m_sample_rate,m_channels,m_channel_config;
|
||||
const audio_sample * m_data;
|
||||
};
|
||||
|
||||
|
||||
|
||||
//! 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;
|
||||
}
|
||||
void add(const audio_chunk & c) {
|
||||
add(c.get_sample_count(), c.get_sample_rate());
|
||||
}
|
||||
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;
|
||||
};
|
||||
|
||||
class audio_chunk_partial_ref : public audio_chunk_temp_impl {
|
||||
public:
|
||||
audio_chunk_partial_ref(const audio_chunk & chunk, t_size base, t_size count) : audio_chunk_temp_impl(chunk.get_data() + base * chunk.get_channels(), count, chunk.get_sample_rate(), chunk.get_channels(), chunk.get_channel_config()) {}
|
||||
};
|
|
@ -0,0 +1,131 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <ks.h>
|
||||
#include <ksmedia.h>
|
||||
|
||||
#if 0
|
||||
#define SPEAKER_FRONT_LEFT 0x1
|
||||
#define SPEAKER_FRONT_RIGHT 0x2
|
||||
#define SPEAKER_FRONT_CENTER 0x4
|
||||
#define SPEAKER_LOW_FREQUENCY 0x8
|
||||
#define SPEAKER_BACK_LEFT 0x10
|
||||
#define SPEAKER_BACK_RIGHT 0x20
|
||||
#define SPEAKER_FRONT_LEFT_OF_CENTER 0x40
|
||||
#define SPEAKER_FRONT_RIGHT_OF_CENTER 0x80
|
||||
#define SPEAKER_BACK_CENTER 0x100
|
||||
#define SPEAKER_SIDE_LEFT 0x200
|
||||
#define SPEAKER_SIDE_RIGHT 0x400
|
||||
#define SPEAKER_TOP_CENTER 0x800
|
||||
#define SPEAKER_TOP_FRONT_LEFT 0x1000
|
||||
#define SPEAKER_TOP_FRONT_CENTER 0x2000
|
||||
#define SPEAKER_TOP_FRONT_RIGHT 0x4000
|
||||
#define SPEAKER_TOP_BACK_LEFT 0x8000
|
||||
#define SPEAKER_TOP_BACK_CENTER 0x10000
|
||||
#define SPEAKER_TOP_BACK_RIGHT 0x20000
|
||||
#endif
|
||||
|
||||
static struct {DWORD m_wfx; unsigned m_native; } const g_translation_table[] =
|
||||
{
|
||||
{SPEAKER_FRONT_LEFT, audio_chunk::channel_front_left},
|
||||
{SPEAKER_FRONT_RIGHT, audio_chunk::channel_front_right},
|
||||
{SPEAKER_FRONT_CENTER, audio_chunk::channel_front_center},
|
||||
{SPEAKER_LOW_FREQUENCY, audio_chunk::channel_lfe},
|
||||
{SPEAKER_BACK_LEFT, audio_chunk::channel_back_left},
|
||||
{SPEAKER_BACK_RIGHT, audio_chunk::channel_back_right},
|
||||
{SPEAKER_FRONT_LEFT_OF_CENTER, audio_chunk::channel_front_center_left},
|
||||
{SPEAKER_FRONT_RIGHT_OF_CENTER, audio_chunk::channel_front_center_right},
|
||||
{SPEAKER_BACK_CENTER, audio_chunk::channel_back_center},
|
||||
{SPEAKER_SIDE_LEFT, audio_chunk::channel_side_left},
|
||||
{SPEAKER_SIDE_RIGHT, audio_chunk::channel_side_right},
|
||||
{SPEAKER_TOP_CENTER, audio_chunk::channel_top_center},
|
||||
{SPEAKER_TOP_FRONT_LEFT, audio_chunk::channel_top_front_left},
|
||||
{SPEAKER_TOP_FRONT_CENTER, audio_chunk::channel_top_front_center},
|
||||
{SPEAKER_TOP_FRONT_RIGHT, audio_chunk::channel_top_front_right},
|
||||
{SPEAKER_TOP_BACK_LEFT, audio_chunk::channel_top_back_left},
|
||||
{SPEAKER_TOP_BACK_CENTER, audio_chunk::channel_top_back_center},
|
||||
{SPEAKER_TOP_BACK_RIGHT, audio_chunk::channel_top_back_right},
|
||||
};
|
||||
|
||||
|
||||
DWORD audio_chunk::g_channel_config_to_wfx(unsigned p_config)
|
||||
{
|
||||
DWORD ret = 0;
|
||||
unsigned n;
|
||||
for(n=0;n<tabsize(g_translation_table);n++)
|
||||
{
|
||||
if (p_config & g_translation_table[n].m_native) ret |= g_translation_table[n].m_wfx;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
unsigned audio_chunk::g_channel_config_from_wfx(DWORD p_wfx)
|
||||
{
|
||||
unsigned ret = 0;
|
||||
unsigned n;
|
||||
for(n=0;n<tabsize(g_translation_table);n++)
|
||||
{
|
||||
if (p_wfx & g_translation_table[n].m_wfx) ret |= g_translation_table[n].m_native;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
|
||||
static unsigned g_audio_channel_config_table[] =
|
||||
{
|
||||
0,
|
||||
audio_chunk::channel_config_mono,
|
||||
audio_chunk::channel_config_stereo,
|
||||
audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_lfe,
|
||||
audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_back_left | audio_chunk::channel_back_right,
|
||||
audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_back_left | audio_chunk::channel_back_right | audio_chunk::channel_lfe,
|
||||
audio_chunk::channel_config_5point1,
|
||||
audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_back_left | audio_chunk::channel_back_right | audio_chunk::channel_lfe | audio_chunk::channel_front_center_right | audio_chunk::channel_front_center_left,
|
||||
audio_chunk::channel_front_left | audio_chunk::channel_front_right | audio_chunk::channel_back_left | audio_chunk::channel_back_right | audio_chunk::channel_front_center | audio_chunk::channel_lfe | audio_chunk::channel_front_center_right | audio_chunk::channel_front_center_left,
|
||||
};
|
||||
|
||||
|
||||
unsigned audio_chunk::g_guess_channel_config(unsigned count)
|
||||
{
|
||||
if (count >= tabsize(g_audio_channel_config_table)) return 0;
|
||||
return g_audio_channel_config_table[count];
|
||||
}
|
||||
|
||||
|
||||
unsigned audio_chunk::g_channel_index_from_flag(unsigned p_config,unsigned p_flag) {
|
||||
unsigned index = 0;
|
||||
for(unsigned walk = 0; walk < 32; walk++) {
|
||||
unsigned query = 1 << walk;
|
||||
if (p_flag & query) return index;
|
||||
if (p_config & query) index++;
|
||||
}
|
||||
return infinite;
|
||||
}
|
||||
|
||||
unsigned audio_chunk::g_extract_channel_flag(unsigned p_config,unsigned p_index)
|
||||
{
|
||||
unsigned toskip = p_index;
|
||||
unsigned flag = 1;
|
||||
while(flag)
|
||||
{
|
||||
if (p_config & flag)
|
||||
{
|
||||
if (toskip == 0) break;
|
||||
toskip--;
|
||||
}
|
||||
flag <<= 1;
|
||||
}
|
||||
return flag;
|
||||
}
|
||||
|
||||
unsigned audio_chunk::g_count_channels(unsigned p_config)
|
||||
{
|
||||
unsigned ret = 0;
|
||||
while(p_config) {
|
||||
ret += (p_config & 1);
|
||||
p_config >>= 1;
|
||||
}
|
||||
return ret;
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
//! This class handles conversion of audio data (audio_chunk) to various linear PCM types, with optional dithering.
|
||||
|
||||
class NOVTABLE audio_postprocessor : public service_base
|
||||
{
|
||||
public:
|
||||
//! Processes one chunk of audio data.
|
||||
//! @param p_chunk Chunk of audio data to process.
|
||||
//! @param p_output Receives output linear signed PCM data.
|
||||
//! @param p_out_bps Desired bit depth of output.
|
||||
//! @param p_out_bps_physical Desired physical word width of output. Must be either 8, 16, 24 or 32, greater or equal to p_out_bps. This is typically set to same value as p_out_bps.
|
||||
//! @param p_dither Indicates whether dithering should be used. Note that dithering is CPU-heavy.
|
||||
//! @param p_prescale Value to scale all audio samples by when converting. Set to 1.0 to do nothing.
|
||||
|
||||
virtual void run(const audio_chunk & p_chunk,
|
||||
mem_block_container & p_output,
|
||||
t_uint32 p_out_bps,
|
||||
t_uint32 p_out_bps_physical,
|
||||
bool p_dither,
|
||||
audio_sample p_prescale
|
||||
) = 0;
|
||||
|
||||
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(audio_postprocessor);
|
||||
};
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
Autoplaylist APIs
|
||||
These APIs were introduced in foobar2000 0.9.5, to reduce amount of code required to create your own autoplaylists. Creation of autoplaylists is was also possible before through playlist lock APIs.
|
||||
In most cases, you'll want to turn regular playlists into autoplaylists using the following code:
|
||||
static_api_ptr_t<autoplaylist_manager>()->add_client_simple(querypattern, sortpattern, playlistindex, forceSort ? autoplaylist_flag_sort : 0);
|
||||
If you require more advanced functionality, such as using your own code to filter which part of user's Media Library should be placed in specific autoplaylist, you must implement autoplaylist_client (to let autoplaylist manager invoke your handlers when needed) / autoplaylist_client_factory (to re-instantiate your autoplaylist_client after a foobar2000 restart cycle).
|
||||
*/
|
||||
|
||||
enum {
|
||||
//! When set, core will keep the autoplaylist sorted and prevent user from reordering it.
|
||||
autoplaylist_flag_sort = 1 << 0,
|
||||
};
|
||||
//! Main class controlling autoplaylist behaviors. Implemented by autoplaylist client in scenarios where simple query/sort strings are not enough (core provides a standard implementation for simple queries).
|
||||
class NOVTABLE autoplaylist_client : public service_base {
|
||||
public:
|
||||
virtual GUID get_guid() = 0;
|
||||
//! Called only inside a metadb lock for performance reasons.
|
||||
virtual void filter(metadb_handle_list_cref data, bool * out) = 0;
|
||||
//! Return true when you have filled p_orderbuffer with a permutation to apply to p_items, false when you don't support sorting (core's own sort scheme will be applied).
|
||||
virtual bool sort(metadb_handle_list_cref p_items,t_size * p_orderbuffer) = 0;
|
||||
//! Retrieves your configuration data to be used later when re-instantiating your autoplaylist_client after a restart.
|
||||
virtual void get_configuration(stream_writer * p_stream,abort_callback & p_abort) = 0;
|
||||
|
||||
virtual void show_ui(t_size p_source_playlist) = 0;
|
||||
|
||||
//! Helper.
|
||||
template<typename t_array> void get_configuration(t_array & p_out) {
|
||||
pfc::static_assert<sizeof(p_out[0]) == 1>();
|
||||
typedef pfc::array_t<t_uint8,pfc::alloc_fast_aggressive> t_temp; t_temp temp;
|
||||
get_configuration(&stream_writer_buffer_append_ref_t<t_temp>(temp),abort_callback_impl());
|
||||
p_out = temp;
|
||||
}
|
||||
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(autoplaylist_client,service_base)
|
||||
};
|
||||
|
||||
typedef service_ptr_t<autoplaylist_client> autoplaylist_client_ptr;
|
||||
|
||||
//! Supported from 0.9.5.3 up.
|
||||
class NOVTABLE autoplaylist_client_v2 : public autoplaylist_client {
|
||||
public:
|
||||
//! Sets a completion_notify object that the autoplaylist_client implementation should call when its filtering behaviors have changed so the whole playlist needs to be rebuilt. \n
|
||||
//! completion_notify::on_completion() status parameter meaning: \n
|
||||
//! 0.9.5.3 : ignored. \n
|
||||
//! 0.9.5.4 and newer: set to 1 to indicate that your configuration has changed as well (for an example as a result of user edits) to get a get_configuration() call as well as cause the playlist to be rebuilt; set to zero otherwise - when the configuration hasn't changed but the playlist needs to be rebuilt as a result of some other event.
|
||||
virtual void set_full_refresh_notify(completion_notify::ptr notify) = 0;
|
||||
|
||||
//! Returns whether the show_ui() method is available / does anything useful with out implementation (not everyone implements show_ui).
|
||||
virtual bool show_ui_available() = 0;
|
||||
|
||||
//! Returns a human-readable autoplaylist implementer's label to display in playlist's context menu / description / etc.
|
||||
virtual void get_display_name(pfc::string_base & out) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(autoplaylist_client_v2, autoplaylist_client);
|
||||
};
|
||||
|
||||
//! Class needed to re-instantiate autoplaylist_client after a restart. Not directly needed to set up an autoplaylist_client, but without it, your autoplaylist will be lost after a restart.
|
||||
class NOVTABLE autoplaylist_client_factory : public service_base {
|
||||
public:
|
||||
//! Must return same GUID as your autoplaylist_client::get_guid()
|
||||
virtual GUID get_guid() = 0;
|
||||
//! Instantiates your autoplaylist_client with specified configuration.
|
||||
virtual autoplaylist_client_ptr instantiate(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(autoplaylist_client_factory)
|
||||
};
|
||||
|
||||
PFC_DECLARE_EXCEPTION(exception_autoplaylist,pfc::exception,"Autoplaylist error")
|
||||
|
||||
PFC_DECLARE_EXCEPTION(exception_autoplaylist_already_owned,exception_autoplaylist,"This playlist is already an autoplaylist")
|
||||
PFC_DECLARE_EXCEPTION(exception_autoplaylist_not_owned,exception_autoplaylist,"This playlist is not an autoplaylist")
|
||||
PFC_DECLARE_EXCEPTION(exception_autoplaylist_lock_failure,exception_autoplaylist,"Playlist could not be locked")
|
||||
|
||||
|
||||
//! Primary class for managing autoplaylists. Implemented by core, do not reimplement; instantiate using static_api_ptr_t<autoplaylist_manager>.
|
||||
class NOVTABLE autoplaylist_manager : public service_base {
|
||||
public:
|
||||
//! Throws exception_autoplaylist or one of its subclasses on failure.
|
||||
//! @param p_flags See autoplaylist_flag_* constants.
|
||||
virtual void add_client(autoplaylist_client_ptr p_client,t_size p_playlist,t_uint32 p_flags) = 0;
|
||||
virtual bool is_client_present(t_size p_playlist) = 0;
|
||||
//! Throws exception_autoplaylist or one of its subclasses on failure (eg. not an autoplaylist).
|
||||
virtual autoplaylist_client_ptr query_client(t_size p_playlist) = 0;
|
||||
virtual void remove_client(t_size p_playlist) = 0;
|
||||
//! Helper; sets up an autoplaylist using standard autoplaylist_client implementation based on simple query/sort strings. When using this, you don't need to maintain own autoplaylist_client/autoplaylist_client_factory implementations, and autoplaylists that you create will not be lost when your DLL is removed, as opposed to using add_client() directly.
|
||||
//! Throws exception_autoplaylist or one of its subclasses on failure.
|
||||
//! @param p_flags See autoplaylist_flag_* constants.
|
||||
virtual void add_client_simple(const char * p_query,const char * p_sort,t_size p_playlist,t_uint32 p_flags) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(autoplaylist_manager)
|
||||
};
|
||||
|
||||
//! \since 0.9.5.4
|
||||
//! Extended version of autoplaylist_manager, available from 0.9.5.4 up, with methods allowing modification of autoplaylist flags.
|
||||
class NOVTABLE autoplaylist_manager_v2 : public autoplaylist_manager {
|
||||
FB2K_MAKE_SERVICE_INTERFACE(autoplaylist_manager_v2, autoplaylist_manager)
|
||||
public:
|
||||
virtual t_uint32 get_client_flags(t_size playlist) = 0;
|
||||
virtual void set_client_flags(t_size playlist, t_uint32 newFlags) = 0;
|
||||
|
||||
//! For use with autoplaylist client configuration dialogs. It's recommended not to call this from anything else.
|
||||
virtual t_uint32 get_client_flags(autoplaylist_client::ptr client) = 0;
|
||||
//! For use with autoplaylist client configuration dialogs. It's recommended not to call this from anything else.
|
||||
virtual void set_client_flags(autoplaylist_client::ptr client, t_uint32 newFlags) = 0;
|
||||
};
|
|
@ -0,0 +1,57 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
cfg_var_reader * cfg_var_reader::g_list = NULL;
|
||||
cfg_var_writer * cfg_var_writer::g_list = NULL;
|
||||
|
||||
void cfg_var_reader::config_read_file(stream_reader * p_stream,abort_callback & p_abort)
|
||||
{
|
||||
pfc::map_t<GUID,cfg_var_reader*> vars;
|
||||
for(cfg_var_reader * walk = g_list; walk != NULL; walk = walk->m_next) {
|
||||
vars.set(walk->m_guid,walk);
|
||||
}
|
||||
for(;;) {
|
||||
|
||||
GUID guid;
|
||||
t_uint32 size;
|
||||
|
||||
if (p_stream->read(&guid,sizeof(guid),p_abort) != sizeof(guid)) break;
|
||||
guid = pfc::byteswap_if_be_t(guid);
|
||||
p_stream->read_lendian_t(size,p_abort);
|
||||
|
||||
cfg_var_reader * var;
|
||||
if (vars.query(guid,var)) {
|
||||
stream_reader_limited_ref wrapper(p_stream,size);
|
||||
try {
|
||||
var->set_data_raw(&wrapper,size,p_abort);
|
||||
} catch(exception_io_data) {}
|
||||
wrapper.flush_remaining(p_abort);
|
||||
} else {
|
||||
p_stream->skip_object(size,p_abort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void cfg_var_writer::config_write_file(stream_writer * p_stream,abort_callback & p_abort) {
|
||||
cfg_var_writer * ptr;
|
||||
pfc::array_t<t_uint8,pfc::alloc_fast_aggressive> temp;
|
||||
for(ptr = g_list; ptr; ptr = ptr->m_next) {
|
||||
temp.set_size(0);
|
||||
ptr->get_data_raw(&stream_writer_buffer_append_ref_t<pfc::array_t<t_uint8,pfc::alloc_fast_aggressive> >(temp),p_abort);
|
||||
p_stream->write_lendian_t(ptr->m_guid,p_abort);
|
||||
p_stream->write_lendian_t(pfc::downcast_guarded<t_uint32>(temp.get_size()),p_abort);
|
||||
if (temp.get_size() > 0) {
|
||||
p_stream->write_object(temp.get_ptr(),temp.get_size(),p_abort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void cfg_string::get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {
|
||||
p_stream->write_object(get_ptr(),length(),p_abort);
|
||||
}
|
||||
|
||||
void cfg_string::set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {
|
||||
pfc::string8_fastalloc temp;
|
||||
p_stream->read_string_raw(temp,p_abort);
|
||||
set_string(temp);
|
||||
}
|
|
@ -0,0 +1,235 @@
|
|||
#ifndef _FOOBAR2000_SDK_CFG_VAR_H_
|
||||
#define _FOOBAR2000_SDK_CFG_VAR_H_
|
||||
|
||||
#define CFG_VAR_ASSERT_SAFEINIT PFC_ASSERT(!core_api::are_services_available());/*imperfect check for nonstatic instantiation*/
|
||||
|
||||
|
||||
//! Reader part of cfg_var object. In most cases, you should use cfg_var instead of using cfg_var_reader directly.
|
||||
class NOVTABLE cfg_var_reader {
|
||||
public:
|
||||
//! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var.
|
||||
cfg_var_reader(const GUID & guid) : m_guid(guid) { CFG_VAR_ASSERT_SAFEINIT; m_next = g_list; g_list = this; }
|
||||
~cfg_var_reader() { CFG_VAR_ASSERT_SAFEINIT; }
|
||||
|
||||
//! Sets state of the variable. Called only from main thread, when reading configuration file.
|
||||
//! @param p_stream Stream containing new state of the variable.
|
||||
//! @param p_sizehint Number of bytes contained in the stream; reading past p_sizehint bytes will fail (EOF).
|
||||
virtual void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) = 0;
|
||||
|
||||
//! For internal use only, do not call.
|
||||
static void config_read_file(stream_reader * p_stream,abort_callback & p_abort);
|
||||
|
||||
const GUID m_guid;
|
||||
private:
|
||||
static cfg_var_reader * g_list;
|
||||
cfg_var_reader * m_next;
|
||||
|
||||
PFC_CLASS_NOT_COPYABLE_EX(cfg_var_reader)
|
||||
};
|
||||
|
||||
//! Writer part of cfg_var object. In most cases, you should use cfg_var instead of using cfg_var_writer directly.
|
||||
class NOVTABLE cfg_var_writer {
|
||||
public:
|
||||
//! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var.
|
||||
cfg_var_writer(const GUID & guid) : m_guid(guid) { CFG_VAR_ASSERT_SAFEINIT; m_next = g_list; g_list = this;}
|
||||
~cfg_var_writer() { CFG_VAR_ASSERT_SAFEINIT; }
|
||||
|
||||
//! Retrieves state of the variable. Called only from main thread, when writing configuration file.
|
||||
//! @param p_stream Stream receiving state of the variable.
|
||||
virtual void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) = 0;
|
||||
|
||||
//! For internal use only, do not call.
|
||||
static void config_write_file(stream_writer * p_stream,abort_callback & p_abort);
|
||||
|
||||
const GUID m_guid;
|
||||
private:
|
||||
static cfg_var_writer * g_list;
|
||||
cfg_var_writer * m_next;
|
||||
|
||||
PFC_CLASS_NOT_COPYABLE_EX(cfg_var_writer)
|
||||
};
|
||||
|
||||
//! Base class for configuration variable classes; provides self-registration mechaisms and methods to set/retrieve configuration data; those methods are automatically called for all registered instances by backend when configuration file is being read or written.\n
|
||||
//! Note that cfg_var class and its derivatives may be only instantiated statically (as static objects or members of other static objects), NEVER dynamically (operator new, local variables, members of objects instantiated as such).
|
||||
class NOVTABLE cfg_var : public cfg_var_reader, public cfg_var_writer {
|
||||
protected:
|
||||
//! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var.
|
||||
cfg_var(const GUID & p_guid) : cfg_var_reader(p_guid), cfg_var_writer(p_guid) {}
|
||||
public:
|
||||
GUID get_guid() const {return cfg_var_reader::m_guid;}
|
||||
};
|
||||
|
||||
//! Generic integer config variable class. Template parameter can be used to specify integer type to use.\n
|
||||
//! Note that cfg_var class and its derivatives may be only instantiated statically (as static objects or members of other static objects), NEVER dynamically (operator new, local variables, members of objects instantiated as such).
|
||||
template<typename t_inttype>
|
||||
class cfg_int_t : public cfg_var {
|
||||
private:
|
||||
t_inttype m_val;
|
||||
protected:
|
||||
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {p_stream->write_lendian_t(m_val,p_abort);}
|
||||
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {
|
||||
t_inttype temp;
|
||||
p_stream->read_lendian_t(temp,p_abort);//alter member data only on success, this will throw an exception when something isn't right
|
||||
m_val = temp;
|
||||
}
|
||||
|
||||
public:
|
||||
//! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var.
|
||||
//! @param p_default Default value of the variable.
|
||||
explicit inline cfg_int_t(const GUID & p_guid,t_inttype p_default) : cfg_var(p_guid), m_val(p_default) {}
|
||||
|
||||
inline const cfg_int_t<t_inttype> & operator=(const cfg_int_t<t_inttype> & p_val) {m_val=p_val.m_val;return *this;}
|
||||
inline t_inttype operator=(t_inttype p_val) {m_val=p_val;return m_val;}
|
||||
|
||||
inline operator t_inttype() const {return m_val;}
|
||||
|
||||
inline t_inttype get_value() const {return m_val;}
|
||||
};
|
||||
|
||||
typedef cfg_int_t<t_int32> cfg_int;
|
||||
typedef cfg_int_t<t_uint32> cfg_uint;
|
||||
//! Since relevant byteswapping functions also understand GUIDs, this can be abused to declare a cfg_guid.
|
||||
typedef cfg_int_t<GUID> cfg_guid;
|
||||
typedef cfg_int_t<bool> cfg_bool;
|
||||
|
||||
//! String config variable. Stored in the stream with int32 header containing size in bytes, followed by non-null-terminated UTF-8 data.\n
|
||||
//! Note that cfg_var class and its derivatives may be only instantiated statically (as static objects or members of other static objects), NEVER dynamically (operator new, local variables, members of objects instantiated as such).
|
||||
class cfg_string : public cfg_var, public pfc::string8
|
||||
{
|
||||
protected:
|
||||
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort);
|
||||
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort);
|
||||
|
||||
public:
|
||||
//! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var.
|
||||
//! @param p_defaultval Default/initial value of the variable.
|
||||
explicit inline cfg_string(const GUID & p_guid,const char * p_defaultval) : cfg_var(p_guid), pfc::string8(p_defaultval) {}
|
||||
|
||||
inline const cfg_string& operator=(const cfg_string & p_val) {set_string(p_val);return *this;}
|
||||
inline const cfg_string& operator=(const char* p_val) {set_string(p_val);return *this;}
|
||||
|
||||
inline operator const char * () const {return get_ptr();}
|
||||
|
||||
};
|
||||
|
||||
//! Struct config variable template. Warning: not endian safe, should be used only for nonportable code.\n
|
||||
//! Note that cfg_var class and its derivatives may be only instantiated statically (as static objects or members of other static objects), NEVER dynamically (operator new, local variables, members of objects instantiated as such).
|
||||
template<typename t_struct>
|
||||
class cfg_struct_t : public cfg_var {
|
||||
private:
|
||||
t_struct m_val;
|
||||
protected:
|
||||
|
||||
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {p_stream->write_object(&m_val,sizeof(m_val),p_abort);}
|
||||
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {
|
||||
t_struct temp;
|
||||
p_stream->read_object(&temp,sizeof(temp),p_abort);
|
||||
m_val = temp;
|
||||
}
|
||||
public:
|
||||
//! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var.
|
||||
inline cfg_struct_t(const GUID & p_guid,const t_struct & p_val) : cfg_var(p_guid), m_val(p_val) {}
|
||||
//! @param p_guid GUID of the variable, used to identify variable implementations owning specific configuration file entries when reading the configuration file back. You must generate a new GUID every time you declare a new cfg_var.
|
||||
inline cfg_struct_t(const GUID & p_guid,int filler) : cfg_var(p_guid) {memset(&m_val,filler,sizeof(t_struct));}
|
||||
|
||||
inline const cfg_struct_t<t_struct> & operator=(const cfg_struct_t<t_struct> & p_val) {m_val = p_val.get_value();return *this;}
|
||||
inline const cfg_struct_t<t_struct> & operator=(const t_struct & p_val) {m_val = p_val;return *this;}
|
||||
|
||||
inline const t_struct& get_value() const {return m_val;}
|
||||
inline t_struct& get_value() {return m_val;}
|
||||
inline operator t_struct() const {return m_val;}
|
||||
};
|
||||
|
||||
|
||||
template<typename TObj>
|
||||
class cfg_objList : public cfg_var, public pfc::list_t<TObj> {
|
||||
public:
|
||||
cfg_objList(const GUID& guid) : cfg_var(guid) {}
|
||||
template<typename TSource, unsigned Count> cfg_objList(const GUID& guid, const TSource (& source)[Count]) : cfg_var(guid) {
|
||||
reset(source);
|
||||
}
|
||||
template<typename TSource, unsigned Count> void reset(const TSource (& source)[Count]) {
|
||||
set_size(Count); for(t_size walk = 0; walk < Count; ++walk) (*this)[walk] = source[walk];
|
||||
}
|
||||
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {
|
||||
stream_writer_formatter<> out(*p_stream,p_abort);
|
||||
out << pfc::downcast_guarded<t_uint32>(get_size());
|
||||
for(t_size walk = 0; walk < get_size(); ++walk) out << (*this)[walk];
|
||||
}
|
||||
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {
|
||||
try {
|
||||
stream_reader_formatter<> in(*p_stream,p_abort);
|
||||
t_uint32 count; in >> count;
|
||||
set_count(count);
|
||||
for(t_uint32 walk = 0; walk < count; ++walk) in >> (*this)[walk];
|
||||
} catch(...) {
|
||||
remove_all();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
};
|
||||
template<typename TList>
|
||||
class cfg_objListEx : public cfg_var, public TList {
|
||||
public:
|
||||
cfg_objListEx(const GUID & guid) : cfg_var(guid) {}
|
||||
void get_data_raw(stream_writer * p_stream, abort_callback & p_abort) {
|
||||
stream_writer_formatter<> out(*p_stream,p_abort);
|
||||
out << pfc::downcast_guarded<t_uint32>(this->get_count());
|
||||
for(typename TList::const_iterator walk = this->first(); walk.is_valid(); ++walk) out << *walk;
|
||||
}
|
||||
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {
|
||||
remove_all();
|
||||
stream_reader_formatter<> in(*p_stream,p_abort);
|
||||
t_uint32 count; in >> count;
|
||||
for(t_uint32 walk = 0; walk < count; ++walk) {
|
||||
typename TList::t_item item; in >> item; this->add_item(item);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
template<typename TObj>
|
||||
class cfg_obj : public cfg_var, public TObj {
|
||||
public:
|
||||
cfg_obj(const GUID& guid) : cfg_var(guid), TObj() {}
|
||||
template<typename TInitData> cfg_obj(const GUID& guid,const TInitData& initData) : cfg_var(guid), TObj(initData) {}
|
||||
|
||||
TObj & val() {return *this;}
|
||||
TObj const & val() const {return *this;}
|
||||
|
||||
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {
|
||||
stream_writer_formatter<> out(*p_stream,p_abort);
|
||||
const TObj * ptr = this;
|
||||
out << *ptr;
|
||||
}
|
||||
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {
|
||||
stream_reader_formatter<> in(*p_stream,p_abort);
|
||||
TObj * ptr = this;
|
||||
in >> *ptr;
|
||||
}
|
||||
};
|
||||
|
||||
template<typename TObj, typename TImport> class cfg_objListImporter : private cfg_var_reader {
|
||||
public:
|
||||
typedef cfg_objList<TObj> TMasterVar;
|
||||
cfg_objListImporter(TMasterVar & var, const GUID & guid) : m_var(var), cfg_var_reader(guid) {}
|
||||
|
||||
private:
|
||||
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {
|
||||
TImport temp;
|
||||
try {
|
||||
stream_reader_formatter<> in(*p_stream,p_abort);
|
||||
t_uint32 count; in >> count;
|
||||
m_var.set_count(count);
|
||||
for(t_uint32 walk = 0; walk < count; ++walk) {
|
||||
in >> temp;
|
||||
m_var[walk] = temp;
|
||||
}
|
||||
} catch(...) {
|
||||
m_var.remove_all();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
TMasterVar & m_var;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,24 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
void chapter_list::copy(const chapter_list & p_source)
|
||||
{
|
||||
t_size n, count = p_source.get_chapter_count();
|
||||
set_chapter_count(count);
|
||||
for(n=0;n<count;n++)
|
||||
set_info(n,p_source.get_info(n));
|
||||
}
|
||||
|
||||
bool chapterizer::g_find(service_ptr_t<chapterizer> & p_out,const char * p_path,abort_callback & p_abort)
|
||||
{
|
||||
service_ptr_t<chapterizer> ptr;
|
||||
service_enum_t<chapterizer> e;
|
||||
while(e.next(ptr))
|
||||
{
|
||||
if (ptr->is_our_file(p_path,p_abort))
|
||||
{
|
||||
p_out = ptr;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
//! Interface for object storing list of chapters.
|
||||
class NOVTABLE chapter_list {
|
||||
public:
|
||||
//! Returns number of chapters.
|
||||
virtual t_size get_chapter_count() const = 0;
|
||||
//! Queries description of specified chapter.
|
||||
//! @param p_chapter Index of chapter to query, greater or equal zero and less than get_chapter_count() value. If p_chapter value is out of valid range, results are undefined (e.g. crash).
|
||||
//! @returns reference to file_info object describing specified chapter (length part of file_info indicates distance between beginning of this chapter and next chapter mark). Returned reference value for temporary use only, becomes invalid after any non-const operation on the chapter_list object.
|
||||
virtual const file_info & get_info(t_size p_chapter) const = 0;
|
||||
|
||||
//! Sets number of chapters.
|
||||
virtual void set_chapter_count(t_size p_count) = 0;
|
||||
//! Modifies description of specified chapter.
|
||||
//! @param p_chapter_index Index of chapter to modify, greater or equal zero and less than get_chapter_count() value. If p_chapter value is out of valid range, results are undefined (e.g. crash).
|
||||
//! @param p_info New chapter description. Note that length part of file_info is used to calculate chapter marks.
|
||||
virtual void set_info(t_size p_chapter,const file_info & p_info) = 0;
|
||||
|
||||
//! Copies contents of specified chapter_list object to this object.
|
||||
void copy(const chapter_list & p_source);
|
||||
|
||||
inline const chapter_list & operator=(const chapter_list & p_source) {copy(p_source); return *this;}
|
||||
|
||||
protected:
|
||||
chapter_list() {}
|
||||
~chapter_list() {}
|
||||
};
|
||||
|
||||
//! Implements chapter_list.
|
||||
class chapter_list_impl : public chapter_list
|
||||
{
|
||||
public:
|
||||
chapter_list_impl(const chapter_list_impl & p_source) {copy(p_source);}
|
||||
chapter_list_impl(const chapter_list & p_source) {copy(p_source);}
|
||||
chapter_list_impl() {}
|
||||
|
||||
const chapter_list_impl & operator=(const chapter_list_impl & p_source) {copy(p_source); return *this;}
|
||||
const chapter_list_impl & operator=(const chapter_list & p_source) {copy(p_source); return *this;}
|
||||
|
||||
t_size get_chapter_count() const {return m_infos.get_size();}
|
||||
const file_info & get_info(t_size p_chapter) const {return m_infos[p_chapter];}
|
||||
|
||||
void set_chapter_count(t_size p_count) {m_infos.set_size(p_count);}
|
||||
void set_info(t_size p_chapter,const file_info & p_info) {m_infos[p_chapter] = p_info;}
|
||||
private:
|
||||
pfc::array_t<file_info_impl> m_infos;
|
||||
};
|
||||
|
||||
|
||||
//! This service implements chapter list editing operations for various file formats, e.g. for MP4 chapters or CD images with embedded cuesheets. Used by converter "encode single file with chapters" feature.
|
||||
class NOVTABLE chapterizer : public service_base {
|
||||
public:
|
||||
//! Tests whether specified path is supported by this implementation.
|
||||
//! @param p_path Path of file to examine.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
virtual bool is_our_file(const char * p_path,abort_callback & p_abort) = 0;
|
||||
|
||||
//! Writes new chapter list to specified file.
|
||||
//! @param p_path Path of file to modify.
|
||||
//! @param p_list New chapter list to write.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
virtual void set_chapters(const char * p_path,chapter_list const & p_list,abort_callback & p_abort) = 0;
|
||||
//! Retrieves chapter list from specified file.
|
||||
//! @param p_path Path of file to examine.
|
||||
//! @param p_list Object receiving chapter list.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
virtual void get_chapters(const char * p_path,chapter_list & p_list,abort_callback & p_abort) = 0;
|
||||
|
||||
//! Static helper, tries to find chapterizer interface that supports specified file.
|
||||
static bool g_find(service_ptr_t<chapterizer> & p_out,const char * p_path,abort_callback & p_abort);
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(chapterizer);
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
void commandline_handler_metadb_handle::on_file(const char * url)
|
||||
{
|
||||
|
||||
abort_callback_dummy blah;
|
||||
|
||||
{
|
||||
playlist_loader_callback_impl callback(blah);
|
||||
|
||||
bool fail = false;
|
||||
try {
|
||||
playlist_loader::g_process_path_ex(url,callback);
|
||||
} catch(std::exception const & e) {
|
||||
console::complain("Unhandled exception in playlist loader", e);
|
||||
fail = true;
|
||||
}
|
||||
|
||||
if (!fail) {
|
||||
t_size n,m=callback.get_count();
|
||||
for(n=0;n<m;n++) on_file(callback.get_item(n));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
class NOVTABLE commandline_handler : public service_base
|
||||
{
|
||||
public:
|
||||
enum result
|
||||
{
|
||||
RESULT_NOT_OURS,//not our command
|
||||
RESULT_PROCESSED,//command processed
|
||||
RESULT_PROCESSED_EXPECT_FILES,//command processed, we want to takeover file urls after this command
|
||||
};
|
||||
virtual result on_token(const char * token)=0;
|
||||
virtual void on_file(const char * url) {};//optional
|
||||
virtual void on_files_done() {};//optional
|
||||
virtual bool want_directories() {return false;}
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(commandline_handler);
|
||||
};
|
||||
|
||||
class commandline_handler_metadb_handle : public commandline_handler//helper
|
||||
{
|
||||
protected:
|
||||
virtual void on_file(const char * url);
|
||||
virtual bool want_directories() {return true;}
|
||||
public:
|
||||
virtual result on_token(const char * token)=0;
|
||||
virtual void on_files_done() {};
|
||||
|
||||
virtual void on_file(const metadb_handle_ptr & ptr)=0;
|
||||
};
|
||||
|
||||
/*
|
||||
|
||||
how commandline_handler is used:
|
||||
|
||||
scenario #1:
|
||||
creation => on_token() => deletion
|
||||
scenario #2:
|
||||
creation => on_token() returning RESULT_PROCESSED_EXPECT_FILES => on_file(), on_file().... => on_files_done() => deletion
|
||||
*/
|
||||
|
||||
template<typename T>
|
||||
class commandline_handler_factory_t : public service_factory_t<T> {};
|
|
@ -0,0 +1,25 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
namespace {
|
||||
class main_thread_callback_myimpl : public main_thread_callback {
|
||||
public:
|
||||
void callback_run() {
|
||||
m_notify->on_completion(m_code);
|
||||
}
|
||||
|
||||
main_thread_callback_myimpl(completion_notify_ptr p_notify,unsigned p_code) : m_notify(p_notify), m_code(p_code) {}
|
||||
private:
|
||||
completion_notify_ptr m_notify;
|
||||
unsigned m_code;
|
||||
};
|
||||
}
|
||||
|
||||
void completion_notify::g_signal_completion_async(completion_notify_ptr p_notify,unsigned p_code) {
|
||||
if (p_notify.is_valid()) {
|
||||
static_api_ptr_t<main_thread_callback_manager>()->add_callback(new service_impl_t<main_thread_callback_myimpl>(p_notify,p_code));
|
||||
}
|
||||
}
|
||||
|
||||
void completion_notify::on_completion_async(unsigned p_code) {
|
||||
static_api_ptr_t<main_thread_callback_manager>()->add_callback(new service_impl_t<main_thread_callback_myimpl>(this,p_code));
|
||||
}
|
|
@ -0,0 +1,89 @@
|
|||
//! Generic service for receiving notifications about async operation completion. Used by various other services.
|
||||
class completion_notify : public service_base {
|
||||
public:
|
||||
//! Called when an async operation has been completed. Note that on_completion is always called from main thread. You can use on_completion_async() helper if you need to signal completion while your context is in another thread.\n
|
||||
//! IMPLEMENTATION WARNING: If process being completed creates a window taking caller's window as parent, you must not destroy the parent window inside on_completion(). If you need to do so, use PostMessage() or main_thread_callback to delay the deletion.
|
||||
//! @param p_code Context-specific status code. Possible values depend on the operation being performed.
|
||||
virtual void on_completion(unsigned p_code) = 0;
|
||||
|
||||
//! Helper. Queues a notification, using main_thread_callback.
|
||||
void on_completion_async(unsigned p_code);
|
||||
|
||||
//! Helper. Checks for null ptr and calls on_completion_async when the ptr is not null.
|
||||
static void g_signal_completion_async(service_ptr_t<completion_notify> p_notify,unsigned p_code);
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(completion_notify,service_base);
|
||||
};
|
||||
|
||||
//! Implementation helper.
|
||||
class completion_notify_dummy : public completion_notify {
|
||||
public:
|
||||
void on_completion(unsigned p_code) {}
|
||||
};
|
||||
|
||||
//! Implementation helper.
|
||||
class completion_notify_orphanable : public completion_notify {
|
||||
public:
|
||||
virtual void orphan() = 0;
|
||||
};
|
||||
|
||||
//! Helper implementation.
|
||||
//! IMPLEMENTATION WARNING: If process being completed creates a window taking caller's window as parent, you must not destroy the parent window inside on_task_completion(). If you need to do so, use PostMessage() or main_thread_callback to delay the deletion.
|
||||
template<typename t_receiver>
|
||||
class completion_notify_impl : public completion_notify_orphanable {
|
||||
public:
|
||||
void on_completion(unsigned p_code) {
|
||||
if (m_receiver != NULL) {
|
||||
m_receiver->on_task_completion(m_taskid,p_code);
|
||||
}
|
||||
}
|
||||
void setup(t_receiver * p_receiver, unsigned p_task_id) {m_receiver = p_receiver; m_taskid = p_task_id;}
|
||||
void orphan() {m_receiver = NULL; m_taskid = 0;}
|
||||
private:
|
||||
t_receiver * m_receiver;
|
||||
unsigned m_taskid;
|
||||
};
|
||||
|
||||
template<typename t_receiver>
|
||||
service_ptr_t<completion_notify_orphanable> completion_notify_create(t_receiver * p_receiver,unsigned p_taskid) {
|
||||
service_ptr_t<completion_notify_impl<t_receiver> > instance = new service_impl_t<completion_notify_impl<t_receiver> >();
|
||||
instance->setup(p_receiver,p_taskid);
|
||||
return instance;
|
||||
}
|
||||
|
||||
typedef service_ptr_t<completion_notify> completion_notify_ptr;
|
||||
typedef service_ptr_t<completion_notify_orphanable> completion_notify_orphanable_ptr;
|
||||
|
||||
//! Helper base class for classes that manage nonblocking tasks and get notified back thru completion_notify interface.
|
||||
class completion_notify_receiver {
|
||||
public:
|
||||
completion_notify_ptr create_task(unsigned p_id) {
|
||||
completion_notify_orphanable_ptr ptr;
|
||||
if (m_tasks.query(p_id,ptr)) ptr->orphan();
|
||||
ptr = completion_notify_create(this,p_id);
|
||||
m_tasks.set(p_id,ptr);
|
||||
return ptr;
|
||||
}
|
||||
bool have_task(unsigned p_id) const {
|
||||
return m_tasks.have_item(p_id);
|
||||
}
|
||||
void orphan_task(unsigned p_id) {
|
||||
completion_notify_orphanable_ptr ptr;
|
||||
if (m_tasks.query(p_id,ptr)) {
|
||||
ptr->orphan();
|
||||
m_tasks.remove(p_id);
|
||||
}
|
||||
}
|
||||
~completion_notify_receiver() {
|
||||
orphan_all_tasks();
|
||||
}
|
||||
void orphan_all_tasks() {
|
||||
m_tasks.enumerate(orphanfunc);
|
||||
m_tasks.remove_all();
|
||||
}
|
||||
|
||||
virtual void on_task_completion(unsigned p_id,unsigned p_status) {}
|
||||
private:
|
||||
static void orphanfunc(unsigned,completion_notify_orphanable_ptr p_item) {p_item->orphan();}
|
||||
pfc::map_t<unsigned,completion_notify_orphanable_ptr> m_tasks;
|
||||
};
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef _COMPONENT_H_
|
||||
#define _COMPONENT_H_
|
||||
|
||||
#include "foobar2000.h"
|
||||
|
||||
class NOVTABLE foobar2000_client
|
||||
{
|
||||
public:
|
||||
typedef service_factory_base* pservice_factory_base;
|
||||
|
||||
enum {FOOBAR2000_CLIENT_VERSION_COMPATIBLE = 70, FOOBAR2000_CLIENT_VERSION = 73}; //changes everytime global compatibility is broken
|
||||
virtual t_uint32 FB2KAPI get_version() = 0;
|
||||
virtual pservice_factory_base FB2KAPI get_service_list() = 0;
|
||||
|
||||
virtual void FB2KAPI get_config(stream_writer * p_stream,abort_callback & p_abort) = 0;
|
||||
virtual void FB2KAPI set_config(stream_reader * p_stream,abort_callback & p_abort) = 0;
|
||||
virtual void FB2KAPI set_library_path(const char * path,const char * name) = 0;
|
||||
virtual void FB2KAPI services_init(bool val) = 0;
|
||||
virtual bool is_debug() = 0;
|
||||
protected:
|
||||
foobar2000_client() {}
|
||||
~foobar2000_client() {}
|
||||
};
|
||||
|
||||
class NOVTABLE foobar2000_api
|
||||
{
|
||||
public:
|
||||
virtual service_class_ref FB2KAPI service_enum_find_class(const GUID & p_guid) = 0;
|
||||
virtual bool FB2KAPI service_enum_create(service_ptr_t<service_base> & p_out,service_class_ref p_class,t_size p_index) = 0;
|
||||
virtual t_size FB2KAPI service_enum_get_count(service_class_ref p_class) = 0;
|
||||
virtual HWND FB2KAPI get_main_window()=0;
|
||||
virtual bool FB2KAPI assert_main_thread()=0;
|
||||
virtual bool FB2KAPI is_main_thread()=0;
|
||||
virtual bool FB2KAPI is_shutting_down()=0;
|
||||
virtual pcchar FB2KAPI get_profile_path()=0;
|
||||
virtual bool FB2KAPI is_initializing() = 0;
|
||||
protected:
|
||||
foobar2000_api() {}
|
||||
~foobar2000_api() {}
|
||||
};
|
||||
|
||||
extern foobar2000_api * g_api;
|
||||
|
||||
#endif
|
|
@ -0,0 +1,6 @@
|
|||
#ifndef _COMPONENT_CLIENT_H_
|
||||
#define _COMPONENT_CLIENT_H_
|
||||
|
||||
|
||||
|
||||
#endif //_COMPONENT_CLIENT_H_
|
|
@ -0,0 +1,7 @@
|
|||
#ifndef _COMPONENTS_MENU_H_
|
||||
#define _COMPONENTS_MENU_H_
|
||||
|
||||
#error deprecated, see menu_item.h
|
||||
|
||||
|
||||
#endif
|
|
@ -0,0 +1,56 @@
|
|||
//! Entrypoint interface for declaring component's version information. Instead of implementing this directly, use DECLARE_COMPONENT_VERSION().
|
||||
class NOVTABLE componentversion : public service_base {
|
||||
public:
|
||||
virtual void get_file_name(pfc::string_base & out)=0;
|
||||
virtual void get_component_name(pfc::string_base & out)=0;
|
||||
virtual void get_component_version(pfc::string_base & out)=0;
|
||||
virtual void get_about_message(pfc::string_base & out)=0;//about message uses "\n" for line separators
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(componentversion);
|
||||
};
|
||||
|
||||
//! Implementation helper.
|
||||
class componentversion_impl_simple : public componentversion {
|
||||
const char * name,*version,*about;
|
||||
public:
|
||||
//do not derive/override
|
||||
virtual void get_file_name(pfc::string_base & out) {out.set_string(core_api::get_my_file_name());}
|
||||
virtual void get_component_name(pfc::string_base & out) {out.set_string(name?name:"");}
|
||||
virtual void get_component_version(pfc::string_base & out) {out.set_string(version?version:"");}
|
||||
virtual void get_about_message(pfc::string_base & out) {out.set_string(about?about:"");}
|
||||
explicit componentversion_impl_simple(const char * p_name,const char * p_version,const char * p_about) : name(p_name), version(p_version), about(p_about ? p_about : "") {}
|
||||
};
|
||||
|
||||
//! Implementation helper.
|
||||
class componentversion_impl_copy : public componentversion {
|
||||
pfc::string8 name,version,about;
|
||||
public:
|
||||
//do not derive/override
|
||||
virtual void get_file_name(pfc::string_base & out) {out.set_string(core_api::get_my_file_name());}
|
||||
virtual void get_component_name(pfc::string_base & out) {out.set_string(name);}
|
||||
virtual void get_component_version(pfc::string_base & out) {out.set_string(version);}
|
||||
virtual void get_about_message(pfc::string_base & out) {out.set_string(about);}
|
||||
explicit componentversion_impl_copy(const char * p_name,const char * p_version,const char * p_about) : name(p_name), version(p_version), about(p_about ? p_about : "") {}
|
||||
};
|
||||
|
||||
typedef service_factory_single_transparent_t<componentversion_impl_simple> __componentversion_impl_simple_factory;
|
||||
typedef service_factory_single_transparent_t<componentversion_impl_copy> __componentversion_impl_copy_factory;
|
||||
|
||||
class componentversion_impl_simple_factory : public __componentversion_impl_simple_factory {
|
||||
public:
|
||||
componentversion_impl_simple_factory(const char * p_name,const char * p_version,const char * p_about) : __componentversion_impl_simple_factory(p_name,p_version,p_about) {}
|
||||
};
|
||||
|
||||
class componentversion_impl_copy_factory : public __componentversion_impl_copy_factory {
|
||||
public:
|
||||
componentversion_impl_copy_factory(const char * p_name,const char * p_version,const char * p_about) : __componentversion_impl_copy_factory(p_name,p_version,p_about) {}
|
||||
};
|
||||
|
||||
//! Use this to declare your component's version information. Parameters must ba plain const char * string constants. The ABOUT string can be NULL if you don't provide any information to show in the "About" dialog. \n
|
||||
//! Example: DECLARE_COMPONENT_VERSION("blah","v1.337",NULL)
|
||||
#define DECLARE_COMPONENT_VERSION(NAME,VERSION,ABOUT) \
|
||||
static componentversion_impl_simple_factory g_componentversion_service(NAME,VERSION,ABOUT);
|
||||
|
||||
//! Same as DECLARE_COMPONENT_VERSION(), but parameters can be dynamically generated strings rather than compile-time constants.
|
||||
#define DECLARE_COMPONENT_VERSION_COPY(NAME,VERSION,ABOUT) \
|
||||
static componentversion_impl_copy_factory g_componentversion_service(NAME,VERSION,ABOUT);
|
|
@ -0,0 +1,15 @@
|
|||
//! Implementing this interface lets you maintain your own configuration files rather than depending on the cfg_var system. \n
|
||||
//! Note that you must not make assumptions about what happens first: config_io_callback::on_read(), initialization of cfg_var values or config_io_callback::on_read() in other components. Order of these things is undefined and will change with each run. \n
|
||||
//! Use service_factory_single_t<myclass> to register your implementations. Do not call other people's implementations, core is responsible for doing that when appropriate.
|
||||
class NOVTABLE config_io_callback : public service_base {
|
||||
public:
|
||||
//! Called on startup. You can read your configuration file from here. \n
|
||||
//! Hint: use core_api::get_profile_path() to retrieve the path of the folder where foobar2000 configuration files are stored.
|
||||
virtual void on_read() = 0;
|
||||
//! Called on shutdown. You can write your configuration file from here.
|
||||
//! Hint: use core_api::get_profile_path() to retrieve the path of the folder where foobar2000 configuration files are stored.
|
||||
//! @param reset If set to true, our configuration is being reset, so you should wipe your files rather than rewrite them with current configuration.
|
||||
virtual void on_write(bool reset) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(config_io_callback);
|
||||
};
|
|
@ -0,0 +1,206 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
void config_object_notify_manager::g_on_changed(const service_ptr_t<config_object> & p_object)
|
||||
{
|
||||
if (core_api::assert_main_thread())
|
||||
{
|
||||
service_enum_t<config_object_notify_manager> e;
|
||||
service_ptr_t<config_object_notify_manager> ptr;
|
||||
while(e.next(ptr))
|
||||
ptr->on_changed(p_object);
|
||||
}
|
||||
}
|
||||
|
||||
bool config_object::g_find(service_ptr_t<config_object> & p_out,const GUID & p_guid)
|
||||
{
|
||||
service_ptr_t<config_object> ptr;
|
||||
service_enum_t<config_object> e;
|
||||
while(e.next(ptr))
|
||||
{
|
||||
if (ptr->get_guid() == p_guid)
|
||||
{
|
||||
p_out = ptr;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void config_object::g_get_data_string(const GUID & p_guid,pfc::string_base & p_out)
|
||||
{
|
||||
service_ptr_t<config_object> ptr;
|
||||
if (!g_find(ptr,p_guid)) throw exception_service_not_found();
|
||||
ptr->get_data_string(p_out);
|
||||
}
|
||||
|
||||
void config_object::g_set_data_string(const GUID & p_guid,const char * p_data,t_size p_length)
|
||||
{
|
||||
service_ptr_t<config_object> ptr;
|
||||
if (!g_find(ptr,p_guid)) throw exception_service_not_found();
|
||||
ptr->set_data_string(p_data,p_length);
|
||||
}
|
||||
|
||||
void config_object::get_data_int32(t_int32 & p_out)
|
||||
{
|
||||
t_int32 temp;
|
||||
get_data_struct_t<t_int32>(temp);
|
||||
byte_order::order_le_to_native_t(temp);
|
||||
p_out = temp;
|
||||
}
|
||||
|
||||
void config_object::set_data_int32(t_int32 p_val)
|
||||
{
|
||||
t_int32 temp = p_val;
|
||||
byte_order::order_native_to_le_t(temp);
|
||||
set_data_struct_t<t_int32>(temp);
|
||||
}
|
||||
|
||||
bool config_object::get_data_bool_simple(bool p_default) {
|
||||
try {
|
||||
bool ret = p_default;
|
||||
get_data_bool(ret);
|
||||
return ret;
|
||||
} catch(...) {return p_default;}
|
||||
}
|
||||
|
||||
t_int32 config_object::get_data_int32_simple(t_int32 p_default) {
|
||||
try {
|
||||
t_int32 ret = p_default;
|
||||
get_data_int32(ret);
|
||||
return ret;
|
||||
} catch(...) {return p_default;}
|
||||
}
|
||||
|
||||
void config_object::g_get_data_int32(const GUID & p_guid,t_int32 & p_out) {
|
||||
service_ptr_t<config_object> ptr;
|
||||
if (!g_find(ptr,p_guid)) throw exception_service_not_found();
|
||||
ptr->get_data_int32(p_out);
|
||||
}
|
||||
|
||||
void config_object::g_set_data_int32(const GUID & p_guid,t_int32 p_val) {
|
||||
service_ptr_t<config_object> ptr;
|
||||
if (!g_find(ptr,p_guid)) throw exception_service_not_found();
|
||||
ptr->set_data_int32(p_val);
|
||||
}
|
||||
|
||||
bool config_object::g_get_data_bool_simple(const GUID & p_guid,bool p_default)
|
||||
{
|
||||
service_ptr_t<config_object> ptr;
|
||||
if (!g_find(ptr,p_guid)) throw exception_service_not_found();
|
||||
return ptr->get_data_bool_simple(p_default);
|
||||
}
|
||||
|
||||
t_int32 config_object::g_get_data_int32_simple(const GUID & p_guid,t_int32 p_default)
|
||||
{
|
||||
service_ptr_t<config_object> ptr;
|
||||
if (!g_find(ptr,p_guid)) throw exception_service_not_found();
|
||||
return ptr->get_data_int32_simple(p_default);
|
||||
}
|
||||
|
||||
void config_object::get_data_bool(bool & p_out) {get_data_struct_t<bool>(p_out);}
|
||||
void config_object::set_data_bool(bool p_val) {set_data_struct_t<bool>(p_val);}
|
||||
|
||||
void config_object::g_get_data_bool(const GUID & p_guid,bool & p_out) {g_get_data_struct_t<bool>(p_guid,p_out);}
|
||||
void config_object::g_set_data_bool(const GUID & p_guid,bool p_val) {g_set_data_struct_t<bool>(p_guid,p_val);}
|
||||
|
||||
namespace {
|
||||
class stream_writer_string : public stream_writer {
|
||||
public:
|
||||
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
||||
m_out.add_string((const char*)p_buffer,p_bytes);
|
||||
}
|
||||
stream_writer_string(pfc::string_base & p_out) : m_out(p_out) {m_out.reset();}
|
||||
private:
|
||||
pfc::string_base & m_out;
|
||||
};
|
||||
|
||||
class stream_writer_fixedbuffer : public stream_writer {
|
||||
public:
|
||||
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
||||
if (p_bytes > 0) {
|
||||
if (p_bytes > m_bytes - m_bytes_read) throw pfc::exception_overflow();
|
||||
memcpy((t_uint8*)m_out,p_buffer,p_bytes);
|
||||
m_bytes_read += p_bytes;
|
||||
}
|
||||
}
|
||||
stream_writer_fixedbuffer(void * p_out,t_size p_bytes,t_size & p_bytes_read) : m_out(p_out), m_bytes(p_bytes), m_bytes_read(p_bytes_read) {m_bytes_read = 0;}
|
||||
private:
|
||||
void * m_out;
|
||||
t_size m_bytes;
|
||||
t_size & m_bytes_read;
|
||||
};
|
||||
|
||||
|
||||
|
||||
class stream_writer_get_length : public stream_writer {
|
||||
public:
|
||||
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
||||
m_length += p_bytes;
|
||||
}
|
||||
stream_writer_get_length(t_size & p_length) : m_length(p_length) {m_length = 0;}
|
||||
private:
|
||||
t_size & m_length;
|
||||
};
|
||||
};
|
||||
|
||||
t_size config_object::get_data_raw(void * p_out,t_size p_bytes) {
|
||||
t_size ret = 0;
|
||||
get_data(&stream_writer_fixedbuffer(p_out,p_bytes,ret),abort_callback_impl());
|
||||
return ret;
|
||||
}
|
||||
|
||||
t_size config_object::get_data_raw_length() {
|
||||
t_size ret = 0;
|
||||
get_data(&stream_writer_get_length(ret),abort_callback_impl());
|
||||
return ret;
|
||||
}
|
||||
|
||||
void config_object::set_data_raw(const void * p_data,t_size p_bytes, bool p_notify) {
|
||||
set_data(&stream_reader_memblock_ref(p_data,p_bytes),abort_callback_impl(),p_notify);
|
||||
}
|
||||
|
||||
void config_object::set_data_string(const char * p_data,t_size p_length) {
|
||||
set_data_raw(p_data,pfc::strlen_max(p_data,p_length));
|
||||
}
|
||||
|
||||
void config_object::get_data_string(pfc::string_base & p_out) {
|
||||
get_data(&stream_writer_string(p_out),abort_callback_impl());
|
||||
}
|
||||
|
||||
|
||||
//config_object_impl stuff
|
||||
|
||||
|
||||
void config_object_impl::get_data(stream_writer * p_stream,abort_callback & p_abort) const {
|
||||
insync(m_sync);
|
||||
p_stream->write_object(m_data.get_ptr(),m_data.get_size(),p_abort);
|
||||
}
|
||||
|
||||
void config_object_impl::set_data(stream_reader * p_stream,abort_callback & p_abort,bool p_notify) {
|
||||
core_api::ensure_main_thread();
|
||||
|
||||
{
|
||||
insync(m_sync);
|
||||
m_data.set_size(0);
|
||||
enum {delta = 1024};
|
||||
t_uint8 buffer[delta];
|
||||
for(;;)
|
||||
{
|
||||
t_size delta_done = p_stream->read(buffer,delta,p_abort);
|
||||
|
||||
if (delta_done > 0)
|
||||
{
|
||||
m_data.append_fromptr(buffer,delta_done);
|
||||
}
|
||||
|
||||
if (delta_done != delta) break;
|
||||
}
|
||||
}
|
||||
|
||||
if (p_notify) config_object_notify_manager::g_on_changed(this);
|
||||
}
|
||||
|
||||
config_object_impl::config_object_impl(const GUID & p_guid,const void * p_data,t_size p_bytes) : cfg_var(p_guid)
|
||||
{
|
||||
m_data.set_data_fromptr((const t_uint8*)p_data,p_bytes);
|
||||
}
|
|
@ -0,0 +1,85 @@
|
|||
#ifndef _CONFIG_OBJECT_H_
|
||||
#define _CONFIG_OBJECT_H_
|
||||
|
||||
class config_object;
|
||||
|
||||
class NOVTABLE config_object_notify_manager : public service_base
|
||||
{
|
||||
public:
|
||||
virtual void on_changed(const service_ptr_t<config_object> & p_object) = 0;
|
||||
static void g_on_changed(const service_ptr_t<config_object> & p_object);
|
||||
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(config_object_notify_manager);
|
||||
};
|
||||
|
||||
class NOVTABLE config_object : public service_base
|
||||
{
|
||||
public:
|
||||
//interface
|
||||
virtual GUID get_guid() const = 0;
|
||||
virtual void get_data(stream_writer * p_stream,abort_callback & p_abort) const = 0;
|
||||
virtual void set_data(stream_reader * p_stream,abort_callback & p_abort,bool p_sendnotify = true) = 0;
|
||||
|
||||
//helpers
|
||||
static bool g_find(service_ptr_t<config_object> & p_out,const GUID & p_guid);
|
||||
|
||||
void set_data_raw(const void * p_data,t_size p_bytes,bool p_sendnotify = true);
|
||||
t_size get_data_raw(void * p_out,t_size p_bytes);
|
||||
t_size get_data_raw_length();
|
||||
|
||||
template<class T> void get_data_struct_t(T& p_out);
|
||||
template<class T> void set_data_struct_t(const T& p_in);
|
||||
template<class T> static void g_get_data_struct_t(const GUID & p_guid,T & p_out);
|
||||
template<class T> static void g_set_data_struct_t(const GUID & p_guid,const T & p_in);
|
||||
|
||||
void set_data_string(const char * p_data,t_size p_length);
|
||||
void get_data_string(pfc::string_base & p_out);
|
||||
|
||||
void get_data_bool(bool & p_out);
|
||||
void set_data_bool(bool p_val);
|
||||
void get_data_int32(t_int32 & p_out);
|
||||
void set_data_int32(t_int32 p_val);
|
||||
bool get_data_bool_simple(bool p_default);
|
||||
t_int32 get_data_int32_simple(t_int32 p_default);
|
||||
|
||||
static void g_get_data_string(const GUID & p_guid,pfc::string_base & p_out);
|
||||
static void g_set_data_string(const GUID & p_guid,const char * p_data,t_size p_length = ~0);
|
||||
|
||||
static void g_get_data_bool(const GUID & p_guid,bool & p_out);
|
||||
static void g_set_data_bool(const GUID & p_guid,bool p_val);
|
||||
static void g_get_data_int32(const GUID & p_guid,t_int32 & p_out);
|
||||
static void g_set_data_int32(const GUID & p_guid,t_int32 p_val);
|
||||
static bool g_get_data_bool_simple(const GUID & p_guid,bool p_default);
|
||||
static t_int32 g_get_data_int32_simple(const GUID & p_guid,t_int32 p_default);
|
||||
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(config_object);
|
||||
};
|
||||
|
||||
class standard_config_objects
|
||||
{
|
||||
public:
|
||||
static const GUID bool_remember_window_positions, bool_ui_always_on_top,bool_playlist_stop_after_current;
|
||||
static const GUID bool_playback_follows_cursor, bool_cursor_follows_playback;
|
||||
static const GUID bool_show_keyboard_shortcuts_in_menus;
|
||||
static const GUID string_gui_last_directory_media,string_gui_last_directory_playlists;
|
||||
static const GUID int32_dynamic_bitrate_display_rate;
|
||||
|
||||
|
||||
inline static bool query_show_keyboard_shortcuts_in_menus() {return config_object::g_get_data_bool_simple(standard_config_objects::bool_show_keyboard_shortcuts_in_menus,true);}
|
||||
inline static bool query_remember_window_positions() {return config_object::g_get_data_bool_simple(standard_config_objects::bool_remember_window_positions,true);}
|
||||
|
||||
};
|
||||
|
||||
class config_object_notify : public service_base
|
||||
{
|
||||
public:
|
||||
virtual t_size get_watched_object_count() = 0;
|
||||
virtual GUID get_watched_object(t_size p_index) = 0;
|
||||
virtual void on_watched_object_changed(const service_ptr_t<config_object> & p_object) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(config_object_notify);
|
||||
};
|
||||
|
||||
#endif _CONFIG_OBJECT_H_
|
|
@ -0,0 +1,174 @@
|
|||
#ifndef _CONFIG_OBJECT_IMPL_H_
|
||||
#define _CONFIG_OBJECT_IMPL_H_
|
||||
|
||||
//template function bodies from config_object class
|
||||
|
||||
template<class T>
|
||||
void config_object::get_data_struct_t(T& p_out) {
|
||||
if (get_data_raw(&p_out,sizeof(T)) != sizeof(T)) throw exception_io_data_truncation();
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void config_object::set_data_struct_t(const T& p_in) {
|
||||
return set_data_raw(&p_in,sizeof(T));
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void config_object::g_get_data_struct_t(const GUID & p_guid,T & p_out) {
|
||||
service_ptr_t<config_object> ptr;
|
||||
if (!g_find(ptr,p_guid)) throw exception_service_not_found();
|
||||
return ptr->get_data_struct_t<T>(p_out);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void config_object::g_set_data_struct_t(const GUID & p_guid,const T & p_in) {
|
||||
service_ptr_t<config_object> ptr;
|
||||
if (!g_find(ptr,p_guid)) throw exception_service_not_found();
|
||||
return ptr->set_data_struct_t<T>(p_in);
|
||||
}
|
||||
|
||||
|
||||
class config_object_impl : public config_object, private cfg_var
|
||||
{
|
||||
public:
|
||||
GUID get_guid() const {return cfg_var::get_guid();}
|
||||
void get_data(stream_writer * p_stream,abort_callback & p_abort) const ;
|
||||
void set_data(stream_reader * p_stream,abort_callback & p_abort,bool p_notify);
|
||||
|
||||
config_object_impl(const GUID & p_guid,const void * p_data,t_size p_bytes);
|
||||
private:
|
||||
|
||||
//cfg_var methods
|
||||
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {get_data(p_stream,p_abort);}
|
||||
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {set_data(p_stream,p_abort,false);}
|
||||
|
||||
mutable critical_section m_sync;
|
||||
pfc::array_t<t_uint8> m_data;
|
||||
};
|
||||
|
||||
typedef service_factory_single_transparent_t<config_object_impl> config_object_factory;
|
||||
|
||||
template<t_size p_size>
|
||||
class config_object_fixed_const_impl_t : public config_object {
|
||||
public:
|
||||
config_object_fixed_const_impl_t(const GUID & p_guid, const void * p_data) : m_guid(p_guid) {memcpy(m_data,p_data,p_size);}
|
||||
GUID get_guid() const {return m_guid;}
|
||||
|
||||
void get_data(stream_writer * p_stream, abort_callback & p_abort) const { p_stream->write_object(m_data,p_size,p_abort); }
|
||||
void set_data(stream_reader * p_stream, abort_callback & p_abort, bool p_notify) { PFC_ASSERT(!"Should not get here."); }
|
||||
|
||||
private:
|
||||
t_uint8 m_data[p_size];
|
||||
const GUID m_guid;
|
||||
};
|
||||
|
||||
template<t_size p_size>
|
||||
class config_object_fixed_impl_t : public config_object, private cfg_var {
|
||||
public:
|
||||
GUID get_guid() const {return cfg_var::get_guid();}
|
||||
|
||||
void get_data(stream_writer * p_stream,abort_callback & p_abort) const {
|
||||
insync(m_sync);
|
||||
p_stream->write_object(m_data,p_size,p_abort);
|
||||
}
|
||||
|
||||
void set_data(stream_reader * p_stream,abort_callback & p_abort,bool p_notify) {
|
||||
core_api::ensure_main_thread();
|
||||
|
||||
{
|
||||
t_uint8 temp[p_size];
|
||||
p_stream->read_object(temp,p_size,p_abort);
|
||||
insync(m_sync);
|
||||
memcpy(m_data,temp,p_size);
|
||||
}
|
||||
|
||||
if (p_notify) config_object_notify_manager::g_on_changed(this);
|
||||
}
|
||||
|
||||
config_object_fixed_impl_t (const GUID & p_guid,const void * p_data)
|
||||
: cfg_var(p_guid)
|
||||
{
|
||||
memcpy(m_data,p_data,p_size);
|
||||
}
|
||||
|
||||
private:
|
||||
//cfg_var methods
|
||||
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {get_data(p_stream,p_abort);}
|
||||
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort) {set_data(p_stream,p_abort,false);}
|
||||
|
||||
mutable critical_section m_sync;
|
||||
t_uint8 m_data[p_size];
|
||||
|
||||
};
|
||||
|
||||
template<t_size p_size, bool isConst> class _config_object_fixed_impl_switch;
|
||||
template<t_size p_size> class _config_object_fixed_impl_switch<p_size,false> { public: typedef config_object_fixed_impl_t<p_size> type; };
|
||||
template<t_size p_size> class _config_object_fixed_impl_switch<p_size,true> { public: typedef config_object_fixed_const_impl_t<p_size> type; };
|
||||
|
||||
template<t_size p_size, bool isConst = false>
|
||||
class config_object_fixed_factory_t : public service_factory_single_transparent_t< typename _config_object_fixed_impl_switch<p_size,isConst>::type >
|
||||
{
|
||||
public:
|
||||
config_object_fixed_factory_t(const GUID & p_guid,const void * p_initval)
|
||||
:
|
||||
service_factory_single_transparent_t< typename _config_object_fixed_impl_switch<p_size,isConst>::type >
|
||||
(p_guid,p_initval)
|
||||
{}
|
||||
};
|
||||
|
||||
|
||||
class config_object_string_factory : public config_object_factory
|
||||
{
|
||||
public:
|
||||
config_object_string_factory(const GUID & p_guid,const char * p_string,t_size p_string_length = infinite)
|
||||
: config_object_factory(p_guid,p_string,pfc::strlen_max(p_string,infinite)) {}
|
||||
|
||||
};
|
||||
|
||||
template<bool isConst = false>
|
||||
class config_object_bool_factory_t : public config_object_fixed_factory_t<1,isConst> {
|
||||
public:
|
||||
config_object_bool_factory_t(const GUID & p_guid,bool p_initval)
|
||||
: config_object_fixed_factory_t<1,isConst>(p_guid,&p_initval) {}
|
||||
};
|
||||
typedef config_object_bool_factory_t<> config_object_bool_factory;
|
||||
|
||||
template<class T,bool isConst = false>
|
||||
class config_object_int_factory_t : public config_object_fixed_factory_t<sizeof(T),isConst>
|
||||
{
|
||||
private:
|
||||
template<class T>
|
||||
struct t_initval
|
||||
{
|
||||
T m_initval;
|
||||
t_initval(T p_initval) : m_initval(p_initval) {byte_order::order_native_to_le_t(m_initval);}
|
||||
T * get_ptr() {return &m_initval;}
|
||||
};
|
||||
public:
|
||||
config_object_int_factory_t(const GUID & p_guid,T p_initval)
|
||||
: config_object_fixed_factory_t<sizeof(T)>(p_guid,t_initval<T>(p_initval).get_ptr() )
|
||||
{}
|
||||
};
|
||||
|
||||
typedef config_object_int_factory_t<t_int32> config_object_int32_factory;
|
||||
|
||||
|
||||
|
||||
class config_object_notify_impl_simple : public config_object_notify
|
||||
{
|
||||
public:
|
||||
t_size get_watched_object_count() {return 1;}
|
||||
GUID get_watched_object(t_size p_index) {return m_guid;}
|
||||
void on_watched_object_changed(const service_ptr_t<config_object> & p_object) {m_func(p_object);}
|
||||
|
||||
typedef void (*t_func)(const service_ptr_t<config_object> &);
|
||||
|
||||
config_object_notify_impl_simple(const GUID & p_guid,t_func p_func) : m_guid(p_guid), m_func(p_func) {}
|
||||
private:
|
||||
GUID m_guid;
|
||||
t_func m_func;
|
||||
};
|
||||
|
||||
typedef service_factory_single_transparent_t<config_object_notify_impl_simple> config_object_notify_simple_factory;
|
||||
|
||||
#endif _CONFIG_OBJECT_IMPL_H_
|
|
@ -0,0 +1,50 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
|
||||
void console::info(const char * p_message) {print(p_message);}
|
||||
void console::error(const char * p_message) {complain("Error", p_message);}
|
||||
void console::warning(const char * p_message) {complain("Warning", p_message);}
|
||||
|
||||
void console::info_location(const playable_location & src) {print_location(src);}
|
||||
void console::info_location(const metadb_handle_ptr & src) {print_location(src);}
|
||||
|
||||
void console::print_location(const metadb_handle_ptr & src)
|
||||
{
|
||||
print_location(src->get_location());
|
||||
}
|
||||
|
||||
void console::print_location(const playable_location & src)
|
||||
{
|
||||
formatter() << src;
|
||||
}
|
||||
|
||||
void console::complain(const char * what, const char * msg) {
|
||||
formatter() << what << ": " << msg;
|
||||
}
|
||||
void console::complain(const char * what, std::exception const & e) {
|
||||
complain(what, e.what());
|
||||
}
|
||||
|
||||
void console::print(const char* p_message)
|
||||
{
|
||||
if (core_api::are_services_available()) {
|
||||
service_ptr_t<console_receiver> ptr;
|
||||
service_enum_t<console_receiver> e;
|
||||
while(e.next(ptr)) ptr->print(p_message,infinite);
|
||||
}
|
||||
}
|
||||
|
||||
void console::printf(const char* p_format,...)
|
||||
{
|
||||
va_list list;
|
||||
va_start(list,p_format);
|
||||
printfv(p_format,list);
|
||||
va_end(list);
|
||||
}
|
||||
|
||||
void console::printfv(const char* p_format,va_list p_arglist)
|
||||
{
|
||||
pfc::string8_fastalloc temp;
|
||||
uPrintfV(temp,p_format,p_arglist);
|
||||
print(temp);
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
//! Namespace with functions for sending text to console. All functions are fully multi-thread safe, though they must not be called during dll initialization or deinitialization (e.g. static object constructors or destructors) when service system is not available.
|
||||
namespace console
|
||||
{
|
||||
void info(const char * p_message);
|
||||
void error(const char * p_message);
|
||||
void warning(const char * p_message);
|
||||
void info_location(const playable_location & src);
|
||||
void info_location(const metadb_handle_ptr & src);
|
||||
void print_location(const playable_location & src);
|
||||
void print_location(const metadb_handle_ptr & src);
|
||||
|
||||
void print(const char*);
|
||||
void printf(const char*,...);
|
||||
void printfv(const char*,va_list p_arglist);
|
||||
|
||||
//! Usage: console::formatter() << "blah " << somenumber << " asdf" << somestring;
|
||||
class formatter : public pfc::string_formatter {
|
||||
public:
|
||||
~formatter() {if (!is_empty()) console::print(get_ptr());}
|
||||
};
|
||||
void complain(const char * what, const char * msg);
|
||||
void complain(const char * what, std::exception const & e);
|
||||
};
|
||||
|
||||
//! Interface receiving console output. Do not call directly; use console namespace functions instead.
|
||||
class NOVTABLE console_receiver : public service_base {
|
||||
public:
|
||||
virtual void print(const char * p_message,t_size p_message_length) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(console_receiver);
|
||||
};
|
|
@ -0,0 +1,242 @@
|
|||
//! Reserved for future use.
|
||||
typedef void * t_glyph;
|
||||
|
||||
|
||||
class NOVTABLE contextmenu_item_node {
|
||||
public:
|
||||
enum t_flags {
|
||||
FLAG_CHECKED = 1,
|
||||
FLAG_DISABLED = 2,
|
||||
FLAG_GRAYED = 4,
|
||||
FLAG_DISABLED_GRAYED = FLAG_DISABLED|FLAG_GRAYED,
|
||||
FLAG_RADIOCHECKED = 8, //new in 0.9.5.2 - overrides FLAG_CHECKED, set together with FLAG_CHECKED for backwards compatibility.
|
||||
};
|
||||
|
||||
enum t_type {
|
||||
TYPE_POPUP,TYPE_COMMAND,TYPE_SEPARATOR
|
||||
};
|
||||
|
||||
virtual bool get_display_data(pfc::string_base & p_out,unsigned & p_displayflags,metadb_handle_list_cref p_data,const GUID & p_caller) = 0;
|
||||
virtual t_type get_type() = 0;
|
||||
virtual void execute(metadb_handle_list_cref p_data,const GUID & p_caller) = 0;
|
||||
virtual t_glyph get_glyph(metadb_handle_list_cref p_data,const GUID & p_caller) {return 0;}//RESERVED
|
||||
virtual t_size get_children_count() = 0;
|
||||
virtual contextmenu_item_node * get_child(t_size p_index) = 0;
|
||||
virtual bool get_description(pfc::string_base & p_out) = 0;
|
||||
virtual GUID get_guid() = 0;
|
||||
virtual bool is_mappable_shortcut() = 0;
|
||||
|
||||
protected:
|
||||
contextmenu_item_node() {}
|
||||
~contextmenu_item_node() {}
|
||||
};
|
||||
|
||||
class NOVTABLE contextmenu_item_node_root : public contextmenu_item_node
|
||||
{
|
||||
public:
|
||||
virtual ~contextmenu_item_node_root() {}
|
||||
};
|
||||
|
||||
class NOVTABLE contextmenu_item_node_leaf : public contextmenu_item_node
|
||||
{
|
||||
public:
|
||||
t_type get_type() {return TYPE_COMMAND;}
|
||||
t_size get_children_count() {return 0;}
|
||||
contextmenu_item_node * get_child(t_size) {return NULL;}
|
||||
};
|
||||
|
||||
class NOVTABLE contextmenu_item_node_root_leaf : public contextmenu_item_node_root
|
||||
{
|
||||
public:
|
||||
t_type get_type() {return TYPE_COMMAND;}
|
||||
t_size get_children_count() {return 0;}
|
||||
contextmenu_item_node * get_child(t_size) {return NULL;}
|
||||
};
|
||||
|
||||
class NOVTABLE contextmenu_item_node_popup : public contextmenu_item_node
|
||||
{
|
||||
public:
|
||||
t_type get_type() {return TYPE_POPUP;}
|
||||
void execute(metadb_handle_list_cref data,const GUID & caller) {}
|
||||
bool get_description(pfc::string_base & p_out) {return false;}
|
||||
};
|
||||
|
||||
class NOVTABLE contextmenu_item_node_root_popup : public contextmenu_item_node_root
|
||||
{
|
||||
public:
|
||||
t_type get_type() {return TYPE_POPUP;}
|
||||
void execute(metadb_handle_list_cref data,const GUID & caller) {}
|
||||
bool get_description(pfc::string_base & p_out) {return false;}
|
||||
};
|
||||
|
||||
class contextmenu_item_node_separator : public contextmenu_item_node
|
||||
{
|
||||
public:
|
||||
t_type get_type() {return TYPE_SEPARATOR;}
|
||||
void execute(metadb_handle_list_cref data,const GUID & caller) {}
|
||||
bool get_description(pfc::string_base & p_out) {return false;}
|
||||
t_size get_children_count() {return 0;}
|
||||
bool get_display_data(pfc::string_base & p_out,unsigned & p_displayflags,metadb_handle_list_cref p_data,const GUID & p_caller)
|
||||
{
|
||||
p_displayflags = 0;
|
||||
p_out = "---";
|
||||
return true;
|
||||
}
|
||||
contextmenu_item_node * get_child(t_size) {return NULL;}
|
||||
GUID get_guid() {return pfc::guid_null;}
|
||||
bool is_mappable_shortcut() {return false;}
|
||||
};
|
||||
|
||||
/*!
|
||||
Service class for declaring context menu commands.\n
|
||||
See contextmenu_item_simple for implementation helper without dynamic menu generation features.\n
|
||||
All methods are valid from main app thread only.
|
||||
*/
|
||||
class NOVTABLE contextmenu_item : public service_base {
|
||||
public:
|
||||
enum t_enabled_state {
|
||||
FORCE_OFF,
|
||||
DEFAULT_OFF,
|
||||
DEFAULT_ON,
|
||||
};
|
||||
|
||||
//! Retrieves number of menu items provided by this contextmenu_item implementation.
|
||||
virtual unsigned get_num_items() = 0;
|
||||
//! Instantiates a context menu item (including sub-node tree for items that contain dynamically-generated sub-items).
|
||||
virtual contextmenu_item_node_root * instantiate_item(unsigned p_index,metadb_handle_list_cref p_data,const GUID & p_caller) = 0;
|
||||
//! Retrieves GUID of the context menu item.
|
||||
virtual GUID get_item_guid(unsigned p_index) = 0;
|
||||
//! Retrieves human-readable name of the context menu item.
|
||||
virtual void get_item_name(unsigned p_index,pfc::string_base & p_out) = 0;
|
||||
//! Retrieves default path of the context menu item ("" for root).
|
||||
virtual void get_item_default_path(unsigned p_index,pfc::string_base & p_out) = 0;
|
||||
//! Retrieves item's description to show in the status bar. Set p_out to the string to be displayed and return true if you provide a description, return false otherwise.
|
||||
virtual bool get_item_description(unsigned p_index,pfc::string_base & p_out) = 0;
|
||||
//! Signals whether the item should be forcefully hidden (FORCE_OFF), hidden by default but possible to add (DEFAULT_OFF) or shown by default (DEFAULT_ON).
|
||||
virtual t_enabled_state get_enabled_state(unsigned p_index) = 0;
|
||||
//! Executes the menu item command without going thru the instantiate_item path. For items with dynamically-generated sub-items, p_node is identifies of the sub-item command to execute.
|
||||
virtual void item_execute_simple(unsigned p_index,const GUID & p_node,metadb_handle_list_cref p_data,const GUID & p_caller) = 0;
|
||||
|
||||
bool item_get_display_data_root(pfc::string_base & p_out,unsigned & displayflags,unsigned p_index,metadb_handle_list_cref p_data,const GUID & p_caller);
|
||||
bool item_get_display_data(pfc::string_base & p_out,unsigned & displayflags,unsigned p_index,const GUID & p_node,metadb_handle_list_cref p_data,const GUID & p_caller);
|
||||
|
||||
//! Deprecated - use caller_active_playlist_selection instead.
|
||||
static const GUID caller_playlist;
|
||||
|
||||
static const GUID caller_active_playlist_selection, caller_active_playlist, caller_playlist_manager, caller_now_playing, caller_keyboard_shortcut_list, caller_media_library_viewer;
|
||||
static const GUID caller_undefined;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(contextmenu_item);
|
||||
};
|
||||
|
||||
//! contextmenu_item implementation helper for implementing non-dynamically-generated context menu items; derive from this instead of from contextmenu_item directly if your menu items are static.
|
||||
class NOVTABLE contextmenu_item_simple : public contextmenu_item {
|
||||
private:
|
||||
public:
|
||||
//! Same as contextmenu_item_node::t_flags.
|
||||
enum t_flags
|
||||
{
|
||||
FLAG_CHECKED = 1,
|
||||
FLAG_DISABLED = 2,
|
||||
FLAG_GRAYED = 4,
|
||||
FLAG_DISABLED_GRAYED = FLAG_DISABLED|FLAG_GRAYED,
|
||||
FLAG_RADIOCHECKED = 8, //new in 0.9.5.2 - overrides FLAG_CHECKED, set together with FLAG_CHECKED for backwards compatibility.
|
||||
};
|
||||
|
||||
|
||||
// Functions to be overridden by implementers (some are not mandatory).
|
||||
virtual t_enabled_state get_enabled_state(unsigned p_index) {return contextmenu_item::DEFAULT_ON;}
|
||||
virtual unsigned get_num_items() = 0;
|
||||
virtual void get_item_name(unsigned p_index,pfc::string_base & p_out) = 0;
|
||||
virtual void get_item_default_path(unsigned p_index,pfc::string_base & p_out) = 0;
|
||||
virtual void context_command(unsigned p_index,metadb_handle_list_cref p_data,const GUID& p_caller) = 0;
|
||||
virtual bool context_get_display(unsigned p_index,metadb_handle_list_cref p_data,pfc::string_base & p_out,unsigned & p_displayflags,const GUID & p_caller) {
|
||||
PFC_ASSERT(p_index>=0 && p_index<get_num_items());
|
||||
get_item_name(p_index,p_out);
|
||||
return true;
|
||||
}
|
||||
virtual GUID get_item_guid(unsigned p_index) = 0;
|
||||
virtual bool get_item_description(unsigned p_index,pfc::string_base & p_out) = 0;
|
||||
|
||||
|
||||
private:
|
||||
class contextmenu_item_node_impl : public contextmenu_item_node_root_leaf {
|
||||
public:
|
||||
contextmenu_item_node_impl(contextmenu_item_simple * p_owner,unsigned p_index) : m_owner(p_owner), m_index(p_index) {}
|
||||
bool get_display_data(pfc::string_base & p_out,unsigned & p_displayflags,metadb_handle_list_cref p_data,const GUID & p_caller) {return m_owner->get_display_data(m_index,p_data,p_out,p_displayflags,p_caller);}
|
||||
void execute(metadb_handle_list_cref p_data,const GUID & p_caller) {m_owner->context_command(m_index,p_data,p_caller);}
|
||||
bool get_description(pfc::string_base & p_out) {return m_owner->get_item_description(m_index,p_out);}
|
||||
GUID get_guid() {return pfc::guid_null;}
|
||||
bool is_mappable_shortcut() {return m_owner->item_is_mappable_shortcut(m_index);}
|
||||
private:
|
||||
service_ptr_t<contextmenu_item_simple> m_owner;
|
||||
unsigned m_index;
|
||||
};
|
||||
|
||||
contextmenu_item_node_root * instantiate_item(unsigned p_index,metadb_handle_list_cref p_data,const GUID & p_caller)
|
||||
{
|
||||
return new contextmenu_item_node_impl(this,p_index);
|
||||
}
|
||||
|
||||
|
||||
void item_execute_simple(unsigned p_index,const GUID & p_node,metadb_handle_list_cref p_data,const GUID & p_caller)
|
||||
{
|
||||
if (p_node == pfc::guid_null)
|
||||
context_command(p_index,p_data,p_caller);
|
||||
}
|
||||
|
||||
virtual bool item_is_mappable_shortcut(unsigned p_index)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
virtual bool get_display_data(unsigned n,metadb_handle_list_cref data,pfc::string_base & p_out,unsigned & displayflags,const GUID & caller)
|
||||
{
|
||||
bool rv = false;
|
||||
assert(n>=0 && n<get_num_items());
|
||||
if (data.get_count()>0)
|
||||
{
|
||||
rv = context_get_display(n,data,p_out,displayflags,caller);
|
||||
}
|
||||
return rv;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
//! Helper.
|
||||
template<typename T>
|
||||
class contextmenu_item_factory_t : public service_factory_single_t<T> {};
|
||||
|
||||
|
||||
//! Helper.
|
||||
#define DECLARE_CONTEXT_MENU_ITEM(P_CLASSNAME,P_NAME,P_DEFAULTPATH,P_FUNC,P_GUID,P_DESCRIPTION) \
|
||||
namespace { \
|
||||
class P_CLASSNAME : public contextmenu_item_simple { \
|
||||
public: \
|
||||
unsigned get_num_items() {return 1;} \
|
||||
void get_item_name(unsigned p_index,pfc::string_base & p_out) {p_out = P_NAME;} \
|
||||
void get_item_default_path(unsigned p_index,pfc::string_base & p_out) {p_out = P_DEFAULTPATH;} \
|
||||
void context_command(unsigned p_index,metadb_handle_list_cref p_data,const GUID& p_caller) {P_FUNC(p_data);} \
|
||||
GUID get_item_guid(unsigned p_index) {return P_GUID;} \
|
||||
bool get_item_description(unsigned p_index,pfc::string_base & p_out) {if (P_DESCRIPTION[0] == 0) return false;p_out = P_DESCRIPTION; return true;} \
|
||||
}; \
|
||||
static contextmenu_item_factory_t<P_CLASSNAME> g_##P_CLASSNAME##_factory; \
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
//! New in 0.9.5.1. Static methods safe to use in prior versions as it will use slow fallback mode when the service isn't present. \n
|
||||
//! Functionality provided by menu_item_resolver methods isn't much different from just walking all registered contextmenu_item / mainmenu_commands implementations to find the command we want, but it uses a hint map to locate the service we're looking for without walking all of them which may be significantly faster in certain scenarios.
|
||||
class menu_item_resolver : public service_base {
|
||||
public:
|
||||
virtual bool resolve_context_command(const GUID & id, service_ptr_t<class contextmenu_item> & out, t_uint32 & out_index) = 0;
|
||||
virtual bool resolve_main_command(const GUID & id, service_ptr_t<class mainmenu_commands> & out, t_uint32 & out_index) = 0;
|
||||
|
||||
static bool g_resolve_context_command(const GUID & id, service_ptr_t<class contextmenu_item> & out, t_uint32 & out_index);
|
||||
static bool g_resolve_main_command(const GUID & id, service_ptr_t<class mainmenu_commands> & out, t_uint32 & out_index);
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(menu_item_resolver)
|
||||
};
|
|
@ -0,0 +1,109 @@
|
|||
class NOVTABLE keyboard_shortcut_manager : public service_base
|
||||
{
|
||||
public:
|
||||
static bool g_get(service_ptr_t<keyboard_shortcut_manager> & p_out) {return service_enum_create_t(p_out,0);}
|
||||
|
||||
enum shortcut_type
|
||||
{
|
||||
TYPE_MAIN,
|
||||
TYPE_CONTEXT,
|
||||
TYPE_CONTEXT_PLAYLIST,
|
||||
TYPE_CONTEXT_NOW_PLAYING,
|
||||
};
|
||||
|
||||
|
||||
virtual bool process_keydown(shortcut_type type,const pfc::list_base_const_t<metadb_handle_ptr> & data,unsigned keycode)=0;
|
||||
virtual bool process_keydown_ex(shortcut_type type,const pfc::list_base_const_t<metadb_handle_ptr> & data,unsigned keycode,const GUID & caller)=0;
|
||||
bool on_keydown(shortcut_type type,WPARAM wp);
|
||||
bool on_keydown_context(const pfc::list_base_const_t<metadb_handle_ptr> & data,WPARAM wp,const GUID & caller);
|
||||
|
||||
bool on_keydown_auto(WPARAM wp);
|
||||
bool on_keydown_auto_playlist(WPARAM wp);
|
||||
bool on_keydown_auto_context(const pfc::list_base_const_t<metadb_handle_ptr> & data,WPARAM wp,const GUID & caller);
|
||||
|
||||
bool on_keydown_restricted_auto(WPARAM wp);
|
||||
bool on_keydown_restricted_auto_playlist(WPARAM wp);
|
||||
bool on_keydown_restricted_auto_context(const pfc::list_base_const_t<metadb_handle_ptr> & data,WPARAM wp,const GUID & caller);
|
||||
|
||||
virtual bool get_key_description_for_action(const GUID & p_command,const GUID & p_subcommand, pfc::string_base & out, shortcut_type type, bool is_global)=0;
|
||||
|
||||
static bool is_text_key(t_uint32 vkCode);
|
||||
static bool is_typing_key(t_uint32 vkCode);
|
||||
static bool is_typing_key_combo(t_uint32 vkCode, t_uint32 modifiers);
|
||||
static bool is_typing_modifier(t_uint32 flags);
|
||||
static bool is_typing_message(HWND editbox, const MSG * msg);
|
||||
static bool is_typing_message(const MSG * msg);
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(keyboard_shortcut_manager);
|
||||
};
|
||||
|
||||
|
||||
//! New in 0.9.5.
|
||||
class keyboard_shortcut_manager_v2 : public keyboard_shortcut_manager {
|
||||
public:
|
||||
//! Deprecates old keyboard_shortcut_manager methods. If the action requires selected items, they're obtained from ui_selection_manager API automatically.
|
||||
virtual bool process_keydown_simple(t_uint32 keycode) = 0;
|
||||
|
||||
//! Helper for use with message filters.
|
||||
bool pretranslate_message(const MSG * msg, HWND thisPopupWnd);
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(keyboard_shortcut_manager_v2,keyboard_shortcut_manager);
|
||||
};
|
||||
|
||||
class NOVTABLE contextmenu_node {
|
||||
public:
|
||||
virtual contextmenu_item_node::t_type get_type()=0;
|
||||
virtual const char * get_name()=0;
|
||||
virtual t_size get_num_children()=0;//TYPE_POPUP only
|
||||
virtual contextmenu_node * get_child(t_size n)=0;//TYPE_POPUP only
|
||||
virtual unsigned get_display_flags()=0;//TYPE_COMMAND/TYPE_POPUP only, see contextmenu_item::FLAG_*
|
||||
virtual unsigned get_id()=0;//TYPE_COMMAND only, returns zero-based index (helpful for win32 menu command ids)
|
||||
virtual void execute()=0;//TYPE_COMMAND only
|
||||
virtual bool get_description(pfc::string_base & out)=0;//TYPE_COMMAND only
|
||||
virtual bool get_full_name(pfc::string_base & out)=0;//TYPE_COMMAND only
|
||||
virtual void * get_glyph()=0;//RESERVED, do not use
|
||||
protected:
|
||||
contextmenu_node() {}
|
||||
~contextmenu_node() {}
|
||||
};
|
||||
|
||||
|
||||
|
||||
class NOVTABLE contextmenu_manager : public service_base
|
||||
{
|
||||
public:
|
||||
enum
|
||||
{
|
||||
FLAG_SHOW_SHORTCUTS = 1,
|
||||
FLAG_SHOW_SHORTCUTS_GLOBAL = 2,
|
||||
};
|
||||
virtual void init_context(const pfc::list_base_const_t<metadb_handle_ptr> & data,unsigned flags)=0;//flags - see FLAG_* above
|
||||
virtual void init_context_playlist(unsigned flags)=0;
|
||||
virtual contextmenu_node * get_root()=0;//releasing contextmenu_manager service releaases nodes; root may be null in case of error or something
|
||||
virtual contextmenu_node * find_by_id(unsigned id)=0;
|
||||
virtual void set_shortcut_preference(const keyboard_shortcut_manager::shortcut_type * data,unsigned count)=0;
|
||||
|
||||
|
||||
|
||||
static void g_create(service_ptr_t<contextmenu_manager> & p_out) {p_out = standard_api_create_t<contextmenu_manager>();}
|
||||
|
||||
#ifdef WIN32
|
||||
static void win32_build_menu(HMENU menu,contextmenu_node * parent,int base_id,int max_id);//menu item identifiers are base_id<=N<base_id+max_id (if theres too many items, they will be clipped)
|
||||
static void win32_run_menu_context(HWND parent,const pfc::list_base_const_t<metadb_handle_ptr> & data, const POINT * pt = 0,unsigned flags = 0);
|
||||
static void win32_run_menu_context_playlist(HWND parent,const POINT * pt = 0,unsigned flags = 0);
|
||||
void win32_run_menu_popup(HWND parent,const POINT * pt = 0);
|
||||
void win32_build_menu(HMENU menu,int base_id,int max_id) {win32_build_menu(menu,get_root(),base_id,max_id);}
|
||||
|
||||
|
||||
#endif
|
||||
|
||||
virtual void init_context_ex(const pfc::list_base_const_t<metadb_handle_ptr> & data,unsigned flags,const GUID & caller)=0;
|
||||
virtual bool init_context_now_playing(unsigned flags)=0;//returns false if not playing
|
||||
|
||||
bool execute_by_id(unsigned id);
|
||||
|
||||
bool get_description_by_id(unsigned id,pfc::string_base & out);
|
||||
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(contextmenu_manager);
|
||||
};
|
|
@ -0,0 +1,33 @@
|
|||
#ifndef _CORE_API_H_
|
||||
#define _CORE_API_H_
|
||||
|
||||
namespace core_api {
|
||||
|
||||
//! Exception thrown by APIs locked to main app thread when called from another thread.
|
||||
PFC_DECLARE_EXCEPTION(exception_wrong_thread,pfc::exception_bug_check,"This method can be called only from the main thread");
|
||||
|
||||
//! Retrieves HINSTANCE of calling DLL.
|
||||
HINSTANCE get_my_instance();
|
||||
//! Retrieves filename of calling dll, excluding extension, e.g. "foo_asdf"
|
||||
const char * get_my_file_name();
|
||||
//! Retrieves full path of calling dll, e.g. file://c:\blah\foobar2000\foo_asdf.dll
|
||||
const char * get_my_full_path();
|
||||
//! Retrieves main app window. WARNING: this is provided for parent of dialog windows and such only; using it for anything else (such as hooking windowproc to alter app behaviors) is absolutely illegal.
|
||||
HWND get_main_window();
|
||||
//! Tests whether services are available at this time. They are not available only during DLL startup or shutdown (e.g. inside static object constructors or destructors).
|
||||
bool are_services_available();
|
||||
//! Tests whether calling thread is main app thread, and shows diagnostic message in debugger output if it's not.
|
||||
bool assert_main_thread();
|
||||
//! Throws exception_wrong_thread if calling thread is not main app thread.
|
||||
void ensure_main_thread();
|
||||
//! Returns true if calling thread is main app thread, false otherwise.
|
||||
bool is_main_thread();
|
||||
//! Returns whether the app is currently shutting down.
|
||||
bool is_shutting_down();
|
||||
//! Returns whether the app is currently initializing.
|
||||
bool is_initializing();
|
||||
//! Returns filesystem path to directory with user settings, e.g. file://c:\documents_and_settings\username\blah\foobar2000
|
||||
const char * get_profile_path();
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,36 @@
|
|||
class NOVTABLE core_version_info : public service_base {
|
||||
public:
|
||||
virtual const char * get_version_string() = 0;
|
||||
static const char * g_get_version_string() {return static_api_ptr_t<core_version_info>()->get_version_string();}
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(core_version_info);
|
||||
};
|
||||
|
||||
struct t_core_version_data {
|
||||
t_uint32 m_major, m_minor1, m_minor2, m_minor3;
|
||||
};
|
||||
|
||||
//! New (0.9.4.2)
|
||||
class NOVTABLE core_version_info_v2 : public core_version_info {
|
||||
public:
|
||||
virtual const char * get_name() = 0;//"foobar2000"
|
||||
virtual const char * get_version_as_text() = 0;//"N.N.N.N"
|
||||
virtual t_core_version_data get_version() = 0;
|
||||
|
||||
//! Determine whether running foobar2000 version is newer or equal to the specified version, eg. test_version(0,9,5,0) for 0.9.5.
|
||||
bool test_version(t_uint32 major, t_uint32 minor1, t_uint32 minor2, t_uint32 minor3) {
|
||||
const t_core_version_data v = get_version();
|
||||
if (v.m_major < major) return false;
|
||||
else if (v.m_major > major) return true;
|
||||
// major version matches
|
||||
else if (v.m_minor1 < minor1) return false;
|
||||
else if (v.m_minor1 > minor1) return true;
|
||||
// minor1 version matches
|
||||
else if (v.m_minor2 < minor2) return false;
|
||||
else if (v.m_minor2 > minor2) return true;
|
||||
// minor2 version matches
|
||||
else if (v.m_minor3 < minor3) return false;
|
||||
else return true;
|
||||
}
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(core_version_info_v2, core_version_info);
|
||||
};
|
|
@ -0,0 +1,395 @@
|
|||
#include "foobar2000.h"
|
||||
#include <math.h>
|
||||
|
||||
t_size dsp_chunk_list_impl::get_count() const {return m_data.get_count();}
|
||||
|
||||
audio_chunk * dsp_chunk_list_impl::get_item(t_size n) const {return n>=0 && n<m_data.get_count() ? &*m_data[n] : 0;}
|
||||
|
||||
void dsp_chunk_list_impl::remove_by_idx(t_size idx)
|
||||
{
|
||||
if (idx>=0 && idx<m_data.get_count())
|
||||
m_recycled.add_item(m_data.remove_by_idx(idx));
|
||||
}
|
||||
|
||||
void dsp_chunk_list_impl::remove_mask(const bit_array & mask)
|
||||
{
|
||||
t_size n, m = m_data.get_count();
|
||||
for(n=0;n<m;n++)
|
||||
if (mask[m])
|
||||
m_recycled.add_item(m_data[n]);
|
||||
m_data.remove_mask(mask);
|
||||
}
|
||||
|
||||
audio_chunk * dsp_chunk_list_impl::insert_item(t_size idx,t_size hint_size)
|
||||
{
|
||||
t_size max = get_count();
|
||||
if (idx<0) idx=0;
|
||||
else if (idx>max) idx = max;
|
||||
pfc::rcptr_t<audio_chunk> ret;
|
||||
if (m_recycled.get_count()>0)
|
||||
{
|
||||
t_size best;
|
||||
if (hint_size>0)
|
||||
{
|
||||
best = 0;
|
||||
t_size best_found = m_recycled[0]->get_data_size(), n, total = m_recycled.get_count();
|
||||
for(n=1;n<total;n++)
|
||||
{
|
||||
if (best_found==hint_size) break;
|
||||
t_size size = m_recycled[n]->get_data_size();
|
||||
int delta_old = abs((int)best_found - (int)hint_size), delta_new = abs((int)size - (int)hint_size);
|
||||
if (delta_new < delta_old)
|
||||
{
|
||||
best_found = size;
|
||||
best = n;
|
||||
}
|
||||
}
|
||||
}
|
||||
else best = m_recycled.get_count()-1;
|
||||
|
||||
ret = m_recycled.remove_by_idx(best);
|
||||
ret->set_sample_count(0);
|
||||
ret->set_channels(0);
|
||||
ret->set_srate(0);
|
||||
}
|
||||
else ret = pfc::rcnew_t<audio_chunk_impl>();
|
||||
if (idx==max) m_data.add_item(ret);
|
||||
else m_data.insert_item(ret,idx);
|
||||
return &*ret;
|
||||
}
|
||||
|
||||
void dsp_chunk_list::remove_bad_chunks()
|
||||
{
|
||||
bool blah = false;
|
||||
t_size idx;
|
||||
for(idx=0;idx<get_count();)
|
||||
{
|
||||
audio_chunk * chunk = get_item(idx);
|
||||
if (!chunk->is_valid())
|
||||
{
|
||||
chunk->reset();
|
||||
remove_by_idx(idx);
|
||||
blah = true;
|
||||
}
|
||||
else idx++;
|
||||
}
|
||||
if (blah) console::info("one or more bad chunks removed from dsp chunk list");
|
||||
}
|
||||
|
||||
|
||||
bool dsp_entry::g_instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset)
|
||||
{
|
||||
service_ptr_t<dsp_entry> ptr;
|
||||
if (!g_get_interface(ptr,p_preset.get_owner())) return false;
|
||||
return ptr->instantiate(p_out,p_preset);
|
||||
}
|
||||
|
||||
bool dsp_entry::g_instantiate_default(service_ptr_t<dsp> & p_out,const GUID & p_guid)
|
||||
{
|
||||
service_ptr_t<dsp_entry> ptr;
|
||||
if (!g_get_interface(ptr,p_guid)) return false;
|
||||
dsp_preset_impl preset;
|
||||
if (!ptr->get_default_preset(preset)) return false;
|
||||
return ptr->instantiate(p_out,preset);
|
||||
}
|
||||
|
||||
bool dsp_entry::g_name_from_guid(pfc::string_base & p_out,const GUID & p_guid)
|
||||
{
|
||||
service_ptr_t<dsp_entry> ptr;
|
||||
if (!g_get_interface(ptr,p_guid)) return false;
|
||||
ptr->get_name(p_out);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool dsp_entry::g_dsp_exists(const GUID & p_guid)
|
||||
{
|
||||
service_ptr_t<dsp_entry> blah;
|
||||
return g_get_interface(blah,p_guid);
|
||||
}
|
||||
|
||||
bool dsp_entry::g_get_default_preset(dsp_preset & p_out,const GUID & p_guid)
|
||||
{
|
||||
service_ptr_t<dsp_entry> ptr;
|
||||
if (!g_get_interface(ptr,p_guid)) return false;
|
||||
return ptr->get_default_preset(p_out);
|
||||
}
|
||||
|
||||
void dsp_chain_config::contents_to_stream(stream_writer * p_stream,abort_callback & p_abort) const {
|
||||
t_size n, count = get_count();
|
||||
p_stream->write_lendian_t(count,p_abort);
|
||||
for(n=0;n<count;n++) {
|
||||
get_item(n).contents_to_stream(p_stream,p_abort);
|
||||
}
|
||||
}
|
||||
|
||||
void dsp_chain_config::contents_from_stream(stream_reader * p_stream,abort_callback & p_abort) {
|
||||
t_uint32 n,count;
|
||||
|
||||
remove_all();
|
||||
|
||||
p_stream->read_lendian_t(count,p_abort);
|
||||
|
||||
dsp_preset_impl temp;
|
||||
|
||||
for(n=0;n<count;n++) {
|
||||
temp.contents_from_stream(p_stream,p_abort);
|
||||
add_item(temp);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool cfg_dsp_chain_config::get_data(dsp_chain_config & p_data) const {
|
||||
p_data.copy(m_data);
|
||||
return true;
|
||||
}
|
||||
|
||||
void cfg_dsp_chain_config::set_data(const dsp_chain_config & p_data) {
|
||||
m_data.copy(p_data);
|
||||
}
|
||||
|
||||
void cfg_dsp_chain_config::reset() {
|
||||
m_data.remove_all();
|
||||
}
|
||||
|
||||
void cfg_dsp_chain_config::get_data_raw(stream_writer * p_stream,abort_callback & p_abort) {
|
||||
m_data.contents_to_stream(p_stream,p_abort);
|
||||
}
|
||||
|
||||
void cfg_dsp_chain_config::set_data_raw(stream_reader * p_stream,t_size,abort_callback & p_abort) {
|
||||
m_data.contents_from_stream(p_stream,p_abort);
|
||||
}
|
||||
|
||||
void dsp_chain_config::remove_item(t_size p_index)
|
||||
{
|
||||
remove_mask(bit_array_one(p_index));
|
||||
}
|
||||
|
||||
void dsp_chain_config::add_item(const dsp_preset & p_data)
|
||||
{
|
||||
insert_item(p_data,get_count());
|
||||
}
|
||||
|
||||
void dsp_chain_config::remove_all()
|
||||
{
|
||||
remove_mask(bit_array_true());
|
||||
}
|
||||
|
||||
void dsp_chain_config::instantiate(service_list_t<dsp> & p_out)
|
||||
{
|
||||
p_out.remove_all();
|
||||
t_size n, m = get_count();
|
||||
for(n=0;n<m;n++)
|
||||
{
|
||||
service_ptr_t<dsp> temp;
|
||||
if (dsp_entry::g_instantiate(temp,get_item(n)))
|
||||
p_out.add_item(temp);
|
||||
}
|
||||
}
|
||||
|
||||
t_size dsp_chain_config_impl::get_count() const
|
||||
{
|
||||
return m_data.get_count();
|
||||
}
|
||||
|
||||
const dsp_preset & dsp_chain_config_impl::get_item(t_size p_index) const
|
||||
{
|
||||
return *m_data[p_index];
|
||||
}
|
||||
|
||||
void dsp_chain_config_impl::replace_item(const dsp_preset & p_data,t_size p_index)
|
||||
{
|
||||
*m_data[p_index] = p_data;
|
||||
}
|
||||
|
||||
void dsp_chain_config_impl::insert_item(const dsp_preset & p_data,t_size p_index)
|
||||
{
|
||||
m_data.insert_item(new dsp_preset_impl(p_data),p_index);
|
||||
}
|
||||
|
||||
void dsp_chain_config_impl::remove_mask(const bit_array & p_mask)
|
||||
{
|
||||
m_data.delete_mask(p_mask);
|
||||
}
|
||||
|
||||
dsp_chain_config_impl::~dsp_chain_config_impl()
|
||||
{
|
||||
m_data.delete_all();
|
||||
}
|
||||
|
||||
void dsp_preset::contents_to_stream(stream_writer * p_stream,abort_callback & p_abort) const {
|
||||
t_size size = get_data_size();
|
||||
p_stream->write_lendian_t(get_owner(),p_abort);
|
||||
p_stream->write_lendian_t(size,p_abort);
|
||||
if (size > 0) {
|
||||
p_stream->write_object(get_data(),size,p_abort);
|
||||
}
|
||||
}
|
||||
|
||||
void dsp_preset::contents_from_stream(stream_reader * p_stream,abort_callback & p_abort) {
|
||||
t_uint32 size;
|
||||
GUID guid;
|
||||
p_stream->read_lendian_t(guid,p_abort);
|
||||
set_owner(guid);
|
||||
p_stream->read_lendian_t(size,p_abort);
|
||||
if (size > 1024*1024*32) throw exception_io_data();
|
||||
set_data_from_stream(p_stream,size,p_abort);
|
||||
}
|
||||
|
||||
void dsp_preset::g_contents_from_stream_skip(stream_reader * p_stream,abort_callback & p_abort) {
|
||||
t_uint32 size;
|
||||
GUID guid;
|
||||
p_stream->read_lendian_t(guid,p_abort);
|
||||
p_stream->read_lendian_t(size,p_abort);
|
||||
if (size > 1024*1024*32) throw exception_io_data();
|
||||
p_stream->skip_object(size,p_abort);
|
||||
}
|
||||
|
||||
void dsp_preset_impl::set_data_from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort) {
|
||||
m_data.set_size(p_bytes);
|
||||
if (p_bytes > 0) p_stream->read_object(m_data.get_ptr(),p_bytes,p_abort);
|
||||
}
|
||||
|
||||
void dsp_chain_config::copy(const dsp_chain_config & p_source) {
|
||||
remove_all();
|
||||
t_size n, m = p_source.get_count();
|
||||
for(n=0;n<m;n++)
|
||||
add_item(p_source.get_item(n));
|
||||
}
|
||||
|
||||
bool dsp_entry::g_have_config_popup(const GUID & p_guid)
|
||||
{
|
||||
service_ptr_t<dsp_entry> entry;
|
||||
if (!g_get_interface(entry,p_guid)) return false;
|
||||
return entry->have_config_popup();
|
||||
}
|
||||
|
||||
bool dsp_entry::g_have_config_popup(const dsp_preset & p_preset)
|
||||
{
|
||||
return g_have_config_popup(p_preset.get_owner());
|
||||
}
|
||||
|
||||
bool dsp_entry::g_show_config_popup(dsp_preset & p_preset,HWND p_parent)
|
||||
{
|
||||
service_ptr_t<dsp_entry> entry;
|
||||
if (!g_get_interface(entry,p_preset.get_owner())) return false;
|
||||
return entry->show_config_popup(p_preset,p_parent);
|
||||
}
|
||||
|
||||
void dsp_entry::g_show_config_popup_v2(const dsp_preset & p_preset,HWND p_parent,dsp_preset_edit_callback & p_callback) {
|
||||
service_ptr_t<dsp_entry> entry;
|
||||
if (g_get_interface(entry,p_preset.get_owner())) {
|
||||
service_ptr_t<dsp_entry_v2> entry_v2;
|
||||
if (entry->service_query_t(entry_v2)) {
|
||||
entry_v2->show_config_popup_v2(p_preset,p_parent,p_callback);
|
||||
} else {
|
||||
dsp_preset_impl temp(p_preset);
|
||||
if (entry->show_config_popup(temp,p_parent)) p_callback.on_preset_changed(temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool dsp_entry::g_get_interface(service_ptr_t<dsp_entry> & p_out,const GUID & p_guid)
|
||||
{
|
||||
service_ptr_t<dsp_entry> ptr;
|
||||
service_enum_t<dsp_entry> e;
|
||||
e.reset();
|
||||
while(e.next(ptr))
|
||||
{
|
||||
if (ptr->get_guid() == p_guid)
|
||||
{
|
||||
p_out = ptr;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool resampler_entry::g_get_interface(service_ptr_t<resampler_entry> & p_out,unsigned p_srate_from,unsigned p_srate_to)
|
||||
{
|
||||
service_ptr_t<dsp_entry> ptr_dsp;
|
||||
service_ptr_t<resampler_entry> ptr_resampler;
|
||||
service_enum_t<dsp_entry> e;
|
||||
e.reset();
|
||||
float found_priority = 0;
|
||||
service_ptr_t<resampler_entry> found;
|
||||
while(e.next(ptr_dsp))
|
||||
{
|
||||
if (ptr_dsp->service_query_t(ptr_resampler))
|
||||
{
|
||||
if (p_srate_from == 0 || ptr_resampler->is_conversion_supported(p_srate_from,p_srate_to))
|
||||
{
|
||||
float priority = ptr_resampler->get_priority();
|
||||
if (found.is_empty() || priority > found_priority)
|
||||
{
|
||||
found = ptr_resampler;
|
||||
found_priority = priority;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (found.is_empty()) return false;
|
||||
p_out = found;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool resampler_entry::g_create_preset(dsp_preset & p_out,unsigned p_srate_from,unsigned p_srate_to,float p_qualityscale)
|
||||
{
|
||||
service_ptr_t<resampler_entry> entry;
|
||||
if (!g_get_interface(entry,p_srate_from,p_srate_to)) return false;
|
||||
return entry->create_preset(p_out,p_srate_to,p_qualityscale);
|
||||
}
|
||||
|
||||
bool resampler_entry::g_create(service_ptr_t<dsp> & p_out,unsigned p_srate_from,unsigned p_srate_to,float p_qualityscale)
|
||||
{
|
||||
service_ptr_t<resampler_entry> entry;
|
||||
if (!g_get_interface(entry,p_srate_from,p_srate_to)) return false;
|
||||
dsp_preset_impl preset;
|
||||
if (!entry->create_preset(preset,p_srate_to,p_qualityscale)) return false;
|
||||
return entry->instantiate(p_out,preset);
|
||||
}
|
||||
|
||||
|
||||
void dsp_chain_config::get_name_list(pfc::string_base & p_out) const
|
||||
{
|
||||
const t_size count = get_count();
|
||||
bool added = false;
|
||||
for(unsigned n=0;n<count;n++)
|
||||
{
|
||||
service_ptr_t<dsp_entry> ptr;
|
||||
if (dsp_entry::g_get_interface(ptr,get_item(n).get_owner()))
|
||||
{
|
||||
if (added) p_out += ", ";
|
||||
added = true;
|
||||
|
||||
pfc::string8 temp;
|
||||
ptr->get_name(temp);
|
||||
p_out += temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dsp::run_abortable(dsp_chunk_list * p_chunk_list,const metadb_handle_ptr & p_cur_file,int p_flags,abort_callback & p_abort) {
|
||||
service_ptr_t<dsp_v2> this_v2;
|
||||
if (this->service_query_t(this_v2)) this_v2->run_v2(p_chunk_list,p_cur_file,p_flags,p_abort);
|
||||
else run(p_chunk_list,p_cur_file,p_flags);
|
||||
}
|
||||
|
||||
namespace {
|
||||
class dsp_preset_edit_callback_impl : public dsp_preset_edit_callback {
|
||||
public:
|
||||
dsp_preset_edit_callback_impl(dsp_preset & p_data) : m_data(p_data) {}
|
||||
void on_preset_changed(const dsp_preset & p_data) {m_data = p_data;}
|
||||
private:
|
||||
dsp_preset & m_data;
|
||||
};
|
||||
};
|
||||
|
||||
bool dsp_entry_v2::show_config_popup(dsp_preset & p_data,HWND p_parent) {
|
||||
PFC_ASSERT(p_data.get_owner() == get_guid());
|
||||
dsp_preset_impl temp(p_data);
|
||||
show_config_popup_v2(p_data,p_parent,dsp_preset_edit_callback_impl(temp));
|
||||
PFC_ASSERT(temp.get_owner() == get_guid());
|
||||
if (temp == p_data) return false;
|
||||
p_data = temp;
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,482 @@
|
|||
//! Interface to a DSP chunk list. A DSP chunk list object is passed to the DSP chain each time, since DSPs are allowed to remove processed chunks or insert new ones.
|
||||
class NOVTABLE dsp_chunk_list {
|
||||
public:
|
||||
virtual t_size get_count() const = 0;
|
||||
virtual audio_chunk * get_item(t_size n) const = 0;
|
||||
virtual void remove_by_idx(t_size idx) = 0;
|
||||
virtual void remove_mask(const bit_array & mask) = 0;
|
||||
virtual audio_chunk * insert_item(t_size idx,t_size hint_size=0) = 0;
|
||||
|
||||
audio_chunk * add_item(t_size hint_size=0) {return insert_item(get_count(),hint_size);}
|
||||
|
||||
void remove_all() {remove_mask(bit_array_true());}
|
||||
|
||||
double get_duration() {
|
||||
double rv = 0;
|
||||
t_size n,m = get_count();
|
||||
for(n=0;n<m;n++) rv += get_item(n)->get_duration();
|
||||
return rv;
|
||||
}
|
||||
|
||||
void add_chunk(const audio_chunk * chunk) {
|
||||
audio_chunk * dst = insert_item(get_count(),chunk->get_data_length());
|
||||
if (dst) dst->copy(*chunk);
|
||||
}
|
||||
|
||||
void remove_bad_chunks();
|
||||
protected:
|
||||
dsp_chunk_list() {}
|
||||
~dsp_chunk_list() {}
|
||||
};
|
||||
|
||||
class dsp_chunk_list_impl : public dsp_chunk_list//implementation
|
||||
{
|
||||
pfc::list_t<pfc::rcptr_t<audio_chunk> > m_data, m_recycled;
|
||||
public:
|
||||
t_size get_count() const;
|
||||
audio_chunk * get_item(t_size n) const;
|
||||
void remove_by_idx(t_size idx);
|
||||
void remove_mask(const bit_array & mask);
|
||||
audio_chunk * insert_item(t_size idx,t_size hint_size=0);
|
||||
};
|
||||
|
||||
//! Instance of a DSP.\n
|
||||
//! Implementation: Derive from dsp_impl_base instead of deriving from dsp directly.\n
|
||||
//! Instantiation: Use dsp_entry static helper methods to instantiate DSPs, or dsp_chain_config / dsp_manager to deal with entire DSP chains.
|
||||
class NOVTABLE dsp : public service_base {
|
||||
public:
|
||||
enum {
|
||||
//! Flush whatever you need to when tracks change.
|
||||
END_OF_TRACK = 1,
|
||||
//! Flush everything.
|
||||
FLUSH = 2
|
||||
};
|
||||
|
||||
//! @param p_chunk_list List of chunks to process. The implementation may alter the list in any way, inserting chunks of different sample rate / channel configuration etc.
|
||||
//! @param p_cur_file Optional, location of currently decoded file. May be null.
|
||||
//! @param p_flags Flags. Can be null, or a combination of END_OF_TRACK and FLUSH constants.
|
||||
virtual void run(dsp_chunk_list * p_chunk_list,const metadb_handle_ptr & p_cur_file,int p_flags)=0;
|
||||
|
||||
//! Flushes the DSP (reinitializes / drops any buffered data). Called after seeking, etc.
|
||||
virtual void flush() = 0;
|
||||
|
||||
//! Retrieves amount of data buffered by the DSP, for syncing visualisation.
|
||||
//! @returns Amount of buffered audio data, in seconds.
|
||||
virtual double get_latency() = 0;
|
||||
//! Returns true if DSP needs to know exact track change point (eg. for crossfading, removing silence).\n
|
||||
//! Signaling this will force-flush any DSPs placed before this DSP so when it gets END_OF_TRACK, relevant chunks contain last samples of the track.\n
|
||||
//! Signaling this will often break regular gapless playback so don't use it unless you have reasons to.
|
||||
virtual bool need_track_change_mark() = 0;
|
||||
|
||||
void run_abortable(dsp_chunk_list * p_chunk_list,const metadb_handle_ptr & p_cur_file,int p_flags,abort_callback & p_abort);
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(dsp,service_base);
|
||||
};
|
||||
|
||||
//! Backwards-compatible extension to dsp interface, allows abortable operation. Introduced in 0.9.2.
|
||||
class NOVTABLE dsp_v2 : public dsp {
|
||||
public:
|
||||
//! Abortable version of dsp::run(). See dsp::run() for descriptions of parameters.
|
||||
virtual void run_v2(dsp_chunk_list * p_chunk_list,const metadb_handle_ptr & p_cur_file,int p_flags,abort_callback & p_abort) = 0;
|
||||
private:
|
||||
void run(dsp_chunk_list * p_chunk_list,const metadb_handle_ptr & p_cur_file,int p_flags) {
|
||||
run_v2(p_chunk_list,p_cur_file,p_flags,abort_callback_dummy());
|
||||
}
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(dsp_v2,dsp);
|
||||
};
|
||||
|
||||
//! Helper class for implementing dsps. You should derive from dsp_impl_base instead of from dsp directly.\n
|
||||
//! The dsp_impl_base_t template allows you to use a custom interface class as a base class for your implementation, in case you provide extended functionality.\n
|
||||
//! Use dsp_factory_t<> template to register your dsp implementation.
|
||||
//! The implementation - as required by dsp_factory_t<> template - must also provide following methods:\n
|
||||
//! A constructor taking const dsp_preset&, initializing the DSP with specified preset data.\n
|
||||
//! static void g_get_name(pfc::string_base &); - retrieving human-readable name of the DSP to display.\n
|
||||
//! static bool g_get_default_preset(dsp_preset &); - retrieving default preset for this DSP. Return value is reserved for future use and should always be true.\n
|
||||
//! static GUID g_get_guid(); - retrieving GUID of your DSP implementation, to be used to identify it when storing DSP chain configuration.\n
|
||||
//! static bool g_have_config_popup(); - retrieving whether your DSP implementation supplies a popup dialog for configuring it.\n
|
||||
//! static void g_show_config_popup(const dsp_preset & p_data,HWND p_parent, dsp_preset_edit_callback & p_callback); - displaying your DSP's settings dialog; called only when g_have_config_popup() returns true; call p_callback.on_preset_changed() whenever user has made adjustments to the preset data.\n
|
||||
template<class t_baseclass>
|
||||
class dsp_impl_base_t : public t_baseclass {
|
||||
private:
|
||||
typedef dsp_impl_base_t<t_baseclass> t_self;
|
||||
dsp_chunk_list * m_list;
|
||||
t_size m_chunk_ptr;
|
||||
metadb_handle* m_cur_file;
|
||||
void run_v2(dsp_chunk_list * p_list,const metadb_handle_ptr & p_cur_file,int p_flags,abort_callback & p_abort);
|
||||
protected:
|
||||
bool get_cur_file(metadb_handle_ptr & p_out) {p_out = m_cur_file; return p_out.is_valid();}// call only from on_chunk / on_endoftrack (on_endoftrack will give info on track being finished); may return null !!
|
||||
|
||||
dsp_impl_base_t() : m_list(NULL), m_cur_file(NULL), m_chunk_ptr(0) {}
|
||||
|
||||
audio_chunk * insert_chunk(t_size p_hint_size = 0) //call only from on_endoftrack / on_endofplayback / on_chunk
|
||||
{//hint_size - optional, amout of buffer space you want to use
|
||||
PFC_ASSERT(m_list != NULL);
|
||||
return m_list->insert_item(m_chunk_ptr++,p_hint_size);
|
||||
}
|
||||
|
||||
|
||||
//! To be overridden by a DSP implementation.\n
|
||||
//! Called on track change. You can use insert_chunk() to dump any data you have to flush. \n
|
||||
//! Note that you must implement need_track_change_mark() to return true if you need this method called.
|
||||
virtual void on_endoftrack(abort_callback & p_abort) = 0;
|
||||
//! To be overridden by a DSP implementation.\n
|
||||
//! Called at the end of played stream, typically at the end of last played track, to allow the DSP to return all data it has buffered-ahead.\n
|
||||
//! Use insert_chunk() to return any data you have buffered.\n
|
||||
//! Note that this call does not imply that the DSP will be destroyed next. \n
|
||||
//! This is also called on track changes if some DSP placed after your DSP requests track change marks.
|
||||
virtual void on_endofplayback(abort_callback & p_abort) = 0;
|
||||
//! To be overridden by a DSP implementation.\n
|
||||
//! Processes a chunk of audio data.\n
|
||||
//! You can call insert_chunk() from inside on_chunk() to insert any audio data before currently processed chunk.\n
|
||||
//! @param p_chunk Current chunk being processed. You can alter it in any way you like.
|
||||
//! @returns True to keep p_chunk (with alterations made inside on_chunk()) in the stream, false to remove it.
|
||||
virtual bool on_chunk(audio_chunk * p_chunk,abort_callback & p_abort) = 0;
|
||||
|
||||
public:
|
||||
//! To be overridden by a DSP implementation.\n
|
||||
//! Flushes the DSP (reinitializes / drops any buffered data). Called after seeking, etc.
|
||||
virtual void flush() = 0;
|
||||
//! To be overridden by a DSP implementation.\n
|
||||
//! Retrieves amount of data buffered by the DSP, for syncing visualisation.
|
||||
//! @returns Amount of buffered audio data, in seconds.
|
||||
virtual double get_latency() = 0;
|
||||
//! To be overridden by a DSP implementation.\n
|
||||
//! Returns true if DSP needs to know exact track change point (eg. for crossfading, removing silence).\n
|
||||
//! Signaling this will force-flush any DSPs placed before this DSP so when it gets on_endoftrack(), relevant chunks contain last samples of the track.\n
|
||||
//! Signaling this may interfere with gapless playback in certain scenarios (forces flush of DSPs placed before you) so don't use it unless you have reasons to.
|
||||
virtual bool need_track_change_mark() = 0;
|
||||
private:
|
||||
dsp_impl_base_t(const t_self&) {throw pfc::exception_bug_check_v2();}
|
||||
const t_self & operator=(const t_self &) {throw pfc::exception_bug_check_v2();}
|
||||
};
|
||||
|
||||
template<class t_baseclass>
|
||||
void dsp_impl_base_t<t_baseclass>::run_v2(dsp_chunk_list * p_list,const metadb_handle_ptr & p_cur_file,int p_flags,abort_callback & p_abort) {
|
||||
pfc::vartoggle_t<dsp_chunk_list*> l_list_toggle(m_list,p_list);
|
||||
pfc::vartoggle_t<metadb_handle*> l_cur_file_toggle(m_cur_file,p_cur_file.get_ptr());
|
||||
|
||||
for(m_chunk_ptr = 0;m_chunk_ptr<m_list->get_count();m_chunk_ptr++) {
|
||||
audio_chunk * c = m_list->get_item(m_chunk_ptr);
|
||||
if (c->is_empty() || !on_chunk(c,p_abort))
|
||||
m_list->remove_by_idx(m_chunk_ptr--);
|
||||
}
|
||||
|
||||
if (p_flags & FLUSH) {
|
||||
on_endofplayback(p_abort);
|
||||
} else if (p_flags & END_OF_TRACK) {
|
||||
if (need_track_change_mark()) on_endoftrack(p_abort);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
typedef dsp_impl_base_t<dsp_v2> dsp_impl_base;
|
||||
|
||||
class NOVTABLE dsp_preset {
|
||||
public:
|
||||
virtual GUID get_owner() const = 0;
|
||||
virtual void set_owner(const GUID & p_owner) = 0;
|
||||
virtual const void * get_data() const = 0;
|
||||
virtual t_size get_data_size() const = 0;
|
||||
virtual void set_data(const void * p_data,t_size p_data_size) = 0;
|
||||
virtual void set_data_from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort) = 0;
|
||||
|
||||
const dsp_preset & operator=(const dsp_preset & p_source) {copy(p_source); return *this;}
|
||||
|
||||
void copy(const dsp_preset & p_source) {set_owner(p_source.get_owner());set_data(p_source.get_data(),p_source.get_data_size());}
|
||||
|
||||
void contents_to_stream(stream_writer * p_stream,abort_callback & p_abort) const;
|
||||
void contents_from_stream(stream_reader * p_stream,abort_callback & p_abort);
|
||||
static void g_contents_from_stream_skip(stream_reader * p_stream,abort_callback & p_abort);
|
||||
|
||||
bool operator==(const dsp_preset & p_other) const {
|
||||
if (get_owner() != p_other.get_owner()) return false;
|
||||
if (get_data_size() != p_other.get_data_size()) return false;
|
||||
if (memcmp(get_data(),p_other.get_data(),get_data_size()) != 0) return false;
|
||||
return true;
|
||||
}
|
||||
bool operator!=(const dsp_preset & p_other) const {
|
||||
return !(*this == p_other);
|
||||
}
|
||||
protected:
|
||||
dsp_preset() {}
|
||||
~dsp_preset() {}
|
||||
};
|
||||
|
||||
class dsp_preset_writer : public stream_writer {
|
||||
public:
|
||||
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
||||
p_abort.check();
|
||||
m_data.append_fromptr((const t_uint8 *) p_buffer,p_bytes);
|
||||
}
|
||||
void flush(dsp_preset & p_preset) {
|
||||
p_preset.set_data(m_data.get_ptr(),m_data.get_size());
|
||||
m_data.set_size(0);
|
||||
}
|
||||
private:
|
||||
pfc::array_t<t_uint8,pfc::alloc_fast_aggressive> m_data;
|
||||
};
|
||||
|
||||
class dsp_preset_reader : public stream_reader {
|
||||
public:
|
||||
dsp_preset_reader() : m_walk(0) {}
|
||||
dsp_preset_reader(const dsp_preset_reader & p_source) : m_walk(0) {*this = p_source;}
|
||||
void init(const dsp_preset & p_preset) {
|
||||
m_data.set_data_fromptr( (const t_uint8*) p_preset.get_data(), p_preset.get_data_size() );
|
||||
m_walk = 0;
|
||||
}
|
||||
t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
||||
p_abort.check();
|
||||
t_size todo = pfc::min_t<t_size>(p_bytes,m_data.get_size()-m_walk);
|
||||
memcpy(p_buffer,m_data.get_ptr()+m_walk,todo);
|
||||
m_walk += todo;
|
||||
return todo;
|
||||
}
|
||||
bool is_finished() {return m_walk == m_data.get_size();}
|
||||
private:
|
||||
t_size m_walk;
|
||||
pfc::array_t<t_uint8> m_data;
|
||||
};
|
||||
|
||||
class dsp_preset_impl : public dsp_preset
|
||||
{
|
||||
public:
|
||||
dsp_preset_impl() {}
|
||||
dsp_preset_impl(const dsp_preset_impl & p_source) {copy(p_source);}
|
||||
dsp_preset_impl(const dsp_preset & p_source) {copy(p_source);}
|
||||
|
||||
const dsp_preset_impl& operator=(const dsp_preset_impl & p_source) {copy(p_source); return *this;}
|
||||
const dsp_preset_impl& operator=(const dsp_preset & p_source) {copy(p_source); return *this;}
|
||||
|
||||
GUID get_owner() const {return m_owner;}
|
||||
void set_owner(const GUID & p_owner) {m_owner = p_owner;}
|
||||
const void * get_data() const {return m_data.get_ptr();}
|
||||
t_size get_data_size() const {return m_data.get_size();}
|
||||
void set_data(const void * p_data,t_size p_data_size) {m_data.set_data_fromptr((const t_uint8*)p_data,p_data_size);}
|
||||
void set_data_from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort);
|
||||
private:
|
||||
GUID m_owner;
|
||||
pfc::array_t<t_uint8> m_data;
|
||||
};
|
||||
|
||||
class NOVTABLE dsp_preset_edit_callback {
|
||||
public:
|
||||
virtual void on_preset_changed(const dsp_preset &) = 0;
|
||||
private:
|
||||
dsp_preset_edit_callback(const dsp_preset_edit_callback&) {throw pfc::exception_not_implemented();}
|
||||
const dsp_preset_edit_callback & operator=(const dsp_preset_edit_callback &) {throw pfc::exception_not_implemented();}
|
||||
protected:
|
||||
dsp_preset_edit_callback() {}
|
||||
~dsp_preset_edit_callback() {}
|
||||
};
|
||||
|
||||
class NOVTABLE dsp_entry : public service_base {
|
||||
public:
|
||||
virtual void get_name(pfc::string_base & p_out) = 0;
|
||||
virtual bool get_default_preset(dsp_preset & p_out) = 0;
|
||||
virtual bool instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset) = 0;
|
||||
virtual GUID get_guid() = 0;
|
||||
virtual bool have_config_popup() = 0;
|
||||
virtual bool show_config_popup(dsp_preset & p_data,HWND p_parent) = 0;
|
||||
|
||||
|
||||
static bool g_get_interface(service_ptr_t<dsp_entry> & p_out,const GUID & p_guid);
|
||||
static bool g_instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset);
|
||||
static bool g_instantiate_default(service_ptr_t<dsp> & p_out,const GUID & p_guid);
|
||||
static bool g_name_from_guid(pfc::string_base & p_out,const GUID & p_guid);
|
||||
static bool g_dsp_exists(const GUID & p_guid);
|
||||
static bool g_get_default_preset(dsp_preset & p_out,const GUID & p_guid);
|
||||
static bool g_have_config_popup(const GUID & p_guid);
|
||||
static bool g_have_config_popup(const dsp_preset & p_preset);
|
||||
static bool g_show_config_popup(dsp_preset & p_preset,HWND p_parent);
|
||||
|
||||
static void g_show_config_popup_v2(const dsp_preset & p_preset,HWND p_parent,dsp_preset_edit_callback & p_callback);
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(dsp_entry);
|
||||
};
|
||||
|
||||
class NOVTABLE dsp_entry_v2 : public dsp_entry {
|
||||
public:
|
||||
virtual void show_config_popup_v2(const dsp_preset & p_data,HWND p_parent,dsp_preset_edit_callback & p_callback) = 0;
|
||||
|
||||
private:
|
||||
bool show_config_popup(dsp_preset & p_data,HWND p_parent);
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(dsp_entry_v2,dsp_entry);
|
||||
};
|
||||
|
||||
template<class T,class t_entry = dsp_entry>
|
||||
class dsp_entry_impl_nopreset_t : public t_entry {
|
||||
public:
|
||||
void get_name(pfc::string_base & p_out) {T::g_get_name(p_out);}
|
||||
bool get_default_preset(dsp_preset & p_out)
|
||||
{
|
||||
p_out.set_owner(T::g_get_guid());
|
||||
p_out.set_data(0,0);
|
||||
return true;
|
||||
}
|
||||
bool instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset)
|
||||
{
|
||||
if (p_preset.get_owner() == T::g_get_guid() && p_preset.get_data_size() == 0)
|
||||
{
|
||||
p_out = new service_impl_t<T>();
|
||||
return p_out.is_valid();
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
GUID get_guid() {return T::g_get_guid();}
|
||||
|
||||
bool have_config_popup() {return false;}
|
||||
bool show_config_popup(dsp_preset & p_data,HWND p_parent) {return false;}
|
||||
};
|
||||
|
||||
template<class T, class t_entry = dsp_entry_v2>
|
||||
class dsp_entry_impl_t : public t_entry {
|
||||
public:
|
||||
void get_name(pfc::string_base & p_out) {T::g_get_name(p_out);}
|
||||
bool get_default_preset(dsp_preset & p_out) {return T::g_get_default_preset(p_out);}
|
||||
bool instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset) {
|
||||
if (p_preset.get_owner() == T::g_get_guid()) {
|
||||
p_out = new service_impl_t<T>(p_preset);
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
GUID get_guid() {return T::g_get_guid();}
|
||||
|
||||
bool have_config_popup() {return T::g_have_config_popup();}
|
||||
bool show_config_popup(dsp_preset & p_data,HWND p_parent) {return T::g_show_config_popup(p_data,p_parent);}
|
||||
//void show_config_popup_v2(const dsp_preset & p_data,HWND p_parent,dsp_preset_edit_callback & p_callback) {T::g_show_config_popup(p_data,p_parent,p_callback);}
|
||||
};
|
||||
|
||||
template<class T, class t_entry = dsp_entry_v2>
|
||||
class dsp_entry_v2_impl_t : public t_entry {
|
||||
public:
|
||||
void get_name(pfc::string_base & p_out) {T::g_get_name(p_out);}
|
||||
bool get_default_preset(dsp_preset & p_out) {return T::g_get_default_preset(p_out);}
|
||||
bool instantiate(service_ptr_t<dsp> & p_out,const dsp_preset & p_preset) {
|
||||
if (p_preset.get_owner() == T::g_get_guid()) {
|
||||
p_out = new service_impl_t<T>(p_preset);
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
GUID get_guid() {return T::g_get_guid();}
|
||||
|
||||
bool have_config_popup() {return T::g_have_config_popup();}
|
||||
//bool show_config_popup(dsp_preset & p_data,HWND p_parent) {return T::g_show_config_popup(p_data,p_parent);}
|
||||
void show_config_popup_v2(const dsp_preset & p_data,HWND p_parent,dsp_preset_edit_callback & p_callback) {T::g_show_config_popup(p_data,p_parent,p_callback);}
|
||||
};
|
||||
|
||||
|
||||
template<class T>
|
||||
class dsp_factory_nopreset_t : public service_factory_single_t<dsp_entry_impl_nopreset_t<T> > {};
|
||||
|
||||
template<class T>
|
||||
class dsp_factory_t : public service_factory_single_t<dsp_entry_v2_impl_t<T> > {};
|
||||
|
||||
class NOVTABLE dsp_chain_config
|
||||
{
|
||||
public:
|
||||
virtual t_size get_count() const = 0;
|
||||
virtual const dsp_preset & get_item(t_size p_index) const = 0;
|
||||
virtual void replace_item(const dsp_preset & p_data,t_size p_index) = 0;
|
||||
virtual void insert_item(const dsp_preset & p_data,t_size p_index) = 0;
|
||||
virtual void remove_mask(const bit_array & p_mask) = 0;
|
||||
|
||||
void remove_item(t_size p_index);
|
||||
void remove_all();
|
||||
void add_item(const dsp_preset & p_data);
|
||||
void copy(const dsp_chain_config & p_source);
|
||||
|
||||
const dsp_chain_config & operator=(const dsp_chain_config & p_source) {copy(p_source); return *this;}
|
||||
|
||||
void contents_to_stream(stream_writer * p_stream,abort_callback & p_abort) const;
|
||||
void contents_from_stream(stream_reader * p_stream,abort_callback & p_abort);
|
||||
|
||||
void instantiate(service_list_t<dsp> & p_out);
|
||||
|
||||
void get_name_list(pfc::string_base & p_out) const;
|
||||
};
|
||||
|
||||
FB2K_STREAM_READER_OVERLOAD(dsp_chain_config) {
|
||||
value.contents_from_stream(&stream.m_stream, stream.m_abort); return stream;
|
||||
}
|
||||
|
||||
FB2K_STREAM_WRITER_OVERLOAD(dsp_chain_config) {
|
||||
value.contents_to_stream(&stream.m_stream, stream.m_abort); return stream;
|
||||
}
|
||||
|
||||
class dsp_chain_config_impl : public dsp_chain_config
|
||||
{
|
||||
public:
|
||||
dsp_chain_config_impl() {}
|
||||
dsp_chain_config_impl(const dsp_chain_config & p_source) {copy(p_source);}
|
||||
dsp_chain_config_impl(const dsp_chain_config_impl & p_source) {copy(p_source);}
|
||||
t_size get_count() const;
|
||||
const dsp_preset & get_item(t_size p_index) const;
|
||||
void replace_item(const dsp_preset & p_data,t_size p_index);
|
||||
void insert_item(const dsp_preset & p_data,t_size p_index);
|
||||
void remove_mask(const bit_array & p_mask);
|
||||
|
||||
const dsp_chain_config_impl & operator=(const dsp_chain_config & p_source) {copy(p_source); return *this;}
|
||||
const dsp_chain_config_impl & operator=(const dsp_chain_config_impl & p_source) {copy(p_source); return *this;}
|
||||
|
||||
~dsp_chain_config_impl();
|
||||
private:
|
||||
pfc::ptr_list_t<dsp_preset_impl> m_data;
|
||||
};
|
||||
|
||||
class cfg_dsp_chain_config : public cfg_var {
|
||||
protected:
|
||||
void get_data_raw(stream_writer * p_stream,abort_callback & p_abort);
|
||||
void set_data_raw(stream_reader * p_stream,t_size p_sizehint,abort_callback & p_abort);
|
||||
public:
|
||||
void reset();
|
||||
inline cfg_dsp_chain_config(const GUID & p_guid) : cfg_var(p_guid) {}
|
||||
t_size get_count() const {return m_data.get_count();}
|
||||
const dsp_preset & get_item(t_size p_index) const {return m_data.get_item(p_index);}
|
||||
bool get_data(dsp_chain_config & p_data) const;
|
||||
void set_data(const dsp_chain_config & p_data);
|
||||
private:
|
||||
dsp_chain_config_impl m_data;
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
//! Helper.
|
||||
class dsp_preset_parser : public stream_reader_formatter<> {
|
||||
public:
|
||||
dsp_preset_parser(const dsp_preset & in) : m_data(in), _m_stream(in.get_data(),in.get_data_size()), stream_reader_formatter(_m_stream,_m_abort) {}
|
||||
|
||||
void reset() {_m_stream.reset();}
|
||||
t_size get_remaining() const {return _m_stream.get_remaining();}
|
||||
|
||||
void assume_empty() const {
|
||||
if (get_remaining() != 0) throw exception_io_data();
|
||||
}
|
||||
|
||||
GUID get_owner() const {return m_data.get_owner();}
|
||||
private:
|
||||
const dsp_preset & m_data;
|
||||
abort_callback_dummy _m_abort;
|
||||
stream_reader_memblock_ref _m_stream;
|
||||
};
|
||||
|
||||
//! Helper.
|
||||
class dsp_preset_builder : public stream_writer_formatter<> {
|
||||
public:
|
||||
dsp_preset_builder() : stream_writer_formatter(_m_stream,_m_abort) {}
|
||||
void finish(const GUID & id, dsp_preset & out) {
|
||||
out.set_owner(id);
|
||||
out.set_data(_m_stream.m_buffer.get_ptr(), _m_stream.m_buffer.get_size());
|
||||
}
|
||||
void reset() {
|
||||
_m_stream.m_buffer.set_size(0);
|
||||
}
|
||||
private:
|
||||
abort_callback_dummy _m_abort;
|
||||
stream_writer_buffer_simple _m_stream;
|
||||
};
|
|
@ -0,0 +1,188 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
void dsp_manager::close() {
|
||||
m_chain.remove_all();
|
||||
m_config_changed = true;
|
||||
}
|
||||
|
||||
void dsp_manager::set_config( const dsp_chain_config & p_data )
|
||||
{
|
||||
//dsp_chain_config::g_instantiate(m_dsp_list,p_data);
|
||||
m_config.copy(p_data);
|
||||
m_config_changed = true;
|
||||
}
|
||||
|
||||
void dsp_manager::dsp_run(t_dsp_chain::const_iterator p_iter,dsp_chunk_list * p_list,const metadb_handle_ptr & cur_file,unsigned flags,double & latency,abort_callback & p_abort)
|
||||
{
|
||||
p_list->remove_bad_chunks();
|
||||
|
||||
TRACK_CODE("dsp::run",p_iter->m_dsp->run_abortable(p_list,cur_file,flags,p_abort));
|
||||
TRACK_CODE("dsp::get_latency",latency += p_iter->m_dsp->get_latency());
|
||||
}
|
||||
|
||||
double dsp_manager::run(dsp_chunk_list * p_list,const metadb_handle_ptr & p_cur_file,unsigned p_flags,abort_callback & p_abort) {
|
||||
TRACK_CALL_TEXT("dsp_manager::run");
|
||||
|
||||
try {
|
||||
fpu_control_default l_fpu_control;
|
||||
|
||||
double latency=0;
|
||||
bool done = false;
|
||||
|
||||
t_dsp_chain::const_iterator flush_mark;
|
||||
if ((p_flags & dsp::END_OF_TRACK) && ! (p_flags & dsp::FLUSH)) {
|
||||
for(t_dsp_chain::const_iterator iter = m_chain.first(); iter.is_valid(); ++iter) {
|
||||
if (iter->m_dsp->need_track_change_mark()) flush_mark = iter;
|
||||
}
|
||||
}
|
||||
|
||||
if (m_config_changed)
|
||||
{
|
||||
t_dsp_chain newchain;
|
||||
bool recycle_available = true;
|
||||
|
||||
for(t_size n=0;n<m_config.get_count();n++) {
|
||||
service_ptr_t<dsp> temp;
|
||||
|
||||
const dsp_preset & preset = m_config.get_item(n);
|
||||
if (dsp_entry::g_dsp_exists(preset.get_owner())) {
|
||||
t_dsp_chain::iterator iter = newchain.insert_last();
|
||||
iter->m_preset = m_config.get_item(n);
|
||||
iter->m_recycle_flag = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//HACK: recycle existing DSPs in a special case when user has apparently only altered settings of one of DSPs.
|
||||
if (newchain.get_count() == m_chain.get_count()) {
|
||||
t_size data_mismatch_count = 0;
|
||||
t_size owner_mismatch_count = 0;
|
||||
t_dsp_chain::iterator iter_src, iter_dst;
|
||||
iter_src = m_chain.first(); iter_dst = newchain.first();
|
||||
while(iter_src.is_valid() && iter_dst.is_valid()) {
|
||||
if (iter_src->m_preset.get_owner() != iter_dst->m_preset.get_owner()) {
|
||||
owner_mismatch_count++;
|
||||
} else if (iter_src->m_preset != iter_dst->m_preset) {
|
||||
data_mismatch_count++;
|
||||
}
|
||||
++iter_src; ++iter_dst;
|
||||
}
|
||||
recycle_available = (owner_mismatch_count == 0 && data_mismatch_count <= 1);
|
||||
} else {
|
||||
recycle_available = false;
|
||||
}
|
||||
|
||||
if (recycle_available) {
|
||||
t_dsp_chain::iterator iter_src, iter_dst;
|
||||
iter_src = m_chain.first(); iter_dst = newchain.first();
|
||||
while(iter_src.is_valid() && iter_dst.is_valid()) {
|
||||
if (iter_src->m_preset == iter_dst->m_preset) {
|
||||
iter_src->m_recycle_flag = true;
|
||||
iter_dst->m_dsp = iter_src->m_dsp;
|
||||
}
|
||||
++iter_src; ++iter_dst;
|
||||
}
|
||||
}
|
||||
|
||||
for(t_dsp_chain::iterator iter = newchain.first(); iter.is_valid(); ++iter) {
|
||||
if (iter->m_dsp.is_empty()) {
|
||||
if (!dsp_entry::g_instantiate(iter->m_dsp,iter->m_preset)) throw pfc::exception_bug_check_v2();
|
||||
}
|
||||
}
|
||||
|
||||
if (m_chain.get_count()>0) {
|
||||
bool flushflag = flush_mark.is_valid();
|
||||
for(t_dsp_chain::const_iterator iter = m_chain.first(); iter.is_valid(); ++iter) {
|
||||
unsigned flags2 = p_flags;
|
||||
if (iter == flush_mark) flushflag = false;
|
||||
if (flushflag || !iter->m_recycle_flag) flags2|=dsp::FLUSH;
|
||||
dsp_run(iter,p_list,p_cur_file,flags2,latency,p_abort);
|
||||
}
|
||||
done = true;
|
||||
}
|
||||
|
||||
m_chain = newchain;
|
||||
m_config_changed = false;
|
||||
}
|
||||
|
||||
if (!done)
|
||||
{
|
||||
bool flushflag = flush_mark.is_valid();
|
||||
for(t_dsp_chain::const_iterator iter = m_chain.first(); iter.is_valid(); ++iter) {
|
||||
unsigned flags2 = p_flags;
|
||||
if (iter == flush_mark) flushflag = false;
|
||||
if (flushflag) flags2|=dsp::FLUSH;
|
||||
dsp_run(iter,p_list,p_cur_file,flags2,latency,p_abort);
|
||||
}
|
||||
done = true;
|
||||
}
|
||||
|
||||
p_list->remove_bad_chunks();
|
||||
|
||||
return latency;
|
||||
} catch(...) {
|
||||
p_list->remove_all();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
void dsp_manager::flush()
|
||||
{
|
||||
for(t_dsp_chain::const_iterator iter = m_chain.first(); iter.is_valid(); ++iter) {
|
||||
TRACK_CODE("dsp::flush",iter->m_dsp->flush());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool dsp_manager::is_active() const {return m_config.get_count()>0;}
|
||||
|
||||
void dsp_config_manager::core_enable_dsp(const dsp_preset & preset) {
|
||||
dsp_chain_config_impl cfg;
|
||||
get_core_settings(cfg);
|
||||
|
||||
bool found = false;
|
||||
bool changed = false;
|
||||
t_size n,m = cfg.get_count();
|
||||
for(n=0;n<m;n++) {
|
||||
if (cfg.get_item(n).get_owner() == preset.get_owner()) {
|
||||
found = true;
|
||||
if (cfg.get_item(n) != preset) {
|
||||
cfg.replace_item(preset,n);
|
||||
changed = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {cfg.insert_item(preset,0); changed = true;}
|
||||
|
||||
if (changed) set_core_settings(cfg);
|
||||
}
|
||||
void dsp_config_manager::core_disable_dsp(const GUID & id) {
|
||||
dsp_chain_config_impl cfg;
|
||||
get_core_settings(cfg);
|
||||
|
||||
t_size n,m = cfg.get_count();
|
||||
bit_array_bittable mask(m);
|
||||
bool changed = false;
|
||||
for(n=0;n<m;n++) {
|
||||
bool axe = (cfg.get_item(n).get_owner() == id) ? true : false;
|
||||
if (axe) changed = true;
|
||||
mask.set(n,axe);
|
||||
}
|
||||
if (changed) {
|
||||
cfg.remove_mask(mask);
|
||||
set_core_settings(cfg);
|
||||
}
|
||||
}
|
||||
|
||||
bool dsp_config_manager::core_query_dsp(const GUID & id, dsp_preset & out) {
|
||||
dsp_chain_config_impl cfg;
|
||||
get_core_settings(cfg);
|
||||
for(t_size n=0;n<cfg.get_count();n++) {
|
||||
const dsp_preset & entry = cfg.get_item(n);
|
||||
if (entry.get_owner() == id) {
|
||||
out = entry; return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,81 @@
|
|||
//! Helper class for running audio data through a DSP chain.
|
||||
class dsp_manager {
|
||||
public:
|
||||
dsp_manager() : m_config_changed(false) {}
|
||||
|
||||
//! Alters the DSP chain configuration. Should be called before the first run() to set the configuration but can be also called anytime later between run() calls.
|
||||
void set_config( const dsp_chain_config & p_data );
|
||||
//! Runs DSP on the specified chunk list.
|
||||
//! @returns Current DSP latency in seconds.
|
||||
double run(dsp_chunk_list * p_list,const metadb_handle_ptr & p_cur_file,unsigned p_flags,abort_callback & p_abort);
|
||||
//! Flushes the DSP (e.g. when seeking).
|
||||
void flush();
|
||||
|
||||
//! Equivalent to set_config() with empty configuration.
|
||||
void close();
|
||||
|
||||
//! Returns whether there's at least one active DSP in the configuration.
|
||||
bool is_active() const;
|
||||
|
||||
private:
|
||||
struct t_dsp_chain_entry {
|
||||
service_ptr_t<dsp> m_dsp;
|
||||
dsp_preset_impl m_preset;
|
||||
bool m_recycle_flag;
|
||||
};
|
||||
typedef pfc::chain_list_v2_t<t_dsp_chain_entry> t_dsp_chain;
|
||||
|
||||
t_dsp_chain m_chain;
|
||||
dsp_chain_config_impl m_config;
|
||||
bool m_config_changed;
|
||||
|
||||
void dsp_run(t_dsp_chain::const_iterator p_iter,dsp_chunk_list * list,const metadb_handle_ptr & cur_file,unsigned flags,double & latency,abort_callback&);
|
||||
|
||||
dsp_manager(const dsp_manager &) {throw pfc::exception_not_implemented();}
|
||||
const dsp_manager & operator=(const dsp_manager&) {throw pfc::exception_not_implemented();}
|
||||
};
|
||||
|
||||
//! Core API for accessing core playback DSP settings as well as spawning DSP configuration dialogs. \n
|
||||
//! Use static_api_ptr_t<dsp_config_manager>() to instantiate.
|
||||
class dsp_config_manager : public service_base {
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(dsp_config_manager);
|
||||
public:
|
||||
//! Retrieves current core playback DSP settings.
|
||||
virtual void get_core_settings(dsp_chain_config & p_out) = 0;
|
||||
//! Changes current core playback DSP settings.
|
||||
virtual void set_core_settings(const dsp_chain_config & p_data) = 0;
|
||||
|
||||
//! Runs a modal DSP settings dialog.
|
||||
//! @param p_data DSP chain configuration to edit - contains initial configuration to put in the dialog when called, receives the new configuration on successful edit.
|
||||
//! @returns True when user approved DSP configuration changes (pressed the "OK" button), false when the user cancelled them ("Cancel" button).
|
||||
virtual bool configure_popup(dsp_chain_config & p_data,HWND p_parent,const char * p_title) = 0;
|
||||
|
||||
//! Spawns an embedded DSP settings dialog.
|
||||
//! @param p_initdata Initial DSP chain configuration to put in the dialog.
|
||||
//! @param p_parent Parent window to contain the embedded dialog.
|
||||
//! @param p_id Control ID of the embedded dialog. The parent window will receive a WM_COMMAND with BN_CLICKED and this identifier when user changes settings in the embedded dialog.
|
||||
//! @param p_from_modal Must be set to true when the parent window is a modal dialog, false otherwise.
|
||||
virtual HWND configure_embedded(const dsp_chain_config & p_initdata,HWND p_parent,unsigned p_id,bool p_from_modal) = 0;
|
||||
//! Retrieves current settings from an embedded DSP settings dialog. See also: configure_embedded().
|
||||
virtual void configure_embedded_retrieve(HWND wnd,dsp_chain_config & p_data) = 0;
|
||||
//! Changes current settings in an embedded DSP settings dialog. See also: configure_embedded().
|
||||
virtual void configure_embedded_change(HWND wnd,const dsp_chain_config & p_data) = 0;
|
||||
|
||||
|
||||
//! Helper - enables a DSP in core playback settings.
|
||||
void core_enable_dsp(const dsp_preset & preset);
|
||||
//! Helper - disables a DSP in core playback settings.
|
||||
void core_disable_dsp(const GUID & id);
|
||||
//! Helper - if a DSP with the specified identifier is present in playback settings, retrieves its configuration and returns true, otherwise returns false.
|
||||
bool core_query_dsp(const GUID & id, dsp_preset & out);
|
||||
};
|
||||
|
||||
//! Callback class for getting notified about core playback DSP settings getting altered. \n
|
||||
//! Register your implementations with static service_factory_single_t<myclass> g_myclass_factory;
|
||||
class NOVTABLE dsp_config_callback : public service_base {
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(dsp_config_callback);
|
||||
public:
|
||||
//! Called when core playback DSP settings change. \n
|
||||
//! Note: you must not try to alter core playback DSP settings inside this callback, or call anything else that possibly alters core playback DSP settings.
|
||||
virtual void on_core_settings_change(const dsp_chain_config & p_newdata) = 0;
|
||||
};
|
|
@ -0,0 +1,19 @@
|
|||
class NOVTABLE event_logger : public service_base {
|
||||
FB2K_MAKE_SERVICE_INTERFACE(event_logger, service_base);
|
||||
public:
|
||||
enum {
|
||||
severity_status,
|
||||
severity_warning,
|
||||
severity_error
|
||||
};
|
||||
void log_status(const char * line) {log_entry(line, severity_status);}
|
||||
void log_warning(const char * line) {log_entry(line, severity_warning);}
|
||||
void log_error(const char * line) {log_entry(line, severity_error);}
|
||||
|
||||
virtual void log_entry(const char * line, unsigned severity) = 0;
|
||||
};
|
||||
|
||||
class event_logger_fallback : public event_logger {
|
||||
public:
|
||||
void log_entry(const char * line, unsigned) {console::print(line);}
|
||||
};
|
|
@ -0,0 +1,451 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
t_size file_info::meta_find_ex(const char * p_name,t_size p_name_length) const
|
||||
{
|
||||
t_size n, m = meta_get_count();
|
||||
for(n=0;n<m;n++)
|
||||
{
|
||||
if (pfc::stricmp_ascii_ex(meta_enum_name(n),infinite,p_name,p_name_length) == 0) return n;
|
||||
}
|
||||
return infinite;
|
||||
}
|
||||
|
||||
bool file_info::meta_exists_ex(const char * p_name,t_size p_name_length) const
|
||||
{
|
||||
return meta_find_ex(p_name,p_name_length) != infinite;
|
||||
}
|
||||
|
||||
void file_info::meta_remove_field_ex(const char * p_name,t_size p_name_length)
|
||||
{
|
||||
t_size index = meta_find_ex(p_name,p_name_length);
|
||||
if (index!=infinite) meta_remove_index(index);
|
||||
}
|
||||
|
||||
|
||||
void file_info::meta_remove_index(t_size p_index)
|
||||
{
|
||||
meta_remove_mask(bit_array_one(p_index));
|
||||
}
|
||||
|
||||
void file_info::meta_remove_all()
|
||||
{
|
||||
meta_remove_mask(bit_array_true());
|
||||
}
|
||||
|
||||
void file_info::meta_remove_value(t_size p_index,t_size p_value)
|
||||
{
|
||||
meta_remove_values(p_index,bit_array_one(p_value));
|
||||
}
|
||||
|
||||
t_size file_info::meta_get_count_by_name_ex(const char * p_name,t_size p_name_length) const
|
||||
{
|
||||
t_size index = meta_find_ex(p_name,p_name_length);
|
||||
if (index == infinite) return 0;
|
||||
return meta_enum_value_count(index);
|
||||
}
|
||||
|
||||
t_size file_info::info_find_ex(const char * p_name,t_size p_name_length) const
|
||||
{
|
||||
t_size n, m = info_get_count();
|
||||
for(n=0;n<m;n++) {
|
||||
if (pfc::stricmp_ascii_ex(info_enum_name(n),infinite,p_name,p_name_length) == 0) return n;
|
||||
}
|
||||
return infinite;
|
||||
}
|
||||
|
||||
bool file_info::info_exists_ex(const char * p_name,t_size p_name_length) const
|
||||
{
|
||||
return info_find_ex(p_name,p_name_length) != infinite;
|
||||
}
|
||||
|
||||
void file_info::info_remove_index(t_size p_index)
|
||||
{
|
||||
info_remove_mask(bit_array_one(p_index));
|
||||
}
|
||||
|
||||
void file_info::info_remove_all()
|
||||
{
|
||||
info_remove_mask(bit_array_true());
|
||||
}
|
||||
|
||||
bool file_info::info_remove_ex(const char * p_name,t_size p_name_length)
|
||||
{
|
||||
t_size index = info_find_ex(p_name,p_name_length);
|
||||
if (index != infinite)
|
||||
{
|
||||
info_remove_index(index);
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
void file_info::copy_meta_single(const file_info & p_source,t_size p_index)
|
||||
{
|
||||
copy_meta_single_rename(p_source,p_index,p_source.meta_enum_name(p_index));
|
||||
}
|
||||
|
||||
void file_info::copy_meta_single_nocheck(const file_info & p_source,t_size p_index)
|
||||
{
|
||||
const char * name = p_source.meta_enum_name(p_index);
|
||||
t_size n, m = p_source.meta_enum_value_count(p_index);
|
||||
t_size new_index = infinite;
|
||||
for(n=0;n<m;n++)
|
||||
{
|
||||
const char * value = p_source.meta_enum_value(p_index,n);
|
||||
if (n == 0) new_index = meta_set_nocheck(name,value);
|
||||
else meta_add_value(new_index,value);
|
||||
}
|
||||
}
|
||||
|
||||
void file_info::copy_meta_single_by_name_ex(const file_info & p_source,const char * p_name,t_size p_name_length)
|
||||
{
|
||||
t_size index = p_source.meta_find_ex(p_name,p_name_length);
|
||||
if (index != infinite) copy_meta_single(p_source,index);
|
||||
}
|
||||
|
||||
void file_info::copy_info_single_by_name_ex(const file_info & p_source,const char * p_name,t_size p_name_length)
|
||||
{
|
||||
t_size index = p_source.info_find_ex(p_name,p_name_length);
|
||||
if (index != infinite) copy_info_single(p_source,index);
|
||||
}
|
||||
|
||||
void file_info::copy_meta_single_by_name_nocheck_ex(const file_info & p_source,const char * p_name,t_size p_name_length)
|
||||
{
|
||||
t_size index = p_source.meta_find_ex(p_name,p_name_length);
|
||||
if (index != infinite) copy_meta_single_nocheck(p_source,index);
|
||||
}
|
||||
|
||||
void file_info::copy_info_single_by_name_nocheck_ex(const file_info & p_source,const char * p_name,t_size p_name_length)
|
||||
{
|
||||
t_size index = p_source.info_find_ex(p_name,p_name_length);
|
||||
if (index != infinite) copy_info_single_nocheck(p_source,index);
|
||||
}
|
||||
|
||||
void file_info::copy_info_single(const file_info & p_source,t_size p_index)
|
||||
{
|
||||
info_set(p_source.info_enum_name(p_index),p_source.info_enum_value(p_index));
|
||||
}
|
||||
|
||||
void file_info::copy_info_single_nocheck(const file_info & p_source,t_size p_index)
|
||||
{
|
||||
info_set_nocheck(p_source.info_enum_name(p_index),p_source.info_enum_value(p_index));
|
||||
}
|
||||
|
||||
void file_info::copy_meta(const file_info & p_source)
|
||||
{
|
||||
if (&p_source != this) {
|
||||
meta_remove_all();
|
||||
t_size n, m = p_source.meta_get_count();
|
||||
for(n=0;n<m;n++)
|
||||
copy_meta_single_nocheck(p_source,n);
|
||||
}
|
||||
}
|
||||
|
||||
void file_info::copy_info(const file_info & p_source)
|
||||
{
|
||||
if (&p_source != this) {
|
||||
info_remove_all();
|
||||
t_size n, m = p_source.info_get_count();
|
||||
for(n=0;n<m;n++)
|
||||
copy_info_single_nocheck(p_source,n);
|
||||
}
|
||||
}
|
||||
|
||||
void file_info::copy(const file_info & p_source)
|
||||
{
|
||||
if (&p_source != this) {
|
||||
copy_meta(p_source);
|
||||
copy_info(p_source);
|
||||
set_length(p_source.get_length());
|
||||
set_replaygain(p_source.get_replaygain());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const char * file_info::meta_get_ex(const char * p_name,t_size p_name_length,t_size p_index) const
|
||||
{
|
||||
t_size index = meta_find_ex(p_name,p_name_length);
|
||||
if (index == infinite) return 0;
|
||||
t_size max = meta_enum_value_count(index);
|
||||
if (p_index >= max) return 0;
|
||||
return meta_enum_value(index,p_index);
|
||||
}
|
||||
|
||||
const char * file_info::info_get_ex(const char * p_name,t_size p_name_length) const
|
||||
{
|
||||
t_size index = info_find_ex(p_name,p_name_length);
|
||||
if (index == infinite) return 0;
|
||||
return info_enum_value(index);
|
||||
}
|
||||
|
||||
t_int64 file_info::info_get_int(const char * name) const
|
||||
{
|
||||
PFC_ASSERT(pfc::is_valid_utf8(name));
|
||||
const char * val = info_get(name);
|
||||
if (val==0) return 0;
|
||||
return _atoi64(val);
|
||||
}
|
||||
|
||||
t_int64 file_info::info_get_length_samples() const
|
||||
{
|
||||
t_int64 ret = 0;
|
||||
double len = get_length();
|
||||
t_int64 srate = info_get_int("samplerate");
|
||||
|
||||
if (srate>0 && len>0)
|
||||
{
|
||||
ret = audio_math::time_to_samples(len,(unsigned)srate);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
double file_info::info_get_float(const char * name) const
|
||||
{
|
||||
const char * ptr = info_get(name);
|
||||
if (ptr) return pfc::string_to_float(ptr);
|
||||
else return 0;
|
||||
}
|
||||
|
||||
void file_info::info_set_int(const char * name,t_int64 value)
|
||||
{
|
||||
PFC_ASSERT(pfc::is_valid_utf8(name));
|
||||
info_set(name,pfc::format_int(value));
|
||||
}
|
||||
|
||||
void file_info::info_set_float(const char * name,double value,unsigned precision,bool force_sign,const char * unit)
|
||||
{
|
||||
PFC_ASSERT(pfc::is_valid_utf8(name));
|
||||
PFC_ASSERT(unit==0 || strlen(unit) <= 64);
|
||||
char temp[128];
|
||||
pfc::float_to_string(temp,64,value,precision,force_sign);
|
||||
temp[63] = 0;
|
||||
if (unit)
|
||||
{
|
||||
strcat(temp," ");
|
||||
strcat(temp,unit);
|
||||
}
|
||||
info_set(name,temp);
|
||||
}
|
||||
|
||||
|
||||
void file_info::info_set_replaygain_album_gain(float value)
|
||||
{
|
||||
replaygain_info temp = get_replaygain();
|
||||
temp.m_album_gain = value;
|
||||
set_replaygain(temp);
|
||||
}
|
||||
|
||||
void file_info::info_set_replaygain_album_peak(float value)
|
||||
{
|
||||
replaygain_info temp = get_replaygain();
|
||||
temp.m_album_peak = value;
|
||||
set_replaygain(temp);
|
||||
}
|
||||
|
||||
void file_info::info_set_replaygain_track_gain(float value)
|
||||
{
|
||||
replaygain_info temp = get_replaygain();
|
||||
temp.m_track_gain = value;
|
||||
set_replaygain(temp);
|
||||
}
|
||||
|
||||
void file_info::info_set_replaygain_track_peak(float value)
|
||||
{
|
||||
replaygain_info temp = get_replaygain();
|
||||
temp.m_track_peak = value;
|
||||
set_replaygain(temp);
|
||||
}
|
||||
|
||||
|
||||
static bool is_valid_bps(t_int64 val)
|
||||
{
|
||||
return val>0 && val<=256;
|
||||
}
|
||||
|
||||
unsigned file_info::info_get_decoded_bps() const
|
||||
{
|
||||
t_int64 val = info_get_int("decoded_bitspersample");
|
||||
if (is_valid_bps(val)) return (unsigned)val;
|
||||
val = info_get_int("bitspersample");
|
||||
if (is_valid_bps(val)) return (unsigned)val;
|
||||
return 0;
|
||||
|
||||
}
|
||||
|
||||
void file_info::reset()
|
||||
{
|
||||
info_remove_all();
|
||||
meta_remove_all();
|
||||
set_length(0);
|
||||
reset_replaygain();
|
||||
}
|
||||
|
||||
void file_info::reset_replaygain()
|
||||
{
|
||||
replaygain_info temp;
|
||||
temp.reset();
|
||||
set_replaygain(temp);
|
||||
}
|
||||
|
||||
void file_info::copy_meta_single_rename_ex(const file_info & p_source,t_size p_index,const char * p_new_name,t_size p_new_name_length)
|
||||
{
|
||||
t_size n, m = p_source.meta_enum_value_count(p_index);
|
||||
t_size new_index = infinite;
|
||||
for(n=0;n<m;n++)
|
||||
{
|
||||
const char * value = p_source.meta_enum_value(p_index,n);
|
||||
if (n == 0) new_index = meta_set_ex(p_new_name,p_new_name_length,value,infinite);
|
||||
else meta_add_value(new_index,value);
|
||||
}
|
||||
}
|
||||
|
||||
t_size file_info::meta_add_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length)
|
||||
{
|
||||
t_size index = meta_find_ex(p_name,p_name_length);
|
||||
if (index == infinite) return meta_set_nocheck_ex(p_name,p_name_length,p_value,p_value_length);
|
||||
else
|
||||
{
|
||||
meta_add_value_ex(index,p_value,p_value_length);
|
||||
return index;
|
||||
}
|
||||
}
|
||||
|
||||
void file_info::meta_add_value_ex(t_size p_index,const char * p_value,t_size p_value_length)
|
||||
{
|
||||
meta_insert_value_ex(p_index,meta_enum_value_count(p_index),p_value,p_value_length);
|
||||
}
|
||||
|
||||
|
||||
t_size file_info::meta_calc_total_value_count() const
|
||||
{
|
||||
t_size n, m = meta_get_count(), ret = 0;
|
||||
for(n=0;n<m;n++) ret += meta_enum_value_count(n);
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool file_info::info_set_replaygain_ex(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len)
|
||||
{
|
||||
replaygain_info temp = get_replaygain();
|
||||
if (temp.set_from_meta_ex(p_name,p_name_len,p_value,p_value_len))
|
||||
{
|
||||
set_replaygain(temp);
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
void file_info::info_set_replaygain_auto_ex(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len)
|
||||
{
|
||||
if (!info_set_replaygain_ex(p_name,p_name_len,p_value,p_value_len))
|
||||
info_set_ex(p_name,p_name_len,p_value,p_value_len);
|
||||
}
|
||||
|
||||
bool replaygain_info::g_equal(const replaygain_info & item1,const replaygain_info & item2)
|
||||
{
|
||||
return item1.m_album_gain == item2.m_album_gain &&
|
||||
item1.m_track_gain == item2.m_track_gain &&
|
||||
item1.m_album_peak == item2.m_album_peak &&
|
||||
item1.m_track_peak == item2.m_track_peak;
|
||||
}
|
||||
|
||||
bool file_info::are_meta_fields_identical(t_size p_index1,t_size p_index2) const
|
||||
{
|
||||
const t_size count = meta_enum_value_count(p_index1);
|
||||
if (count != meta_enum_value_count(p_index2)) return false;
|
||||
t_size n;
|
||||
for(n=0;n<count;n++)
|
||||
{
|
||||
if (strcmp(meta_enum_value(p_index1,n),meta_enum_value(p_index2,n))) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
void file_info::meta_format_entry(t_size index, pfc::string_base & out, const char * separator) const {
|
||||
out.reset();
|
||||
t_size val, count = meta_enum_value_count(index);
|
||||
PFC_ASSERT( count > 0);
|
||||
for(val=0;val<count;val++)
|
||||
{
|
||||
if (val > 0) out += separator;
|
||||
out += meta_enum_value(index,val);
|
||||
}
|
||||
}
|
||||
|
||||
bool file_info::meta_format(const char * p_name,pfc::string_base & p_out, const char * separator) const {
|
||||
p_out.reset();
|
||||
t_size index = meta_find(p_name);
|
||||
if (index == infinite) return false;
|
||||
meta_format_entry(index, p_out, separator);
|
||||
return true;
|
||||
}
|
||||
|
||||
void file_info::info_calculate_bitrate(t_filesize p_filesize,double p_length)
|
||||
{
|
||||
info_set_bitrate((unsigned)floor((double)p_filesize * 8 / (p_length * 1000) + 0.5));
|
||||
}
|
||||
|
||||
bool file_info::is_encoding_lossy() const {
|
||||
const char * encoding = info_get("encoding");
|
||||
if (encoding != NULL) {
|
||||
if (pfc::stricmp_ascii(encoding,"lossy") == 0 /*|| pfc::stricmp_ascii(encoding,"hybrid") == 0*/) return true;
|
||||
} else {
|
||||
//the old way
|
||||
if (info_get("bitspersample") == NULL) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool file_info::g_is_meta_equal(const file_info & p_item1,const file_info & p_item2) {
|
||||
const t_size count = p_item1.meta_get_count();
|
||||
if (count != p_item2.meta_get_count()) {
|
||||
//uDebugLog() << "meta count mismatch";
|
||||
return false;
|
||||
}
|
||||
pfc::map_t<const char*,t_size,field_name_comparator> item2_meta_map;
|
||||
for(t_size n=0; n<count; n++) {
|
||||
item2_meta_map.set(p_item2.meta_enum_name(n),n);
|
||||
}
|
||||
for(t_size n1=0; n1<count; n1++) {
|
||||
t_size n2;
|
||||
if (!item2_meta_map.query(p_item1.meta_enum_name(n1),n2)) {
|
||||
//uDebugLog() << "item2 doesn't have " << p_item1.meta_enum_name(n1);
|
||||
return false;
|
||||
}
|
||||
t_size value_count = p_item1.meta_enum_value_count(n1);
|
||||
if (value_count != p_item2.meta_enum_value_count(n2)) {
|
||||
//uDebugLog() << "meta value count mismatch: " << p_item1.meta_enum_name(n1) << " : " << value_count << " vs " << p_item2.meta_enum_value_count(n2);
|
||||
return false;
|
||||
}
|
||||
for(t_size v = 0; v < value_count; v++) {
|
||||
if (strcmp(p_item1.meta_enum_value(n1,v),p_item2.meta_enum_value(n2,v)) != 0) {
|
||||
//uDebugLog() << "meta mismatch: " << p_item1.meta_enum_name(n1) << " : " << p_item1.meta_enum_value(n1,v) << " vs " << p_item2.meta_enum_value(n2,v);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool file_info::g_is_info_equal(const file_info & p_item1,const file_info & p_item2) {
|
||||
t_size count = p_item1.info_get_count();
|
||||
if (count != p_item2.info_get_count()) return false;
|
||||
for(t_size n1=0; n1<count; n1++) {
|
||||
t_size n2 = p_item2.info_find(p_item1.info_enum_name(n1));
|
||||
if (n2 == infinite) return false;
|
||||
if (strcmp(p_item1.info_enum_value(n1),p_item2.info_enum_value(n2)) != 0) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static bool is_valid_field_name_char(char p_char) {
|
||||
return p_char >= 32 && p_char < 127 && p_char != '=' && p_char != '%' && p_char != '<' && p_char != '>';
|
||||
}
|
||||
|
||||
bool file_info::g_is_valid_field_name(const char * p_name,t_size p_length) {
|
||||
t_size walk;
|
||||
for(walk = 0; walk < p_length && p_name[walk] != 0; walk++) {
|
||||
if (!is_valid_field_name_char(p_name[walk])) return false;
|
||||
}
|
||||
return walk > 0;
|
||||
}
|
|
@ -0,0 +1,230 @@
|
|||
#ifndef _FILE_INFO_H_
|
||||
#define _FILE_INFO_H_
|
||||
|
||||
//! Structure containing ReplayGain scan results from some playable object, also providing various helper methods to manipulate those results.
|
||||
struct replaygain_info
|
||||
{
|
||||
float m_album_gain,m_track_gain;
|
||||
float m_album_peak,m_track_peak;
|
||||
|
||||
enum {text_buffer_size = 16 };
|
||||
typedef char t_text_buffer[text_buffer_size];
|
||||
|
||||
enum { peak_invalid = -1, gain_invalid = -1000 };
|
||||
|
||||
static bool g_format_gain(float p_value,char p_buffer[text_buffer_size]);
|
||||
static bool g_format_peak(float p_value,char p_buffer[text_buffer_size]);
|
||||
|
||||
inline bool format_album_gain(char p_buffer[text_buffer_size]) const {return g_format_gain(m_album_gain,p_buffer);}
|
||||
inline bool format_track_gain(char p_buffer[text_buffer_size]) const {return g_format_gain(m_track_gain,p_buffer);}
|
||||
inline bool format_album_peak(char p_buffer[text_buffer_size]) const {return g_format_peak(m_album_peak,p_buffer);}
|
||||
inline bool format_track_peak(char p_buffer[text_buffer_size]) const {return g_format_peak(m_track_peak,p_buffer);}
|
||||
|
||||
void set_album_gain_text(const char * p_text,t_size p_text_len = infinite);
|
||||
void set_track_gain_text(const char * p_text,t_size p_text_len = infinite);
|
||||
void set_album_peak_text(const char * p_text,t_size p_text_len = infinite);
|
||||
void set_track_peak_text(const char * p_text,t_size p_text_len = infinite);
|
||||
|
||||
static bool g_is_meta_replaygain(const char * p_name,t_size p_name_len = infinite);
|
||||
bool set_from_meta_ex(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len);
|
||||
inline bool set_from_meta(const char * p_name,const char * p_value) {return set_from_meta_ex(p_name,infinite,p_value,infinite);}
|
||||
|
||||
inline bool is_album_gain_present() const {return m_album_gain != gain_invalid;}
|
||||
inline bool is_track_gain_present() const {return m_track_gain != gain_invalid;}
|
||||
inline bool is_album_peak_present() const {return m_album_peak != peak_invalid;}
|
||||
inline bool is_track_peak_present() const {return m_track_peak != peak_invalid;}
|
||||
|
||||
inline void remove_album_gain() {m_album_gain = gain_invalid;}
|
||||
inline void remove_track_gain() {m_track_gain = gain_invalid;}
|
||||
inline void remove_album_peak() {m_album_peak = peak_invalid;}
|
||||
inline void remove_track_peak() {m_track_peak = peak_invalid;}
|
||||
|
||||
t_size get_value_count();
|
||||
|
||||
static replaygain_info g_merge(replaygain_info r1,replaygain_info r2);
|
||||
|
||||
static bool g_equal(const replaygain_info & item1,const replaygain_info & item2);
|
||||
|
||||
void reset();
|
||||
};
|
||||
|
||||
inline bool operator==(const replaygain_info & item1,const replaygain_info & item2) {return replaygain_info::g_equal(item1,item2);}
|
||||
inline bool operator!=(const replaygain_info & item1,const replaygain_info & item2) {return !replaygain_info::g_equal(item1,item2);}
|
||||
|
||||
static const replaygain_info replaygain_info_invalid = {replaygain_info::gain_invalid,replaygain_info::gain_invalid,replaygain_info::peak_invalid,replaygain_info::peak_invalid};
|
||||
|
||||
|
||||
//! Main interface class for information about some playable object.
|
||||
class NOVTABLE file_info {
|
||||
public:
|
||||
//! Retrieves length, in seconds.
|
||||
virtual double get_length() const = 0;
|
||||
//! Sets length, in seconds.
|
||||
virtual void set_length(double p_length) = 0;
|
||||
|
||||
//! Sets ReplayGain information.
|
||||
virtual void set_replaygain(const replaygain_info & p_info) = 0;
|
||||
//! Retrieves ReplayGain information.
|
||||
virtual replaygain_info get_replaygain() const = 0;
|
||||
|
||||
//! Retrieves count of metadata entries.
|
||||
virtual t_size meta_get_count() const = 0;
|
||||
//! Retrieves the name of metadata entry of specified index. Return value is a null-terminated UTF-8 encoded string.
|
||||
virtual const char* meta_enum_name(t_size p_index) const = 0;
|
||||
//! Retrieves count of values in metadata entry of specified index. The value is always equal to or greater than 1.
|
||||
virtual t_size meta_enum_value_count(t_size p_index) const = 0;
|
||||
//! Retrieves specified value from specified metadata entry. Return value is a null-terminated UTF-8 encoded string.
|
||||
virtual const char* meta_enum_value(t_size p_index,t_size p_value_number) const = 0;
|
||||
//! Finds index of metadata entry of specified name. Returns infinite when not found.
|
||||
virtual t_size meta_find_ex(const char * p_name,t_size p_name_length) const;
|
||||
//! Creates a new metadata entry of specified name with specified value. If an entry of same name already exists, it is erased. Return value is the index of newly created metadata entry.
|
||||
virtual t_size meta_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) = 0;
|
||||
//! Inserts a new value into specified metadata entry.
|
||||
virtual void meta_insert_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length) = 0;
|
||||
//! Removes metadata entries according to specified bit mask.
|
||||
virtual void meta_remove_mask(const bit_array & p_mask) = 0;
|
||||
//! Reorders metadata entries according to specified permutation.
|
||||
virtual void meta_reorder(const t_size * p_order) = 0;
|
||||
//! Removes values according to specified bit mask from specified metadata entry. If all values are removed, entire metadata entry is removed as well.
|
||||
virtual void meta_remove_values(t_size p_index,const bit_array & p_mask) = 0;
|
||||
//! Alters specified value in specified metadata entry.
|
||||
virtual void meta_modify_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length) = 0;
|
||||
|
||||
//! Retrieves number of technical info entries.
|
||||
virtual t_size info_get_count() const = 0;
|
||||
//! Retrieves the name of specified technical info entry. Return value is a null-terminated UTF-8 encoded string.
|
||||
virtual const char* info_enum_name(t_size p_index) const = 0;
|
||||
//! Retrieves the value of specified technical info entry. Return value is a null-terminated UTF-8 encoded string.
|
||||
virtual const char* info_enum_value(t_size p_index) const = 0;
|
||||
//! Creates a new technical info entry with specified name and specified value. If an entry of the same name already exists, it is erased. Return value is the index of newly created entry.
|
||||
virtual t_size info_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) = 0;
|
||||
//! Removes technical info entries indicated by specified bit mask.
|
||||
virtual void info_remove_mask(const bit_array & p_mask) = 0;
|
||||
//! Finds technical info entry of specified name. Returns index of found entry on success, infinite on failure.
|
||||
virtual t_size info_find_ex(const char * p_name,t_size p_name_length) const;
|
||||
|
||||
//! Copies entire file_info contents from specified file_info object.
|
||||
virtual void copy(const file_info & p_source);//virtualized for performance reasons, can be faster in two-pass
|
||||
//! Copies metadata from specified file_info object.
|
||||
virtual void copy_meta(const file_info & p_source);//virtualized for performance reasons, can be faster in two-pass
|
||||
//! Copies technical info from specified file_info object.
|
||||
virtual void copy_info(const file_info & p_source);//virtualized for performance reasons, can be faster in two-pass
|
||||
|
||||
bool meta_exists_ex(const char * p_name,t_size p_name_length) const;
|
||||
void meta_remove_field_ex(const char * p_name,t_size p_name_length);
|
||||
void meta_remove_index(t_size p_index);
|
||||
void meta_remove_all();
|
||||
void meta_remove_value(t_size p_index,t_size p_value);
|
||||
const char * meta_get_ex(const char * p_name,t_size p_name_length,t_size p_index) const;
|
||||
t_size meta_get_count_by_name_ex(const char * p_name,t_size p_name_length) const;
|
||||
void meta_add_value_ex(t_size p_index,const char * p_value,t_size p_value_length);
|
||||
t_size meta_add_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length);
|
||||
t_size meta_calc_total_value_count() const;
|
||||
bool meta_format(const char * p_name,pfc::string_base & p_out, const char * separator = ", ") const;
|
||||
void meta_format_entry(t_size index, pfc::string_base & p_out, const char * separator = ", ") const;//same as meta_format but takes index instead of meta name.
|
||||
|
||||
|
||||
bool info_exists_ex(const char * p_name,t_size p_name_length) const;
|
||||
void info_remove_index(t_size p_index);
|
||||
void info_remove_all();
|
||||
bool info_remove_ex(const char * p_name,t_size p_name_length);
|
||||
const char * info_get_ex(const char * p_name,t_size p_name_length) const;
|
||||
|
||||
inline t_size meta_find(const char * p_name) const {return meta_find_ex(p_name,infinite);}
|
||||
inline bool meta_exists(const char * p_name) const {return meta_exists_ex(p_name,infinite);}
|
||||
inline void meta_remove_field(const char * p_name) {meta_remove_field_ex(p_name,infinite);}
|
||||
inline t_size meta_set(const char * p_name,const char * p_value) {return meta_set_ex(p_name,infinite,p_value,infinite);}
|
||||
inline void meta_insert_value(t_size p_index,t_size p_value_index,const char * p_value) {meta_insert_value_ex(p_index,p_value_index,p_value,infinite);}
|
||||
inline void meta_add_value(t_size p_index,const char * p_value) {meta_add_value_ex(p_index,p_value,infinite);}
|
||||
inline const char* meta_get(const char * p_name,t_size p_index) const {return meta_get_ex(p_name,infinite,p_index);}
|
||||
inline t_size meta_get_count_by_name(const char * p_name) const {return meta_get_count_by_name_ex(p_name,infinite);}
|
||||
inline t_size meta_add(const char * p_name,const char * p_value) {return meta_add_ex(p_name,infinite,p_value,infinite);}
|
||||
inline void meta_modify_value(t_size p_index,t_size p_value_index,const char * p_value) {meta_modify_value_ex(p_index,p_value_index,p_value,infinite);}
|
||||
|
||||
|
||||
|
||||
inline t_size info_set(const char * p_name,const char * p_value) {return info_set_ex(p_name,infinite,p_value,infinite);}
|
||||
inline t_size info_find(const char * p_name) const {return info_find_ex(p_name,infinite);}
|
||||
inline t_size info_exists(const char * p_name) const {return info_exists_ex(p_name,infinite);}
|
||||
inline bool info_remove(const char * p_name) {return info_remove_ex(p_name,infinite);}
|
||||
inline const char * info_get(const char * p_name) const {return info_get_ex(p_name,infinite);}
|
||||
|
||||
bool info_set_replaygain_ex(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len);
|
||||
inline bool info_set_replaygain(const char * p_name,const char * p_value) {return info_set_replaygain_ex(p_name,infinite,p_value,infinite);}
|
||||
void info_set_replaygain_auto_ex(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len);
|
||||
inline void info_set_replaygain_auto(const char * p_name,const char * p_value) {info_set_replaygain_auto_ex(p_name,infinite,p_value,infinite);}
|
||||
|
||||
|
||||
|
||||
void copy_meta_single(const file_info & p_source,t_size p_index);
|
||||
void copy_info_single(const file_info & p_source,t_size p_index);
|
||||
void copy_meta_single_by_name_ex(const file_info & p_source,const char * p_name,t_size p_name_length);
|
||||
void copy_info_single_by_name_ex(const file_info & p_source,const char * p_name,t_size p_name_length);
|
||||
inline void copy_meta_single_by_name(const file_info & p_source,const char * p_name) {copy_meta_single_by_name_ex(p_source,p_name,infinite);}
|
||||
inline void copy_info_single_by_name(const file_info & p_source,const char * p_name) {copy_info_single_by_name_ex(p_source,p_name,infinite);}
|
||||
void reset();
|
||||
void reset_replaygain();
|
||||
void copy_meta_single_rename_ex(const file_info & p_source,t_size p_index,const char * p_new_name,t_size p_new_name_length);
|
||||
inline void copy_meta_single_rename(const file_info & p_source,t_size p_index,const char * p_new_name) {copy_meta_single_rename_ex(p_source,p_index,p_new_name,infinite);}
|
||||
void overwrite_info(const file_info & p_source);
|
||||
|
||||
t_int64 info_get_int(const char * name) const;
|
||||
t_int64 info_get_length_samples() const;
|
||||
double info_get_float(const char * name) const;
|
||||
void info_set_int(const char * name,t_int64 value);
|
||||
void info_set_float(const char * name,double value,unsigned precision,bool force_sign = false,const char * unit = 0);
|
||||
void info_set_replaygain_track_gain(float value);
|
||||
void info_set_replaygain_album_gain(float value);
|
||||
void info_set_replaygain_track_peak(float value);
|
||||
void info_set_replaygain_album_peak(float value);
|
||||
|
||||
inline t_int64 info_get_bitrate_vbr() const {return info_get_int("bitrate_dynamic");}
|
||||
inline void info_set_bitrate_vbr(t_int64 val) {info_set_int("bitrate_dynamic",val);}
|
||||
inline t_int64 info_get_bitrate() const {return info_get_int("bitrate");}
|
||||
inline void info_set_bitrate(t_int64 val) {info_set_int("bitrate",val);}
|
||||
bool is_encoding_lossy() const;
|
||||
|
||||
void info_calculate_bitrate(t_filesize p_filesize,double p_length);
|
||||
|
||||
unsigned info_get_decoded_bps() const;//what bps the stream originally was (before converting to audio_sample), 0 if unknown
|
||||
|
||||
void merge(const pfc::list_base_const_t<const file_info*> & p_sources);
|
||||
|
||||
bool are_meta_fields_identical(t_size p_index1,t_size p_index2) const;
|
||||
|
||||
inline const file_info & operator=(const file_info & p_source) {copy(p_source);return *this;}
|
||||
|
||||
static bool g_is_meta_equal(const file_info & p_item1,const file_info & p_item2);
|
||||
static bool g_is_info_equal(const file_info & p_item1,const file_info & p_item2);
|
||||
|
||||
//! Unsafe - does not check whether the field already exists and will result in duplicates if it does - call only when appropriate checks have been applied externally.
|
||||
t_size __meta_add_unsafe_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) {return meta_set_nocheck_ex(p_name,p_name_length,p_value,p_value_length);}
|
||||
//! Unsafe - does not check whether the field already exists and will result in duplicates if it does - call only when appropriate checks have been applied externally.
|
||||
t_size __meta_add_unsafe(const char * p_name,const char * p_value) {return meta_set_nocheck_ex(p_name,infinite,p_value,infinite);}
|
||||
|
||||
//! Unsafe - does not check whether the field already exists and will result in duplicates if it does - call only when appropriate checks have been applied externally.
|
||||
t_size __info_add_unsafe_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) {return info_set_nocheck_ex(p_name,p_name_length,p_value,p_value_length);}
|
||||
//! Unsafe - does not check whether the field already exists and will result in duplicates if it does - call only when appropriate checks have been applied externally.
|
||||
t_size __info_add_unsafe(const char * p_name,const char * p_value) {return info_set_nocheck_ex(p_name,infinite,p_value,infinite);}
|
||||
|
||||
static bool g_is_valid_field_name(const char * p_name,t_size p_length = infinite);
|
||||
//typedef pfc::comparator_stricmp_ascii field_name_comparator;
|
||||
typedef pfc::string::comparatorCaseInsensitiveASCII field_name_comparator;
|
||||
protected:
|
||||
file_info() {}
|
||||
~file_info() {}
|
||||
void copy_meta_single_nocheck(const file_info & p_source,t_size p_index);
|
||||
void copy_info_single_nocheck(const file_info & p_source,t_size p_index);
|
||||
void copy_meta_single_by_name_nocheck_ex(const file_info & p_source,const char * p_name,t_size p_name_length);
|
||||
void copy_info_single_by_name_nocheck_ex(const file_info & p_source,const char * p_name,t_size p_name_length);
|
||||
inline void copy_meta_single_by_name_nocheck(const file_info & p_source,const char * p_name) {copy_meta_single_by_name_nocheck_ex(p_source,p_name,infinite);}
|
||||
inline void copy_info_single_by_name_nocheck(const file_info & p_source,const char * p_name) {copy_info_single_by_name_nocheck_ex(p_source,p_name,infinite);}
|
||||
|
||||
virtual t_size meta_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) = 0;
|
||||
virtual t_size info_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) = 0;
|
||||
inline t_size meta_set_nocheck(const char * p_name,const char * p_value) {return meta_set_nocheck_ex(p_name,infinite,p_value,infinite);}
|
||||
inline t_size info_set_nocheck(const char * p_name,const char * p_value) {return info_set_nocheck_ex(p_name,infinite,p_value,infinite);}
|
||||
};
|
||||
|
||||
|
||||
#endif //_FILE_INFO_H_
|
|
@ -0,0 +1,243 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
|
||||
t_size file_info_impl::meta_get_count() const
|
||||
{
|
||||
return m_meta.get_count();
|
||||
}
|
||||
|
||||
const char* file_info_impl::meta_enum_name(t_size p_index) const
|
||||
{
|
||||
return m_meta.get_name(p_index);
|
||||
}
|
||||
|
||||
t_size file_info_impl::meta_enum_value_count(t_size p_index) const
|
||||
{
|
||||
return m_meta.get_value_count(p_index);
|
||||
}
|
||||
|
||||
const char* file_info_impl::meta_enum_value(t_size p_index,t_size p_value_number) const
|
||||
{
|
||||
return m_meta.get_value(p_index,p_value_number);
|
||||
}
|
||||
|
||||
t_size file_info_impl::meta_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length)
|
||||
{
|
||||
meta_remove_field_ex(p_name,p_name_length);
|
||||
return meta_set_nocheck_ex(p_name,p_name_length,p_value,p_value_length);
|
||||
}
|
||||
|
||||
t_size file_info_impl::meta_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length)
|
||||
{
|
||||
return m_meta.add_entry(p_name,p_name_length,p_value,p_value_length);
|
||||
}
|
||||
|
||||
void file_info_impl::meta_insert_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length)
|
||||
{
|
||||
m_meta.insert_value(p_index,p_value_index,p_value,p_value_length);
|
||||
}
|
||||
|
||||
void file_info_impl::meta_remove_mask(const bit_array & p_mask)
|
||||
{
|
||||
m_meta.remove_mask(p_mask);
|
||||
}
|
||||
|
||||
void file_info_impl::meta_reorder(const t_size * p_order)
|
||||
{
|
||||
m_meta.reorder(p_order);
|
||||
}
|
||||
|
||||
void file_info_impl::meta_remove_values(t_size p_index,const bit_array & p_mask)
|
||||
{
|
||||
m_meta.remove_values(p_index,p_mask);
|
||||
if (m_meta.get_value_count(p_index) == 0)
|
||||
m_meta.remove_mask(bit_array_one(p_index));
|
||||
}
|
||||
|
||||
t_size file_info_impl::info_get_count() const
|
||||
{
|
||||
return m_info.get_count();
|
||||
}
|
||||
|
||||
const char* file_info_impl::info_enum_name(t_size p_index) const
|
||||
{
|
||||
return m_info.get_name(p_index);
|
||||
}
|
||||
|
||||
const char* file_info_impl::info_enum_value(t_size p_index) const
|
||||
{
|
||||
return m_info.get_value(p_index);
|
||||
}
|
||||
|
||||
t_size file_info_impl::info_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length)
|
||||
{
|
||||
info_remove_ex(p_name,p_name_length);
|
||||
return info_set_nocheck_ex(p_name,p_name_length,p_value,p_value_length);
|
||||
}
|
||||
|
||||
t_size file_info_impl::info_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length)
|
||||
{
|
||||
return m_info.add_item(p_name,p_name_length,p_value,p_value_length);
|
||||
}
|
||||
|
||||
void file_info_impl::info_remove_mask(const bit_array & p_mask)
|
||||
{
|
||||
m_info.remove_mask(p_mask);
|
||||
}
|
||||
|
||||
|
||||
file_info_impl::file_info_impl(const file_info & p_source) : m_length(0)
|
||||
{
|
||||
copy(p_source);
|
||||
}
|
||||
|
||||
file_info_impl::file_info_impl(const file_info_impl & p_source) : m_length(0)
|
||||
{
|
||||
copy(p_source);
|
||||
}
|
||||
|
||||
const file_info_impl & file_info_impl::operator=(const file_info_impl & p_source)
|
||||
{
|
||||
copy(p_source);
|
||||
return *this;
|
||||
}
|
||||
|
||||
file_info_impl::file_info_impl() : m_length(0)
|
||||
{
|
||||
m_replaygain.reset();
|
||||
}
|
||||
|
||||
double file_info_impl::get_length() const
|
||||
{
|
||||
return m_length;
|
||||
}
|
||||
|
||||
void file_info_impl::set_length(double p_length)
|
||||
{
|
||||
m_length = p_length;
|
||||
}
|
||||
|
||||
void file_info_impl::meta_modify_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length)
|
||||
{
|
||||
m_meta.modify_value(p_index,p_value_index,p_value,p_value_length);
|
||||
}
|
||||
|
||||
replaygain_info file_info_impl::get_replaygain() const
|
||||
{
|
||||
return m_replaygain;
|
||||
}
|
||||
|
||||
void file_info_impl::set_replaygain(const replaygain_info & p_info)
|
||||
{
|
||||
m_replaygain = p_info;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
file_info_impl::~file_info_impl()
|
||||
{
|
||||
}
|
||||
|
||||
t_size file_info_impl_utils::info_storage::add_item(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length) {
|
||||
t_size index = m_info.get_size();
|
||||
m_info.set_size(index + 1);
|
||||
m_info[index].init(p_name,p_name_length,p_value,p_value_length);
|
||||
return index;
|
||||
}
|
||||
|
||||
void file_info_impl_utils::info_storage::remove_mask(const bit_array & p_mask) {
|
||||
pfc::remove_mask_t(m_info,p_mask);
|
||||
}
|
||||
|
||||
|
||||
|
||||
t_size file_info_impl_utils::meta_storage::add_entry(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length)
|
||||
{
|
||||
meta_entry temp(p_name,p_name_length,p_value,p_value_length);
|
||||
return pfc::append_swap_t(m_data,temp);
|
||||
}
|
||||
|
||||
void file_info_impl_utils::meta_storage::insert_value(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length)
|
||||
{
|
||||
m_data[p_index].insert_value(p_value_index,p_value,p_value_length);
|
||||
}
|
||||
|
||||
void file_info_impl_utils::meta_storage::modify_value(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length)
|
||||
{
|
||||
m_data[p_index].modify_value(p_value_index,p_value,p_value_length);
|
||||
}
|
||||
|
||||
void file_info_impl_utils::meta_storage::remove_values(t_size p_index,const bit_array & p_mask)
|
||||
{
|
||||
m_data[p_index].remove_values(p_mask);
|
||||
}
|
||||
|
||||
void file_info_impl_utils::meta_storage::remove_mask(const bit_array & p_mask)
|
||||
{
|
||||
pfc::remove_mask_t(m_data,p_mask);
|
||||
}
|
||||
|
||||
|
||||
file_info_impl_utils::meta_entry::meta_entry(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len)
|
||||
{
|
||||
m_name.set_string(p_name,p_name_len);
|
||||
m_values.set_size(1);
|
||||
m_values[0].set_string(p_value,p_value_len);
|
||||
}
|
||||
|
||||
|
||||
void file_info_impl_utils::meta_entry::remove_values(const bit_array & p_mask)
|
||||
{
|
||||
pfc::remove_mask_t(m_values,p_mask);
|
||||
}
|
||||
|
||||
void file_info_impl_utils::meta_entry::insert_value(t_size p_value_index,const char * p_value,t_size p_value_length)
|
||||
{
|
||||
pfc::string_simple temp;
|
||||
temp.set_string(p_value,p_value_length);
|
||||
pfc::insert_swap_t(m_values,temp,p_value_index);
|
||||
}
|
||||
|
||||
void file_info_impl_utils::meta_entry::modify_value(t_size p_value_index,const char * p_value,t_size p_value_length)
|
||||
{
|
||||
m_values[p_value_index].set_string(p_value,p_value_length);
|
||||
}
|
||||
|
||||
void file_info_impl_utils::meta_storage::reorder(const t_size * p_order)
|
||||
{
|
||||
pfc::reorder_t(m_data,p_order,m_data.get_size());
|
||||
}
|
||||
|
||||
void file_info_impl::copy_meta(const file_info & p_source)
|
||||
{
|
||||
m_meta.copy_from(p_source);
|
||||
}
|
||||
|
||||
void file_info_impl::copy_info(const file_info & p_source)
|
||||
{
|
||||
m_info.copy_from(p_source);
|
||||
}
|
||||
|
||||
void file_info_impl_utils::meta_storage::copy_from(const file_info & p_info)
|
||||
{
|
||||
t_size meta_index,meta_count = p_info.meta_get_count();
|
||||
m_data.set_size(meta_count);
|
||||
for(meta_index=0;meta_index<meta_count;meta_index++)
|
||||
{
|
||||
meta_entry & entry = m_data[meta_index];
|
||||
t_size value_index,value_count = p_info.meta_enum_value_count(meta_index);
|
||||
entry.m_name = p_info.meta_enum_name(meta_index);
|
||||
entry.m_values.set_size(value_count);
|
||||
for(value_index=0;value_index<value_count;value_index++)
|
||||
entry.m_values[value_index] = p_info.meta_enum_value(meta_index,value_index);
|
||||
}
|
||||
}
|
||||
|
||||
void file_info_impl_utils::info_storage::copy_from(const file_info & p_info)
|
||||
{
|
||||
t_size n, count;
|
||||
count = p_info.info_get_count();
|
||||
m_info.set_count(count);
|
||||
for(n=0;n<count;n++) m_info[n].init(p_info.info_enum_name(n),infinite,p_info.info_enum_value(n),infinite);
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
#ifndef _FOOBAR2000_SDK_FILE_INFO_IMPL_H_
|
||||
#define _FOOBAR2000_SDK_FILE_INFO_IMPL_H_
|
||||
|
||||
namespace file_info_impl_utils {
|
||||
|
||||
struct info_entry {
|
||||
void init(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len) {
|
||||
m_name.set_string(p_name,p_name_len);
|
||||
m_value.set_string(p_value,p_value_len);
|
||||
}
|
||||
|
||||
inline const char * get_name() const {return m_name;}
|
||||
inline const char * get_value() const {return m_value;}
|
||||
|
||||
pfc::string_simple m_name,m_value;
|
||||
};
|
||||
|
||||
typedef pfc::array_t<info_entry,pfc::alloc_fast> info_entry_array;
|
||||
|
||||
}
|
||||
|
||||
namespace pfc {
|
||||
template<> class traits_t<file_info_impl_utils::info_entry> : public traits_t<pfc::string_simple> {};
|
||||
};
|
||||
|
||||
|
||||
namespace file_info_impl_utils {
|
||||
class info_storage
|
||||
{
|
||||
public:
|
||||
t_size add_item(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length);
|
||||
void remove_mask(const bit_array & p_mask);
|
||||
inline t_size get_count() const {return m_info.get_count();}
|
||||
inline const char * get_name(t_size p_index) const {return m_info[p_index].get_name();}
|
||||
inline const char * get_value(t_size p_index) const {return m_info[p_index].get_value();}
|
||||
void copy_from(const file_info & p_info);
|
||||
private:
|
||||
info_entry_array m_info;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
namespace file_info_impl_utils {
|
||||
typedef pfc::array_hybrid_t<pfc::string_simple,1,pfc::alloc_fast > meta_value_array;
|
||||
struct meta_entry {
|
||||
meta_entry() {}
|
||||
meta_entry(const char * p_name,t_size p_name_len,const char * p_value,t_size p_value_len);
|
||||
|
||||
void remove_values(const bit_array & p_mask);
|
||||
void insert_value(t_size p_value_index,const char * p_value,t_size p_value_length);
|
||||
void modify_value(t_size p_value_index,const char * p_value,t_size p_value_length);
|
||||
|
||||
inline const char * get_name() const {return m_name;}
|
||||
inline const char * get_value(t_size p_index) const {return m_values[p_index];}
|
||||
inline t_size get_value_count() const {return m_values.get_size();}
|
||||
|
||||
|
||||
pfc::string_simple m_name;
|
||||
meta_value_array m_values;
|
||||
};
|
||||
typedef pfc::array_hybrid_t<meta_entry,10, pfc::alloc_fast> meta_entry_array;
|
||||
}
|
||||
namespace pfc {
|
||||
template<> class traits_t<file_info_impl_utils::meta_entry> : public pfc::traits_combined<pfc::string_simple,file_info_impl_utils::meta_value_array> {};
|
||||
}
|
||||
|
||||
|
||||
namespace file_info_impl_utils {
|
||||
class meta_storage
|
||||
{
|
||||
public:
|
||||
t_size add_entry(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length);
|
||||
void insert_value(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length);
|
||||
void modify_value(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length);
|
||||
void remove_values(t_size p_index,const bit_array & p_mask);
|
||||
void remove_mask(const bit_array & p_mask);
|
||||
void copy_from(const file_info & p_info);
|
||||
|
||||
inline void reorder(const t_size * p_order);
|
||||
|
||||
inline t_size get_count() const {return m_data.get_size();}
|
||||
|
||||
inline const char * get_name(t_size p_index) const {assert(p_index < m_data.get_size()); return m_data[p_index].get_name();}
|
||||
inline const char * get_value(t_size p_index,t_size p_value_index) const {assert(p_index < m_data.get_size()); return m_data[p_index].get_value(p_value_index);}
|
||||
inline t_size get_value_count(t_size p_index) const {assert(p_index < m_data.get_size()); return m_data[p_index].get_value_count();}
|
||||
|
||||
private:
|
||||
meta_entry_array m_data;
|
||||
};
|
||||
}
|
||||
|
||||
//! Implements file_info.
|
||||
class file_info_impl : public file_info
|
||||
{
|
||||
public:
|
||||
file_info_impl(const file_info_impl & p_source);
|
||||
file_info_impl(const file_info & p_source);
|
||||
file_info_impl();
|
||||
~file_info_impl();
|
||||
|
||||
double get_length() const;
|
||||
void set_length(double p_length);
|
||||
|
||||
void copy_meta(const file_info & p_source);//virtualized for performance reasons, can be faster in two-pass
|
||||
void copy_info(const file_info & p_source);//virtualized for performance reasons, can be faster in two-pass
|
||||
|
||||
t_size meta_get_count() const;
|
||||
const char* meta_enum_name(t_size p_index) const;
|
||||
t_size meta_enum_value_count(t_size p_index) const;
|
||||
const char* meta_enum_value(t_size p_index,t_size p_value_number) const;
|
||||
t_size meta_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length);
|
||||
void meta_insert_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length);
|
||||
void meta_remove_mask(const bit_array & p_mask);
|
||||
void meta_reorder(const t_size * p_order);
|
||||
void meta_remove_values(t_size p_index,const bit_array & p_mask);
|
||||
void meta_modify_value_ex(t_size p_index,t_size p_value_index,const char * p_value,t_size p_value_length);
|
||||
|
||||
t_size info_get_count() const;
|
||||
const char* info_enum_name(t_size p_index) const;
|
||||
const char* info_enum_value(t_size p_index) const;
|
||||
t_size info_set_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length);
|
||||
void info_remove_mask(const bit_array & p_mask);
|
||||
|
||||
const file_info_impl & operator=(const file_info_impl & p_source);
|
||||
|
||||
replaygain_info get_replaygain() const;
|
||||
void set_replaygain(const replaygain_info & p_info);
|
||||
|
||||
protected:
|
||||
t_size meta_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length);
|
||||
t_size info_set_nocheck_ex(const char * p_name,t_size p_name_length,const char * p_value,t_size p_value_length);
|
||||
private:
|
||||
|
||||
|
||||
file_info_impl_utils::meta_storage m_meta;
|
||||
file_info_impl_utils::info_storage m_info;
|
||||
|
||||
|
||||
double m_length;
|
||||
|
||||
replaygain_info m_replaygain;
|
||||
};
|
||||
|
||||
#endif
|
|
@ -0,0 +1,130 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
static t_size merge_tags_calc_rating_by_index(const file_info & p_info,t_size p_index) {
|
||||
t_size n,m = p_info.meta_enum_value_count(p_index);
|
||||
t_size ret = 0;
|
||||
for(n=0;n<m;n++)
|
||||
ret += strlen(p_info.meta_enum_value(p_index,n)) + 10;//yes, strlen on utf8 data, plus a slight bump to prefer multivalue over singlevalue w/ separator
|
||||
return ret;
|
||||
}
|
||||
|
||||
static t_size merge_tags_calc_rating(const file_info & p_info,const char * p_field) {
|
||||
t_size field_index = p_info.meta_find(p_field);
|
||||
t_size ret = 0;
|
||||
if (field_index != infinite) {
|
||||
return merge_tags_calc_rating_by_index(p_info,field_index);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
static void merge_tags_copy_info(const char * field,const file_info * from,file_info * to)
|
||||
{
|
||||
const char * val = from->info_get(field);
|
||||
if (val) to->info_set(field,val);
|
||||
}
|
||||
|
||||
namespace {
|
||||
struct meta_merge_entry {
|
||||
meta_merge_entry() : m_rating(0) {}
|
||||
t_size m_rating;
|
||||
pfc::array_t<const char *> m_data;
|
||||
};
|
||||
|
||||
class meta_merge_map_enumerator {
|
||||
public:
|
||||
meta_merge_map_enumerator(file_info & p_out) : m_out(p_out) {
|
||||
m_out.meta_remove_all();
|
||||
}
|
||||
void operator() (const char * p_name, const meta_merge_entry & p_entry) {
|
||||
if (p_entry.m_data.get_size() > 0) {
|
||||
t_size index = m_out.__meta_add_unsafe(p_name,p_entry.m_data[0]);
|
||||
for(t_size walk = 1; walk < p_entry.m_data.get_size(); ++walk) {
|
||||
m_out.meta_add_value(index,p_entry.m_data[walk]);
|
||||
}
|
||||
}
|
||||
}
|
||||
private:
|
||||
file_info & m_out;
|
||||
};
|
||||
}
|
||||
|
||||
static void merge_meta(file_info & p_out,const pfc::list_base_const_t<const file_info*> & p_in) {
|
||||
pfc::map_t<const char *,meta_merge_entry,pfc::comparator_stricmp_ascii> map;
|
||||
for(t_size in_walk = 0; in_walk < p_in.get_count(); in_walk++) {
|
||||
const file_info & in = * p_in[in_walk];
|
||||
for(t_size meta_walk = 0, meta_count = in.meta_get_count(); meta_walk < meta_count; meta_walk++ ) {
|
||||
meta_merge_entry & entry = map.find_or_add(in.meta_enum_name(meta_walk));
|
||||
t_size rating = merge_tags_calc_rating_by_index(in,meta_walk);
|
||||
if (rating > entry.m_rating) {
|
||||
entry.m_rating = rating;
|
||||
const t_size value_count = in.meta_enum_value_count(meta_walk);
|
||||
entry.m_data.set_size(value_count);
|
||||
for(t_size value_walk = 0; value_walk < value_count; value_walk++ ) {
|
||||
entry.m_data[value_walk] = in.meta_enum_value(meta_walk,value_walk);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
map.enumerate(meta_merge_map_enumerator(p_out));
|
||||
}
|
||||
|
||||
void file_info::merge(const pfc::list_base_const_t<const file_info*> & p_in)
|
||||
{
|
||||
t_size in_count = p_in.get_count();
|
||||
if (in_count == 0)
|
||||
{
|
||||
meta_remove_all();
|
||||
return;
|
||||
}
|
||||
else if (in_count == 1)
|
||||
{
|
||||
const file_info * info = p_in[0];
|
||||
|
||||
copy_meta(*info);
|
||||
|
||||
set_replaygain(replaygain_info::g_merge(get_replaygain(),info->get_replaygain()));
|
||||
|
||||
overwrite_info(*info);
|
||||
|
||||
//copy_info_single_by_name(*info,"tagtype");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
merge_meta(*this,p_in);
|
||||
|
||||
{
|
||||
pfc::string8_fastalloc tagtype;
|
||||
replaygain_info rg = get_replaygain();
|
||||
t_size in_ptr;
|
||||
for(in_ptr = 0; in_ptr < in_count; in_ptr++ )
|
||||
{
|
||||
const file_info * info = p_in[in_ptr];
|
||||
rg = replaygain_info::g_merge(rg, info->get_replaygain());
|
||||
t_size field_ptr, field_max = info->info_get_count();
|
||||
for(field_ptr = 0; field_ptr < field_max; field_ptr++ )
|
||||
{
|
||||
const char * field_name = info->info_enum_name(field_ptr), * field_value = info->info_enum_value(field_ptr);
|
||||
if (*field_value)
|
||||
{
|
||||
if (!stricmp_utf8(field_name,"tagtype"))
|
||||
{
|
||||
if (!tagtype.is_empty()) tagtype += "|";
|
||||
tagtype += field_value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!tagtype.is_empty()) info_set("tagtype",tagtype);
|
||||
set_replaygain(rg);
|
||||
}
|
||||
}
|
||||
|
||||
void file_info::overwrite_info(const file_info & p_source) {
|
||||
t_size count = p_source.info_get_count();
|
||||
for(t_size n=0;n<count;n++) {
|
||||
info_set(p_source.info_enum_name(n),p_source.info_enum_value(n));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,144 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
|
||||
static void g_on_files_deleted_sorted(const pfc::list_base_const_t<const char *> & p_items)
|
||||
{
|
||||
static_api_ptr_t<library_manager>()->on_files_deleted_sorted(p_items);
|
||||
static_api_ptr_t<playlist_manager>()->on_files_deleted_sorted(p_items);
|
||||
|
||||
service_ptr_t<file_operation_callback> ptr;
|
||||
service_enum_t<file_operation_callback> e;
|
||||
while(e.next(ptr))
|
||||
{
|
||||
ptr->on_files_deleted_sorted(p_items);
|
||||
}
|
||||
}
|
||||
|
||||
static void g_on_files_moved_sorted(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to)
|
||||
{
|
||||
static_api_ptr_t<playlist_manager>()->on_files_moved_sorted(p_from,p_to);
|
||||
static_api_ptr_t<playlist_manager>()->on_files_deleted_sorted(p_from);
|
||||
|
||||
service_ptr_t<file_operation_callback> ptr;
|
||||
service_enum_t<file_operation_callback> e;
|
||||
while(e.next(ptr))
|
||||
{
|
||||
ptr->on_files_moved_sorted(p_from,p_to);
|
||||
}
|
||||
}
|
||||
|
||||
static void g_on_files_copied_sorted(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to)
|
||||
{
|
||||
service_ptr_t<file_operation_callback> ptr;
|
||||
service_enum_t<file_operation_callback> e;
|
||||
while(e.next(ptr))
|
||||
{
|
||||
ptr->on_files_copied_sorted(p_from,p_to);
|
||||
}
|
||||
}
|
||||
|
||||
void file_operation_callback::g_on_files_deleted(const pfc::list_base_const_t<const char *> & p_items)
|
||||
{
|
||||
core_api::ensure_main_thread();
|
||||
t_size count = p_items.get_count();
|
||||
if (count > 0)
|
||||
{
|
||||
if (count == 1) g_on_files_deleted_sorted(p_items);
|
||||
else
|
||||
{
|
||||
pfc::array_t<t_size> order; order.set_size(count);
|
||||
order_helper::g_fill(order);
|
||||
p_items.sort_get_permutation_t(metadb::path_compare,order.get_ptr());
|
||||
g_on_files_deleted_sorted(pfc::list_permutation_t<const char*>(p_items,order.get_ptr(),count));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void file_operation_callback::g_on_files_moved(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to)
|
||||
{
|
||||
core_api::ensure_main_thread();
|
||||
pfc::dynamic_assert(p_from.get_count() == p_to.get_count());
|
||||
t_size count = p_from.get_count();
|
||||
if (count > 0)
|
||||
{
|
||||
if (count == 1) g_on_files_moved_sorted(p_from,p_to);
|
||||
else
|
||||
{
|
||||
pfc::array_t<t_size> order; order.set_size(count);
|
||||
order_helper::g_fill(order);
|
||||
p_from.sort_get_permutation_t(metadb::path_compare,order.get_ptr());
|
||||
g_on_files_moved_sorted(pfc::list_permutation_t<const char*>(p_from,order.get_ptr(),count),pfc::list_permutation_t<const char*>(p_to,order.get_ptr(),count));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void file_operation_callback::g_on_files_copied(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to)
|
||||
{
|
||||
if (core_api::assert_main_thread())
|
||||
{
|
||||
assert(p_from.get_count() == p_to.get_count());
|
||||
t_size count = p_from.get_count();
|
||||
if (count > 0)
|
||||
{
|
||||
if (count == 1) g_on_files_copied_sorted(p_from,p_to);
|
||||
else
|
||||
{
|
||||
pfc::array_t<t_size> order; order.set_size(count);
|
||||
order_helper::g_fill(order);
|
||||
p_from.sort_get_permutation_t(metadb::path_compare,order.get_ptr());
|
||||
g_on_files_copied_sorted(pfc::list_permutation_t<const char*>(p_from,order.get_ptr(),count),pfc::list_permutation_t<const char*>(p_to,order.get_ptr(),count));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
bool file_operation_callback::g_search_sorted_list(const pfc::list_base_const_t<const char*> & p_list,const char * p_string,t_size & p_index) {
|
||||
return pfc::binarySearch<metadb::path_comparator>::run(p_list,0,p_list.get_count(),p_string,p_index);
|
||||
}
|
||||
|
||||
bool file_operation_callback::g_update_list_on_moved_ex(metadb_handle_list_ref p_list,t_pathlist p_from,t_pathlist p_to, metadb_handle_list_ref itemsAdded, metadb_handle_list_ref itemsRemoved) {
|
||||
static_api_ptr_t<metadb> api;
|
||||
bool changed = false;
|
||||
itemsAdded.remove_all(); itemsRemoved.remove_all();
|
||||
for(t_size walk = 0; walk < p_list.get_count(); ++walk) {
|
||||
metadb_handle_ptr item = p_list[walk];
|
||||
t_size index;
|
||||
if (g_search_sorted_list(p_from,item->get_path(),index)) {
|
||||
metadb_handle_ptr newItem;
|
||||
api->handle_create_replace_path_canonical(newItem,item,p_to[index]);
|
||||
p_list.replace_item(walk,newItem);
|
||||
changed = true;
|
||||
itemsAdded.add_item(newItem); itemsRemoved.add_item(item);
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
bool file_operation_callback::g_update_list_on_moved(metadb_handle_list_ref p_list,const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to) {
|
||||
static_api_ptr_t<metadb> api;
|
||||
bool changed = false;
|
||||
for(t_size walk = 0; walk < p_list.get_count(); ++walk) {
|
||||
metadb_handle_ptr item = p_list[walk];
|
||||
t_size index;
|
||||
if (g_search_sorted_list(p_from,item->get_path(),index)) {
|
||||
metadb_handle_ptr newItem;
|
||||
api->handle_create_replace_path_canonical(newItem,item,p_to[index]);
|
||||
p_list.replace_item(walk,newItem);
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
return changed;
|
||||
}
|
||||
|
||||
|
||||
bool file_operation_callback::g_mark_dead_entries(metadb_handle_list_cref items, bit_array_var & mask, t_pathlist deadPaths) {
|
||||
bool found = false;
|
||||
const t_size total = items.get_count();
|
||||
for(t_size walk = 0; walk < total; ++walk) {
|
||||
t_size index;
|
||||
if (g_search_sorted_list(deadPaths,items[walk]->get_path(),index)) {
|
||||
mask.set(walk,true); found = true;
|
||||
} else {
|
||||
mask.set(walk,false);
|
||||
}
|
||||
}
|
||||
return found;
|
||||
}
|
|
@ -0,0 +1,66 @@
|
|||
#ifndef _FILE_OPERATION_CALLBACK_H_
|
||||
#define _FILE_OPERATION_CALLBACK_H_
|
||||
|
||||
//! Interface to notify component system about files being deleted or moved. Operates in app's main thread only.
|
||||
|
||||
class NOVTABLE file_operation_callback : public service_base {
|
||||
public:
|
||||
typedef const pfc::list_base_const_t<const char *> & t_pathlist;
|
||||
//! p_items is a metadb::path_compare sorted list of files that have been deleted.
|
||||
virtual void on_files_deleted_sorted(t_pathlist p_items) = 0;
|
||||
//! p_from is a metadb::path_compare sorted list of files that have been moved, p_to is a list of corresponding target locations.
|
||||
virtual void on_files_moved_sorted(t_pathlist p_from,t_pathlist p_to) = 0;
|
||||
//! p_from is a metadb::path_compare sorted list of files that have been copied, p_to is a list of corresponding target locations.
|
||||
virtual void on_files_copied_sorted(t_pathlist p_from,t_pathlist p_to) = 0;
|
||||
|
||||
static void g_on_files_deleted(const pfc::list_base_const_t<const char *> & p_items);
|
||||
static void g_on_files_moved(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to);
|
||||
static void g_on_files_copied(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to);
|
||||
|
||||
static bool g_search_sorted_list(const pfc::list_base_const_t<const char*> & p_list,const char * p_string,t_size & p_index);
|
||||
static bool g_update_list_on_moved(metadb_handle_list_ref p_list,t_pathlist p_from,t_pathlist p_to);
|
||||
|
||||
static bool g_update_list_on_moved_ex(metadb_handle_list_ref p_list,t_pathlist p_from,t_pathlist p_to, metadb_handle_list_ref itemsAdded, metadb_handle_list_ref itemsRemoved);
|
||||
|
||||
static bool g_mark_dead_entries(metadb_handle_list_cref items, bit_array_var & mask, t_pathlist deadPaths);
|
||||
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(file_operation_callback);
|
||||
};
|
||||
|
||||
|
||||
|
||||
//! New in 0.9.5.
|
||||
class NOVTABLE file_operation_callback_dynamic {
|
||||
public:
|
||||
//! p_items is a metadb::path_compare sorted list of files that have been deleted.
|
||||
virtual void on_files_deleted_sorted(const pfc::list_base_const_t<const char *> & p_items) = 0;
|
||||
//! p_from is a metadb::path_compare sorted list of files that have been moved, p_to is a list of corresponding target locations.
|
||||
virtual void on_files_moved_sorted(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to) = 0;
|
||||
//! p_from is a metadb::path_compare sorted list of files that have been copied, p_to is a list of corresponding target locations.
|
||||
virtual void on_files_copied_sorted(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to) = 0;
|
||||
};
|
||||
|
||||
//! New in 0.9.5.
|
||||
class NOVTABLE file_operation_callback_dynamic_manager : public service_base {
|
||||
public:
|
||||
virtual void register_callback(file_operation_callback_dynamic * p_callback) = 0;
|
||||
virtual void unregister_callback(file_operation_callback_dynamic * p_callback) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(file_operation_callback_dynamic_manager);
|
||||
};
|
||||
|
||||
//! New in 0.9.5.
|
||||
class file_operation_callback_dynamic_impl_base : public file_operation_callback_dynamic {
|
||||
public:
|
||||
file_operation_callback_dynamic_impl_base() {static_api_ptr_t<file_operation_callback_dynamic_manager>()->register_callback(this);}
|
||||
~file_operation_callback_dynamic_impl_base() {static_api_ptr_t<file_operation_callback_dynamic_manager>()->unregister_callback(this);}
|
||||
|
||||
void on_files_deleted_sorted(const pfc::list_base_const_t<const char *> & p_items) {}
|
||||
void on_files_moved_sorted(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to) {}
|
||||
void on_files_copied_sorted(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to) {}
|
||||
|
||||
PFC_CLASS_NOT_COPYABLE_EX(file_operation_callback_dynamic_impl_base);
|
||||
};
|
||||
|
||||
#endif //_FILE_OPERATION_CALLBACK_H_
|
|
@ -0,0 +1,863 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
|
||||
void unpacker::g_open(service_ptr_t<file> & p_out,const service_ptr_t<file> & p,abort_callback & p_abort)
|
||||
{
|
||||
service_enum_t<unpacker> e;
|
||||
service_ptr_t<unpacker> ptr;
|
||||
if (e.first(ptr)) do {
|
||||
p->reopen(p_abort);
|
||||
try {
|
||||
ptr->open(p_out,p,p_abort);
|
||||
return;
|
||||
} catch(exception_io_data const &) {}
|
||||
} while(e.next(ptr));
|
||||
throw exception_io_data();
|
||||
}
|
||||
|
||||
void file::seek_ex(t_sfilesize p_position, file::t_seek_mode p_mode, abort_callback &p_abort) {
|
||||
switch(p_mode) {
|
||||
case seek_from_beginning:
|
||||
seek(p_position,p_abort);
|
||||
break;
|
||||
case seek_from_current:
|
||||
seek(p_position + get_position(p_abort),p_abort);
|
||||
break;
|
||||
case seek_from_eof:
|
||||
seek(p_position + get_size_ex(p_abort),p_abort);
|
||||
break;
|
||||
default:
|
||||
throw exception_io_data();
|
||||
}
|
||||
}
|
||||
|
||||
t_filesize file::g_transfer(stream_reader * p_src,stream_writer * p_dst,t_filesize p_bytes,abort_callback & p_abort) {
|
||||
enum {BUFSIZE = 1024*1024*8};
|
||||
pfc::array_t<t_uint8> temp;
|
||||
temp.set_size((t_size)pfc::min_t<t_filesize>(BUFSIZE,p_bytes));
|
||||
void* ptr = temp.get_ptr();
|
||||
t_filesize done = 0;
|
||||
while(done<p_bytes) {
|
||||
p_abort.check_e();
|
||||
t_size delta = (t_size)pfc::min_t<t_filesize>(BUFSIZE,p_bytes-done);
|
||||
delta = p_src->read(ptr,delta,p_abort);
|
||||
if (delta<=0) break;
|
||||
p_dst->write(ptr,delta,p_abort);
|
||||
done += delta;
|
||||
}
|
||||
return done;
|
||||
}
|
||||
|
||||
void file::g_transfer_object(stream_reader * p_src,stream_writer * p_dst,t_filesize p_bytes,abort_callback & p_abort) {
|
||||
if (g_transfer(p_src,p_dst,p_bytes,p_abort) != p_bytes)
|
||||
throw exception_io_data_truncation();
|
||||
}
|
||||
|
||||
|
||||
void filesystem::g_get_canonical_path(const char * path,pfc::string_base & out)
|
||||
{
|
||||
TRACK_CALL_TEXT("filesystem::g_get_canonical_path");
|
||||
|
||||
service_enum_t<filesystem> e;
|
||||
service_ptr_t<filesystem> ptr;
|
||||
if (e.first(ptr)) do {
|
||||
if (ptr->get_canonical_path(path,out)) return;
|
||||
} while(e.next(ptr));
|
||||
//no one wants to process this, let's copy over
|
||||
out = path;
|
||||
}
|
||||
|
||||
void filesystem::g_get_display_path(const char * path,pfc::string_base & out)
|
||||
{
|
||||
TRACK_CALL_TEXT("filesystem::g_get_display_path");
|
||||
service_ptr_t<filesystem> ptr;
|
||||
if (!g_get_interface(ptr,path))
|
||||
{
|
||||
//no one wants to process this, let's copy over
|
||||
out = path;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (!ptr->get_display_path(path,out))
|
||||
out = path;
|
||||
}
|
||||
}
|
||||
|
||||
bool filesystem::g_get_interface(service_ptr_t<filesystem> & p_out,const char * path)
|
||||
{
|
||||
service_enum_t<filesystem> e;
|
||||
service_ptr_t<filesystem> ptr;
|
||||
if (e.first(ptr)) do {
|
||||
if (ptr->is_our_path(path))
|
||||
{
|
||||
p_out = ptr;
|
||||
return true;
|
||||
}
|
||||
} while(e.next(ptr));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void filesystem::g_open(service_ptr_t<file> & p_out,const char * path,t_open_mode mode,abort_callback & p_abort)
|
||||
{
|
||||
TRACK_CALL_TEXT("filesystem::g_open");
|
||||
service_ptr_t<filesystem> fs;
|
||||
if (!g_get_interface(fs,path)) throw exception_io_no_handler_for_path();
|
||||
fs->open(p_out,path,mode,p_abort);
|
||||
}
|
||||
|
||||
void filesystem::g_open_timeout(service_ptr_t<file> & p_out,const char * p_path,t_open_mode p_mode,double p_timeout,abort_callback & p_abort) {
|
||||
pfc::lores_timer timer;
|
||||
timer.start();
|
||||
for(;;) {
|
||||
try {
|
||||
g_open(p_out,p_path,p_mode,p_abort);
|
||||
break;
|
||||
} catch(exception_io_sharing_violation) {
|
||||
if (timer.query() > p_timeout) throw;
|
||||
p_abort.sleep(0.01);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool filesystem::g_exists(const char * p_path,abort_callback & p_abort)
|
||||
{
|
||||
t_filestats stats;
|
||||
bool dummy;
|
||||
try {
|
||||
g_get_stats(p_path,stats,dummy,p_abort);
|
||||
} catch(exception_io_not_found) {return false;}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool filesystem::g_exists_writeable(const char * p_path,abort_callback & p_abort)
|
||||
{
|
||||
t_filestats stats;
|
||||
bool writeable;
|
||||
try {
|
||||
g_get_stats(p_path,stats,writeable,p_abort);
|
||||
} catch(exception_io_not_found) {return false;}
|
||||
return writeable;
|
||||
}
|
||||
|
||||
void filesystem::g_remove(const char * p_path,abort_callback & p_abort) {
|
||||
service_ptr_t<filesystem> fs;
|
||||
if (!g_get_interface(fs,p_path)) throw exception_io_no_handler_for_path();
|
||||
fs->remove(p_path,p_abort);
|
||||
}
|
||||
|
||||
void filesystem::g_remove_timeout(const char * p_path,double p_timeout,abort_callback & p_abort) {
|
||||
pfc::lores_timer timer;
|
||||
timer.start();
|
||||
for(;;) {
|
||||
try {
|
||||
g_remove(p_path,p_abort);
|
||||
break;
|
||||
} catch(exception_io_sharing_violation) {
|
||||
if (timer.query() > p_timeout) throw;
|
||||
p_abort.sleep(0.01);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void filesystem::g_move_timeout(const char * p_src,const char * p_dst,double p_timeout,abort_callback & p_abort) {
|
||||
pfc::lores_timer timer;
|
||||
timer.start();
|
||||
for(;;) {
|
||||
try {
|
||||
g_move(p_src,p_dst,p_abort);
|
||||
break;
|
||||
} catch(exception_io_sharing_violation) {
|
||||
if (timer.query() > p_timeout) throw;
|
||||
p_abort.sleep(0.01);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void filesystem::g_copy_timeout(const char * p_src,const char * p_dst,double p_timeout,abort_callback & p_abort) {
|
||||
pfc::lores_timer timer;
|
||||
timer.start();
|
||||
for(;;) {
|
||||
try {
|
||||
g_copy(p_src,p_dst,p_abort);
|
||||
break;
|
||||
} catch(exception_io_sharing_violation) {
|
||||
if (timer.query() > p_timeout) throw;
|
||||
p_abort.sleep(0.01);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void filesystem::g_create_directory(const char * p_path,abort_callback & p_abort)
|
||||
{
|
||||
service_ptr_t<filesystem> fs;
|
||||
if (!g_get_interface(fs,p_path)) throw exception_io_no_handler_for_path();
|
||||
fs->create_directory(p_path,p_abort);
|
||||
}
|
||||
|
||||
void filesystem::g_move(const char * src,const char * dst,abort_callback & p_abort) {
|
||||
service_enum_t<filesystem> e;
|
||||
service_ptr_t<filesystem> ptr;
|
||||
if (e.first(ptr)) do {
|
||||
if (ptr->is_our_path(src) && ptr->is_our_path(dst)) {
|
||||
ptr->move(src,dst,p_abort);
|
||||
return;
|
||||
}
|
||||
} while(e.next(ptr));
|
||||
throw exception_io_no_handler_for_path();
|
||||
}
|
||||
|
||||
void filesystem::g_list_directory(const char * p_path,directory_callback & p_out,abort_callback & p_abort)
|
||||
{
|
||||
TRACK_CALL_TEXT("filesystem::g_list_directory");
|
||||
service_ptr_t<filesystem> ptr;
|
||||
if (!g_get_interface(ptr,p_path)) throw exception_io_no_handler_for_path();
|
||||
ptr->list_directory(p_path,p_out,p_abort);
|
||||
}
|
||||
|
||||
|
||||
static void path_pack_string(pfc::string_base & out,const char * src)
|
||||
{
|
||||
out.add_char('|');
|
||||
out << strlen(src);
|
||||
out.add_char('|');
|
||||
out << src;
|
||||
out.add_char('|');
|
||||
}
|
||||
|
||||
static int path_unpack_string(pfc::string8 & out,const char * src)
|
||||
{
|
||||
int ptr=0;
|
||||
if (src[ptr++]!='|') return -1;
|
||||
int len = atoi(src+ptr);
|
||||
if (len<=0) return -1;
|
||||
while(src[ptr]!=0 && src[ptr]!='|') ptr++;
|
||||
if (src[ptr]!='|') return -1;
|
||||
ptr++;
|
||||
int start = ptr;
|
||||
while(ptr-start<len)
|
||||
{
|
||||
if (src[ptr]==0) return -1;
|
||||
ptr++;
|
||||
}
|
||||
if (src[ptr]!='|') return -1;
|
||||
out.add_string(&src[start],len);
|
||||
ptr++;
|
||||
return ptr;
|
||||
}
|
||||
|
||||
|
||||
void filesystem::g_open_precache(service_ptr_t<file> & p_out,const char * p_path,abort_callback & p_abort) {
|
||||
service_ptr_t<filesystem> fs;
|
||||
if (!g_get_interface(fs,p_path)) throw exception_io_no_handler_for_path();
|
||||
if (fs->is_remote(p_path)) throw exception_io_object_is_remote();
|
||||
fs->open(p_out,p_path,open_mode_read,p_abort);
|
||||
}
|
||||
|
||||
bool filesystem::g_is_remote(const char * p_path) {
|
||||
service_ptr_t<filesystem> fs;
|
||||
if (g_get_interface(fs,p_path)) return fs->is_remote(p_path);
|
||||
else throw exception_io_no_handler_for_path();
|
||||
}
|
||||
|
||||
bool filesystem::g_is_recognized_and_remote(const char * p_path) {
|
||||
service_ptr_t<filesystem> fs;
|
||||
if (g_get_interface(fs,p_path)) return fs->is_remote(p_path);
|
||||
else return false;
|
||||
}
|
||||
|
||||
bool filesystem::g_is_remote_or_unrecognized(const char * p_path) {
|
||||
service_ptr_t<filesystem> fs;
|
||||
if (g_get_interface(fs,p_path)) return fs->is_remote(p_path);
|
||||
else return true;
|
||||
}
|
||||
|
||||
bool filesystem::g_relative_path_create(const char * file_path,const char * playlist_path,pfc::string_base & out)
|
||||
{
|
||||
|
||||
bool rv = false;
|
||||
service_ptr_t<filesystem> fs;
|
||||
|
||||
if (g_get_interface(fs,file_path))
|
||||
rv = fs->relative_path_create(file_path,playlist_path,out);
|
||||
|
||||
return rv;
|
||||
}
|
||||
|
||||
bool filesystem::g_relative_path_parse(const char * relative_path,const char * playlist_path,pfc::string_base & out)
|
||||
{
|
||||
service_enum_t<filesystem> e;
|
||||
service_ptr_t<filesystem> ptr;
|
||||
if (e.first(ptr)) do {
|
||||
if (ptr->relative_path_parse(relative_path,playlist_path,out)) return true;
|
||||
} while(e.next(ptr));
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool archive_impl::get_canonical_path(const char * path,pfc::string_base & out)
|
||||
{
|
||||
if (is_our_path(path))
|
||||
{
|
||||
pfc::string8 archive,file,archive_canonical;
|
||||
if (g_parse_unpack_path(path,archive,file))
|
||||
{
|
||||
g_get_canonical_path(archive,archive_canonical);
|
||||
make_unpack_path(out,archive_canonical,file);
|
||||
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
bool archive_impl::is_our_path(const char * path)
|
||||
{
|
||||
if (strncmp(path,"unpack://",9)) return false;
|
||||
const char * type = get_archive_type();
|
||||
path += 9;
|
||||
while(*type)
|
||||
{
|
||||
if (*type!=*path) return false;
|
||||
type++;
|
||||
path++;
|
||||
}
|
||||
if (*path!='|') return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool archive_impl::get_display_path(const char * path,pfc::string_base & out)
|
||||
{
|
||||
pfc::string8 archive,file;
|
||||
if (g_parse_unpack_path(path,archive,file))
|
||||
{
|
||||
g_get_display_path(archive,out);
|
||||
out.add_string("|");
|
||||
out.add_string(file);
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
void archive_impl::open(service_ptr_t<file> & p_out,const char * path,t_open_mode mode, abort_callback & p_abort)
|
||||
{
|
||||
if (mode != open_mode_read) throw exception_io_denied();
|
||||
pfc::string8 archive,file;
|
||||
if (!g_parse_unpack_path(path,archive,file)) throw exception_io_not_found();
|
||||
open_archive(p_out,archive,file,p_abort);
|
||||
}
|
||||
|
||||
|
||||
void archive_impl::remove(const char * path,abort_callback & p_abort) {
|
||||
throw exception_io_denied();
|
||||
}
|
||||
|
||||
void archive_impl::move(const char * src,const char * dst,abort_callback & p_abort) {
|
||||
throw exception_io_denied();
|
||||
}
|
||||
|
||||
bool archive_impl::is_remote(const char * src) {
|
||||
pfc::string8 archive,file;
|
||||
if (g_parse_unpack_path(src,archive,file)) return g_is_remote(archive);
|
||||
else throw exception_io_not_found();
|
||||
}
|
||||
|
||||
bool archive_impl::relative_path_create(const char * file_path,const char * playlist_path,pfc::string_base & out) {
|
||||
pfc::string8 archive,file;
|
||||
if (g_parse_unpack_path(file_path,archive,file))
|
||||
{
|
||||
pfc::string8 archive_rel;
|
||||
if (g_relative_path_create(archive,playlist_path,archive_rel))
|
||||
{
|
||||
pfc::string8 out_path;
|
||||
make_unpack_path(out_path,archive_rel,file);
|
||||
out.set_string(out_path);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool archive_impl::relative_path_parse(const char * relative_path,const char * playlist_path,pfc::string_base & out)
|
||||
{
|
||||
if (!is_our_path(relative_path)) return false;
|
||||
pfc::string8 archive_rel,file;
|
||||
if (g_parse_unpack_path(relative_path,archive_rel,file))
|
||||
{
|
||||
pfc::string8 archive;
|
||||
if (g_relative_path_parse(archive_rel,playlist_path,archive))
|
||||
{
|
||||
pfc::string8 out_path;
|
||||
make_unpack_path(out_path,archive,file);
|
||||
out.set_string(out_path);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool archive_impl::g_parse_unpack_path(const char * path,pfc::string8 & archive,pfc::string8 & file)
|
||||
{
|
||||
path = strchr(path,'|');
|
||||
if (!path) return false;
|
||||
int delta = path_unpack_string(archive,path);
|
||||
if (delta<0) return false;
|
||||
path += delta;
|
||||
file = path;
|
||||
return true;
|
||||
}
|
||||
|
||||
void archive_impl::g_make_unpack_path(pfc::string_base & path,const char * archive,const char * file,const char * name)
|
||||
{
|
||||
path = "unpack://";
|
||||
path += name;
|
||||
path_pack_string(path,archive);
|
||||
path += file;
|
||||
}
|
||||
|
||||
void archive_impl::make_unpack_path(pfc::string_base & path,const char * archive,const char * file) {g_make_unpack_path(path,archive,file,get_archive_type());}
|
||||
|
||||
|
||||
FILE * filesystem::streamio_open(const char * path,const char * flags)
|
||||
{
|
||||
FILE * ret = 0;
|
||||
pfc::string8 temp;
|
||||
g_get_canonical_path(path,temp);
|
||||
if (!strncmp(temp,"file://",7))
|
||||
{
|
||||
ret = _wfopen(pfc::stringcvt::string_wide_from_utf8(path+7),pfc::stringcvt::string_wide_from_utf8(flags));
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
class directory_callback_isempty : public directory_callback
|
||||
{
|
||||
bool m_isempty;
|
||||
public:
|
||||
directory_callback_isempty() : m_isempty(true) {}
|
||||
bool on_entry(filesystem * owner,abort_callback & p_abort,const char * url,bool is_subdirectory,const t_filestats & p_stats)
|
||||
{
|
||||
m_isempty = false;
|
||||
return false;
|
||||
}
|
||||
bool isempty() {return m_isempty;}
|
||||
};
|
||||
|
||||
class directory_callback_dummy : public directory_callback
|
||||
{
|
||||
public:
|
||||
bool on_entry(filesystem * owner,abort_callback & p_abort,const char * url,bool is_subdirectory,const t_filestats & p_stats) {return false;}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
bool filesystem::g_is_empty_directory(const char * path,abort_callback & p_abort)
|
||||
{
|
||||
directory_callback_isempty callback;
|
||||
try {
|
||||
g_list_directory(path,callback,p_abort);
|
||||
} catch(exception_io const &) {return false;}
|
||||
return callback.isempty();
|
||||
}
|
||||
|
||||
bool filesystem::g_is_valid_directory(const char * path,abort_callback & p_abort) {
|
||||
try {
|
||||
g_list_directory(path,directory_callback_dummy(),p_abort);
|
||||
return true;
|
||||
} catch(exception_io const &) {return false;}
|
||||
}
|
||||
|
||||
bool directory_callback_impl::on_entry(filesystem * owner,abort_callback & p_abort,const char * url,bool is_subdirectory,const t_filestats & p_stats) {
|
||||
p_abort.check_e();
|
||||
if (is_subdirectory) {
|
||||
if (m_recur) {
|
||||
try {
|
||||
owner->list_directory(url,*this,p_abort);
|
||||
} catch(exception_io const &) {}
|
||||
}
|
||||
} else {
|
||||
m_data.add_item(pfc::rcnew_t<t_entry>(url,p_stats));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
class directory_callback_impl_copy : public directory_callback
|
||||
{
|
||||
public:
|
||||
directory_callback_impl_copy(const char * p_target)
|
||||
{
|
||||
m_target = p_target;
|
||||
m_target.fix_dir_separator('\\');
|
||||
}
|
||||
|
||||
bool on_entry(filesystem * owner,abort_callback & p_abort,const char * url,bool is_subdirectory,const t_filestats & p_stats) {
|
||||
const char * fn = url + pfc::scan_filename(url);
|
||||
t_size truncat = m_target.length();
|
||||
m_target += fn;
|
||||
if (is_subdirectory) {
|
||||
try {
|
||||
filesystem::g_create_directory(m_target,p_abort);
|
||||
} catch(exception_io_already_exists) {}
|
||||
m_target += "\\";
|
||||
owner->list_directory(url,*this,p_abort);
|
||||
} else {
|
||||
filesystem::g_copy(url,m_target,p_abort);
|
||||
}
|
||||
m_target.truncate(truncat);
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
pfc::string8_fastalloc m_target;
|
||||
};
|
||||
}
|
||||
|
||||
void filesystem::g_copy_directory(const char * src,const char * dst,abort_callback & p_abort) {
|
||||
//UNTESTED
|
||||
filesystem::g_list_directory(src,directory_callback_impl_copy(dst),p_abort);
|
||||
}
|
||||
|
||||
void filesystem::g_copy(const char * src,const char * dst,abort_callback & p_abort) {
|
||||
service_ptr_t<file> r_src,r_dst;
|
||||
t_filesize size;
|
||||
|
||||
g_open(r_src,src,open_mode_read,p_abort);
|
||||
size = r_src->get_size_ex(p_abort);
|
||||
g_open(r_dst,dst,open_mode_write_new,p_abort);
|
||||
|
||||
if (size > 0) {
|
||||
try {
|
||||
file::g_transfer_object(r_src,r_dst,size,p_abort);
|
||||
} catch(...) {
|
||||
r_dst.release();
|
||||
try {g_remove(dst,abort_callback_impl());} catch(...) {}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void stream_reader::read_object(void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
||||
if (read(p_buffer,p_bytes,p_abort) != p_bytes) throw exception_io_data_truncation();
|
||||
}
|
||||
|
||||
t_filestats file::get_stats(abort_callback & p_abort)
|
||||
{
|
||||
t_filestats temp;
|
||||
temp.m_size = get_size(p_abort);
|
||||
temp.m_timestamp = get_timestamp(p_abort);
|
||||
return temp;
|
||||
}
|
||||
|
||||
t_filesize stream_reader::skip(t_filesize p_bytes,abort_callback & p_abort)
|
||||
{
|
||||
t_uint8 temp[256];
|
||||
t_filesize todo = p_bytes, done = 0;
|
||||
while(todo > 0) {
|
||||
t_size delta,deltadone;
|
||||
delta = sizeof(temp);
|
||||
if (delta > todo) delta = (t_size) todo;
|
||||
deltadone = read(temp,delta,p_abort);
|
||||
done += deltadone;
|
||||
todo -= deltadone;
|
||||
if (deltadone < delta) break;
|
||||
}
|
||||
return done;
|
||||
}
|
||||
|
||||
void stream_reader::skip_object(t_filesize p_bytes,abort_callback & p_abort) {
|
||||
if (skip(p_bytes,p_abort) != p_bytes) throw exception_io_data_truncation();
|
||||
}
|
||||
|
||||
void filesystem::g_open_write_new(service_ptr_t<file> & p_out,const char * p_path,abort_callback & p_abort) {
|
||||
g_open(p_out,p_path,open_mode_write_new,p_abort);
|
||||
}
|
||||
void file::g_transfer_file(const service_ptr_t<file> & p_from,const service_ptr_t<file> & p_to,abort_callback & p_abort) {
|
||||
t_filesize length = p_from->get_size_ex(p_abort);
|
||||
p_from->seek(0,p_abort);
|
||||
p_to->seek(0,p_abort);
|
||||
p_to->set_eof(p_abort);
|
||||
if (length > 0) {
|
||||
g_transfer_object(p_from,p_to,length,p_abort);
|
||||
}
|
||||
}
|
||||
|
||||
void filesystem::g_open_temp(service_ptr_t<file> & p_out,abort_callback & p_abort) {
|
||||
g_open(p_out,"tempfile://",open_mode_write_new,p_abort);
|
||||
}
|
||||
|
||||
void filesystem::g_open_tempmem(service_ptr_t<file> & p_out,abort_callback & p_abort) {
|
||||
g_open(p_out,"tempmem://",open_mode_write_new,p_abort);
|
||||
}
|
||||
|
||||
void archive_impl::list_directory(const char * p_path,directory_callback & p_out,abort_callback & p_abort) {
|
||||
throw exception_io_not_found();
|
||||
}
|
||||
|
||||
void archive_impl::create_directory(const char * path,abort_callback &) {
|
||||
throw exception_io_denied();
|
||||
}
|
||||
|
||||
void filesystem::g_get_stats(const char * p_path,t_filestats & p_stats,bool & p_is_writeable,abort_callback & p_abort) {
|
||||
TRACK_CALL_TEXT("filesystem::g_get_stats");
|
||||
service_ptr_t<filesystem> fs;
|
||||
if (!g_get_interface(fs,p_path)) throw exception_io_no_handler_for_path();
|
||||
return fs->get_stats(p_path,p_stats,p_is_writeable,p_abort);
|
||||
}
|
||||
|
||||
void archive_impl::get_stats(const char * p_path,t_filestats & p_stats,bool & p_is_writeable,abort_callback & p_abort) {
|
||||
pfc::string8 archive,file;
|
||||
if (g_parse_unpack_path(p_path,archive,file)) {
|
||||
if (g_is_remote(archive)) throw exception_io_object_is_remote();
|
||||
p_is_writeable = false;
|
||||
p_stats = get_stats_in_archive(archive,file,p_abort);
|
||||
}
|
||||
else throw exception_io_not_found();
|
||||
}
|
||||
|
||||
|
||||
bool file::is_eof(abort_callback & p_abort) {
|
||||
t_filesize position,size;
|
||||
position = get_position(p_abort);
|
||||
size = get_size(p_abort);
|
||||
if (size == filesize_invalid) return false;
|
||||
return position >= size;
|
||||
}
|
||||
|
||||
|
||||
t_filetimestamp foobar2000_io::filetimestamp_from_system_timer()
|
||||
{
|
||||
t_filetimestamp ret;
|
||||
GetSystemTimeAsFileTime((FILETIME*)&ret);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void stream_reader::read_string_ex(pfc::string_base & p_out,t_size p_bytes,abort_callback & p_abort) {
|
||||
char * ptr = p_out.lock_buffer(p_bytes);
|
||||
try {
|
||||
read_object(ptr,p_bytes,p_abort);
|
||||
} catch(...) {
|
||||
p_out.unlock_buffer();
|
||||
throw;
|
||||
}
|
||||
p_out.unlock_buffer();
|
||||
}
|
||||
void stream_reader::read_string(pfc::string_base & p_out,abort_callback & p_abort)
|
||||
{
|
||||
t_uint32 length;
|
||||
read_lendian_t(length,p_abort);
|
||||
read_string_ex(p_out,length,p_abort);
|
||||
}
|
||||
|
||||
void stream_reader::read_string_raw(pfc::string_base & p_out,abort_callback & p_abort) {
|
||||
enum {delta = 256};
|
||||
char buffer[delta];
|
||||
p_out.reset();
|
||||
for(;;) {
|
||||
t_size delta_done;
|
||||
delta_done = read(buffer,delta,p_abort);
|
||||
p_out.add_string(buffer,delta_done);
|
||||
if (delta_done < delta) break;
|
||||
}
|
||||
}
|
||||
void stream_writer::write_string(const char * p_string,t_size p_len,abort_callback & p_abort) {
|
||||
t_uint32 len = pfc::downcast_guarded<t_uint32>(pfc::strlen_max(p_string,p_len));
|
||||
write_lendian_t(len,p_abort);
|
||||
write_object(p_string,len,p_abort);
|
||||
}
|
||||
|
||||
void stream_writer::write_string(const char * p_string,abort_callback & p_abort) {
|
||||
write_string(p_string,infinite,p_abort);
|
||||
}
|
||||
|
||||
void stream_writer::write_string_raw(const char * p_string,abort_callback & p_abort) {
|
||||
write_object(p_string,strlen(p_string),p_abort);
|
||||
}
|
||||
|
||||
void file::truncate(t_uint64 p_position,abort_callback & p_abort) {
|
||||
if (p_position < get_size(p_abort)) resize(p_position,p_abort);
|
||||
}
|
||||
|
||||
|
||||
#ifdef _WIN32
|
||||
namespace {
|
||||
//rare/weird win32 errors that didn't make it to the main API
|
||||
PFC_DECLARE_EXCEPTION(exception_io_device_not_ready, exception_io,"Device not ready");
|
||||
PFC_DECLARE_EXCEPTION(exception_io_invalid_drive, exception_io_not_found,"Drive not found");
|
||||
PFC_DECLARE_EXCEPTION(exception_io_win32, exception_io,"Generic win32 I/O error");
|
||||
PFC_DECLARE_EXCEPTION(exception_io_buffer_overflow, exception_io,"The file name is too long");
|
||||
PFC_DECLARE_EXCEPTION(exception_io_invalid_path_syntax, exception_io,"Invalid path syntax");
|
||||
|
||||
class exception_io_win32_ex : public exception_io_win32 {
|
||||
public:
|
||||
exception_io_win32_ex(DWORD p_code) : m_msg(pfc::string_formatter() << "I/O error (win32 #" << (t_uint32)p_code << ")") {}
|
||||
exception_io_win32_ex(const exception_io_win32_ex & p_other) {*this = p_other;}
|
||||
const char * what() const throw() {return m_msg;}
|
||||
private:
|
||||
pfc::string8 m_msg;
|
||||
};
|
||||
}
|
||||
void foobar2000_io::exception_io_from_win32(DWORD p_code) {
|
||||
switch(p_code) {
|
||||
case ERROR_ALREADY_EXISTS:
|
||||
case ERROR_FILE_EXISTS:
|
||||
throw exception_io_already_exists();
|
||||
case ERROR_NETWORK_ACCESS_DENIED:
|
||||
case ERROR_ACCESS_DENIED:
|
||||
throw exception_io_denied();
|
||||
case ERROR_WRITE_PROTECT:
|
||||
throw exception_io_write_protected();
|
||||
case ERROR_BUSY:
|
||||
case ERROR_PATH_BUSY:
|
||||
case ERROR_SHARING_VIOLATION:
|
||||
case ERROR_LOCK_VIOLATION:
|
||||
throw exception_io_sharing_violation();
|
||||
case ERROR_HANDLE_DISK_FULL:
|
||||
case ERROR_DISK_FULL:
|
||||
throw exception_io_device_full();
|
||||
case ERROR_FILE_NOT_FOUND:
|
||||
case ERROR_PATH_NOT_FOUND:
|
||||
throw exception_io_not_found();
|
||||
case ERROR_BROKEN_PIPE:
|
||||
case ERROR_NO_DATA:
|
||||
throw exception_io_no_data();
|
||||
case ERROR_NETWORK_UNREACHABLE:
|
||||
case ERROR_NETNAME_DELETED:
|
||||
throw exception_io_network_not_reachable();
|
||||
case ERROR_NOT_READY:
|
||||
throw exception_io_device_not_ready();
|
||||
case ERROR_INVALID_DRIVE:
|
||||
throw exception_io_invalid_drive();
|
||||
case ERROR_CRC:
|
||||
case ERROR_FILE_CORRUPT:
|
||||
case ERROR_DISK_CORRUPT:
|
||||
throw exception_io_file_corrupted();
|
||||
case ERROR_BUFFER_OVERFLOW:
|
||||
throw exception_io_buffer_overflow();
|
||||
case ERROR_DISK_CHANGE:
|
||||
throw exception_io_disk_change();
|
||||
case ERROR_DIR_NOT_EMPTY:
|
||||
throw exception_io_directory_not_empty();
|
||||
case ERROR_INVALID_NAME:
|
||||
throw exception_io_invalid_path_syntax();
|
||||
default:
|
||||
throw exception_io_win32_ex(p_code);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
t_filesize file::get_size_ex(abort_callback & p_abort) {
|
||||
t_filesize temp = get_size(p_abort);
|
||||
if (temp == filesize_invalid) throw exception_io_no_length();
|
||||
return temp;
|
||||
}
|
||||
|
||||
void file::ensure_local() {
|
||||
if (is_remote()) throw exception_io_object_is_remote();
|
||||
}
|
||||
|
||||
void file::ensure_seekable() {
|
||||
if (!can_seek()) throw exception_io_object_not_seekable();
|
||||
}
|
||||
|
||||
bool filesystem::g_is_recognized_path(const char * p_path) {
|
||||
return g_get_interface(service_ptr_t<filesystem>(),p_path);
|
||||
}
|
||||
|
||||
t_filesize file::get_remaining(abort_callback & p_abort) {
|
||||
t_filesize length = get_size_ex(p_abort);
|
||||
t_filesize position = get_position(p_abort);
|
||||
pfc::dynamic_assert(position <= length);
|
||||
return length - position;
|
||||
}
|
||||
|
||||
|
||||
t_filesize file::g_transfer(service_ptr_t<file> p_src,service_ptr_t<file> p_dst,t_filesize p_bytes,abort_callback & p_abort) {
|
||||
return g_transfer(pfc::safe_cast<stream_reader*>(p_src.get_ptr()),pfc::safe_cast<stream_writer*>(p_dst.get_ptr()),p_bytes,p_abort);
|
||||
}
|
||||
|
||||
void file::g_transfer_object(service_ptr_t<file> p_src,service_ptr_t<file> p_dst,t_filesize p_bytes,abort_callback & p_abort) {
|
||||
if (p_bytes > 1024) /* don't bother on small objects */
|
||||
{
|
||||
t_filesize oldsize = p_dst->get_size(p_abort);
|
||||
if (oldsize != filesize_invalid) {
|
||||
t_filesize newpos = p_dst->get_position(p_abort) + p_bytes;
|
||||
if (newpos > oldsize) p_dst->resize(newpos ,p_abort);
|
||||
}
|
||||
}
|
||||
g_transfer_object(pfc::safe_cast<stream_reader*>(p_src.get_ptr()),pfc::safe_cast<stream_writer*>(p_dst.get_ptr()),p_bytes,p_abort);
|
||||
}
|
||||
|
||||
|
||||
void foobar2000_io::generate_temp_location_for_file(pfc::string_base & p_out, const char * p_origpath,const char * p_extension,const char * p_magic) {
|
||||
hasher_md5_result hash;
|
||||
{
|
||||
static_api_ptr_t<hasher_md5> hasher;
|
||||
hasher_md5_state state;
|
||||
hasher->initialize(state);
|
||||
hasher->process(state,p_origpath,strlen(p_origpath));
|
||||
hasher->process(state,p_extension,strlen(p_extension));
|
||||
hasher->process(state,p_magic,strlen(p_magic));
|
||||
hash = hasher->get_result(state);
|
||||
}
|
||||
|
||||
p_out = p_origpath;
|
||||
p_out.truncate(p_out.scan_filename());
|
||||
p_out += "temp-";
|
||||
p_out += pfc::format_hexdump(hash.m_data,sizeof(hash.m_data),"");
|
||||
p_out += ".";
|
||||
p_out += p_extension;
|
||||
}
|
||||
|
||||
|
||||
t_filesize file::skip(t_filesize p_bytes,abort_callback & p_abort) {
|
||||
if (p_bytes > 1024 && can_seek()) {
|
||||
const t_filesize size = get_size(p_abort);
|
||||
if (size != filesize_invalid) {
|
||||
const t_filesize position = get_position(p_abort);
|
||||
const t_filesize toskip = pfc::min_t( p_bytes, size - position );
|
||||
seek(position + toskip,p_abort);
|
||||
return toskip;
|
||||
}
|
||||
}
|
||||
return stream_reader::skip(p_bytes,p_abort);
|
||||
}
|
||||
|
||||
bool foobar2000_io::_extract_native_path_ptr(const char * & p_fspath) {
|
||||
static const char header[] = "file://"; static const t_size headerLen = 7;
|
||||
if (strncmp(p_fspath,header,headerLen) != 0) return false;
|
||||
p_fspath += headerLen;
|
||||
return true;
|
||||
}
|
||||
bool foobar2000_io::extract_native_path(const char * p_fspath,pfc::string_base & p_native) {
|
||||
if (!_extract_native_path_ptr(p_fspath)) return false;
|
||||
p_native = p_fspath;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool foobar2000_io::extract_native_path_ex(const char * p_fspath, pfc::string_base & p_native) {
|
||||
if (!_extract_native_path_ptr(p_fspath)) return false;
|
||||
if (p_fspath[0] != '\\' || p_fspath[0] != '\\') {
|
||||
p_native = "\\\\?\\";
|
||||
p_native += p_fspath;
|
||||
} else {
|
||||
p_native = p_fspath;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
pfc::string stream_reader::read_string(abort_callback & p_abort) {
|
||||
t_uint32 len;
|
||||
read_lendian_t(len,p_abort);
|
||||
return read_string_ex(len,p_abort);
|
||||
}
|
||||
pfc::string stream_reader::read_string_ex(t_size p_len,abort_callback & p_abort) {
|
||||
pfc::rcptr_t<pfc::string8> temp; temp.new_t();
|
||||
read_object(temp->lock_buffer(p_len),p_len,p_abort);
|
||||
temp->unlock_buffer();
|
||||
return pfc::string::t_data(temp);
|
||||
}
|
|
@ -0,0 +1,598 @@
|
|||
class file_info;
|
||||
|
||||
//! Contains various I/O related structures and interfaces.
|
||||
|
||||
namespace foobar2000_io
|
||||
{
|
||||
//! Type used for file size related variables.
|
||||
typedef t_uint64 t_filesize;
|
||||
//! Type used for file size related variables when signed value is needed.
|
||||
typedef t_int64 t_sfilesize;
|
||||
//! Type used for file timestamp related variables. 64-bit value representing the number of 100-nanosecond intervals since January 1, 1601; 0 for invalid/unknown time.
|
||||
typedef t_uint64 t_filetimestamp;
|
||||
//! Invalid/unknown file timestamp constant. Also see: t_filetimestamp.
|
||||
const t_filetimestamp filetimestamp_invalid = 0;
|
||||
//! Invalid/unknown file size constant. Also see: t_filesize.
|
||||
static const t_filesize filesize_invalid = (t_filesize)(~0);
|
||||
|
||||
static const t_filetimestamp filetimestamp_1second_increment = 10000000;
|
||||
|
||||
//! Generic I/O error. Root class for I/O failure exception. See relevant default message for description of each derived exception class.
|
||||
PFC_DECLARE_EXCEPTION(exception_io, pfc::exception,"I/O error");
|
||||
//! Object not found.
|
||||
PFC_DECLARE_EXCEPTION(exception_io_not_found, exception_io,"Object not found");
|
||||
//! Access denied.
|
||||
PFC_DECLARE_EXCEPTION(exception_io_denied, exception_io,"Access denied");
|
||||
//! Unsupported format or corrupted file (unexpected data encountered).
|
||||
PFC_DECLARE_EXCEPTION(exception_io_data, exception_io,"Unsupported format or corrupted file");
|
||||
//! Unsupported format or corrupted file (truncation encountered).
|
||||
PFC_DECLARE_EXCEPTION(exception_io_data_truncation, exception_io_data,"Unsupported format or corrupted file");
|
||||
//! Unsupported format (a subclass of "unsupported format or corrupted file" exception).
|
||||
PFC_DECLARE_EXCEPTION(exception_io_unsupported_format, exception_io_data,"Unsupported file format");
|
||||
//! Object is remote, while specific operation is supported only for local objects.
|
||||
PFC_DECLARE_EXCEPTION(exception_io_object_is_remote, exception_io,"This operation is not supported on remote objects");
|
||||
//! Sharing violation.
|
||||
PFC_DECLARE_EXCEPTION(exception_io_sharing_violation, exception_io,"Sharing violation");
|
||||
//! Device full.
|
||||
PFC_DECLARE_EXCEPTION(exception_io_device_full, exception_io,"Device full");
|
||||
//! Attempt to seek outside valid range.
|
||||
PFC_DECLARE_EXCEPTION(exception_io_seek_out_of_range, exception_io,"Seek offset out of range");
|
||||
//! This operation requires a seekable object.
|
||||
PFC_DECLARE_EXCEPTION(exception_io_object_not_seekable, exception_io,"Object is not seekable");
|
||||
//! This operation requires an object with known length.
|
||||
PFC_DECLARE_EXCEPTION(exception_io_no_length, exception_io,"Length of object is unknown");
|
||||
//! Invalid path.
|
||||
PFC_DECLARE_EXCEPTION(exception_io_no_handler_for_path, exception_io,"Invalid path");
|
||||
//! Object already exists.
|
||||
PFC_DECLARE_EXCEPTION(exception_io_already_exists, exception_io,"Object already exists");
|
||||
//! Pipe error.
|
||||
PFC_DECLARE_EXCEPTION(exception_io_no_data, exception_io,"The process receiving or sending data has terminated");
|
||||
//! Network not reachable.
|
||||
PFC_DECLARE_EXCEPTION(exception_io_network_not_reachable,exception_io,"Network not reachable");
|
||||
//! Media is write protected.
|
||||
PFC_DECLARE_EXCEPTION(exception_io_write_protected, exception_io_denied,"The media is write protected");
|
||||
//! File is corrupted. This indicates filesystem call failure, not actual invalid data being read by the app.
|
||||
PFC_DECLARE_EXCEPTION(exception_io_file_corrupted, exception_io,"The file is corrupted");
|
||||
//! The disc required for requested operation is not available.
|
||||
PFC_DECLARE_EXCEPTION(exception_io_disk_change, exception_io,"Disc not available");
|
||||
//! The directory is not empty.
|
||||
PFC_DECLARE_EXCEPTION(exception_io_directory_not_empty, exception_io,"Directory not empty");
|
||||
|
||||
//! Stores file stats (size and timestamp).
|
||||
struct t_filestats {
|
||||
//! Size of the file.
|
||||
t_filesize m_size;
|
||||
//! Time of last file modification.
|
||||
t_filetimestamp m_timestamp;
|
||||
|
||||
inline bool operator==(const t_filestats & param) const {return m_size == param.m_size && m_timestamp == param.m_timestamp;}
|
||||
inline bool operator!=(const t_filestats & param) const {return m_size != param.m_size || m_timestamp != param.m_timestamp;}
|
||||
};
|
||||
|
||||
//! Invalid/unknown file stats constant. See: t_filestats.
|
||||
static const t_filestats filestats_invalid = {filesize_invalid,filetimestamp_invalid};
|
||||
|
||||
#ifdef _WIN32
|
||||
void exception_io_from_win32(DWORD p_code);
|
||||
#define WIN32_IO_OP(X) {SetLastError(NO_ERROR); if (!(X)) exception_io_from_win32(GetLastError());}
|
||||
#endif
|
||||
|
||||
//! Generic interface to read data from a nonseekable stream. Also see: stream_writer, file. \n
|
||||
//! Error handling: all methods may throw exception_io or one of derivatives on failure; exception_aborted when abort_callback is signaled.
|
||||
class NOVTABLE stream_reader {
|
||||
public:
|
||||
//! Attempts to reads specified number of bytes from the stream.
|
||||
//! @param p_buffer Receives data being read. Must have at least p_bytes bytes of space allocated.
|
||||
//! @param p_bytes Number of bytes to read.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
//! @returns Number of bytes actually read. May be less than requested when EOF was reached.
|
||||
virtual t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) = 0;
|
||||
//! Reads specified number of bytes from the stream. If requested amount of bytes can't be read (e.g. EOF), throws exception_io_data_truncation.
|
||||
//! @param p_buffer Receives data being read. Must have at least p_bytes bytes of space allocated.
|
||||
//! @param p_bytes Number of bytes to read.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
virtual void read_object(void * p_buffer,t_size p_bytes,abort_callback & p_abort);
|
||||
//! Attempts to skip specified number of bytes in the stream.
|
||||
//! @param p_bytes Number of bytes to skip.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
//! @returns Number of bytes actually skipped, May be less than requested when EOF was reached.
|
||||
virtual t_filesize skip(t_filesize p_bytes,abort_callback & p_abort);
|
||||
//! Skips specified number of bytes in the stream. If requested amount of bytes can't be skipped (e.g. EOF), throws exception_io_data_truncation.
|
||||
//! @param p_bytes Number of bytes to skip.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
virtual void skip_object(t_filesize p_bytes,abort_callback & p_abort);
|
||||
|
||||
//! Helper template built around read_object. Reads single raw object from the stream.
|
||||
//! @param p_object Receives object read from the stream on success.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
template<typename T> inline void read_object_t(T& p_object,abort_callback & p_abort) {pfc::assert_raw_type<T>(); read_object(&p_object,sizeof(p_object),p_abort);}
|
||||
//! Helper template built around read_object. Reads single raw object from the stream; corrects byte order assuming stream uses little endian order.
|
||||
//! @param p_object Receives object read from the stream on success.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
template<typename T> inline void read_lendian_t(T& p_object,abort_callback & p_abort) {read_object_t(p_object,p_abort); byte_order::order_le_to_native_t(p_object);}
|
||||
//! Helper template built around read_object. Reads single raw object from the stream; corrects byte order assuming stream uses big endian order.
|
||||
//! @param p_object Receives object read from the stream on success.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
template<typename T> inline void read_bendian_t(T& p_object,abort_callback & p_abort) {read_object_t(p_object,p_abort); byte_order::order_be_to_native_t(p_object);}
|
||||
|
||||
//! Helper function; reads a string (with a 32-bit header indicating length in bytes followed by UTF-8 encoded data without a null terminator).
|
||||
void read_string(pfc::string_base & p_out,abort_callback & p_abort);
|
||||
//! Helper function; alternate way of storing strings; assumes string takes space up to end of stream.
|
||||
void read_string_raw(pfc::string_base & p_out,abort_callback & p_abort);
|
||||
//! Helper function; reads a string (with a 32-bit header indicating length in bytes followed by UTF-8 encoded data without a null terminator).
|
||||
pfc::string read_string(abort_callback & p_abort);
|
||||
|
||||
//! Helper function; reads a string of specified length from the stream.
|
||||
void read_string_ex(pfc::string_base & p_out,t_size p_bytes,abort_callback & p_abort);
|
||||
//! Helper function; reads a string of specified length from the stream.
|
||||
pfc::string read_string_ex(t_size p_len,abort_callback & p_abort);
|
||||
protected:
|
||||
stream_reader() {}
|
||||
~stream_reader() {}
|
||||
};
|
||||
|
||||
|
||||
//! Generic interface to write data to a nonseekable stream. Also see: stream_reader, file. \n
|
||||
//! Error handling: all methods may throw exception_io or one of derivatives on failure; exception_aborted when abort_callback is signaled.
|
||||
class NOVTABLE stream_writer {
|
||||
public:
|
||||
//! Writes specified number of bytes from specified buffer to the stream.
|
||||
//! @param p_buffer Buffer with data to write. Must contain at least p_bytes bytes.
|
||||
//! @param p_bytes Number of bytes to write.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
virtual void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) = 0;
|
||||
|
||||
//! Helper. Same as write(), provided for consistency.
|
||||
inline void write_object(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {write(p_buffer,p_bytes,p_abort);}
|
||||
|
||||
//! Helper template. Writes single raw object to the stream.
|
||||
//! @param p_object Object to write.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
template<typename T> inline void write_object_t(const T & p_object, abort_callback & p_abort) {pfc::assert_raw_type<T>(); write_object(&p_object,sizeof(p_object),p_abort);}
|
||||
//! Helper template. Writes single raw object to the stream; corrects byte order assuming stream uses little endian order.
|
||||
//! @param p_object Object to write.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
template<typename T> inline void write_lendian_t(const T & p_object, abort_callback & p_abort) {T temp = p_object; byte_order::order_native_to_le_t(temp); write_object_t(temp,p_abort);}
|
||||
//! Helper template. Writes single raw object to the stream; corrects byte order assuming stream uses big endian order.
|
||||
//! @param p_object Object to write.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
template<typename T> inline void write_bendian_t(const T & p_object, abort_callback & p_abort) {T temp = p_object; byte_order::order_native_to_be_t(temp); write_object_t(temp,p_abort);}
|
||||
|
||||
//! Helper function; writes string (with 32-bit header indicating length in bytes followed by UTF-8 encoded data without null terminator).
|
||||
void write_string(const char * p_string,abort_callback & p_abort);
|
||||
void write_string(const char * p_string,t_size p_len,abort_callback & p_abort);
|
||||
|
||||
template<typename T>
|
||||
void write_string(const T& val,abort_callback & p_abort) {write_string(pfc::stringToPtr(val),p_abort);}
|
||||
|
||||
//! Helper function; writes raw string to the stream, with no length info or null terminators.
|
||||
void write_string_raw(const char * p_string,abort_callback & p_abort);
|
||||
protected:
|
||||
stream_writer() {}
|
||||
~stream_writer() {}
|
||||
};
|
||||
|
||||
//! A class providing abstraction for an open file object, with reading/writing/seeking methods. See also: stream_reader, stream_writer (which it inherits read/write methods from). \n
|
||||
//! Error handling: all methods may throw exception_io or one of derivatives on failure; exception_aborted when abort_callback is signaled.
|
||||
class NOVTABLE file : public service_base, public stream_reader, public stream_writer {
|
||||
public:
|
||||
|
||||
//! Seeking mode constants. Note: these are purposedly defined to same values as standard C SEEK_* constants
|
||||
enum t_seek_mode {
|
||||
//! Seek relative to beginning of file (same as seeking to absolute offset).
|
||||
seek_from_beginning = 0,
|
||||
//! Seek relative to current position.
|
||||
seek_from_current = 1,
|
||||
//! Seek relative to end of file.
|
||||
seek_from_eof = 2,
|
||||
};
|
||||
|
||||
//! Retrieves size of the file.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
//! @returns File size on success; filesize_invalid if unknown (nonseekable stream etc).
|
||||
virtual t_filesize get_size(abort_callback & p_abort) = 0;
|
||||
|
||||
|
||||
//! Retrieves read/write cursor position in the file. In case of non-seekable stream, this should return number of bytes read so far since open/reopen call.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
//! @returns Read/write cursor position
|
||||
virtual t_filesize get_position(abort_callback & p_abort) = 0;
|
||||
|
||||
//! Resizes file to the specified size in bytes.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
virtual void resize(t_filesize p_size,abort_callback & p_abort) = 0;
|
||||
|
||||
//! Sets read/write cursor position to the specified offset.
|
||||
//! @param p_position position to seek to.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
virtual void seek(t_filesize p_position,abort_callback & p_abort) = 0;
|
||||
|
||||
|
||||
//! Sets read/write cursor position to the specified offset; extended form allowing seeking relative to current position or to end of file.
|
||||
//! @param p_position Position to seek to; interpretation of this value depends on p_mode parameter.
|
||||
//! @param p_mode Seeking mode; see t_seek_mode enum values for further description.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
virtual void seek_ex(t_sfilesize p_position,t_seek_mode p_mode,abort_callback & p_abort);
|
||||
|
||||
//! Returns whether the file is seekable or not. If can_seek() returns false, all seek() or seek_ex() calls will fail; reopen() is still usable on nonseekable streams.
|
||||
virtual bool can_seek() = 0;
|
||||
|
||||
//! Retrieves mime type of the file.
|
||||
//! @param p_out Receives content type string on success.
|
||||
virtual bool get_content_type(pfc::string_base & p_out) = 0;
|
||||
|
||||
//! Hint, returns whether the file is already fully buffered into memory.
|
||||
virtual bool is_in_memory() {return false;}
|
||||
|
||||
//! Optional, called by owner thread before sleeping.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
virtual void on_idle(abort_callback & p_abort) {}
|
||||
|
||||
//! Retrieves last modification time of the file.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
//! @returns Last modification time o fthe file; filetimestamp_invalid if N/A.
|
||||
virtual t_filetimestamp get_timestamp(abort_callback & p_abort) {return filetimestamp_invalid;}
|
||||
|
||||
//! Resets non-seekable stream, or seeks to zero on seekable file.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
virtual void reopen(abort_callback & p_abort) = 0;
|
||||
|
||||
//! Indicates whether the file is a remote resource and non-sequential access may be slowed down by lag. This is typically returns to true on non-seekable sources but may also return true on seekable sources indicating that seeking is supported but will be relatively slow.
|
||||
virtual bool is_remote() = 0;
|
||||
|
||||
//! Retrieves file stats structure. Usese get_size() and get_timestamp().
|
||||
t_filestats get_stats(abort_callback & p_abort);
|
||||
|
||||
//! Returns whether read/write cursor position is at the end of file.
|
||||
bool is_eof(abort_callback & p_abort);
|
||||
|
||||
//! Truncates file to specified size (while preserving read/write cursor position if possible); uses set_eof().
|
||||
void truncate(t_filesize p_position,abort_callback & p_abort);
|
||||
|
||||
//! Truncates the file at current read/write cursor position.
|
||||
void set_eof(abort_callback & p_abort) {resize(get_position(p_abort),p_abort);}
|
||||
|
||||
|
||||
//! Helper; retrieves size of the file. If size is not available (get_size() returns filesize_invalid), throws exception_io_no_length.
|
||||
t_filesize get_size_ex(abort_callback & p_abort);
|
||||
|
||||
//! Helper; retrieves amount of bytes between read/write cursor position and end of file. Fails when length can't be determined.
|
||||
t_filesize get_remaining(abort_callback & p_abort);
|
||||
|
||||
//! Helper; throws exception_io_object_not_seekable if file is not seekable.
|
||||
void ensure_seekable();
|
||||
|
||||
//! Helper; throws exception_io_object_is_remote if the file is remote.
|
||||
void ensure_local();
|
||||
|
||||
//! Helper; transfers specified number of bytes between streams.
|
||||
//! @returns number of bytes actually transferred. May be less than requested if e.g. EOF is reached.
|
||||
static t_filesize g_transfer(stream_reader * src,stream_writer * dst,t_filesize bytes,abort_callback & p_abort);
|
||||
//! Helper; transfers specified number of bytes between streams. Throws exception if requested number of bytes could not be read (EOF).
|
||||
static void g_transfer_object(stream_reader * src,stream_writer * dst,t_filesize bytes,abort_callback & p_abort);
|
||||
//! Helper; transfers entire file content from one file to another, erasing previous content.
|
||||
static void g_transfer_file(const service_ptr_t<file> & p_from,const service_ptr_t<file> & p_to,abort_callback & p_abort);
|
||||
|
||||
//! Helper; improved performance over g_transfer on streams (avoids disk fragmentation when transferring large blocks).
|
||||
static t_filesize g_transfer(service_ptr_t<file> p_src,service_ptr_t<file> p_dst,t_filesize p_bytes,abort_callback & p_abort);
|
||||
//! Helper; improved performance over g_transfer_file on streams (avoids disk fragmentation when transferring large blocks).
|
||||
static void g_transfer_object(service_ptr_t<file> p_src,service_ptr_t<file> p_dst,t_filesize p_bytes,abort_callback & p_abort);
|
||||
|
||||
|
||||
t_filesize skip(t_filesize p_bytes,abort_callback & p_abort);
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(file,service_base);
|
||||
};
|
||||
|
||||
typedef service_ptr_t<file> file_ptr;
|
||||
|
||||
//! Special hack for shoutcast metadata nonsense handling. Documentme.
|
||||
class file_dynamicinfo : public file {
|
||||
public:
|
||||
//! Retrieves "static" info that doesn't change in the middle of stream, such as station names etc. Returns true on success; false when static info is not available.
|
||||
virtual bool get_static_info(class file_info & p_out) = 0;
|
||||
//! Returns whether dynamic info is available on this stream or not.
|
||||
virtual bool is_dynamic_info_enabled()=0;
|
||||
//! Retrieves dynamic stream info (e.g. online stream track titles). Returns true on success, false when info has not changed since last call.
|
||||
virtual bool get_dynamic_info(class file_info & p_out) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(file_dynamicinfo,file);
|
||||
};
|
||||
|
||||
//! Implementation helper - contains dummy implementations of methods that modify the file
|
||||
template<typename t_base> class file_readonly_t : public t_base {
|
||||
public:
|
||||
void resize(t_filesize p_size,abort_callback & p_abort) {throw exception_io_denied();}
|
||||
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {throw exception_io_denied();}
|
||||
};
|
||||
typedef file_readonly_t<file> file_readonly;
|
||||
|
||||
class filesystem;
|
||||
|
||||
class NOVTABLE directory_callback {
|
||||
public:
|
||||
//! @returns true to continue enumeration, false to abort.
|
||||
virtual bool on_entry(filesystem * p_owner,abort_callback & p_abort,const char * p_url,bool p_is_subdirectory,const t_filestats & p_stats)=0;
|
||||
};
|
||||
|
||||
|
||||
//! Entrypoint service for all filesystem operations.\n
|
||||
//! Implementation: standard implementations for local filesystem etc are provided by core.\n
|
||||
//! Instantiation: use static helper functions rather than calling filesystem interface methods directly, e.g. filesystem::g_open() to open a file.
|
||||
class NOVTABLE filesystem : public service_base {
|
||||
public:
|
||||
//! Enumeration specifying how to open a file. See: filesystem::open(), filesystem::g_open().
|
||||
enum t_open_mode {
|
||||
//! Opens an existing file for reading; if the file does not exist, the operation will fail.
|
||||
open_mode_read,
|
||||
//! Opens an existing file for writing; if the file does not exist, the operation will fail.
|
||||
open_mode_write_existing,
|
||||
//! Opens a new file for writing; if the file exists, its contents will be wiped.
|
||||
open_mode_write_new,
|
||||
};
|
||||
|
||||
virtual bool get_canonical_path(const char * p_path,pfc::string_base & p_out)=0;
|
||||
virtual bool is_our_path(const char * p_path)=0;
|
||||
virtual bool get_display_path(const char * p_path,pfc::string_base & p_out)=0;
|
||||
|
||||
virtual void open(service_ptr_t<file> & p_out,const char * p_path, t_open_mode p_mode,abort_callback & p_abort)=0;
|
||||
virtual void remove(const char * p_path,abort_callback & p_abort)=0;
|
||||
virtual void move(const char * p_src,const char * p_dst,abort_callback & p_abort)=0;
|
||||
//! Queries whether a file at specified path belonging to this filesystem is a remove object or not.
|
||||
virtual bool is_remote(const char * p_src) = 0;
|
||||
|
||||
//! Retrieves stats of a file at specified path.
|
||||
virtual void get_stats(const char * p_path,t_filestats & p_stats,bool & p_is_writeable,abort_callback & p_abort) = 0;
|
||||
|
||||
virtual bool relative_path_create(const char * file_path,const char * playlist_path,pfc::string_base & out) {return false;}
|
||||
virtual bool relative_path_parse(const char * relative_path,const char * playlist_path,pfc::string_base & out) {return false;}
|
||||
|
||||
//! Creates a directory.
|
||||
virtual void create_directory(const char * p_path,abort_callback & p_abort) = 0;
|
||||
|
||||
virtual void list_directory(const char * p_path,directory_callback & p_out,abort_callback & p_abort)=0;
|
||||
|
||||
//! Hint; returns whether this filesystem supports mime types. \n
|
||||
//! When this returns false, all file::get_content_type() calls on files opened thru this filesystem implementation will return false; otherwise, file::get_content_type() calls may return true depending on the file.
|
||||
virtual bool supports_content_types() = 0;
|
||||
|
||||
static void g_get_canonical_path(const char * path,pfc::string_base & out);
|
||||
static void g_get_display_path(const char * path,pfc::string_base & out);
|
||||
|
||||
static bool g_get_interface(service_ptr_t<filesystem> & p_out,const char * path);//path is AFTER get_canonical_path
|
||||
static bool g_is_remote(const char * p_path);//path is AFTER get_canonical_path
|
||||
static bool g_is_recognized_and_remote(const char * p_path);//path is AFTER get_canonical_path
|
||||
static bool g_is_remote_safe(const char * p_path) {return g_is_recognized_and_remote(p_path);}
|
||||
static bool g_is_remote_or_unrecognized(const char * p_path);
|
||||
static bool g_is_recognized_path(const char * p_path);
|
||||
|
||||
//! Opens file at specified path, with specified access privileges.
|
||||
static void g_open(service_ptr_t<file> & p_out,const char * p_path,t_open_mode p_mode,abort_callback & p_abort);
|
||||
//! Attempts to open file at specified path; if the operation fails with sharing violation error, keeps retrying (with short sleep period between retries) for specified amount of time.
|
||||
static void g_open_timeout(service_ptr_t<file> & p_out,const char * p_path,t_open_mode p_mode,double p_timeout,abort_callback & p_abort);
|
||||
static void g_open_write_new(service_ptr_t<file> & p_out,const char * p_path,abort_callback & p_abort);
|
||||
static void g_open_read(service_ptr_t<file> & p_out,const char * path,abort_callback & p_abort) {return g_open(p_out,path,open_mode_read,p_abort);}
|
||||
static void g_open_precache(service_ptr_t<file> & p_out,const char * path,abort_callback & p_abort);//open only for precaching data (eg. will fail on http etc)
|
||||
static bool g_exists(const char * p_path,abort_callback & p_abort);
|
||||
static bool g_exists_writeable(const char * p_path,abort_callback & p_abort);
|
||||
//! Removes file at specified path.
|
||||
static void g_remove(const char * p_path,abort_callback & p_abort);
|
||||
//! Attempts to remove file at specified path; if the operation fails with a sharing violation error, keeps retrying (with short sleep period between retries) for specified amount of time.
|
||||
static void g_remove_timeout(const char * p_path,double p_timeout,abort_callback & p_abort);
|
||||
//! Moves file from one path to another.
|
||||
static void g_move(const char * p_src,const char * p_dst,abort_callback & p_abort);
|
||||
//! Attempts to move file from one path to another; if the operation fails with a sharing violation error, keeps retrying (with short sleep period between retries) for specified amount of time.
|
||||
static void g_move_timeout(const char * p_src,const char * p_dst,double p_timeout,abort_callback & p_abort);
|
||||
|
||||
static void g_copy(const char * p_src,const char * p_dst,abort_callback & p_abort);//needs canonical path
|
||||
static void g_copy_timeout(const char * p_src,const char * p_dst,double p_timeout,abort_callback & p_abort);//needs canonical path
|
||||
static void g_copy_directory(const char * p_src,const char * p_dst,abort_callback & p_abort);//needs canonical path
|
||||
static void g_get_stats(const char * p_path,t_filestats & p_stats,bool & p_is_writeable,abort_callback & p_abort);
|
||||
static bool g_relative_path_create(const char * p_file_path,const char * p_playlist_path,pfc::string_base & out);
|
||||
static bool g_relative_path_parse(const char * p_relative_path,const char * p_playlist_path,pfc::string_base & out);
|
||||
|
||||
static void g_create_directory(const char * p_path,abort_callback & p_abort);
|
||||
|
||||
//! If for some bloody reason you ever need stream io compatibility, use this, INSTEAD of calling fopen() on the path string you've got; will only work with file:// (and not with http://, unpack:// or whatever)
|
||||
static FILE * streamio_open(const char * p_path,const char * p_flags);
|
||||
|
||||
static void g_open_temp(service_ptr_t<file> & p_out,abort_callback & p_abort);
|
||||
static void g_open_tempmem(service_ptr_t<file> & p_out,abort_callback & p_abort);
|
||||
|
||||
static void g_list_directory(const char * p_path,directory_callback & p_out,abort_callback & p_abort);// path must be canonical
|
||||
|
||||
static bool g_is_valid_directory(const char * path,abort_callback & p_abort);
|
||||
static bool g_is_empty_directory(const char * path,abort_callback & p_abort);
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(filesystem);
|
||||
};
|
||||
|
||||
class directory_callback_impl : public directory_callback
|
||||
{
|
||||
struct t_entry
|
||||
{
|
||||
pfc::string_simple m_path;
|
||||
t_filestats m_stats;
|
||||
t_entry(const char * p_path, const t_filestats & p_stats) : m_path(p_path), m_stats(p_stats) {}
|
||||
};
|
||||
|
||||
|
||||
pfc::list_t<pfc::rcptr_t<t_entry> > m_data;
|
||||
bool m_recur;
|
||||
|
||||
static int sortfunc(const pfc::rcptr_t<const t_entry> & p1, const pfc::rcptr_t<const t_entry> & p2) {return stricmp_utf8(p1->m_path,p2->m_path);}
|
||||
public:
|
||||
bool on_entry(filesystem * owner,abort_callback & p_abort,const char * url,bool is_subdirectory,const t_filestats & p_stats);
|
||||
|
||||
directory_callback_impl(bool p_recur) : m_recur(p_recur) {}
|
||||
t_size get_count() {return m_data.get_count();}
|
||||
const char * operator[](t_size n) const {return m_data[n]->m_path;}
|
||||
const char * get_item(t_size n) const {return m_data[n]->m_path;}
|
||||
const t_filestats & get_item_stats(t_size n) const {return m_data[n]->m_stats;}
|
||||
void sort() {m_data.sort_t(sortfunc);}
|
||||
};
|
||||
|
||||
class archive;
|
||||
|
||||
class NOVTABLE archive_callback : public abort_callback {
|
||||
public:
|
||||
virtual bool on_entry(archive * owner,const char * url,const t_filestats & p_stats,const service_ptr_t<file> & p_reader) = 0;
|
||||
};
|
||||
|
||||
//! Interface for archive reader services. When implementing, derive from archive_impl rather than from deriving from archive directly.
|
||||
class NOVTABLE archive : public filesystem {
|
||||
public:
|
||||
virtual void archive_list(const char * p_path,const service_ptr_t<file> & p_reader,archive_callback & p_callback,bool p_want_readers) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(archive,filesystem);
|
||||
};
|
||||
|
||||
//! Root class for archive implementations. Derive from this instead of from archive directly.
|
||||
class NOVTABLE archive_impl : public archive {
|
||||
private:
|
||||
//do not override these
|
||||
bool get_canonical_path(const char * path,pfc::string_base & out);
|
||||
bool is_our_path(const char * path);
|
||||
bool get_display_path(const char * path,pfc::string_base & out);
|
||||
void remove(const char * path,abort_callback & p_abort);
|
||||
void move(const char * src,const char * dst,abort_callback & p_abort);
|
||||
bool is_remote(const char * src);
|
||||
bool relative_path_create(const char * file_path,const char * playlist_path,pfc::string_base & out);
|
||||
bool relative_path_parse(const char * relative_path,const char * playlist_path,pfc::string_base & out);
|
||||
void open(service_ptr_t<file> & p_out,const char * path, t_open_mode mode,abort_callback & p_abort);
|
||||
void create_directory(const char * path,abort_callback &);
|
||||
void list_directory(const char * p_path,directory_callback & p_out,abort_callback & p_abort);
|
||||
void get_stats(const char * p_path,t_filestats & p_stats,bool & p_is_writeable,abort_callback & p_abort);
|
||||
protected:
|
||||
//override these
|
||||
virtual const char * get_archive_type()=0;//eg. "zip", must be lowercase
|
||||
virtual t_filestats get_stats_in_archive(const char * p_archive,const char * p_file,abort_callback & p_abort) = 0;
|
||||
virtual void open_archive(service_ptr_t<file> & p_out,const char * archive,const char * file, abort_callback & p_abort)=0;//opens for reading
|
||||
public:
|
||||
//override these
|
||||
|
||||
virtual void archive_list(const char * path,const service_ptr_t<file> & p_reader,archive_callback & p_out,bool p_want_readers)=0;
|
||||
|
||||
static bool g_parse_unpack_path(const char * path,pfc::string8 & archive,pfc::string8 & file);
|
||||
static void g_make_unpack_path(pfc::string_base & path,const char * archive,const char * file,const char * name);
|
||||
void make_unpack_path(pfc::string_base & path,const char * archive,const char * file);
|
||||
|
||||
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class archive_factory_t : public service_factory_single_t<T> {};
|
||||
|
||||
|
||||
t_filetimestamp filetimestamp_from_system_timer();
|
||||
|
||||
#ifdef _WIN32
|
||||
inline t_filetimestamp import_filetimestamp(FILETIME ft) {
|
||||
return *reinterpret_cast<t_filetimestamp*>(&ft);
|
||||
}
|
||||
#endif
|
||||
|
||||
void generate_temp_location_for_file(pfc::string_base & p_out, const char * p_origpath,const char * p_extension,const char * p_magic);
|
||||
|
||||
|
||||
static file_ptr fileOpen(const char * p_path,filesystem::t_open_mode p_mode,abort_callback & p_abort,double p_timeout) {
|
||||
file_ptr temp; filesystem::g_open_timeout(temp,p_path,p_mode,p_timeout,p_abort); PFC_ASSERT(temp.is_valid()); return temp;
|
||||
}
|
||||
|
||||
static file_ptr fileOpenReadExisting(const char * p_path,abort_callback & p_abort,double p_timeout = 0) {
|
||||
return fileOpen(p_path,filesystem::open_mode_read,p_abort,p_timeout);
|
||||
}
|
||||
static file_ptr fileOpenWriteExisting(const char * p_path,abort_callback & p_abort,double p_timeout = 0) {
|
||||
return fileOpen(p_path,filesystem::open_mode_write_existing,p_abort,p_timeout);
|
||||
}
|
||||
static file_ptr fileOpenWriteNew(const char * p_path,abort_callback & p_abort,double p_timeout = 0) {
|
||||
return fileOpen(p_path,filesystem::open_mode_write_new,p_abort,p_timeout);
|
||||
}
|
||||
|
||||
template<typename t_list>
|
||||
class directory_callback_retrieveList : public directory_callback {
|
||||
public:
|
||||
directory_callback_retrieveList(t_list & p_list,bool p_getFiles,bool p_getSubDirectories) : m_list(p_list), m_getFiles(p_getFiles), m_getSubDirectories(p_getSubDirectories) {}
|
||||
bool on_entry(filesystem * p_owner,abort_callback & p_abort,const char * p_url,bool p_is_subdirectory,const t_filestats & p_stats) {
|
||||
p_abort.check();
|
||||
if (p_is_subdirectory ? m_getSubDirectories : m_getFiles) {
|
||||
m_list.add_item(p_url);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
const bool m_getSubDirectories;
|
||||
const bool m_getFiles;
|
||||
t_list & m_list;
|
||||
};
|
||||
template<typename t_list>
|
||||
class directory_callback_retrieveListEx : public directory_callback {
|
||||
public:
|
||||
directory_callback_retrieveListEx(t_list & p_files, t_list & p_directories) : m_files(p_files), m_directories(p_directories) {}
|
||||
bool on_entry(filesystem * p_owner,abort_callback & p_abort,const char * p_url,bool p_is_subdirectory,const t_filestats & p_stats) {
|
||||
p_abort.check();
|
||||
if (p_is_subdirectory) m_directories += p_url;
|
||||
else m_files += p_url;
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
t_list & m_files;
|
||||
t_list & m_directories;
|
||||
};
|
||||
template<typename t_list> class directory_callback_retrieveListRecur : public directory_callback {
|
||||
public:
|
||||
directory_callback_retrieveListRecur(t_list & p_list) : m_list(p_list) {}
|
||||
bool on_entry(filesystem * owner,abort_callback & p_abort,const char * path, bool isSubdir, const t_filestats&) {
|
||||
if (isSubdir) {
|
||||
try { owner->list_directory(path,*this,p_abort); } catch(exception_io) {}
|
||||
} else {
|
||||
m_list.add_item(path);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
t_list & m_list;
|
||||
};
|
||||
|
||||
template<typename t_list>
|
||||
static void listFiles(const char * p_path,t_list & p_out,abort_callback & p_abort) {
|
||||
directory_callback_retrieveList<t_list> callback(p_out,true,false);
|
||||
filesystem::g_list_directory(p_path,callback,p_abort);
|
||||
}
|
||||
template<typename t_list>
|
||||
static void listDirectories(const char * p_path,t_list & p_out,abort_callback & p_abort) {
|
||||
directory_callback_retrieveList<t_list> callback(p_out,false,true);
|
||||
filesystem::g_list_directory(p_path,callback,p_abort);
|
||||
}
|
||||
template<typename t_list>
|
||||
static void listFilesAndDirectories(const char * p_path,t_list & p_files,t_list & p_directories,abort_callback & p_abort) {
|
||||
directory_callback_retrieveListEx<t_list> callback(p_files,p_directories);
|
||||
filesystem::g_list_directory(p_path,callback,p_abort);
|
||||
}
|
||||
template<typename t_list>
|
||||
static void listFilesRecur(const char * p_path,t_list & p_out,abort_callback & p_abort) {
|
||||
directory_callback_retrieveListRecur<t_list> callback(p_out);
|
||||
filesystem::g_list_directory(p_path,callback,p_abort);
|
||||
}
|
||||
|
||||
bool extract_native_path(const char * p_fspath,pfc::string_base & p_native);
|
||||
bool _extract_native_path_ptr(const char * & p_fspath);
|
||||
bool extract_native_path_ex(const char * p_fspath, pfc::string_base & p_native);//prepends \\?\ where needed
|
||||
|
||||
template<typename T>
|
||||
pfc::string getPathDisplay(const T& source) {
|
||||
pfc::string_formatter temp;
|
||||
filesystem::g_get_display_path(pfc::stringToPtr(source),temp);
|
||||
return temp.toString();
|
||||
}
|
||||
template<typename T>
|
||||
pfc::string getPathCanonical(const T& source) {
|
||||
pfc::string_formatter temp;
|
||||
filesystem::g_get_canonical_path(pfc::stringToPtr(source),temp);
|
||||
return temp.toString();
|
||||
}
|
||||
}
|
||||
|
||||
using namespace foobar2000_io;
|
||||
|
||||
#include "filesystem_helper.h"
|
|
@ -0,0 +1,106 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
void stream_writer_chunk::write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
||||
t_size remaining = p_bytes, written = 0;
|
||||
while(remaining > 0) {
|
||||
t_size delta = sizeof(m_buffer) - m_buffer_state;
|
||||
if (delta > remaining) delta = remaining;
|
||||
memcpy(m_buffer,(const t_uint8*)p_buffer + written,delta);
|
||||
written += delta;
|
||||
remaining -= delta;
|
||||
|
||||
if (m_buffer_state == sizeof(m_buffer)) {
|
||||
m_writer->write_lendian_t((t_uint8)m_buffer_state,p_abort);
|
||||
m_writer->write_object(m_buffer,m_buffer_state,p_abort);
|
||||
m_buffer_state = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void stream_writer_chunk::flush(abort_callback & p_abort)
|
||||
{
|
||||
m_writer->write_lendian_t((t_uint8)m_buffer_state,p_abort);
|
||||
if (m_buffer_state > 0) {
|
||||
m_writer->write_object(m_buffer,m_buffer_state,p_abort);
|
||||
m_buffer_state = 0;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
stream_writer * m_writer;
|
||||
unsigned m_buffer_state;
|
||||
unsigned char m_buffer[255];
|
||||
*/
|
||||
|
||||
t_size stream_reader_chunk::read(void * p_buffer,t_size p_bytes,abort_callback & p_abort)
|
||||
{
|
||||
t_size todo = p_bytes, done = 0;
|
||||
while(todo > 0) {
|
||||
if (m_buffer_size == m_buffer_state) {
|
||||
if (m_eof) break;
|
||||
t_uint8 temp;
|
||||
m_reader->read_lendian_t(temp,p_abort);
|
||||
m_buffer_size = temp;
|
||||
if (temp != sizeof(m_buffer)) m_eof = true;
|
||||
m_buffer_state = 0;
|
||||
if (m_buffer_size>0) {
|
||||
m_reader->read_object(m_buffer,m_buffer_size,p_abort);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
t_size delta = m_buffer_size - m_buffer_state;
|
||||
if (delta > todo) delta = todo;
|
||||
if (delta > 0) {
|
||||
memcpy((unsigned char*)p_buffer + done,m_buffer + m_buffer_state,delta);
|
||||
todo -= delta;
|
||||
done += delta;
|
||||
m_buffer_state += delta;
|
||||
}
|
||||
}
|
||||
return done;
|
||||
}
|
||||
|
||||
void stream_reader_chunk::flush(abort_callback & p_abort) {
|
||||
while(!m_eof) {
|
||||
p_abort.check_e();
|
||||
t_uint8 temp;
|
||||
m_reader->read_lendian_t(temp,p_abort);
|
||||
m_buffer_size = temp;
|
||||
if (temp != sizeof(m_buffer)) m_eof = true;
|
||||
m_buffer_state = 0;
|
||||
if (m_buffer_size>0) {
|
||||
m_reader->skip_object(m_buffer_size,p_abort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
stream_reader * m_reader;
|
||||
unsigned m_buffer_state, m_buffer_size;
|
||||
bool m_eof;
|
||||
unsigned char m_buffer[255];
|
||||
*/
|
||||
|
||||
void stream_reader_chunk::g_skip(stream_reader * p_stream,abort_callback & p_abort) {
|
||||
stream_reader_chunk(p_stream).flush(p_abort);
|
||||
}
|
||||
|
||||
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) throw pfc::exception_bug_check_v2();
|
||||
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;
|
||||
}
|
|
@ -0,0 +1,579 @@
|
|||
//helper
|
||||
class file_path_canonical {
|
||||
public:
|
||||
file_path_canonical(const char * src) {filesystem::g_get_canonical_path(src,m_data);}
|
||||
operator const char * () const {return m_data.get_ptr();}
|
||||
const char * get_ptr() const {return m_data.get_ptr();}
|
||||
t_size get_length() const {return m_data.get_length();}
|
||||
private:
|
||||
pfc::string8 m_data;
|
||||
};
|
||||
|
||||
class file_path_display {
|
||||
public:
|
||||
file_path_display(const char * src) {filesystem::g_get_display_path(src,m_data);}
|
||||
operator const char * () const {return m_data.get_ptr();}
|
||||
const char * get_ptr() const {return m_data.get_ptr();}
|
||||
t_size get_length() const {return m_data.get_length();}
|
||||
private:
|
||||
pfc::string8 m_data;
|
||||
};
|
||||
|
||||
|
||||
class 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_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;
|
||||
}
|
||||
|
||||
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
|
||||
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;
|
||||
|
||||
};
|
||||
|
||||
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;
|
||||
r->seek(begin,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;
|
||||
r->seek(begin,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) {seek(0,p_abort);}
|
||||
};
|
||||
|
||||
class stream_reader_memblock_ref : public stream_reader
|
||||
{
|
||||
public:
|
||||
template<typename t_array> stream_reader_memblock_ref(const t_array & p_array) : m_data(p_array.get_ptr()), m_data_size(p_array.get_size()), m_pointer(0) {
|
||||
pfc::assert_byte_type<typename t_array::t_item>();
|
||||
}
|
||||
stream_reader_memblock_ref(const void * p_data,t_size p_data_size) : m_data((const unsigned char*)p_data), m_data_size(p_data_size), m_pointer(0) {}
|
||||
stream_reader_memblock_ref() : m_data(NULL), m_data_size(0), m_pointer(0) {}
|
||||
|
||||
template<typename t_array> void set_data(const t_array & data) {
|
||||
pfc::assert_byte_type<typename t_array::t_item>();
|
||||
set_data(data.get_ptr(), data.get_size());
|
||||
}
|
||||
|
||||
void set_data(const void * data, t_size dataSize) {
|
||||
m_pointer = 0;
|
||||
m_data = reinterpret_cast<const unsigned char*>(data);
|
||||
m_data_size = dataSize;
|
||||
}
|
||||
|
||||
t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
||||
t_size remaining = m_data_size - m_pointer;
|
||||
t_size toread = p_bytes;
|
||||
if (toread > remaining) toread = remaining;
|
||||
if (toread > 0) {
|
||||
memcpy(p_buffer,m_data+m_pointer,toread);
|
||||
m_pointer += toread;
|
||||
}
|
||||
|
||||
return toread;
|
||||
}
|
||||
t_size get_remaining() const {return m_data_size - m_pointer;}
|
||||
void reset() {m_pointer = 0;}
|
||||
private:
|
||||
const unsigned char * m_data;
|
||||
t_size m_data_size,m_pointer;
|
||||
};
|
||||
|
||||
class stream_writer_buffer_simple : public stream_writer {
|
||||
public:
|
||||
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
||||
p_abort.check();
|
||||
t_size base = m_buffer.get_size();
|
||||
if (base + p_bytes < base) throw std::bad_alloc();
|
||||
m_buffer.set_size(base + p_bytes);
|
||||
memcpy( (t_uint8*) m_buffer.get_ptr() + base, p_buffer, p_bytes );
|
||||
}
|
||||
|
||||
typedef pfc::array_t<t_uint8,pfc::alloc_fast> t_buffer;
|
||||
|
||||
pfc::array_t<t_uint8,pfc::alloc_fast> m_buffer;
|
||||
};
|
||||
|
||||
template<class t_storage>
|
||||
class stream_writer_buffer_append_ref_t : public stream_writer
|
||||
{
|
||||
public:
|
||||
stream_writer_buffer_append_ref_t(t_storage & p_output) : m_output(p_output) {}
|
||||
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
||||
pfc::static_assert< sizeof(m_output[0]) == 1>();
|
||||
p_abort.check();
|
||||
t_size base = m_output.get_size();
|
||||
if (base + p_bytes < base) throw std::bad_alloc();
|
||||
m_output.set_size(base + p_bytes);
|
||||
memcpy( (t_uint8*) m_output.get_ptr() + base, p_buffer, p_bytes );
|
||||
}
|
||||
private:
|
||||
t_storage & m_output;
|
||||
};
|
||||
|
||||
class stream_reader_limited_ref : public stream_reader {
|
||||
public:
|
||||
stream_reader_limited_ref(stream_reader * p_reader,t_filesize p_limit) : m_reader(p_reader), m_remaining(p_limit) {}
|
||||
|
||||
t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
||||
if (p_bytes > m_remaining) p_bytes = (t_size)m_remaining;
|
||||
|
||||
t_size done = m_reader->read(p_buffer,p_bytes,p_abort);
|
||||
m_remaining -= done;
|
||||
return done;
|
||||
}
|
||||
|
||||
inline t_filesize get_remaining() const {return m_remaining;}
|
||||
|
||||
t_filesize skip(t_filesize p_bytes,abort_callback & p_abort) {
|
||||
if (p_bytes > m_remaining) p_bytes = m_remaining;
|
||||
t_filesize done = m_reader->skip(p_bytes,p_abort);
|
||||
m_remaining -= done;
|
||||
return done;
|
||||
}
|
||||
|
||||
void flush_remaining(abort_callback & p_abort) {
|
||||
if (m_remaining > 0) skip_object(m_remaining,p_abort);
|
||||
}
|
||||
|
||||
private:
|
||||
stream_reader * m_reader;
|
||||
t_filesize m_remaining;
|
||||
};
|
||||
|
||||
class stream_writer_chunk_dwordheader : public stream_writer
|
||||
{
|
||||
public:
|
||||
stream_writer_chunk_dwordheader(const service_ptr_t<file> & p_writer) : m_writer(p_writer) {}
|
||||
|
||||
void initialize(abort_callback & p_abort) {
|
||||
m_headerposition = m_writer->get_position(p_abort);
|
||||
m_written = 0;
|
||||
m_writer->write_lendian_t((t_uint32)0,p_abort);
|
||||
}
|
||||
|
||||
void finalize(abort_callback & p_abort) {
|
||||
t_filesize end_offset;
|
||||
end_offset = m_writer->get_position(p_abort);
|
||||
m_writer->seek(m_headerposition,p_abort);
|
||||
m_writer->write_lendian_t(pfc::downcast_guarded<t_uint32>(m_written),p_abort);
|
||||
m_writer->seek(end_offset,p_abort);
|
||||
}
|
||||
|
||||
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
||||
m_writer->write(p_buffer,p_bytes,p_abort);
|
||||
m_written += p_bytes;
|
||||
}
|
||||
|
||||
private:
|
||||
service_ptr_t<file> m_writer;
|
||||
t_filesize m_headerposition;
|
||||
t_filesize m_written;
|
||||
};
|
||||
|
||||
class stream_writer_chunk : public stream_writer
|
||||
{
|
||||
public:
|
||||
stream_writer_chunk(stream_writer * p_writer) : m_writer(p_writer), m_buffer_state(0) {}
|
||||
|
||||
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort);
|
||||
|
||||
void flush(abort_callback & p_abort);//must be called after writing before object is destroyed
|
||||
|
||||
private:
|
||||
stream_writer * m_writer;
|
||||
unsigned m_buffer_state;
|
||||
unsigned char m_buffer[255];
|
||||
};
|
||||
|
||||
class stream_reader_chunk : public stream_reader
|
||||
{
|
||||
public:
|
||||
stream_reader_chunk(stream_reader * p_reader) : m_reader(p_reader), m_buffer_state(0), m_buffer_size(0), m_eof(false) {}
|
||||
|
||||
t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort);
|
||||
|
||||
void flush(abort_callback & p_abort);//must be called after reading before object is destroyed
|
||||
|
||||
static void g_skip(stream_reader * p_stream,abort_callback & p_abort);
|
||||
|
||||
private:
|
||||
stream_reader * m_reader;
|
||||
t_size m_buffer_state, m_buffer_size;
|
||||
bool m_eof;
|
||||
unsigned char m_buffer[255];
|
||||
};
|
||||
|
||||
class stream_reader_dummy : public stream_reader { t_size read(void * p_buffer,t_size p_bytes,abort_callback & p_abort) {return 0;} };
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
template<bool isBigEndian = false> class stream_reader_formatter {
|
||||
public:
|
||||
stream_reader_formatter(stream_reader & p_stream,abort_callback & p_abort) : m_stream(p_stream), m_abort(p_abort) {}
|
||||
|
||||
template<typename t_int> void read_int(t_int & p_out) {
|
||||
if (isBigEndian) m_stream.read_bendian_t(p_out,m_abort);
|
||||
else m_stream.read_lendian_t(p_out,m_abort);
|
||||
}
|
||||
|
||||
void read_raw(void * p_buffer,t_size p_bytes) {
|
||||
m_stream.read_object(p_buffer,p_bytes,m_abort);
|
||||
}
|
||||
|
||||
void skip(t_size p_bytes) {m_stream.skip_object(p_bytes,m_abort);}
|
||||
|
||||
template<typename TArray> void read_raw(TArray& data) {
|
||||
pfc::assert_byte_type<typename TArray::t_item>();
|
||||
read_raw(data.get_ptr(),data.get_size());
|
||||
}
|
||||
template<typename TArray> void read_byte_block(TArray & data) {
|
||||
pfc::assert_byte_type<typename TArray::t_item>();
|
||||
t_uint32 size; read_int(size); data.set_size(size);
|
||||
read_raw(data);
|
||||
}
|
||||
template<typename TArray> void read_array(TArray & data) {
|
||||
t_uint32 size; *this >> size; data.set_size(size);
|
||||
for(t_uint32 walk = 0; walk < size; ++walk) *this >> data[walk];
|
||||
}
|
||||
|
||||
stream_reader & m_stream;
|
||||
abort_callback & m_abort;
|
||||
};
|
||||
|
||||
template<bool isBigEndian = false> class stream_writer_formatter {
|
||||
public:
|
||||
stream_writer_formatter(stream_writer & p_stream,abort_callback & p_abort) : m_stream(p_stream), m_abort(p_abort) {}
|
||||
|
||||
template<typename t_int> void write_int(t_int p_int) {
|
||||
if (isBigEndian) m_stream.write_bendian_t(p_int,m_abort);
|
||||
else m_stream.write_lendian_t(p_int,m_abort);
|
||||
}
|
||||
|
||||
void write_raw(const void * p_buffer,t_size p_bytes) {
|
||||
m_stream.write_object(p_buffer,p_bytes,m_abort);
|
||||
}
|
||||
template<typename TArray> void write_raw(const TArray& data) {
|
||||
pfc::assert_byte_type<typename TArray::t_item>();
|
||||
write_raw(data.get_ptr(),data.get_size());
|
||||
}
|
||||
|
||||
template<typename TArray> void write_byte_block(const TArray& data) {
|
||||
pfc::assert_byte_type<typename TArray::t_item>();
|
||||
write_int( pfc::downcast_guarded<t_uint32>(data.get_size()) );
|
||||
write_raw( data );
|
||||
}
|
||||
template<typename TArray> void write_array(const TArray& data) {
|
||||
const t_uint32 size = pfc::downcast_guarded<t_uint32>(data.get_size());
|
||||
*this << size;
|
||||
for(t_uint32 walk = 0; walk < size; ++walk) *this << data[walk];
|
||||
}
|
||||
|
||||
void write_string(const char * str) {
|
||||
const t_size len = strlen(str);
|
||||
*this << pfc::downcast_guarded<t_uint32>(len);
|
||||
write_raw(str, len);
|
||||
}
|
||||
void write_string(const char * str, t_size len_) {
|
||||
const t_size len = pfc::strlen_max(str, len_);
|
||||
*this << pfc::downcast_guarded<t_uint32>(len);
|
||||
write_raw(str, len);
|
||||
}
|
||||
|
||||
stream_writer & m_stream;
|
||||
abort_callback & m_abort;
|
||||
};
|
||||
|
||||
#define __DECLARE_UINT_OVERLOADS(TYPE) \
|
||||
template<bool isBigEndian> inline stream_reader_formatter<isBigEndian> & operator>>(stream_reader_formatter<isBigEndian> & p_stream,TYPE & p_int) {p_stream.read_int(p_int); return p_stream;} \
|
||||
template<bool isBigEndian> inline stream_writer_formatter<isBigEndian> & operator<<(stream_writer_formatter<isBigEndian> & p_stream,TYPE p_int) {p_stream.write_int(p_int); return p_stream;}
|
||||
|
||||
__DECLARE_UINT_OVERLOADS(t_uint8);
|
||||
__DECLARE_UINT_OVERLOADS(t_uint16);
|
||||
__DECLARE_UINT_OVERLOADS(t_uint32);
|
||||
__DECLARE_UINT_OVERLOADS(t_uint64);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
//SPECIAL FIX
|
||||
__DECLARE_UINT_OVERLOADS(unsigned long);
|
||||
#endif
|
||||
|
||||
#undef __DECLARE_UINT_OVERLOADS
|
||||
|
||||
#define __DECLARE_INT_OVERLOADS(TYPE) \
|
||||
template<bool isBigEndian> inline stream_reader_formatter<isBigEndian> & operator>>(stream_reader_formatter<isBigEndian> & p_stream,TYPE & p_int) {typename pfc::sized_int_t<sizeof(TYPE)>::t_unsigned temp;p_stream.read_int(temp); p_int = (TYPE) temp; return p_stream;} \
|
||||
template<bool isBigEndian> inline stream_writer_formatter<isBigEndian> & operator<<(stream_writer_formatter<isBigEndian> & p_stream,TYPE p_int) {p_stream.write_int((typename pfc::sized_int_t<sizeof(TYPE)>::t_unsigned)p_int); return p_stream;}
|
||||
|
||||
__DECLARE_INT_OVERLOADS(t_int8);
|
||||
__DECLARE_INT_OVERLOADS(t_int16);
|
||||
__DECLARE_INT_OVERLOADS(t_int32);
|
||||
__DECLARE_INT_OVERLOADS(t_int64);
|
||||
|
||||
#ifdef _MSC_VER
|
||||
//SPECIAL FIX
|
||||
__DECLARE_INT_OVERLOADS(long);
|
||||
#endif
|
||||
|
||||
#undef __DECLARE_INT_OVERLOADS
|
||||
|
||||
template<typename TVal> class __IsTypeByte {
|
||||
public:
|
||||
enum {value = pfc::is_same_type<TVal,t_int8>::value || pfc::is_same_type<TVal,t_uint8>::value};
|
||||
};
|
||||
|
||||
template<bool isBigEndian,typename TVal,size_t Count> stream_reader_formatter<isBigEndian> & operator>>(stream_reader_formatter<isBigEndian> & p_stream,TVal (& p_array)[Count]) {
|
||||
if (__IsTypeByte<TVal>::value) {
|
||||
p_stream.read_raw(p_array,Count);
|
||||
} else {
|
||||
for(t_size walk = 0; walk < Count; ++walk) p_stream >> p_array[walk];
|
||||
}
|
||||
return p_stream;
|
||||
}
|
||||
|
||||
template<bool isBigEndian,typename TVal,size_t Count> stream_writer_formatter<isBigEndian> & operator<<(stream_writer_formatter<isBigEndian> & p_stream,TVal const (& p_array)[Count]) {
|
||||
if (__IsTypeByte<TVal>::value) {
|
||||
p_stream.write_raw(p_array,Count);
|
||||
} else {
|
||||
for(t_size walk = 0; walk < Count; ++walk) p_stream << p_array[walk];
|
||||
}
|
||||
return p_stream;
|
||||
}
|
||||
|
||||
#define FB2K_STREAM_READER_OVERLOAD(type) \
|
||||
template<bool isBigEndian> stream_reader_formatter<isBigEndian> & operator>>(stream_reader_formatter<isBigEndian> & stream,type & value)
|
||||
|
||||
#define FB2K_STREAM_WRITER_OVERLOAD(type) \
|
||||
template<bool isBigEndian> stream_writer_formatter<isBigEndian> & operator<<(stream_writer_formatter<isBigEndian> & stream,const type & value)
|
||||
|
||||
FB2K_STREAM_READER_OVERLOAD(GUID) {
|
||||
return stream >> value.Data1 >> value.Data2 >> value.Data3 >> value.Data4;
|
||||
}
|
||||
|
||||
FB2K_STREAM_WRITER_OVERLOAD(GUID) {
|
||||
return stream << value.Data1 << value.Data2 << value.Data3 << value.Data4;
|
||||
}
|
||||
|
||||
FB2K_STREAM_READER_OVERLOAD(pfc::string) {
|
||||
t_uint32 len; stream >> len;
|
||||
value = stream.m_stream.read_string_ex(len,stream.m_abort);
|
||||
return stream;
|
||||
}
|
||||
|
||||
FB2K_STREAM_WRITER_OVERLOAD(pfc::string) {
|
||||
stream << pfc::downcast_guarded<t_uint32>(value.length());
|
||||
stream.write_raw(value.ptr(),value.length());
|
||||
return stream;
|
||||
}
|
||||
|
||||
FB2K_STREAM_READER_OVERLOAD(pfc::string_base) {
|
||||
t_uint32 len; stream >> len;
|
||||
try {
|
||||
char * buf = value.lock_buffer(len);
|
||||
stream.read_raw(buf,len);
|
||||
} catch(...) {
|
||||
value.unlock_buffer(); throw;
|
||||
}
|
||||
value.unlock_buffer();
|
||||
return stream;
|
||||
}
|
||||
FB2K_STREAM_WRITER_OVERLOAD(pfc::string_base) {
|
||||
const char * val = value.get_ptr();
|
||||
const t_size len = strlen(val);
|
||||
stream << pfc::downcast_guarded<t_uint32>(len);
|
||||
stream.write_raw(val,len);
|
||||
return stream;
|
||||
}
|
||||
|
||||
|
||||
FB2K_STREAM_WRITER_OVERLOAD(float) {
|
||||
union {
|
||||
float f; t_uint32 i;
|
||||
} u; u.f = value;
|
||||
return stream << u.i;
|
||||
}
|
||||
|
||||
FB2K_STREAM_READER_OVERLOAD(float) {
|
||||
union { float f; t_uint32 i;} u;
|
||||
stream >> u.i; value = u.f;
|
||||
return stream;
|
||||
}
|
||||
|
||||
FB2K_STREAM_WRITER_OVERLOAD(double) {
|
||||
union {
|
||||
double f; t_uint64 i;
|
||||
} u; u.f = value;
|
||||
return stream << u.i;
|
||||
}
|
||||
|
||||
FB2K_STREAM_READER_OVERLOAD(double) {
|
||||
union { double f; t_uint64 i;} u;
|
||||
stream >> u.i; value = u.f;
|
||||
return stream;
|
||||
}
|
||||
|
||||
|
||||
template<bool BE = false>
|
||||
class stream_writer_formatter_simple : public stream_writer_formatter<BE> {
|
||||
public:
|
||||
stream_writer_formatter_simple() : stream_writer_formatter(_m_stream,_m_abort), m_buffer(_m_stream.m_buffer) {}
|
||||
|
||||
typedef stream_writer_buffer_simple::t_buffer t_buffer;
|
||||
t_buffer & m_buffer;
|
||||
private:
|
||||
stream_writer_buffer_simple _m_stream;
|
||||
abort_callback_dummy _m_abort;
|
||||
};
|
||||
|
||||
template<bool BE = false>
|
||||
class stream_reader_formatter_simple_ref : public stream_reader_formatter<BE> {
|
||||
public:
|
||||
stream_reader_formatter_simple_ref(const void * source, t_size sourceSize) : stream_reader_formatter(_m_stream,_m_abort), _m_stream(source,sourceSize) {}
|
||||
template<typename TSource> stream_reader_formatter_simple_ref(const TSource& source) : stream_reader_formatter(_m_stream,_m_abort), _m_stream(source) {}
|
||||
stream_reader_formatter_simple_ref() : stream_reader_formatter(_m_stream,_m_abort) {}
|
||||
|
||||
void set_data(const void * source, t_size sourceSize) {_m_stream.set_data(source,sourceSize);}
|
||||
template<typename TSource> void set_data(const TSource & source) {_m_stream.set_data(source);}
|
||||
|
||||
void reset() {_m_stream.reset();}
|
||||
t_size get_remaining() {return _m_stream.get_remaining();}
|
||||
private:
|
||||
stream_reader_memblock_ref _m_stream;
|
||||
abort_callback_dummy _m_abort;
|
||||
};
|
||||
|
||||
template<bool BE = false>
|
||||
class stream_reader_formatter_simple : public stream_reader_formatter_simple_ref<BE> {
|
||||
public:
|
||||
stream_reader_formatter_simple() {}
|
||||
stream_reader_formatter_simple(const void * source, t_size sourceSize) {set_data(source,sourceSize);}
|
||||
template<typename TSource> stream_reader_formatter_simple(const TSource & source) {set_data(source);}
|
||||
|
||||
void set_data(const void * source, t_size sourceSize) {
|
||||
m_content.set_data_fromptr(reinterpret_cast<const t_uint8*>(source), sourceSize);
|
||||
onContentChange();
|
||||
}
|
||||
template<typename TSource> void set_data(const TSource & source) {
|
||||
m_content = source;
|
||||
onContentChange();
|
||||
}
|
||||
private:
|
||||
void onContentChange() {
|
||||
stream_reader_formatter_simple_ref<BE>::set_data(m_content);
|
||||
}
|
||||
pfc::array_t<t_uint8> m_content;
|
||||
};
|
|
@ -0,0 +1,95 @@
|
|||
// This is the master foobar2000 SDK header file; it includes headers for all functionality exposed through the SDK project. #include this in your source code, never reference any of the other headers directly.
|
||||
|
||||
#ifndef _FOOBAR2000_H_
|
||||
#define _FOOBAR2000_H_
|
||||
|
||||
#ifndef UNICODE
|
||||
#error Only UNICODE environment supported.
|
||||
#endif
|
||||
|
||||
#include "../../pfc/pfc.h"
|
||||
|
||||
#include "../shared/shared.h"
|
||||
|
||||
#ifndef NOTHROW
|
||||
#ifdef _MSC_VER
|
||||
#define NOTHROW __declspec(nothrow)
|
||||
#else
|
||||
#define NOTHROW
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#define FB2KAPI /*NOTHROW*/
|
||||
|
||||
typedef const char * pcchar;
|
||||
|
||||
#include "core_api.h"
|
||||
#include "service.h"
|
||||
|
||||
#include "completion_notify.h"
|
||||
#include "abort_callback.h"
|
||||
#include "componentversion.h"
|
||||
#include "preferences_page.h"
|
||||
#include "coreversion.h"
|
||||
#include "filesystem.h"
|
||||
#include "audio_chunk.h"
|
||||
#include "cfg_var.h"
|
||||
#include "mem_block_container.h"
|
||||
#include "audio_postprocessor.h"
|
||||
#include "playable_location.h"
|
||||
#include "file_info.h"
|
||||
#include "file_info_impl.h"
|
||||
#include "metadb_handle.h"
|
||||
#include "metadb.h"
|
||||
#include "console.h"
|
||||
#include "dsp.h"
|
||||
#include "dsp_manager.h"
|
||||
#include "initquit.h"
|
||||
#include "event_logger.h"
|
||||
#include "input.h"
|
||||
#include "input_impl.h"
|
||||
#include "menu.h"
|
||||
#include "contextmenu.h"
|
||||
#include "contextmenu_manager.h"
|
||||
#include "menu_helpers.h"
|
||||
#include "modeless_dialog.h"
|
||||
#include "playback_control.h"
|
||||
#include "play_callback.h"
|
||||
#include "playlist.h"
|
||||
#include "playlist_loader.h"
|
||||
#include "replaygain.h"
|
||||
#include "resampler.h"
|
||||
#include "tag_processor.h"
|
||||
#include "titleformat.h"
|
||||
#include "ui.h"
|
||||
#include "unpack.h"
|
||||
#include "vis.h"
|
||||
#include "packet_decoder.h"
|
||||
#include "commandline.h"
|
||||
#include "genrand.h"
|
||||
#include "file_operation_callback.h"
|
||||
#include "library_manager.h"
|
||||
#include "config_io_callback.h"
|
||||
#include "popup_message.h"
|
||||
#include "app_close_blocker.h"
|
||||
#include "config_object.h"
|
||||
#include "config_object_impl.h"
|
||||
#include "threaded_process.h"
|
||||
#include "hasher_md5.h"
|
||||
#include "message_loop.h"
|
||||
#include "input_file_type.h"
|
||||
#include "chapterizer.h"
|
||||
#include "link_resolver.h"
|
||||
#include "main_thread_callback.h"
|
||||
#include "advconfig.h"
|
||||
#include "info_lookup_handler.h"
|
||||
#include "track_property.h"
|
||||
|
||||
#include "album_art.h"
|
||||
#include "icon_remap.h"
|
||||
#include "ole_interaction.h"
|
||||
#include "search_tools.h"
|
||||
#include "autoplaylist.h"
|
||||
#include "replaygain_scanner.h"
|
||||
|
||||
#endif //_FOOBAR2000_H_
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,13 @@
|
|||
//! PRNG service. Implemented by the core, do not reimplement. Use g_create() helper function to instantiate.
|
||||
class NOVTABLE genrand_service : public service_base
|
||||
{
|
||||
public:
|
||||
//! Seeds the PRNG with specified value.
|
||||
virtual void seed(unsigned val) = 0;
|
||||
//! Returns random value N, where 0 <= N < range.
|
||||
virtual unsigned genrand(unsigned range)=0;
|
||||
|
||||
static service_ptr_t<genrand_service> g_create() {return standard_api_create_t<genrand_service>();}
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(genrand_service);
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,22 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
GUID hasher_md5::guid_from_result(const hasher_md5_result & param)
|
||||
{
|
||||
assert(sizeof(GUID) == sizeof(hasher_md5_result));
|
||||
GUID temp = * reinterpret_cast<const GUID*>(¶m);
|
||||
byte_order::order_le_to_native_t(temp);
|
||||
return temp;
|
||||
}
|
||||
|
||||
hasher_md5_result hasher_md5::process_single(const void * p_buffer,t_size p_bytes)
|
||||
{
|
||||
hasher_md5_state state;
|
||||
initialize(state);
|
||||
process(state,p_buffer,p_bytes);
|
||||
return get_result(state);
|
||||
}
|
||||
|
||||
GUID hasher_md5::process_single_guid(const void * p_buffer,t_size p_bytes)
|
||||
{
|
||||
return guid_from_result(process_single(p_buffer,p_bytes));
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
struct hasher_md5_state {
|
||||
char m_data[128];
|
||||
};
|
||||
|
||||
struct hasher_md5_result {
|
||||
char m_data[16];
|
||||
};
|
||||
|
||||
inline bool operator==(const hasher_md5_result & p_item1,const hasher_md5_result & p_item2) {return memcmp(&p_item1,&p_item2,sizeof(hasher_md5_result)) == 0;}
|
||||
inline bool operator!=(const hasher_md5_result & p_item1,const hasher_md5_result & p_item2) {return memcmp(&p_item1,&p_item2,sizeof(hasher_md5_result)) != 0;}
|
||||
|
||||
namespace pfc {
|
||||
template<> class traits_t<hasher_md5_state> : public traits_rawobject {};
|
||||
template<> class traits_t<hasher_md5_result> : public traits_rawobject {};
|
||||
}
|
||||
|
||||
class NOVTABLE hasher_md5 : public service_base
|
||||
{
|
||||
public:
|
||||
|
||||
virtual void initialize(hasher_md5_state & p_state) = 0;
|
||||
virtual void process(hasher_md5_state & p_state,const void * p_buffer,t_size p_bytes) = 0;
|
||||
virtual hasher_md5_result get_result(const hasher_md5_state & p_state) = 0;
|
||||
|
||||
|
||||
static GUID guid_from_result(const hasher_md5_result & param);
|
||||
|
||||
hasher_md5_result process_single(const void * p_buffer,t_size p_bytes);
|
||||
GUID process_single_guid(const void * p_buffer,t_size p_bytes);
|
||||
GUID get_result_guid(const hasher_md5_state & p_state) {return guid_from_result(get_result(p_state));}
|
||||
|
||||
|
||||
//! Helper
|
||||
void process_string(hasher_md5_state & p_state,const char * p_string,t_size p_length = infinite) {return process(p_state,p_string,pfc::strlen_max(p_string,p_length));}
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(hasher_md5);
|
||||
};
|
||||
|
||||
|
||||
class stream_writer_hasher_md5 : public stream_writer {
|
||||
public:
|
||||
stream_writer_hasher_md5() {
|
||||
m_hasher->initialize(m_state);
|
||||
}
|
||||
void write(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) {
|
||||
p_abort.check();
|
||||
m_hasher->process(m_state,p_buffer,p_bytes);
|
||||
}
|
||||
hasher_md5_result result() const {
|
||||
return m_hasher->get_result(m_state);
|
||||
}
|
||||
GUID resultGuid() const {
|
||||
return hasher_md5::guid_from_result(result());
|
||||
}
|
||||
private:
|
||||
hasher_md5_state m_state;
|
||||
static_api_ptr_t<hasher_md5> m_hasher;
|
||||
};
|
||||
template<bool isBigEndian = false>
|
||||
class stream_formatter_hasher_md5 : public stream_writer_formatter<isBigEndian> {
|
||||
public:
|
||||
stream_formatter_hasher_md5() : stream_writer_formatter<isBigEndian>(_m_stream,_m_abort) {}
|
||||
|
||||
hasher_md5_result result() const {
|
||||
return _m_stream.result();
|
||||
}
|
||||
GUID resultGuid() const {
|
||||
return hasher_md5::guid_from_result(result());
|
||||
}
|
||||
private:
|
||||
abort_callback_dummy _m_abort;
|
||||
stream_writer_hasher_md5 _m_stream;
|
||||
};
|
|
@ -0,0 +1,26 @@
|
|||
//! New in 0.9.5; allows your file format to use another icon than <extension>.ico when registering the file type with Windows shell. \n
|
||||
//! Implementation: use icon_remapping_impl, or simply: static service_factory_single_t<icon_remapping_impl> myicon("ext","iconname.ico");
|
||||
class icon_remapping : public service_base {
|
||||
public:
|
||||
//! @param p_extension File type extension being queried.
|
||||
//! @param p_iconname Receives the icon name to use, including the .ico extension.
|
||||
//! @returns True when p_iconname has been set, false if we don't recognize the specified extension.
|
||||
virtual bool query(const char * p_extension,pfc::string_base & p_iconname) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(icon_remapping);
|
||||
};
|
||||
|
||||
//! Standard implementation of icon_remapping.
|
||||
class icon_remapping_impl : public icon_remapping {
|
||||
public:
|
||||
icon_remapping_impl(const char * p_extension,const char * p_iconname) : m_extension(p_extension), m_iconname(p_iconname) {}
|
||||
bool query(const char * p_extension,pfc::string_base & p_iconname) {
|
||||
if (stricmp_utf8(p_extension,m_extension) == 0) {
|
||||
p_iconname = m_iconname; return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
private:
|
||||
pfc::string8 m_extension,m_iconname;
|
||||
};
|
|
@ -0,0 +1,24 @@
|
|||
//! Service used to access various external (online) track info lookup services, such as freedb, to update file tags with info retrieved from those services.
|
||||
class NOVTABLE info_lookup_handler : public service_base {
|
||||
public:
|
||||
enum {
|
||||
flag_album_lookup = 1 << 0,
|
||||
flag_track_lookup = 1 << 1,
|
||||
};
|
||||
|
||||
//! Retrieves human-readable name of the lookup handler to display in user interface.
|
||||
virtual void get_name(pfc::string_base & p_out) = 0;
|
||||
|
||||
//! Returns one or more of flag_track_lookup, and flag_album_lookup.
|
||||
virtual t_uint32 get_flags() = 0;
|
||||
|
||||
virtual HICON get_icon(int p_width, int p_height) = 0;
|
||||
|
||||
//! Performs a lookup. Creates a modeless dialog and returns immediately.
|
||||
//! @param p_items Items to look up.
|
||||
//! @param p_notify Callback to notify caller when the operation has completed. Call on_completion with status code 0 to signal failure/abort, or with code 1 to signal success / new infos in metadb.
|
||||
//! @param p_parent Parent window for the lookup dialog. Caller will typically disable the window while lookup is in progress and enable it back when completion is signaled.
|
||||
virtual void lookup(const pfc::list_base_const_t<metadb_handle_ptr> & p_items,completion_notify_ptr p_notify,HWND p_parent) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(info_lookup_handler);
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
//! Basic callback startup/shutdown callback, on_init is called after the main window has been created, on_quit is called before the main window is destroyed.
|
||||
//! To register: static initquit_factory_t<myclass> myclass_factory;
|
||||
class NOVTABLE initquit : public service_base
|
||||
{
|
||||
public:
|
||||
virtual void on_init() {}
|
||||
virtual void on_quit() {}
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(initquit);
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class initquit_factory_t : public service_factory_single_t<T> {};
|
|
@ -0,0 +1,248 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
|
||||
bool input_entry::g_find_service_by_path(service_ptr_t<input_entry> & p_out,const char * p_path)
|
||||
{
|
||||
service_ptr_t<input_entry> ptr;
|
||||
service_enum_t<input_entry> e;
|
||||
pfc::string_extension ext(p_path);
|
||||
while(e.next(ptr))
|
||||
{
|
||||
if (ptr->is_our_path(p_path,ext))
|
||||
{
|
||||
p_out = ptr;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool input_entry::g_find_service_by_content_type(service_ptr_t<input_entry> & p_out,const char * p_content_type)
|
||||
{
|
||||
service_ptr_t<input_entry> ptr;
|
||||
service_enum_t<input_entry> e;
|
||||
while(e.next(ptr))
|
||||
{
|
||||
if (ptr->is_our_content_type(p_content_type))
|
||||
{
|
||||
p_out = ptr;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
static void prepare_for_open(service_ptr_t<input_entry> & p_service,service_ptr_t<file> & p_file,const char * p_path,filesystem::t_open_mode p_open_mode,abort_callback & p_abort,bool p_from_redirect)
|
||||
{
|
||||
if (p_file.is_empty())
|
||||
{
|
||||
service_ptr_t<filesystem> fs;
|
||||
if (filesystem::g_get_interface(fs,p_path)) {
|
||||
if (fs->supports_content_types()) {
|
||||
fs->open(p_file,p_path,p_open_mode,p_abort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (p_file.is_valid())
|
||||
{
|
||||
pfc::string8 content_type;
|
||||
if (p_file->get_content_type(content_type))
|
||||
{
|
||||
if (input_entry::g_find_service_by_content_type(p_service,content_type))
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (input_entry::g_find_service_by_path(p_service,p_path))
|
||||
{
|
||||
if (p_from_redirect && p_service->is_redirect()) throw exception_io_unsupported_format();
|
||||
return;
|
||||
}
|
||||
|
||||
throw exception_io_unsupported_format();
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
bool g_find_inputs_by_content_type(pfc::list_base_t<service_ptr_t<input_entry> > & p_out,const char * p_content_type,bool p_from_redirect) {
|
||||
service_enum_t<input_entry> e;
|
||||
service_ptr_t<input_entry> ptr;
|
||||
bool ret = false;
|
||||
while(e.next(ptr)) {
|
||||
if (!(p_from_redirect && ptr->is_redirect())) {
|
||||
if (ptr->is_our_content_type(p_content_type)) {p_out.add_item(ptr); ret = true;}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool g_find_inputs_by_path(pfc::list_base_t<service_ptr_t<input_entry> > & p_out,const char * p_path,bool p_from_redirect) {
|
||||
service_enum_t<input_entry> e;
|
||||
service_ptr_t<input_entry> ptr;
|
||||
pfc::string_extension extension(p_path);
|
||||
bool ret = false;
|
||||
while(e.next(ptr)) {
|
||||
if (!(p_from_redirect && ptr->is_redirect())) {
|
||||
if (ptr->is_our_path(p_path,extension)) {p_out.add_item(ptr); ret = true;}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
template<typename t_service> void g_open_from_list(service_ptr_t<t_service> & p_instance,pfc::list_base_const_t<service_ptr_t<input_entry> > const & p_list,service_ptr_t<file> const & p_filehint,const char * p_path,abort_callback & p_abort) {
|
||||
const t_size count = p_list.get_count();
|
||||
if (count == 1) {
|
||||
p_list[0]->open(p_instance,p_filehint,p_path,p_abort);
|
||||
} else {
|
||||
bool got_bad_data = false, got_bad_data_multi = false;
|
||||
bool done = false;
|
||||
pfc::string8 bad_data_message;
|
||||
for(t_size n=0;n<count && !done;n++) {
|
||||
try {
|
||||
p_list[n]->open(p_instance,p_filehint,p_path,p_abort);
|
||||
done = true;
|
||||
} catch(exception_io_unsupported_format) {
|
||||
//do nothing, skip over
|
||||
} catch(exception_io_data const & e) {
|
||||
if (!got_bad_data) bad_data_message = e.what();
|
||||
else got_bad_data_multi = true;
|
||||
got_bad_data = true;
|
||||
}
|
||||
}
|
||||
if (!done) {
|
||||
if (got_bad_data_multi) throw exception_io_data();
|
||||
else if (got_bad_data) throw exception_io_data(bad_data_message);
|
||||
else throw exception_io_unsupported_format();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template<typename t_service> bool needs_write_access() {return false;}
|
||||
template<> bool needs_write_access<input_info_writer>() {return true;}
|
||||
|
||||
template<typename t_service> void g_open_t(service_ptr_t<t_service> & p_instance,service_ptr_t<file> const & p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect) {
|
||||
service_ptr_t<file> l_file = p_filehint;
|
||||
if (l_file.is_empty()) {
|
||||
service_ptr_t<filesystem> fs;
|
||||
if (filesystem::g_get_interface(fs,p_path)) {
|
||||
if (fs->supports_content_types()) {
|
||||
fs->open(l_file,p_path,needs_write_access<t_service>() ? filesystem::open_mode_write_existing : filesystem::open_mode_read,p_abort);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (l_file.is_valid()) {
|
||||
pfc::string8 content_type;
|
||||
if (l_file->get_content_type(content_type)) {
|
||||
pfc::list_hybrid_t<service_ptr_t<input_entry>,4> list;
|
||||
if (g_find_inputs_by_content_type(list,content_type,p_from_redirect)) {
|
||||
g_open_from_list(p_instance,list,l_file,p_path,p_abort);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
pfc::list_hybrid_t<service_ptr_t<input_entry>,4> list;
|
||||
if (g_find_inputs_by_path(list,p_path,p_from_redirect)) {
|
||||
g_open_from_list(p_instance,list,l_file,p_path,p_abort);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
throw exception_io_unsupported_format();
|
||||
}
|
||||
};
|
||||
|
||||
void input_entry::g_open_for_decoding(service_ptr_t<input_decoder> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect) {
|
||||
TRACK_CALL_TEXT("input_entry::g_open_for_decoding");
|
||||
#if 1
|
||||
g_open_t(p_instance,p_filehint,p_path,p_abort,p_from_redirect);
|
||||
#else
|
||||
service_ptr_t<file> filehint = p_filehint;
|
||||
service_ptr_t<input_entry> entry;
|
||||
|
||||
prepare_for_open(entry,filehint,p_path,filesystem::open_mode_read,p_abort,p_from_redirect);
|
||||
|
||||
entry->open_for_decoding(p_instance,filehint,p_path,p_abort);
|
||||
#endif
|
||||
|
||||
}
|
||||
|
||||
void input_entry::g_open_for_info_read(service_ptr_t<input_info_reader> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect) {
|
||||
TRACK_CALL_TEXT("input_entry::g_open_for_info_read");
|
||||
#if 1
|
||||
g_open_t(p_instance,p_filehint,p_path,p_abort,p_from_redirect);
|
||||
#else
|
||||
service_ptr_t<file> filehint = p_filehint;
|
||||
service_ptr_t<input_entry> entry;
|
||||
|
||||
prepare_for_open(entry,filehint,p_path,filesystem::open_mode_read,p_abort,p_from_redirect);
|
||||
|
||||
entry->open_for_info_read(p_instance,filehint,p_path,p_abort);
|
||||
#endif
|
||||
}
|
||||
|
||||
void input_entry::g_open_for_info_write(service_ptr_t<input_info_writer> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect) {
|
||||
TRACK_CALL_TEXT("input_entry::g_open_for_info_write");
|
||||
#if 1
|
||||
g_open_t(p_instance,p_filehint,p_path,p_abort,p_from_redirect);
|
||||
#else
|
||||
service_ptr_t<file> filehint = p_filehint;
|
||||
service_ptr_t<input_entry> entry;
|
||||
|
||||
prepare_for_open(entry,filehint,p_path,filesystem::open_mode_write_existing,p_abort,p_from_redirect);
|
||||
|
||||
entry->open_for_info_write(p_instance,filehint,p_path,p_abort);
|
||||
#endif
|
||||
}
|
||||
|
||||
void input_entry::g_open_for_info_write_timeout(service_ptr_t<input_info_writer> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,double p_timeout,bool p_from_redirect) {
|
||||
pfc::lores_timer timer;
|
||||
timer.start();
|
||||
for(;;) {
|
||||
try {
|
||||
g_open_for_info_write(p_instance,p_filehint,p_path,p_abort,p_from_redirect);
|
||||
break;
|
||||
} catch(exception_io_sharing_violation) {
|
||||
if (timer.query() > p_timeout) throw;
|
||||
p_abort.sleep(0.01);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool input_entry::g_is_supported_path(const char * p_path)
|
||||
{
|
||||
service_ptr_t<input_entry> ptr;
|
||||
service_enum_t<input_entry> e;
|
||||
pfc::string_extension ext(p_path);
|
||||
while(e.next(ptr))
|
||||
{
|
||||
if (ptr->is_our_path(p_path,ext)) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
void input_open_file_helper(service_ptr_t<file> & p_file,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort)
|
||||
{
|
||||
if (p_file.is_empty()) {
|
||||
switch(p_reason) {
|
||||
default:
|
||||
throw pfc::exception_bug_check_v2();
|
||||
case input_open_info_read:
|
||||
case input_open_decode:
|
||||
filesystem::g_open(p_file,p_path,filesystem::open_mode_read,p_abort);
|
||||
break;
|
||||
case input_open_info_write:
|
||||
filesystem::g_open(p_file,p_path,filesystem::open_mode_write_existing,p_abort);
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
p_file->reopen(p_abort);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,189 @@
|
|||
enum {
|
||||
input_flag_no_seeking = 1 << 0,
|
||||
input_flag_no_looping = 1 << 1,
|
||||
input_flag_playback = 1 << 2,
|
||||
input_flag_testing_integrity = 1 << 3,
|
||||
input_flag_allow_inaccurate_seeking = 1 << 4,
|
||||
|
||||
input_flag_simpledecode = input_flag_no_seeking|input_flag_no_looping,
|
||||
};
|
||||
|
||||
//! Class providing interface for retrieval of information (metadata, duration, replaygain, other tech infos) from files. Also see: file_info. \n
|
||||
//! Instantiating: see input_entry.\n
|
||||
//! Implementing: see input_impl.
|
||||
|
||||
class NOVTABLE input_info_reader : public service_base
|
||||
{
|
||||
public:
|
||||
//! Retrieves count of subsongs in the file. 1 for non-multisubsong-enabled inputs.
|
||||
//! Note: multi-subsong handling is disabled for remote files (see: filesystem::is_remote) for performance reasons. Remote files are always assumed to be single-subsong, with null index.
|
||||
virtual t_uint32 get_subsong_count() = 0;
|
||||
|
||||
//! Retrieves identifier of specified subsong; this identifier is meant to be used in playable_location as well as a parameter for input_info_reader::get_info().
|
||||
//! @param p_index Index of subsong to query. Must be >=0 and < get_subsong_count().
|
||||
virtual t_uint32 get_subsong(t_uint32 p_index) = 0;
|
||||
|
||||
//! Retrieves information about specified subsong.
|
||||
//! @param p_subsong Identifier of the subsong to query. See: input_info_reader::get_subsong(), playable_location.
|
||||
//! @param p_info file_info object to fill. Must be empty on entry.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
virtual void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) = 0;
|
||||
|
||||
//! Retrieves file stats. Equivalent to calling get_stats() on file object.
|
||||
virtual t_filestats get_file_stats(abort_callback & p_abort) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(input_info_reader,service_base);
|
||||
};
|
||||
|
||||
//! Class providing interface for retrieval of PCM audio data from files.\n
|
||||
//! Instantiating: see input_entry.\n
|
||||
//! Implementing: see input_impl.
|
||||
|
||||
class NOVTABLE input_decoder : public input_info_reader
|
||||
{
|
||||
public:
|
||||
//! Prepares to decode specified subsong; resets playback position to the beginning of specified subsong. This must be called first, before any other input_decoder methods (other than those inherited from input_info_reader). \n
|
||||
//! It is legal to set initialize() more than once, with same or different subsong, to play either the same subsong again or another subsong from same file without full reopen.\n
|
||||
//! Warning: this interface inherits from input_info_reader, it is legal to call any input_info_reader methods even during decoding! Call order is not defined, other than initialize() requirement before calling other input_decoder methods.\n
|
||||
//! @param p_subsong Subsong to decode. Should always be 0 for non-multi-subsong-enabled inputs.
|
||||
//! @param p_flags Specifies additional hints for decoding process. It can be null, or a combination of one or more following constants: \n
|
||||
//! input_flag_no_seeking - Indicates that seek() will never be called. Can be used to avoid building potentially expensive seektables when only sequential reading is needed.\n
|
||||
//! input_flag_no_looping - Certain input implementations can be configured to utilize looping info from file formats they process and keep playing single file forever, or keep repeating it specified number of times. This flag indicates that such features should be disabled, for e.g. ReplayGain scan or conversion.\n
|
||||
//! input_flag_playback - Indicates that decoding process will be used for realtime playback rather than conversion. This can be used to reconfigure features that are relevant only for conversion and take a lot of resources, such as very slow secure CDDA reading. \n
|
||||
//! input_flag_testing_integrity - Indicates that we're testing integrity of the file. Any recoverable problems where decoding would normally continue should cause decoder to fail with exception_io_data.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
virtual void initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort) = 0;
|
||||
|
||||
//! Reads/decodes one chunk of audio data. Use false return value to signal end of file (no more data to return). Before calling run(), decoding must be initialized by initialize() call.
|
||||
//! @param p_chunk audio_chunk object receiving decoded data. Contents are valid only the method returns true.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
//! @returns true on success (new data decoded), false on EOF.
|
||||
virtual bool run(audio_chunk & p_chunk,abort_callback & p_abort) = 0;
|
||||
|
||||
//! Seeks to specified time offset. Before seeking or other decoding calls, decoding must be initialized with initialize() call.
|
||||
//! @param p_seconds Time to seek to, in seconds. If p_seconds exceeds length of the object being decoded, succeed, and then return false from next run() call.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
virtual void seek(double p_seconds,abort_callback & p_abort) = 0;
|
||||
|
||||
//! Queries whether resource being read/decoded is seekable. If p_value is set to false, all seek() calls will fail. Before calling can_seek() or other decoding calls, decoding must be initialized with initialize() call.
|
||||
virtual bool can_seek() = 0;
|
||||
|
||||
//! This function is used to signal dynamic VBR bitrate, etc. Called after each run() (or not called at all if caller doesn't care about dynamic info).
|
||||
//! @param p_out Initially contains currently displayed info (either last get_dynamic_info result or current cached info), use this object to return changed info.
|
||||
//! @param p_timestamp_delta Indicates when returned info should be displayed (in seconds, relative to first sample of last decoded chunk), initially set to 0.
|
||||
//! @returns false to keep old info, or true to indicate that changes have been made to p_info and those should be displayed.
|
||||
virtual bool get_dynamic_info(file_info & p_out, double & p_timestamp_delta) = 0;
|
||||
|
||||
//! This function is used to signal dynamic live stream song titles etc. Called after each run() (or not called at all if caller doesn't care about dynamic info). The difference between this and get_dynamic_info() is frequency and relevance of dynamic info changes - get_dynamic_info_track() returns new info only on track change in the stream, returning new titles etc.
|
||||
//! @param p_out Initially contains currently displayed info (either last get_dynamic_info_track result or current cached info), use this object to return changed info.
|
||||
//! @param p_timestamp_delta Indicates when returned info should be displayed (in seconds, relative to first sample of last decoded chunk), initially set to 0.
|
||||
//! @returns false to keep old info, or true to indicate that changes have been made to p_info and those should be displayed.
|
||||
virtual bool get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) = 0;
|
||||
|
||||
//! Called from playback thread before sleeping.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
virtual void on_idle(abort_callback & p_abort) = 0;
|
||||
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(input_decoder,input_info_reader);
|
||||
};
|
||||
|
||||
|
||||
class NOVTABLE input_decoder_v2 : public input_decoder {
|
||||
FB2K_MAKE_SERVICE_INTERFACE(input_decoder_v2, input_decoder)
|
||||
public:
|
||||
|
||||
//! OPTIONAL, throws pfc::exception_not_implemented() when not supported by this implementation.
|
||||
//! Special version of run(). Returns an audio_chunk object as well as a raw data block containing original PCM stream. This is mainly used for MD5 checks on lossless formats. \n
|
||||
//! If you set a "MD5" tech info entry in get_info(), you should make sure that run_raw() returns data stream that can be used to verify it. \n
|
||||
//! Returned raw data should be possible to cut into individual samples; size in bytes should be divisible by audio_chunk's sample count for splitting in case partial output is needed (with cuesheets etc).
|
||||
virtual bool run_raw(audio_chunk & out, mem_block_container & outRaw, abort_callback & abort) = 0;
|
||||
|
||||
//! OPTIONAL, the call is ignored if this implementation doesn't support status logging. \n
|
||||
//! Mainly used to generate logs when ripping CDs etc.
|
||||
virtual void set_logger(event_logger::ptr ptr) = 0;
|
||||
};
|
||||
|
||||
//! Class providing interface for writing metadata and replaygain info to files. Also see: file_info. \n
|
||||
//! Instantiating: see input_entry.\n
|
||||
//! Implementing: see input_impl.
|
||||
|
||||
class NOVTABLE input_info_writer : public input_info_reader
|
||||
{
|
||||
public:
|
||||
//! Tells the service to update file tags with new info for specified subsong.
|
||||
//! @param p_subsong Subsong to update. Should be always 0 for non-multisubsong-enabled inputs.
|
||||
//! @param p_info New info to write. Sometimes not all contents of p_info can be written. Caller will typically read info back after successful write, so e.g. tech infos that change with retag are properly maintained.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation. WARNING: abort_callback object is provided for consistency; if writing tags actually gets aborted, user will be likely left with corrupted file. Anything calling this should make sure that aborting is either impossible, or gives appropriate warning to the user first.
|
||||
virtual void set_info(t_uint32 p_subsong,const file_info & p_info,abort_callback & p_abort) = 0;
|
||||
|
||||
//! Commits pending updates. In case of multisubsong inputs, set_info should queue the update and perform actual file access in commit(). Otherwise, actual writing can be done in set_info() and then Commit() can just do nothing and always succeed.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation. WARNING: abort_callback object is provided for consistency; if writing tags actually gets aborted, user will be likely left with corrupted file. Anything calling this should make sure that aborting is either impossible, or gives appropriate warning to the user first.
|
||||
virtual void commit(abort_callback & p_abort) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(input_info_writer,input_info_reader);
|
||||
};
|
||||
|
||||
class NOVTABLE input_entry : public service_base
|
||||
{
|
||||
public:
|
||||
//! Determines whether specified content type can be handled by this input.
|
||||
//! @param p_type Content type string to test.
|
||||
virtual bool is_our_content_type(const char * p_type)=0;
|
||||
|
||||
//! Determines whether specified file type can be handled by this input. This must not use any kind of file access; the result should be only based on file path / extension.
|
||||
//! @param p_full_path Full URL of file being tested.
|
||||
//! @param p_extension Extension of file being tested, provided by caller for performance reasons.
|
||||
virtual bool is_our_path(const char * p_full_path,const char * p_extension)=0;
|
||||
|
||||
//! Opens specified resource for decoding.
|
||||
//! @param p_instance Receives new input_decoder instance if successful.
|
||||
//! @param p_filehint Optional; passes file object to use for the operation; if set to null, the service will handle opening file by itself. Note that not all inputs operate on physical files that can be reached through filesystem API, some of them require this parameter to be set to null (tone and silence generators for an example).
|
||||
//! @param p_path URL of resource being opened.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
virtual void open_for_decoding(service_ptr_t<input_decoder> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort) = 0;
|
||||
|
||||
//! Opens specified file for reading info.
|
||||
//! @param p_instance Receives new input_info_reader instance if successful.
|
||||
//! @param p_filehint Optional; passes file object to use for the operation; if set to null, the service will handle opening file by itself. Note that not all inputs operate on physical files that can be reached through filesystem API, some of them require this parameter to be set to null (tone and silence generators for an example).
|
||||
//! @param p_path URL of resource being opened.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
virtual void open_for_info_read(service_ptr_t<input_info_reader> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort) = 0;
|
||||
|
||||
//! Opens specified file for writing info.
|
||||
//! @param p_instance Receives new input_info_writer instance if successful.
|
||||
//! @param p_filehint Optional; passes file object to use for the operation; if set to null, the service will handle opening file by itself. Note that not all inputs operate on physical files that can be reached through filesystem API, some of them require this parameter to be set to null (tone and silence generators for an example).
|
||||
//! @param p_path URL of resource being opened.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
virtual void open_for_info_write(service_ptr_t<input_info_writer> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort) = 0;
|
||||
|
||||
//! Reserved for future use. Do nothing and return until specifications are finalized.
|
||||
virtual void get_extended_data(service_ptr_t<file> p_filehint,const playable_location & p_location,const GUID & p_guid,mem_block_container & p_out,abort_callback & p_abort) = 0;
|
||||
|
||||
enum {
|
||||
//! Indicates that this service implements some kind of redirector that opens another input for decoding, used to avoid circular call possibility.
|
||||
flag_redirect = 1,
|
||||
//! Indicates that multi-CPU optimizations should not be used.
|
||||
flag_parallel_reads_slow = 2,
|
||||
};
|
||||
//! See flag_* enums.
|
||||
virtual unsigned get_flags() = 0;
|
||||
|
||||
inline bool is_redirect() {return (get_flags() & flag_redirect) != 0;}
|
||||
inline bool are_parallel_reads_slow() {return (get_flags() & flag_parallel_reads_slow) != 0;}
|
||||
|
||||
static bool g_find_service_by_path(service_ptr_t<input_entry> & p_out,const char * p_path);
|
||||
static bool g_find_service_by_content_type(service_ptr_t<input_entry> & p_out,const char * p_content_type);
|
||||
static void g_open_for_decoding(service_ptr_t<input_decoder> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect = false);
|
||||
static void g_open_for_info_read(service_ptr_t<input_info_reader> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect = false);
|
||||
static void g_open_for_info_write(service_ptr_t<input_info_writer> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,bool p_from_redirect = false);
|
||||
static void g_open_for_info_write_timeout(service_ptr_t<input_info_writer> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort,double p_timeout,bool p_from_redirect = false);
|
||||
static bool g_is_supported_path(const char * p_path);
|
||||
|
||||
|
||||
void open(service_ptr_t<input_decoder> & p_instance,service_ptr_t<file> const & p_filehint,const char * p_path,abort_callback & p_abort) {open_for_decoding(p_instance,p_filehint,p_path,p_abort);}
|
||||
void open(service_ptr_t<input_info_reader> & p_instance,service_ptr_t<file> const & p_filehint,const char * p_path,abort_callback & p_abort) {open_for_info_read(p_instance,p_filehint,p_path,p_abort);}
|
||||
void open(service_ptr_t<input_info_writer> & p_instance,service_ptr_t<file> const & p_filehint,const char * p_path,abort_callback & p_abort) {open_for_info_write(p_instance,p_filehint,p_path,p_abort);}
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(input_entry);
|
||||
};
|
|
@ -0,0 +1,65 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
typedef pfc::avltree_t<pfc::string8,pfc::io::path::comparator> t_fnList;
|
||||
|
||||
static void formatMaskList(pfc::string_base & out, t_fnList const & in) {
|
||||
pfc::const_iterator<pfc::string8> walk = in.first();
|
||||
if (walk.is_valid()) {
|
||||
out << *walk; ++walk;
|
||||
while(walk.is_valid()) {
|
||||
out << ";" << *walk; ++walk;
|
||||
}
|
||||
}
|
||||
}
|
||||
static void formatMaskList(pfc::string_base & out, t_fnList const & in, const char * label) {
|
||||
if (in.get_count() > 0) {
|
||||
out << label << "|";
|
||||
formatMaskList(out,in);
|
||||
out << "|";
|
||||
}
|
||||
}
|
||||
|
||||
void input_file_type::build_openfile_mask(pfc::string_base & out, bool b_include_playlists)
|
||||
{
|
||||
t_fnList extensionsAll, extensionsPl;;
|
||||
|
||||
if (b_include_playlists) {
|
||||
service_enum_t<playlist_loader> e; service_ptr_t<playlist_loader> ptr;
|
||||
while(e.next(ptr)) {
|
||||
if (ptr->is_associatable()) {
|
||||
pfc::string_formatter temp; temp << "*." << ptr->get_extension();
|
||||
extensionsPl += temp;
|
||||
extensionsAll += temp;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
typedef pfc::map_t<pfc::string8,t_fnList,pfc::string::comparatorCaseInsensitive> t_masks;
|
||||
t_masks masks;
|
||||
{
|
||||
service_enum_t<input_file_type> e;
|
||||
service_ptr_t<input_file_type> ptr;
|
||||
pfc::string_formatter name, mask;
|
||||
while(e.next(ptr)) {
|
||||
const unsigned count = ptr->get_count();
|
||||
for(unsigned n=0;n<count;n++) {
|
||||
name.reset();
|
||||
mask.reset();
|
||||
if (ptr->get_name(n,name) && ptr->get_mask(n,mask)) {
|
||||
if (!strchr(name,'|') && !strchr(mask,'|')) {
|
||||
masks.find_or_add(name) += mask;
|
||||
extensionsAll += mask;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pfc::string_formatter outBuf;
|
||||
outBuf << "All files|*.*|";
|
||||
formatMaskList(outBuf, extensionsAll, "All supported types");
|
||||
formatMaskList(outBuf, extensionsPl, "Playlists");
|
||||
for(t_masks::const_iterator walk = masks.first(); walk.is_valid(); ++walk) {
|
||||
formatMaskList(outBuf,walk->m_value,walk->m_key);
|
||||
}
|
||||
out = outBuf;
|
||||
}
|
|
@ -0,0 +1,94 @@
|
|||
//! Entrypoint interface for registering media file types that can be opened through "open file" dialogs or associated with foobar2000 application in Windows shell. \n
|
||||
//! Instead of implementing this directly, use DECLARE_FILE_TYPE() / DECLARE_FILE_TYPE_EX() macros.
|
||||
class input_file_type : public service_base {
|
||||
public:
|
||||
virtual unsigned get_count()=0;
|
||||
virtual bool get_name(unsigned idx,pfc::string_base & out)=0;//eg. "MPEG files"
|
||||
virtual bool get_mask(unsigned idx,pfc::string_base & out)=0;//eg. "*.MP3;*.MP2"; separate with semicolons
|
||||
virtual bool is_associatable(unsigned idx) = 0;
|
||||
|
||||
static void build_openfile_mask(pfc::string_base & out,bool b_include_playlists=true);
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(input_file_type);
|
||||
};
|
||||
|
||||
//! Extended interface for registering media file types that can be associated with foobar2000 application in Windows shell. \n
|
||||
//! Instead of implementing this directly, use DECLARE_FILE_TYPE() / DECLARE_FILE_TYPE_EX() macros.
|
||||
class input_file_type_v2 : public input_file_type {
|
||||
public:
|
||||
virtual void get_format_name(unsigned idx, pfc::string_base & out, bool isPlural) = 0;
|
||||
virtual void get_extensions(unsigned idx, pfc::string_base & out) = 0;
|
||||
|
||||
//Deprecated input_file_type method implementations:
|
||||
bool get_name(unsigned idx, pfc::string_base & out) {get_format_name(idx, out, true); return true;}
|
||||
bool get_mask(unsigned idx, pfc::string_base & out) {
|
||||
pfc::string_formatter temp; get_extensions(idx,temp);
|
||||
pfc::chain_list_v2_t<pfc::string> exts; pfc::splitStringSimple_toList(exts,";",temp);
|
||||
if (exts.get_count() == 0) return false;//should not happen
|
||||
temp.reset();
|
||||
for(pfc::const_iterator<pfc::string> walk = exts.first(); walk.is_valid(); ++walk) {
|
||||
if (!temp.is_empty()) temp << ";";
|
||||
temp << "*." << walk->get_ptr();
|
||||
}
|
||||
out = temp;
|
||||
return true;
|
||||
}
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(input_file_type_v2,input_file_type)
|
||||
};
|
||||
|
||||
|
||||
//! Implementation helper.
|
||||
class input_file_type_impl : public service_impl_single_t<input_file_type>
|
||||
{
|
||||
const char * name, * mask;
|
||||
bool m_associatable;
|
||||
public:
|
||||
input_file_type_impl(const char * p_name, const char * p_mask,bool p_associatable) : name(p_name), mask(p_mask), m_associatable(p_associatable) {}
|
||||
unsigned get_count() {return 1;}
|
||||
bool get_name(unsigned idx,pfc::string_base & out) {if (idx==0) {out = name; return true;} else return false;}
|
||||
bool get_mask(unsigned idx,pfc::string_base & out) {if (idx==0) {out = mask; return true;} else return false;}
|
||||
bool is_associatable(unsigned idx) {return m_associatable;}
|
||||
};
|
||||
|
||||
|
||||
//! Helper macro for registering our media file types.
|
||||
//! Usage: DECLARE_FILE_TYPE("Blah files","*.blah;*.bleh");
|
||||
#define DECLARE_FILE_TYPE(NAME,MASK) \
|
||||
namespace { static input_file_type_impl g_filetype_instance(NAME,MASK,true); \
|
||||
static service_factory_single_ref_t<input_file_type_impl> g_filetype_service(g_filetype_instance); }
|
||||
|
||||
|
||||
|
||||
|
||||
//! Implementation helper.
|
||||
//! Usage: static input_file_type_factory mytype("blah type","*.bla;*.meh",true);
|
||||
class input_file_type_factory : private service_factory_single_transparent_t<input_file_type_impl>
|
||||
{
|
||||
public:
|
||||
input_file_type_factory(const char * p_name,const char * p_mask,bool p_associatable)
|
||||
: service_factory_single_transparent_t<input_file_type_impl>(p_name,p_mask,p_associatable) {}
|
||||
};
|
||||
|
||||
|
||||
|
||||
class input_file_type_v2_impl : public input_file_type_v2 {
|
||||
public:
|
||||
input_file_type_v2_impl(const char * extensions,const char * name, const char * namePlural) : m_extensions(extensions), m_name(name), m_namePlural(namePlural) {}
|
||||
unsigned get_count() {return 1;}
|
||||
bool is_associatable(unsigned idx) {return true;}
|
||||
void get_format_name(unsigned idx, pfc::string_base & out, bool isPlural) {
|
||||
out = isPlural ? m_namePlural : m_name;
|
||||
}
|
||||
void get_extensions(unsigned idx, pfc::string_base & out) {
|
||||
out = m_extensions;
|
||||
}
|
||||
|
||||
private:
|
||||
const pfc::string8 m_name, m_namePlural, m_extensions;
|
||||
};
|
||||
|
||||
//! Helper macro for registering our media file types, extended version providing separate singular/plural type names.
|
||||
//! Usage: DECLARE_FILE_TYPE_EX("mp1;mp2;mp3","MPEG file","MPEG files")
|
||||
#define DECLARE_FILE_TYPE_EX(extensions, name, namePlural) \
|
||||
namespace { static service_factory_single_t<input_file_type_v2_impl> g_myfiletype(extensions, name, namePlural); }
|
|
@ -0,0 +1,314 @@
|
|||
enum t_input_open_reason {
|
||||
input_open_info_read,
|
||||
input_open_decode,
|
||||
input_open_info_write
|
||||
};
|
||||
|
||||
//! Helper function for input implementation use; ensures that file is open with relevant access mode. This is typically called from input_impl::open() and such.
|
||||
//! @param p_file File object pointer to process. If passed pointer is non-null, the function does nothing and always succeeds; otherwise it attempts to open the file using filesystem API methods.
|
||||
//! @param p_path Path to the file.
|
||||
//! @param p_reason Type of input operation requested. See: input_impl::open() parameters.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
void input_open_file_helper(service_ptr_t<file> & p_file,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort);
|
||||
|
||||
|
||||
//! This is a class that just declares prototypes of functions that each input needs to implement. See input_decoder / input_info_reader / input_info_writer interfaces for full descriptions of member functions. Since input implementation class is instantiated using a template, you don't need to derive from input_impl as virtual functions are not used on implementation class level. Use input_factory_t template to register input class based on input_impl.
|
||||
class input_impl
|
||||
{
|
||||
public:
|
||||
//! Opens specified file for info read / decoding / info write. This is called only once, immediately after object creation, before any other methods, and no other methods are called if open() fails.
|
||||
//! @param p_filehint Optional; passes file object to use for the operation; if set to null, the implementation should handle opening file by itself. Note that not all inputs operate on physical files that can be reached through filesystem API, some of them require this parameter to be set to null (tone and silence generators for an example). Typically, an input implementation that requires file access calls input_open_file_helper() function to ensure that file is open with relevant access mode (read or read/write).
|
||||
//! @param p_path URL of resource being opened.
|
||||
//! @param p_reason Type of operation requested. Possible values are: \n
|
||||
//! - input_open_info_read - info retrieval methods are valid; \n
|
||||
//! - input_open_decode - info retrieval and decoding methods are valid; \n
|
||||
//! - input_open_info_write - info retrieval and retagging methods are valid; \n
|
||||
//! Note that info retrieval methods are valid in all cases, and they may be called at any point of decoding/retagging process. Results of info retrieval methods (other than get_subsong_count() / get_subsong()) between retag_set_info() and retag_commit() are undefined however; those should not be called during that period.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
void open(service_ptr_t<file> p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort);
|
||||
|
||||
//! See: input_info_reader::get_subsong_count(). Valid after open() with any reason.
|
||||
unsigned get_subsong_count();
|
||||
//! See: input_info_reader::get_subsong(). Valid after open() with any reason.
|
||||
t_uint32 get_subsong(unsigned p_index);
|
||||
//! See: input_info_reader::get_info(). Valid after open() with any reason.
|
||||
void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort);
|
||||
//! See: input_info_reader::get_file_stats(). Valid after open() with any reason.
|
||||
t_filestats get_file_stats(abort_callback & p_abort);
|
||||
|
||||
//! See: input_decoder::initialize(). Valid after open() with input_open_decode reason.
|
||||
void decode_initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort);
|
||||
//! See: input_decoder::run(). Valid after decode_initialize().
|
||||
bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort);
|
||||
//! See: input_decoder::seek(). Valid after decode_initialize().
|
||||
void decode_seek(double p_seconds,abort_callback & p_abort);
|
||||
//! See: input_decoder::can_seek(). Valid after decode_initialize().
|
||||
bool decode_can_seek();
|
||||
//! See: input_decoder::get_dynamic_info(). Valid after decode_initialize().
|
||||
bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta);
|
||||
//! See: input_decoder::get_dynamic_info_track(). Valid after decode_initialize().
|
||||
bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta);
|
||||
//! See: input_decoder::on_idle(). Valid after decode_initialize().
|
||||
void decode_on_idle(abort_callback & p_abort);
|
||||
|
||||
//! See: input_info_writer::set_info(). Valid after open() with input_open_info_write reason.
|
||||
void retag_set_info(t_uint32 p_subsong,const file_info & p_info,abort_callback & p_abort);
|
||||
//! See: input_info_writer::commit(). Valid after open() with input_open_info_write reason.
|
||||
void retag_commit(abort_callback & p_abort);
|
||||
|
||||
//! See: input_entry::is_our_content_type().
|
||||
static bool g_is_our_content_type(const char * p_content_type);
|
||||
//! See: input_entry::is_our_path().
|
||||
static bool g_is_our_path(const char * p_path,const char * p_extension);
|
||||
|
||||
protected:
|
||||
input_impl() {}
|
||||
~input_impl() {}
|
||||
};
|
||||
|
||||
//! This is a class that just declares prototypes of functions that each non-multitrack-enabled input needs to implement. See input_decoder / input_info_reader / input_info_writer interfaces for full descriptions of member functions. Since input implementation class is instantiated using a template, you don't need to derive from input_singletrack_impl as virtual functions are not used on implementation class level. Use input_singletrack_factory_t template to register input class based on input_singletrack_impl.
|
||||
class input_singletrack_impl
|
||||
{
|
||||
public:
|
||||
//! Opens specified file for info read / decoding / info write. This is called only once, immediately after object creation, before any other methods, and no other methods are called if open() fails.
|
||||
//! @param p_filehint Optional; passes file object to use for the operation; if set to null, the implementation should handle opening file by itself. Note that not all inputs operate on physical files that can be reached through filesystem API, some of them require this parameter to be set to null (tone and silence generators for an example). Typically, an input implementation that requires file access calls input_open_file_helper() function to ensure that file is open with relevant access mode (read or read/write).
|
||||
//! @param p_path URL of resource being opened.
|
||||
//! @param p_reason Type of operation requested. Possible values are: \n
|
||||
//! - input_open_info_read - info retrieval methods are valid; \n
|
||||
//! - input_open_decode - info retrieval and decoding methods are valid; \n
|
||||
//! - input_open_info_write - info retrieval and retagging methods are valid; \n
|
||||
//! Note that info retrieval methods are valid in all cases, and they may be called at any point of decoding/retagging process. Results of info retrieval methods (other than get_subsong_count() / get_subsong()) between retag_set_info() and retag_commit() are undefined however; those should not be called during that period.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
void open(service_ptr_t<file> p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort);
|
||||
|
||||
//! See: input_info_reader::get_info(). Valid after open() with any reason. \n
|
||||
//! Implementation warning: this is typically also called immediately after tag update and should return newly written content then.
|
||||
void get_info(file_info & p_info,abort_callback & p_abort);
|
||||
//! See: input_info_reader::get_file_stats(). Valid after open() with any reason. \n
|
||||
//! Implementation warning: this is typically also called immediately after tag update and should return new values then.
|
||||
t_filestats get_file_stats(abort_callback & p_abort);
|
||||
|
||||
//! See: input_decoder::initialize(). Valid after open() with input_open_decode reason.
|
||||
void decode_initialize(unsigned p_flags,abort_callback & p_abort);
|
||||
//! See: input_decoder::run(). Valid after decode_initialize().
|
||||
bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort);
|
||||
//! See: input_decoder::seek(). Valid after decode_initialize().
|
||||
void decode_seek(double p_seconds,abort_callback & p_abort);
|
||||
//! See: input_decoder::can_seek(). Valid after decode_initialize().
|
||||
bool decode_can_seek();
|
||||
//! See: input_decoder::get_dynamic_info(). Valid after decode_initialize().
|
||||
bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta);
|
||||
//! See: input_decoder::get_dynamic_info_track(). Valid after decode_initialize().
|
||||
bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta);
|
||||
//! See: input_decoder::on_idle(). Valid after decode_initialize().
|
||||
void decode_on_idle(abort_callback & p_abort);
|
||||
|
||||
//! See: input_info_writer::set_info(). Note that input_info_writer::commit() call isn't forwarded because it's useless in case of non-multitrack-enabled inputs. Valid after open() with input_open_info_write reason.
|
||||
void retag(const file_info & p_info,abort_callback & p_abort);
|
||||
|
||||
//! See: input_entry::is_our_content_type().
|
||||
static bool g_is_our_content_type(const char * p_content_type);
|
||||
//! See: input_entry::is_our_path().
|
||||
static bool g_is_our_path(const char * p_path,const char * p_extension);
|
||||
|
||||
protected:
|
||||
input_singletrack_impl() {}
|
||||
~input_singletrack_impl() {}
|
||||
};
|
||||
|
||||
|
||||
//! Used internally by standard input_entry implementation; do not use directly. Translates input_decoder / input_info_reader / input_info_writer calls to input_impl calls.
|
||||
template<typename I, typename t_base>
|
||||
class input_impl_interface_wrapper_t : public t_base
|
||||
{
|
||||
public:
|
||||
void open(service_ptr_t<file> p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort) {
|
||||
m_instance.open(p_filehint,p_path,p_reason,p_abort);
|
||||
}
|
||||
|
||||
// input_info_reader methods
|
||||
|
||||
t_uint32 get_subsong_count() {
|
||||
return m_instance.get_subsong_count();
|
||||
}
|
||||
|
||||
t_uint32 get_subsong(t_uint32 p_index) {
|
||||
return m_instance.get_subsong(p_index);
|
||||
}
|
||||
|
||||
|
||||
void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) {
|
||||
m_instance.get_info(p_subsong,p_info,p_abort);
|
||||
}
|
||||
|
||||
t_filestats get_file_stats(abort_callback & p_abort) {
|
||||
return m_instance.get_file_stats(p_abort);
|
||||
}
|
||||
|
||||
// input_decoder methods
|
||||
|
||||
void initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort) {
|
||||
m_instance.decode_initialize(p_subsong,p_flags,p_abort);
|
||||
}
|
||||
|
||||
bool run(audio_chunk & p_chunk,abort_callback & p_abort) {
|
||||
return m_instance.decode_run(p_chunk,p_abort);
|
||||
}
|
||||
|
||||
bool run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort) {
|
||||
return m_instance.decode_run_raw(p_chunk, p_raw, p_abort);
|
||||
}
|
||||
|
||||
void seek(double p_seconds,abort_callback & p_abort) {
|
||||
m_instance.decode_seek(p_seconds,p_abort);
|
||||
}
|
||||
|
||||
bool can_seek() {
|
||||
return m_instance.decode_can_seek();
|
||||
}
|
||||
|
||||
bool get_dynamic_info(file_info & p_out, double & p_timestamp_delta) {
|
||||
return m_instance.decode_get_dynamic_info(p_out,p_timestamp_delta);
|
||||
}
|
||||
|
||||
bool get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) {
|
||||
return m_instance.decode_get_dynamic_info_track(p_out,p_timestamp_delta);
|
||||
}
|
||||
|
||||
void on_idle(abort_callback & p_abort) {
|
||||
m_instance.decode_on_idle(p_abort);
|
||||
}
|
||||
|
||||
void set_logger(event_logger::ptr ptr) {
|
||||
m_instance.set_logger(ptr);
|
||||
}
|
||||
|
||||
// input_info_writer methods
|
||||
|
||||
void set_info(t_uint32 p_subsong,const file_info & p_info,abort_callback & p_abort) {
|
||||
m_instance.retag_set_info(p_subsong,p_info,p_abort);
|
||||
}
|
||||
|
||||
void commit(abort_callback & p_abort) {
|
||||
m_instance.retag_commit(p_abort);
|
||||
}
|
||||
|
||||
private:
|
||||
I m_instance;
|
||||
};
|
||||
|
||||
//! Helper used by input_singletrack_factory_t, do not use directly. Translates input_impl calls to input_singletrack_impl calls.
|
||||
template<typename I>
|
||||
class input_wrapper_singletrack_t
|
||||
{
|
||||
public:
|
||||
input_wrapper_singletrack_t() {}
|
||||
|
||||
void open(service_ptr_t<file> p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort) {
|
||||
m_instance.open(p_filehint,p_path,p_reason,p_abort);
|
||||
}
|
||||
|
||||
void get_info(t_uint32 p_subsong,file_info & p_info,abort_callback & p_abort) {
|
||||
if (p_subsong != 0) throw exception_io_data();
|
||||
m_instance.get_info(p_info,p_abort);
|
||||
}
|
||||
|
||||
t_uint32 get_subsong_count() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
t_uint32 get_subsong(t_uint32 p_index) {
|
||||
assert(p_index == 0);
|
||||
return 0;
|
||||
}
|
||||
|
||||
t_filestats get_file_stats(abort_callback & p_abort) {
|
||||
return m_instance.get_file_stats(p_abort);
|
||||
}
|
||||
|
||||
void decode_initialize(t_uint32 p_subsong,unsigned p_flags,abort_callback & p_abort) {
|
||||
if (p_subsong != 0) throw exception_io_data();
|
||||
m_instance.decode_initialize(p_flags,p_abort);
|
||||
}
|
||||
|
||||
bool decode_run(audio_chunk & p_chunk,abort_callback & p_abort) {return m_instance.decode_run(p_chunk,p_abort);}
|
||||
void decode_seek(double p_seconds,abort_callback & p_abort) {m_instance.decode_seek(p_seconds,p_abort);}
|
||||
bool decode_can_seek() {return m_instance.decode_can_seek();}
|
||||
bool decode_get_dynamic_info(file_info & p_out, double & p_timestamp_delta) {return m_instance.decode_get_dynamic_info(p_out,p_timestamp_delta);}
|
||||
bool decode_get_dynamic_info_track(file_info & p_out, double & p_timestamp_delta) {return m_instance.decode_get_dynamic_info_track(p_out,p_timestamp_delta);}
|
||||
void decode_on_idle(abort_callback & p_abort) {m_instance.decode_on_idle(p_abort);}
|
||||
|
||||
void retag_set_info(t_uint32 p_subsong,const file_info & p_info,abort_callback & p_abort) {
|
||||
if (p_subsong != 0) throw exception_io_data();
|
||||
m_instance.retag(p_info,p_abort);
|
||||
}
|
||||
|
||||
bool decode_run_raw(audio_chunk & p_chunk, mem_block_container & p_raw, abort_callback & p_abort) {
|
||||
return m_instance.decode_run_raw(p_chunk, p_raw, p_abort);
|
||||
}
|
||||
|
||||
void set_logger(event_logger::ptr ptr) {m_instance.set_logger(ptr);}
|
||||
|
||||
void retag_commit(abort_callback & p_abort) {}
|
||||
|
||||
static bool g_is_our_content_type(const char * p_content_type) {return I::g_is_our_content_type(p_content_type);}
|
||||
static bool g_is_our_path(const char * p_path,const char * p_extension) {return I::g_is_our_path(p_path,p_extension);}
|
||||
|
||||
|
||||
private:
|
||||
I m_instance;
|
||||
};
|
||||
|
||||
//! Helper; standard input_entry implementation. Do not instantiate this directly, use input_factory_t or one of other input_*_factory_t helpers instead.
|
||||
template<typename I,unsigned t_flags, typename t_decoder = input_decoder, typename t_inforeader = input_info_reader, typename t_infowriter = input_info_writer>
|
||||
class input_entry_impl_t : public input_entry
|
||||
{
|
||||
private:
|
||||
|
||||
template<typename T, typename out>
|
||||
void instantiate_t(service_ptr_t<out> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,t_input_open_reason p_reason,abort_callback & p_abort)
|
||||
{
|
||||
service_ptr_t< service_impl_t<input_impl_interface_wrapper_t<I,T> > > temp;
|
||||
temp = new service_impl_t<input_impl_interface_wrapper_t<I,T> >();
|
||||
temp->open(p_filehint,p_path,p_reason,p_abort);
|
||||
p_instance = temp.get_ptr();
|
||||
}
|
||||
public:
|
||||
bool is_our_content_type(const char * p_type) {return I::g_is_our_content_type(p_type);}
|
||||
bool is_our_path(const char * p_full_path,const char * p_extension) {return I::g_is_our_path(p_full_path,p_extension);}
|
||||
|
||||
void open_for_decoding(service_ptr_t<input_decoder> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort) {
|
||||
instantiate_t<t_decoder>(p_instance,p_filehint,p_path,input_open_decode,p_abort);
|
||||
}
|
||||
|
||||
void open_for_info_read(service_ptr_t<input_info_reader> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort) {
|
||||
instantiate_t<t_inforeader>(p_instance,p_filehint,p_path,input_open_info_read,p_abort);
|
||||
}
|
||||
|
||||
void open_for_info_write(service_ptr_t<input_info_writer> & p_instance,service_ptr_t<file> p_filehint,const char * p_path,abort_callback & p_abort) {
|
||||
instantiate_t<t_infowriter>(p_instance,p_filehint,p_path,input_open_info_write,p_abort);
|
||||
}
|
||||
|
||||
void get_extended_data(service_ptr_t<file> p_filehint,const playable_location & p_location,const GUID & p_guid,mem_block_container & p_out,abort_callback & p_abort) {
|
||||
p_out.reset();
|
||||
}
|
||||
|
||||
unsigned get_flags() {return t_flags;}
|
||||
};
|
||||
|
||||
|
||||
//! Stardard input factory. For reference of functions that must be supported by registered class, see input_impl.\n Usage: static input_factory_t<myinputclass> g_myinputclass_factory;\n Note that input classes can't be registered through service_factory_t template directly.
|
||||
template<typename T>
|
||||
class input_factory_t : public service_factory_single_t<input_entry_impl_t<T,0> > {};
|
||||
|
||||
//! Non-multitrack-enabled input factory (helper) - hides multitrack management functions from input implementation; use this for inputs that handle file types where each physical file can contain only one user-visible playable track. For reference of functions that must be supported by registered class, see input_singletrack_impl.\n Usage: static input_singletrack_factory_t<myinputclass> g_myinputclass_factory;\n Note that input classes can't be registered through service_factory_t template directly.template<class T>
|
||||
template<typename T>
|
||||
class input_singletrack_factory_t : public service_factory_single_t<input_entry_impl_t<input_wrapper_singletrack_t<T>,0> > {};
|
||||
|
||||
//! Extended version of input_factory_t, with non-default flags and supported interfaces. See: input_factory_t, input_entry::get_flags().
|
||||
template<typename T,unsigned t_flags = 0, typename t_decoder = input_decoder, typename t_inforeader = input_info_reader, typename t_infowriter = input_info_writer>
|
||||
class input_factory_ex_t : public service_factory_single_t<input_entry_impl_t<T,t_flags, t_decoder, t_inforeader, t_infowriter> > {};
|
||||
|
||||
//! Extended version of input_singletrack_factory_t, with non-default flags and supported interfaces. See: input_singletrack_factory_t, input_entry::get_flags().
|
||||
template<typename T,unsigned t_flags = 0, typename t_decoder = input_decoder, typename t_inforeader = input_info_reader, typename t_infowriter = input_info_writer>
|
||||
class input_singletrack_factory_ex_t : public service_factory_single_t<input_entry_impl_t<input_wrapper_singletrack_t<T>,t_flags, t_decoder, t_inforeader, t_infowriter> > {};
|
|
@ -0,0 +1,181 @@
|
|||
/*!
|
||||
This service implements methods allowing you to interact with the Media Library.\n
|
||||
All methods are valid from main thread only, unless noted otherwise.\n
|
||||
To avoid race conditions, methods that alter library contents should not be called from inside global callbacks.\n
|
||||
Usage: Use static_api_ptr_t<library_manager> to instantiate. \n
|
||||
|
||||
Future compatibility notes: \n
|
||||
In 0.9.6, the Media Library backend will be entirely reimplemented to perform tracking of folder content changes on its own. This API will still be provided for backwards compatibility, though most of methods will become stubs as their original purpose will be no longer valid. \n
|
||||
To keep your component working sanely in future foobar2000 releases, do not depend on functions flagged as scheduled to be dropped - you can still call them, but keep in mind that they will become meaningless in the next major release.
|
||||
*/
|
||||
|
||||
class NOVTABLE library_manager : public service_base {
|
||||
public:
|
||||
//! Interface for use with library_manager::enum_items().
|
||||
class NOVTABLE enum_callback {
|
||||
public:
|
||||
//! Return true to continue enumeration, false to abort.
|
||||
virtual bool on_item(const metadb_handle_ptr & p_item) = 0;
|
||||
};
|
||||
|
||||
//! Returns whether the specified item is in the Media Library or not.
|
||||
virtual bool is_item_in_library(const metadb_handle_ptr & p_item) = 0;
|
||||
//! Returns whether current user settings allow the specified item to be added to the Media Library or not.
|
||||
virtual bool is_item_addable(const metadb_handle_ptr & p_item) = 0;
|
||||
//! Returns whether current user settings allow the specified item path to be added to the Media Library or not.
|
||||
virtual bool is_path_addable(const char * p_path) = 0;
|
||||
//! Retrieves path of the specified item relative to the Media Library folder it is in. Returns true on success, false when the item is not in the Media Library.
|
||||
//! SPECIAL WARNING: to allow multi-CPU optimizations to parse relative track paths, this API works in threads other than the main app thread. Main thread MUST be blocked while working in such scenarios, it's NOT safe to call from worker threads while the Media Library content/configuration might be getting altered.
|
||||
virtual bool get_relative_path(const metadb_handle_ptr & p_item,pfc::string_base & p_out) = 0;
|
||||
//! Calls callback method for every item in the Media Library. Note that order of items in Media Library is undefined.
|
||||
virtual void enum_items(enum_callback & p_callback) = 0;
|
||||
//! Scheduled to be dropped in 0.9.6 (will do nothing). \n
|
||||
//! Adds specified items to the Media Library (items actually added will be filtered according to user settings).
|
||||
virtual void add_items(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0;
|
||||
//! Scheduled to be dropped in 0.9.6 (will do nothing). \n
|
||||
//! Removes specified items from the Media Library (does nothing if specific item is not in the Media Library).
|
||||
virtual void remove_items(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0;
|
||||
//! Scheduled to be dropped in 0.9.6 (will do nothing). \n
|
||||
//! Adds specified items to the Media Library (items actually added will be filtered according to user settings). The difference between this and add_items() is that items are not added immediately; the operation is queued and executed later, so it is safe to call from e.g. global callbacks.
|
||||
virtual void add_items_async(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0;
|
||||
|
||||
//! Scheduled to be dropped in 0.9.6 (will do nothing). \n
|
||||
//! For internal use only; p_data must be sorted by metadb::path_compare; use file_operation_callback static methods instead of calling this directly.
|
||||
virtual void on_files_deleted_sorted(const pfc::list_base_const_t<const char *> & p_data) = 0;
|
||||
|
||||
//! Retrieves the entire Media Library content.
|
||||
virtual void get_all_items(pfc::list_base_t<metadb_handle_ptr> & p_out) = 0;
|
||||
|
||||
//! Returns whether Media Library functionality is enabled or not (to be exact: whether there's at least one Media Library folder present in settings), for e.g. notifying the user to change settings when trying to use a Media Library viewer without having configured the Media Library first.
|
||||
virtual bool is_library_enabled() = 0;
|
||||
//! Pops up the Media Library preferences page.
|
||||
virtual void show_preferences() = 0;
|
||||
|
||||
//! Scheduled to be dropped in 0.9.6. \n
|
||||
//! Deprecated; use library_manager_v2::rescan_async() when possible.\n
|
||||
//! Rescans user-specified Media Library directories for new files and removes references to files that no longer exist from the Media Library.\n
|
||||
//! Note that this function creates modal dialog and does not return until the operation has completed.\n
|
||||
virtual void rescan() = 0;
|
||||
|
||||
//! Scheduled to be dropped in 0.9.6. \n
|
||||
//! Deprecated; use library_manager_v2::check_dead_entries_async() when possible.\n
|
||||
//! Hints Media Library about possible dead items, typically used for "remove dead entries" context action in ML viewers. The implementation will verify whether the items are actually dead before ML contents are altered.\n
|
||||
//! Note that this function creates modal dialog and does not return until the operation has completed.\n
|
||||
virtual void check_dead_entries(const pfc::list_base_t<metadb_handle_ptr> & p_list) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(library_manager);
|
||||
};
|
||||
|
||||
//! \since 0.9.3
|
||||
class NOVTABLE library_manager_v2 : public library_manager {
|
||||
public:
|
||||
//! Scheduled to be dropped in 0.9.6 (will always return false). \n
|
||||
//! Returns whether a rescan process is currently running.
|
||||
virtual bool is_rescan_running() = 0;
|
||||
|
||||
//! Scheduled to be dropped in 0.9.6 (will do nothing and instantly signal completion). \n
|
||||
//! Starts an async rescan process. Note that if another process is already running, the process is silently aborted.
|
||||
//! @param p_parent Parent window for displayed progress dialog.
|
||||
//! @param p_notify Allows caller to receive notifications about the process finishing. Status code: 1 on success, 0 on user abort. Pass NULL if caller doesn't care.
|
||||
virtual void rescan_async(HWND p_parent,completion_notify_ptr p_notify) = 0;
|
||||
|
||||
//! Scheduled to be dropped in 0.9.6 (will do nothing and instantly signal completion). \n
|
||||
//! Hints Media Library about possible dead items, typically used for "remove dead entries" context action in ML viewers. The implementation will verify whether the items are actually dead before ML contents are altered.\n
|
||||
//! @param p_list List of items to process.
|
||||
//! @param p_parent Parent window for displayed progress dialog.
|
||||
//! @param p_notify Allows caller to receive notifications about the process finishing. Status code: 1 on success, 0 on user abort. Pass NULL if caller doesn't care.
|
||||
virtual void check_dead_entries_async(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,HWND p_parent,completion_notify_ptr p_notify) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(library_manager_v2,library_manager);
|
||||
};
|
||||
|
||||
|
||||
class NOVTABLE library_callback_dynamic {
|
||||
public:
|
||||
//! Called when new items are added to the Media Library.
|
||||
virtual void on_items_added(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0;
|
||||
//! Called when some items have been removed from the Media Library.
|
||||
virtual void on_items_removed(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0;
|
||||
//! Called when some items in the Media Library have been modified.
|
||||
virtual void on_items_modified(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0;
|
||||
};
|
||||
|
||||
//! \since 0.9.5
|
||||
class NOVTABLE library_manager_v3 : public library_manager_v2 {
|
||||
public:
|
||||
//! Retrieves directory path and subdirectory/filename formatting scheme for newly encoded/copied/moved tracks.
|
||||
//! @returns True on success, false when the feature has not been configured.
|
||||
virtual bool get_new_file_pattern_tracks(pfc::string_base & p_directory,pfc::string_base & p_format) = 0;
|
||||
//! Retrieves directory path and subdirectory/filename formatting scheme for newly encoded/copied/moved full album images.
|
||||
//! @returns True on success, false when the feature has not been configured.
|
||||
virtual bool get_new_file_pattern_images(pfc::string_base & p_directory,pfc::string_base & p_format) = 0;
|
||||
|
||||
virtual void register_callback(library_callback_dynamic * p_callback) = 0;
|
||||
virtual void unregister_callback(library_callback_dynamic * p_callback) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(library_manager_v3,library_manager_v2);
|
||||
};
|
||||
|
||||
class library_callback_dynamic_impl_base : public library_callback_dynamic {
|
||||
public:
|
||||
library_callback_dynamic_impl_base() {static_api_ptr_t<library_manager_v3>()->register_callback(this);}
|
||||
~library_callback_dynamic_impl_base() {static_api_ptr_t<library_manager_v3>()->unregister_callback(this);}
|
||||
|
||||
//stub implementations - avoid pure virtual function call issues
|
||||
void on_items_added(metadb_handle_list_cref p_data) {}
|
||||
void on_items_removed(metadb_handle_list_cref p_data) {}
|
||||
void on_items_modified(metadb_handle_list_cref p_data) {}
|
||||
|
||||
PFC_CLASS_NOT_COPYABLE_EX(library_callback_dynamic_impl_base);
|
||||
};
|
||||
|
||||
//! Callback service receiving notifications about Media Library content changes. Methods called only from main thread.\n
|
||||
//! Use library_callback_factory_t template to register.
|
||||
class NOVTABLE library_callback : public service_base {
|
||||
public:
|
||||
//! Called when new items are added to the Media Library.
|
||||
virtual void on_items_added(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0;
|
||||
//! Called when some items have been removed from the Media Library.
|
||||
virtual void on_items_removed(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0;
|
||||
//! Called when some items in the Media Library have been modified.
|
||||
virtual void on_items_modified(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(library_callback);
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class library_callback_factory_t : public service_factory_single_t<T> {};
|
||||
|
||||
//! Implement this service to appear on "library viewers" list in Media Library preferences page.\n
|
||||
//! Use library_viewer_factory_t to register.
|
||||
class NOVTABLE library_viewer : public service_base {
|
||||
public:
|
||||
//! Retrieves GUID of your preferences page (pfc::guid_null if you don't have one).
|
||||
virtual GUID get_preferences_page() = 0;
|
||||
//! Queries whether "activate" action is supported (relevant button will be disabled if it's not).
|
||||
virtual bool have_activate() = 0;
|
||||
//! Activates your Media Library viewer component (e.g. shows its window).
|
||||
virtual void activate() = 0;
|
||||
//! Retrieves GUID of your library_viewer implementation, for internal identification. Note that this not the same as preferences page GUID.
|
||||
virtual GUID get_guid() = 0;
|
||||
//! Retrieves name of your Media Library viewer, a null-terminated UTF-8 encoded string.
|
||||
virtual const char * get_name() = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(library_viewer);
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class library_viewer_factory_t : public service_factory_single_t<T> {};
|
||||
|
||||
|
||||
|
||||
|
||||
//! \since 0.9.5.4
|
||||
//! Allows you to spawn a popup Media Library Search window with any query string that you specify. \n
|
||||
//! Usage: static_api_ptr_t<library_search_ui>()->show("querygoeshere");
|
||||
class NOVTABLE library_search_ui : public service_base {
|
||||
public:
|
||||
virtual void show(const char * query) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(library_search_ui)
|
||||
};
|
|
@ -0,0 +1,17 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
bool link_resolver::g_find(service_ptr_t<link_resolver> & p_out,const char * p_path)
|
||||
{
|
||||
service_enum_t<link_resolver> e;
|
||||
service_ptr_t<link_resolver> ptr;
|
||||
pfc::string_extension ext(p_path);
|
||||
while(e.next(ptr))
|
||||
{
|
||||
if (ptr->is_our_path(p_path,ext))
|
||||
{
|
||||
p_out = ptr;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
#ifndef _foobar2000_sdk_link_resolver_h_
|
||||
#define _foobar2000_sdk_link_resolver_h_
|
||||
|
||||
//! Interface for resolving different sorts of link files.
|
||||
//! Utilized by mechanisms that convert filesystem path into list of playable locations.
|
||||
//! For security reasons, link may only point to playable object path, not to a playlist or another link.
|
||||
|
||||
class NOVTABLE link_resolver : public service_base
|
||||
{
|
||||
public:
|
||||
|
||||
//! Tests whether specified file is supported by this link_resolver service.
|
||||
//! @param p_path Path of file being queried.
|
||||
//! @param p_extension Extension of file being queried. This is provided for performance reasons, path already includes it.
|
||||
virtual bool is_our_path(const char * p_path,const char * p_extension) = 0;
|
||||
|
||||
//! Resolves a link file. Before this is called, path must be accepted by is_our_path().
|
||||
//! @param p_filehint Optional file interface to use. If null/empty, implementation should open file by itself.
|
||||
//! @param p_path Path of link file to resolve.
|
||||
//! @param p_out Receives path the link is pointing to.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation.
|
||||
virtual void resolve(service_ptr_t<file> p_filehint,const char * p_path,pfc::string_base & p_out,abort_callback & p_abort) = 0;
|
||||
|
||||
//! Helper function; finds link_resolver interface that supports specified link file.
|
||||
//! @param p_out Receives link_resolver interface on success.
|
||||
//! @param p_path Path of file to query.
|
||||
//! @returns True on success, false on failure (no interface that supports specified path could be found).
|
||||
static bool g_find(service_ptr_t<link_resolver> & p_out,const char * p_path);
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(link_resolver);
|
||||
};
|
||||
|
||||
#endif //_foobar2000_sdk_link_resolver_h_
|
|
@ -0,0 +1,20 @@
|
|||
//! Callback object class for main_thread_callback_manager service.
|
||||
class NOVTABLE main_thread_callback : public service_base {
|
||||
public:
|
||||
//! Gets called from main app thread. See main_thread_callback_manager description for more info.
|
||||
virtual void callback_run() = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(main_thread_callback,service_base);
|
||||
};
|
||||
|
||||
/*!
|
||||
Allows you to queue a callback object to be called from main app thread. This is commonly used to trigger main-thread-only API calls from worker threads.\n
|
||||
This can be also used from main app thread, to avoid race conditions when trying to use APIs that dispatch global callbacks from inside some other global callback, or using message loops / modal dialogs inside global callbacks.
|
||||
*/
|
||||
class NOVTABLE main_thread_callback_manager : public service_base {
|
||||
public:
|
||||
//! Queues a callback object. This can be called from any thread, implementation ensures multithread safety. Implementation will call p_callback->callback_run() once later. To get it called repeatedly, you would need to add your callback again.
|
||||
virtual void add_callback(service_ptr_t<main_thread_callback> p_callback) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(main_thread_callback_manager);
|
||||
};
|
|
@ -0,0 +1,34 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
bool mainmenu_commands::g_execute(const GUID & p_guid,service_ptr_t<service_base> p_callback) {
|
||||
service_enum_t<mainmenu_commands> e;
|
||||
service_ptr_t<mainmenu_commands> ptr;
|
||||
while(e.next(ptr)) {
|
||||
const t_uint32 count = ptr->get_command_count();
|
||||
for(t_uint32 n=0;n<count;n++) {
|
||||
if (ptr->get_command(n) == p_guid) {
|
||||
ptr->execute(n,p_callback);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool mainmenu_commands::g_find_by_name(const char * p_name,GUID & p_guid) {
|
||||
service_enum_t<mainmenu_commands> e;
|
||||
service_ptr_t<mainmenu_commands> ptr;
|
||||
pfc::string8_fastalloc temp;
|
||||
while(e.next(ptr)) {
|
||||
const t_uint32 count = ptr->get_command_count();
|
||||
for(t_uint32 n=0;n<count;n++) {
|
||||
ptr->get_name(n,temp);
|
||||
if (stricmp_utf8(temp,p_name) == 0) {
|
||||
p_guid = ptr->get_command(n);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
void mem_block_container::from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort) {
|
||||
if (p_bytes == 0) {set_size(0);}
|
||||
set_size(p_bytes);
|
||||
p_stream->read_object(get_ptr(),p_bytes,p_abort);
|
||||
}
|
||||
|
||||
void mem_block_container::set(const void * p_buffer,t_size p_size) {
|
||||
set_size(p_size);
|
||||
memcpy(get_ptr(),p_buffer,p_size);
|
||||
}
|
|
@ -0,0 +1,68 @@
|
|||
//! Generic interface for a memory block; used by various other interfaces to return memory blocks while allowing caller to allocate.
|
||||
class NOVTABLE mem_block_container {
|
||||
public:
|
||||
virtual const void * get_ptr() const = 0;
|
||||
virtual void * get_ptr() = 0;
|
||||
virtual t_size get_size() const = 0;
|
||||
virtual void set_size(t_size p_size) = 0;
|
||||
|
||||
void from_stream(stream_reader * p_stream,t_size p_bytes,abort_callback & p_abort);
|
||||
|
||||
void set(const void * p_buffer,t_size p_size);
|
||||
void set(const mem_block_container & source) {copy(source);}
|
||||
template<typename t_source> void set(const t_source & source) {
|
||||
pfc::static_assert<sizeof(source[0]) == 1>();
|
||||
set(source.get_ptr(), source.get_size());
|
||||
}
|
||||
|
||||
inline void copy(const mem_block_container & p_source) {set(p_source.get_ptr(),p_source.get_size());}
|
||||
inline void reset() {set_size(0);}
|
||||
|
||||
const mem_block_container & operator=(const mem_block_container & p_source) {copy(p_source);return *this;}
|
||||
|
||||
protected:
|
||||
mem_block_container() {}
|
||||
~mem_block_container() {}
|
||||
};
|
||||
|
||||
//! mem_block_container implementation.
|
||||
template<template<typename> class t_alloc = pfc::alloc_standard>
|
||||
class mem_block_container_impl_t : public mem_block_container {
|
||||
public:
|
||||
const void * get_ptr() const {return m_data.get_ptr();}
|
||||
void * get_ptr() {return m_data.get_ptr();}
|
||||
t_size get_size() const {return m_data.get_size();}
|
||||
void set_size(t_size p_size) {
|
||||
m_data.set_size(p_size);
|
||||
}
|
||||
private:
|
||||
pfc::array_t<t_uint8,t_alloc> m_data;
|
||||
};
|
||||
|
||||
typedef mem_block_container_impl_t<> mem_block_container_impl;
|
||||
|
||||
class mem_block_container_temp_impl : public mem_block_container {
|
||||
public:
|
||||
mem_block_container_temp_impl(void * p_buffer,t_size p_size) : m_buffer(p_buffer), m_buffer_size(p_size), m_size(0) {}
|
||||
const void * get_ptr() const {return m_buffer;}
|
||||
void * get_ptr() {return m_buffer;}
|
||||
t_size get_size() const {return m_size;}
|
||||
void set_size(t_size p_size) {if (p_size > m_buffer_size) throw pfc::exception_overflow(); m_size = p_size;}
|
||||
private:
|
||||
t_size m_size,m_buffer_size;
|
||||
void * m_buffer;
|
||||
};
|
||||
|
||||
template<typename t_ref>
|
||||
class mem_block_container_ref_impl : public mem_block_container {
|
||||
public:
|
||||
mem_block_container_ref_impl(t_ref & ref) : m_ref(ref) {
|
||||
pfc::static_assert<sizeof(ref[0]) == 1>();
|
||||
}
|
||||
const void * get_ptr() const {return m_ref.get_ptr();}
|
||||
void * get_ptr() {return m_ref.get_ptr();}
|
||||
t_size get_size() const {return m_ref.get_size();}
|
||||
void set_size(t_size p_size) {m_ref.set_size(p_size);}
|
||||
private:
|
||||
t_ref & m_ref;
|
||||
};
|
|
@ -0,0 +1,128 @@
|
|||
class NOVTABLE mainmenu_group : public service_base {
|
||||
public:
|
||||
virtual GUID get_guid() = 0;
|
||||
virtual GUID get_parent() = 0;
|
||||
virtual t_uint32 get_sort_priority() = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(mainmenu_group);
|
||||
};
|
||||
|
||||
class NOVTABLE mainmenu_group_popup : public mainmenu_group {
|
||||
public:
|
||||
virtual void get_display_string(pfc::string_base & p_out) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(mainmenu_group_popup,mainmenu_group);
|
||||
};
|
||||
|
||||
class NOVTABLE mainmenu_commands : public service_base {
|
||||
public:
|
||||
enum {
|
||||
flag_disabled = 1<<0,
|
||||
flag_checked = 1<<1,
|
||||
flag_radiochecked = 1<<2,
|
||||
sort_priority_base = 0x10000,
|
||||
sort_priority_dontcare = 0x80000000,
|
||||
sort_priority_last = infinite,
|
||||
};
|
||||
|
||||
//! Retrieves number of implemented commands. Index parameter of other methods must be in 0....command_count-1 range.
|
||||
virtual t_uint32 get_command_count() = 0;
|
||||
//! Retrieves GUID of specified command.
|
||||
virtual GUID get_command(t_uint32 p_index) = 0;
|
||||
//! Retrieves name of item, for list of commands to assign keyboard shortcuts to etc.
|
||||
virtual void get_name(t_uint32 p_index,pfc::string_base & p_out) = 0;
|
||||
//! Retrieves item's description for statusbar etc.
|
||||
virtual bool get_description(t_uint32 p_index,pfc::string_base & p_out) = 0;
|
||||
//! Retrieves GUID of owning menu group.
|
||||
virtual GUID get_parent() = 0;
|
||||
//! Retrieves sorting priority of the command; the lower the number, the upper in the menu your commands will appear. Third party components should use sorting_priority_base and up (values below are reserved for internal use). In case of equal priority, order is undefined.
|
||||
virtual t_uint32 get_sort_priority() {return sort_priority_dontcare;}
|
||||
//! Retrieves display string and display flags to use when menu is about to be displayed. If returns false, menu item won't be displayed. You can create keyboard-shortcut-only commands by always returning false from get_display().
|
||||
virtual bool get_display(t_uint32 p_index,pfc::string_base & p_text,t_uint32 & p_flags) {p_flags = 0;get_name(p_index,p_text);return true;}
|
||||
//! Executes the command. p_callback parameter is reserved for future use and should be ignored / set to null pointer.
|
||||
virtual void execute(t_uint32 p_index,service_ptr_t<service_base> p_callback) = 0;
|
||||
|
||||
static bool g_execute(const GUID & p_guid,service_ptr_t<service_base> p_callback = service_ptr_t<service_base>());
|
||||
static bool g_find_by_name(const char * p_name,GUID & p_guid);
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(mainmenu_commands);
|
||||
};
|
||||
|
||||
class NOVTABLE mainmenu_manager : public service_base {
|
||||
public:
|
||||
enum {
|
||||
flag_show_shortcuts = 1 << 0,
|
||||
flag_show_shortcuts_global = 1 << 1,
|
||||
};
|
||||
|
||||
virtual void instantiate(const GUID & p_root) = 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
virtual void generate_menu_win32(HMENU p_menu,t_uint32 p_id_base,t_uint32 p_id_count,t_uint32 p_flags) = 0;
|
||||
#else
|
||||
#error portme
|
||||
#endif
|
||||
//@param p_id Identifier of command to execute, relative to p_id_base of generate_menu_*()
|
||||
//@returns true if command was executed successfully, false if not (e.g. command with given identifier not found).
|
||||
virtual bool execute_command(t_uint32 p_id,service_ptr_t<service_base> p_callback = service_ptr_t<service_base>()) = 0;
|
||||
|
||||
virtual bool get_description(t_uint32 p_id,pfc::string_base & p_out) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(mainmenu_manager);
|
||||
};
|
||||
|
||||
class mainmenu_groups {
|
||||
public:
|
||||
static const GUID file,view,edit,playback,library,help;
|
||||
static const GUID file_open,file_add,file_playlist,file_etc;
|
||||
static const GUID playback_controls,playback_etc;
|
||||
static const GUID view_visualisations, view_alwaysontop;
|
||||
static const GUID edit_part1,edit_part2,edit_part3;
|
||||
static const GUID edit_part2_selection,edit_part2_sort,edit_part2_selection_sort;
|
||||
static const GUID file_etc_preferences, file_etc_exit;
|
||||
static const GUID help_about;
|
||||
static const GUID library_refresh;
|
||||
|
||||
enum {priority_edit_part1,priority_edit_part2,priority_edit_part3};
|
||||
enum {priority_edit_part2_commands,priority_edit_part2_selection,priority_edit_part2_sort};
|
||||
enum {priority_edit_part2_selection_commands,priority_edit_part2_selection_sort};
|
||||
enum {priority_file_open,priority_file_add,priority_file_playlist,priority_file_etc = mainmenu_commands::sort_priority_last};
|
||||
};
|
||||
|
||||
|
||||
class mainmenu_group_impl : public mainmenu_group {
|
||||
public:
|
||||
mainmenu_group_impl(const GUID & p_guid,const GUID & p_parent,t_uint32 p_priority) : m_guid(p_guid), m_parent(p_parent), m_priority(p_priority) {}
|
||||
GUID get_guid() {return m_guid;}
|
||||
GUID get_parent() {return m_parent;}
|
||||
t_uint32 get_sort_priority() {return m_priority;}
|
||||
private:
|
||||
GUID m_guid,m_parent; t_uint32 m_priority;
|
||||
};
|
||||
|
||||
class mainmenu_group_popup_impl : public mainmenu_group_popup {
|
||||
public:
|
||||
mainmenu_group_popup_impl(const GUID & p_guid,const GUID & p_parent,t_uint32 p_priority,const char * p_name) : m_guid(p_guid), m_parent(p_parent), m_priority(p_priority), m_name(p_name) {}
|
||||
GUID get_guid() {return m_guid;}
|
||||
GUID get_parent() {return m_parent;}
|
||||
t_uint32 get_sort_priority() {return m_priority;}
|
||||
void get_display_string(pfc::string_base & p_out) {p_out = m_name;}
|
||||
private:
|
||||
GUID m_guid,m_parent; t_uint32 m_priority; pfc::string8 m_name;
|
||||
};
|
||||
|
||||
typedef service_factory_single_t<mainmenu_group_impl> __mainmenu_group_factory;
|
||||
typedef service_factory_single_t<mainmenu_group_popup_impl> __mainmenu_group_popup_factory;
|
||||
|
||||
class mainmenu_group_factory : public __mainmenu_group_factory {
|
||||
public:
|
||||
inline mainmenu_group_factory(const GUID & p_guid,const GUID & p_parent,t_uint32 p_priority) : __mainmenu_group_factory(p_guid,p_parent,p_priority) {}
|
||||
};
|
||||
|
||||
class mainmenu_group_popup_factory : public __mainmenu_group_popup_factory {
|
||||
public:
|
||||
inline mainmenu_group_popup_factory(const GUID & p_guid,const GUID & p_parent,t_uint32 p_priority,const char * p_name) : __mainmenu_group_popup_factory(p_guid,p_parent,p_priority,p_name) {}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class mainmenu_commands_factory_t : public service_factory_single_t<T> {};
|
|
@ -0,0 +1,326 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
|
||||
bool menu_helpers::context_get_description(const GUID& p_guid,pfc::string_base & out) {
|
||||
service_ptr_t<contextmenu_item> ptr; t_uint32 index;
|
||||
if (!menu_item_resolver::g_resolve_context_command(p_guid, ptr, index)) return false;
|
||||
bool rv = ptr->get_item_description(index, out);
|
||||
if (!rv) out.reset();
|
||||
return rv;
|
||||
}
|
||||
|
||||
static bool run_context_command_internal(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data,const GUID & caller) {
|
||||
if (data.get_count() == 0) return false;
|
||||
service_ptr_t<contextmenu_item> ptr; t_uint32 index;
|
||||
if (!menu_item_resolver::g_resolve_context_command(p_command, ptr, index)) return false;
|
||||
|
||||
{
|
||||
TRACK_CALL_TEXT("menu_helpers::run_command(), by GUID");
|
||||
ptr->item_execute_simple(index, p_subcommand, data, caller);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool menu_helpers::run_command_context(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data)
|
||||
{
|
||||
return run_context_command_internal(p_command,p_subcommand,data,contextmenu_item::caller_undefined);
|
||||
}
|
||||
|
||||
bool menu_helpers::run_command_context_ex(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data,const GUID & caller)
|
||||
{
|
||||
return run_context_command_internal(p_command,p_subcommand,data,caller);
|
||||
}
|
||||
|
||||
bool menu_helpers::test_command_context(const GUID & p_guid)
|
||||
{
|
||||
service_ptr_t<contextmenu_item> ptr; t_uint32 index;
|
||||
return menu_item_resolver::g_resolve_context_command(p_guid, ptr, index);
|
||||
}
|
||||
|
||||
static bool g_is_checked(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data,const GUID & caller)
|
||||
{
|
||||
service_ptr_t<contextmenu_item> ptr; t_uint32 index;
|
||||
if (!menu_item_resolver::g_resolve_context_command(p_command, ptr, index)) return false;
|
||||
|
||||
unsigned displayflags = 0;
|
||||
pfc::string_formatter dummystring;
|
||||
if (!ptr->item_get_display_data(dummystring,displayflags,index,p_subcommand,data,caller)) return false;
|
||||
return (displayflags & contextmenu_item_node::FLAG_CHECKED) != 0;
|
||||
|
||||
}
|
||||
|
||||
bool menu_helpers::is_command_checked_context(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data)
|
||||
{
|
||||
return g_is_checked(p_command,p_subcommand,data,contextmenu_item::caller_undefined);
|
||||
}
|
||||
|
||||
bool menu_helpers::is_command_checked_context_playlist(const GUID & p_command,const GUID & p_subcommand)
|
||||
{
|
||||
metadb_handle_list temp;
|
||||
static_api_ptr_t<playlist_manager> api;
|
||||
api->activeplaylist_get_selected_items(temp);
|
||||
return g_is_checked(p_command,p_subcommand,temp,contextmenu_item::caller_playlist);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
bool menu_helpers::run_command_context_playlist(const GUID & p_command,const GUID & p_subcommand)
|
||||
{
|
||||
metadb_handle_list temp;
|
||||
static_api_ptr_t<playlist_manager> api;
|
||||
api->activeplaylist_get_selected_items(temp);
|
||||
return run_command_context_ex(p_command,p_subcommand,temp,contextmenu_item::caller_playlist);
|
||||
}
|
||||
|
||||
bool menu_helpers::run_command_context_now_playing(const GUID & p_command,const GUID & p_subcommand)
|
||||
{
|
||||
metadb_handle_ptr item;
|
||||
if (!static_api_ptr_t<playback_control>()->get_now_playing(item)) return false;//not playing
|
||||
return run_command_context_ex(p_command,p_subcommand,pfc::list_single_ref_t<metadb_handle_ptr>(item),contextmenu_item::caller_now_playing);
|
||||
}
|
||||
|
||||
|
||||
bool menu_helpers::guid_from_name(const char * p_name,unsigned p_name_len,GUID & p_out)
|
||||
{
|
||||
service_enum_t<contextmenu_item> e;
|
||||
service_ptr_t<contextmenu_item> ptr;
|
||||
pfc::string8_fastalloc nametemp;
|
||||
while(e.next(ptr))
|
||||
{
|
||||
unsigned n, m = ptr->get_num_items();
|
||||
for(n=0;n<m;n++)
|
||||
{
|
||||
ptr->get_item_name(n,nametemp);
|
||||
if (!strcmp_ex(nametemp,infinite,p_name,p_name_len))
|
||||
{
|
||||
p_out = ptr->get_item_guid(n);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool menu_helpers::name_from_guid(const GUID & p_guid,pfc::string_base & p_out) {
|
||||
service_ptr_t<contextmenu_item> ptr; t_uint32 index;
|
||||
if (!menu_item_resolver::g_resolve_context_command(p_guid, ptr, index)) return false;
|
||||
ptr->get_item_name(index, p_out);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
static unsigned calc_total_action_count()
|
||||
{
|
||||
service_enum_t<contextmenu_item> e;
|
||||
service_ptr_t<contextmenu_item> ptr;
|
||||
unsigned ret = 0;
|
||||
while(e.next(ptr))
|
||||
ret += ptr->get_num_items();
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
const char * menu_helpers::guid_to_name_table::search(const GUID & p_guid)
|
||||
{
|
||||
if (!m_inited)
|
||||
{
|
||||
m_data.set_size(calc_total_action_count());
|
||||
t_size dataptr = 0;
|
||||
pfc::string8_fastalloc nametemp;
|
||||
|
||||
service_enum_t<contextmenu_item> e;
|
||||
service_ptr_t<contextmenu_item> ptr;
|
||||
while(e.next(ptr))
|
||||
{
|
||||
unsigned n, m = ptr->get_num_items();
|
||||
for(n=0;n<m;n++)
|
||||
{
|
||||
assert(dataptr < m_data.get_size());
|
||||
|
||||
ptr->get_item_name(n,nametemp);
|
||||
m_data[dataptr].m_name = _strdup(nametemp);
|
||||
m_data[dataptr].m_guid = ptr->get_item_guid(n);
|
||||
dataptr++;
|
||||
}
|
||||
}
|
||||
assert(dataptr == m_data.get_size());
|
||||
|
||||
pfc::sort_t(m_data,entry_compare,m_data.get_size());
|
||||
m_inited = true;
|
||||
}
|
||||
t_size index;
|
||||
if (pfc::bsearch_t(m_data.get_size(),m_data,entry_compare_search,p_guid,index))
|
||||
return m_data[index].m_name;
|
||||
else
|
||||
return 0;
|
||||
}
|
||||
|
||||
int menu_helpers::guid_to_name_table::entry_compare_search(const entry & entry1,const GUID & entry2)
|
||||
{
|
||||
return pfc::guid_compare(entry1.m_guid,entry2);
|
||||
}
|
||||
|
||||
int menu_helpers::guid_to_name_table::entry_compare(const entry & entry1,const entry & entry2)
|
||||
{
|
||||
return pfc::guid_compare(entry1.m_guid,entry2.m_guid);
|
||||
}
|
||||
|
||||
menu_helpers::guid_to_name_table::guid_to_name_table()
|
||||
{
|
||||
m_inited = false;
|
||||
}
|
||||
|
||||
menu_helpers::guid_to_name_table::~guid_to_name_table()
|
||||
{
|
||||
t_size n, m = m_data.get_size();
|
||||
for(n=0;n<m;n++) free(m_data[n].m_name);
|
||||
|
||||
}
|
||||
|
||||
|
||||
int menu_helpers::name_to_guid_table::entry_compare_search(const entry & entry1,const search_entry & entry2)
|
||||
{
|
||||
return stricmp_utf8_ex(entry1.m_name,infinite,entry2.m_name,entry2.m_name_len);
|
||||
}
|
||||
|
||||
int menu_helpers::name_to_guid_table::entry_compare(const entry & entry1,const entry & entry2)
|
||||
{
|
||||
return stricmp_utf8(entry1.m_name,entry2.m_name);
|
||||
}
|
||||
|
||||
bool menu_helpers::name_to_guid_table::search(const char * p_name,unsigned p_name_len,GUID & p_out)
|
||||
{
|
||||
if (!m_inited)
|
||||
{
|
||||
m_data.set_size(calc_total_action_count());
|
||||
t_size dataptr = 0;
|
||||
pfc::string8_fastalloc nametemp;
|
||||
|
||||
service_enum_t<contextmenu_item> e;
|
||||
service_ptr_t<contextmenu_item> ptr;
|
||||
while(e.next(ptr))
|
||||
{
|
||||
unsigned n, m = ptr->get_num_items();
|
||||
for(n=0;n<m;n++)
|
||||
{
|
||||
assert(dataptr < m_data.get_size());
|
||||
|
||||
ptr->get_item_name(n,nametemp);
|
||||
m_data[dataptr].m_name = _strdup(nametemp);
|
||||
m_data[dataptr].m_guid = ptr->get_item_guid(n);
|
||||
dataptr++;
|
||||
}
|
||||
}
|
||||
assert(dataptr == m_data.get_size());
|
||||
|
||||
pfc::sort_t(m_data,entry_compare,m_data.get_size());
|
||||
m_inited = true;
|
||||
}
|
||||
t_size index;
|
||||
search_entry temp = {p_name,p_name_len};
|
||||
if (pfc::bsearch_t(m_data.get_size(),m_data,entry_compare_search,temp,index))
|
||||
{
|
||||
p_out = m_data[index].m_guid;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
return false;
|
||||
}
|
||||
|
||||
menu_helpers::name_to_guid_table::name_to_guid_table()
|
||||
{
|
||||
m_inited = false;
|
||||
}
|
||||
|
||||
menu_helpers::name_to_guid_table::~name_to_guid_table()
|
||||
{
|
||||
t_size n, m = m_data.get_size();
|
||||
for(n=0;n<m;n++) free(m_data[n].m_name);
|
||||
|
||||
}
|
||||
|
||||
bool menu_helpers::find_command_by_name(const char * p_name,service_ptr_t<contextmenu_item> & p_item,unsigned & p_index)
|
||||
{
|
||||
pfc::string8_fastalloc path,name;
|
||||
service_enum_t<contextmenu_item> e;
|
||||
service_ptr_t<contextmenu_item> ptr;
|
||||
if (e.first(ptr)) do {
|
||||
// if (ptr->get_type()==type)
|
||||
{
|
||||
unsigned action,num_actions = ptr->get_num_items();
|
||||
for(action=0;action<num_actions;action++)
|
||||
{
|
||||
ptr->get_item_default_path(action,path); ptr->get_item_name(action,name);
|
||||
if (!path.is_empty()) path += "/";
|
||||
path += name;
|
||||
if (!stricmp_utf8(p_name,path))
|
||||
{
|
||||
p_item = ptr;
|
||||
p_index = action;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while(e.next(ptr));
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
bool menu_helpers::find_command_by_name(const char * p_name,GUID & p_command)
|
||||
{
|
||||
service_ptr_t<contextmenu_item> item;
|
||||
unsigned index;
|
||||
bool ret = find_command_by_name(p_name,item,index);
|
||||
if (ret) p_command = item->get_item_guid(index);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
bool standard_commands::run_main(const GUID & p_guid) {
|
||||
t_uint32 index;
|
||||
mainmenu_commands::ptr ptr;
|
||||
if (!menu_item_resolver::g_resolve_main_command(p_guid, ptr, index)) return false;
|
||||
ptr->execute(index,service_ptr_t<service_base>());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool menu_item_resolver::g_resolve_context_command(const GUID & id, contextmenu_item::ptr & out, t_uint32 & out_index) {
|
||||
menu_item_resolver::ptr api;
|
||||
if (service_enum_t<menu_item_resolver>().first(api)) {
|
||||
return api->resolve_context_command(id, out, out_index);
|
||||
} else {
|
||||
service_enum_t<contextmenu_item> e; service_ptr_t<contextmenu_item> ptr;
|
||||
while(e.next(ptr)) {
|
||||
const unsigned num_actions = ptr->get_num_items();
|
||||
for(unsigned action=0;action<num_actions;action++) {
|
||||
if (id == ptr->get_item_guid(action)) {
|
||||
out_index = action; out = ptr; return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool menu_item_resolver::g_resolve_main_command(const GUID & id, mainmenu_commands::ptr & out, t_uint32 & out_index) {
|
||||
menu_item_resolver::ptr api;
|
||||
if (service_enum_t<menu_item_resolver>().first(api)) {
|
||||
return api->resolve_main_command(id, out, out_index);
|
||||
} else {
|
||||
service_enum_t<mainmenu_commands> e; service_ptr_t<mainmenu_commands> ptr;
|
||||
while(e.next(ptr)) {
|
||||
const t_uint32 total = ptr->get_command_count();
|
||||
for(t_uint32 walk = 0; walk < total; ++walk) {
|
||||
if (id == ptr->get_command(walk)) {
|
||||
out_index = walk; out = ptr; return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
namespace menu_helpers {
|
||||
#ifdef _WIN32
|
||||
void win32_auto_mnemonics(HMENU menu);
|
||||
#endif
|
||||
|
||||
bool run_command_context(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data);
|
||||
bool run_command_context_ex(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data,const GUID & caller);
|
||||
bool run_command_context_playlist(const GUID & p_command,const GUID & p_subcommand);
|
||||
bool run_command_context_now_playing(const GUID & p_command,const GUID & p_subcommand);
|
||||
|
||||
bool test_command_context(const GUID & p_guid);
|
||||
|
||||
bool is_command_checked_context(const GUID & p_command,const GUID & p_subcommand,const pfc::list_base_const_t<metadb_handle_ptr> & data);
|
||||
bool is_command_checked_context_playlist(const GUID & p_command,const GUID & p_subcommand);
|
||||
|
||||
bool find_command_by_name(const char * p_name,service_ptr_t<contextmenu_item> & p_item,unsigned & p_index);
|
||||
bool find_command_by_name(const char * p_name,GUID & p_command);
|
||||
|
||||
bool context_get_description(const GUID& p_guid,pfc::string_base & out);
|
||||
|
||||
bool guid_from_name(const char * p_name,unsigned p_name_len,GUID & p_out);
|
||||
bool name_from_guid(const GUID & p_guid,pfc::string_base & p_out);
|
||||
|
||||
class guid_to_name_table
|
||||
{
|
||||
public:
|
||||
guid_to_name_table();
|
||||
~guid_to_name_table();
|
||||
const char * search(const GUID & p_guid);
|
||||
private:
|
||||
struct entry {
|
||||
char* m_name;
|
||||
GUID m_guid;
|
||||
};
|
||||
pfc::array_t<entry> m_data;
|
||||
bool m_inited;
|
||||
|
||||
static int entry_compare_search(const entry & entry1,const GUID & entry2);
|
||||
static int entry_compare(const entry & entry1,const entry & entry2);
|
||||
};
|
||||
|
||||
class name_to_guid_table
|
||||
{
|
||||
public:
|
||||
name_to_guid_table();
|
||||
~name_to_guid_table();
|
||||
bool search(const char * p_name,unsigned p_name_len,GUID & p_out);
|
||||
private:
|
||||
struct entry {
|
||||
char* m_name;
|
||||
GUID m_guid;
|
||||
};
|
||||
pfc::array_t<entry> m_data;
|
||||
bool m_inited;
|
||||
struct search_entry {
|
||||
const char * m_name; unsigned m_name_len;
|
||||
};
|
||||
|
||||
static int entry_compare_search(const entry & entry1,const search_entry & entry2);
|
||||
static int entry_compare(const entry & entry1,const entry & entry2);
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
|
||||
|
||||
class standard_commands
|
||||
{
|
||||
public:
|
||||
static const GUID
|
||||
guid_context_file_properties, guid_context_file_open_directory, guid_context_copy_names,
|
||||
guid_context_send_to_playlist, guid_context_reload_info, guid_context_reload_info_if_changed,
|
||||
guid_context_rewrite_info, guid_context_remove_tags,
|
||||
guid_context_convert_run, guid_context_convert_run_singlefile,guid_context_convert_run_withcue,
|
||||
guid_context_write_cd,
|
||||
guid_context_rg_scan_track, guid_context_rg_scan_album, guid_context_rg_scan_album_multi,
|
||||
guid_context_rg_remove, guid_context_save_playlist, guid_context_masstag_edit,
|
||||
guid_context_masstag_rename,
|
||||
guid_main_always_on_top, guid_main_preferences, guid_main_about,
|
||||
guid_main_exit, guid_main_restart, guid_main_activate,
|
||||
guid_main_hide, guid_main_activate_or_hide, guid_main_titleformat_help,
|
||||
guid_main_next, guid_main_previous,
|
||||
guid_main_next_or_random, guid_main_random, guid_main_pause,
|
||||
guid_main_play, guid_main_play_or_pause, guid_main_rg_set_album,
|
||||
guid_main_rg_set_track, guid_main_rg_disable, guid_main_stop,
|
||||
guid_main_stop_after_current, guid_main_volume_down, guid_main_volume_up,
|
||||
guid_main_volume_mute, guid_main_add_directory, guid_main_add_files,
|
||||
guid_main_add_location, guid_main_add_playlist, guid_main_clear_playlist,
|
||||
guid_main_create_playlist, guid_main_highlight_playing, guid_main_load_playlist,
|
||||
guid_main_next_playlist, guid_main_previous_playlist, guid_main_open,
|
||||
guid_main_remove_playlist, guid_main_remove_dead_entries, guid_main_remove_duplicates,
|
||||
guid_main_rename_playlist, guid_main_save_all_playlists, guid_main_save_playlist,
|
||||
guid_main_playlist_search, guid_main_playlist_sel_crop, guid_main_playlist_sel_remove,
|
||||
guid_main_playlist_sel_invert, guid_main_playlist_undo, guid_main_show_console,
|
||||
guid_main_play_cd, guid_main_restart_resetconfig, guid_main_record,
|
||||
guid_main_playlist_moveback, guid_main_playlist_moveforward, guid_main_playlist_redo,
|
||||
guid_main_playback_follows_cursor, guid_main_cursor_follows_playback, guid_main_saveconfig,
|
||||
guid_main_playlist_select_all, guid_main_show_now_playing,
|
||||
|
||||
guid_seek_ahead_1s, guid_seek_ahead_5s, guid_seek_ahead_10s, guid_seek_ahead_30s,
|
||||
guid_seek_ahead_1min, guid_seek_ahead_2min, guid_seek_ahead_5min, guid_seek_ahead_10min,
|
||||
|
||||
guid_seek_back_1s, guid_seek_back_5s, guid_seek_back_10s, guid_seek_back_30s,
|
||||
guid_seek_back_1min, guid_seek_back_2min, guid_seek_back_5min, guid_seek_back_10min
|
||||
;
|
||||
|
||||
static bool run_main(const GUID & guid);
|
||||
static inline bool run_context(const GUID & guid,const pfc::list_base_const_t<metadb_handle_ptr> &data) {return menu_helpers::run_command_context(guid,pfc::guid_null,data);}
|
||||
static inline bool run_context(const GUID & guid,const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller) {return menu_helpers::run_command_context_ex(guid,pfc::guid_null,data,caller);}
|
||||
|
||||
static inline bool context_file_properties(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_file_properties,data,caller);}
|
||||
static inline bool context_file_open_directory(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_file_open_directory,data,caller);}
|
||||
static inline bool context_copy_names(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_copy_names,data,caller);}
|
||||
static inline bool context_send_to_playlist(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_send_to_playlist,data,caller);}
|
||||
static inline bool context_reload_info(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_reload_info,data,caller);}
|
||||
static inline bool context_reload_info_if_changed(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_reload_info_if_changed,data,caller);}
|
||||
static inline bool context_rewrite_info(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_rewrite_info,data,caller);}
|
||||
static inline bool context_remove_tags(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_remove_tags,data,caller);}
|
||||
static inline bool context_convert_run(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_convert_run,data,caller);}
|
||||
static inline bool context_convert_run_singlefile(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_convert_run_singlefile,data,caller);}
|
||||
static inline bool context_write_cd(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_write_cd,data,caller);}
|
||||
static inline bool context_rg_scan_track(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_rg_scan_track,data,caller);}
|
||||
static inline bool context_rg_scan_album(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_rg_scan_album,data,caller);}
|
||||
static inline bool context_rg_scan_album_multi(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_rg_scan_album_multi,data,caller);}
|
||||
static inline bool context_rg_remove(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_rg_remove,data,caller);}
|
||||
static inline bool context_save_playlist(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_save_playlist,data,caller);}
|
||||
static inline bool context_masstag_edit(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_masstag_edit,data,caller);}
|
||||
static inline bool context_masstag_rename(const pfc::list_base_const_t<metadb_handle_ptr> &data,const GUID& caller = contextmenu_item::caller_undefined) {return run_context(guid_context_masstag_rename,data,caller);}
|
||||
static inline bool main_always_on_top() {return run_main(guid_main_always_on_top);}
|
||||
static inline bool main_preferences() {return run_main(guid_main_preferences);}
|
||||
static inline bool main_about() {return run_main(guid_main_about);}
|
||||
static inline bool main_exit() {return run_main(guid_main_exit);}
|
||||
static inline bool main_restart() {return run_main(guid_main_restart);}
|
||||
static inline bool main_activate() {return run_main(guid_main_activate);}
|
||||
static inline bool main_hide() {return run_main(guid_main_hide);}
|
||||
static inline bool main_activate_or_hide() {return run_main(guid_main_activate_or_hide);}
|
||||
static inline bool main_titleformat_help() {return run_main(guid_main_titleformat_help);}
|
||||
static inline bool main_playback_follows_cursor() {return run_main(guid_main_playback_follows_cursor);}
|
||||
static inline bool main_next() {return run_main(guid_main_next);}
|
||||
static inline bool main_previous() {return run_main(guid_main_previous);}
|
||||
static inline bool main_next_or_random() {return run_main(guid_main_next_or_random);}
|
||||
static inline bool main_random() {return run_main(guid_main_random);}
|
||||
static inline bool main_pause() {return run_main(guid_main_pause);}
|
||||
static inline bool main_play() {return run_main(guid_main_play);}
|
||||
static inline bool main_play_or_pause() {return run_main(guid_main_play_or_pause);}
|
||||
static inline bool main_rg_set_album() {return run_main(guid_main_rg_set_album);}
|
||||
static inline bool main_rg_set_track() {return run_main(guid_main_rg_set_track);}
|
||||
static inline bool main_rg_disable() {return run_main(guid_main_rg_disable);}
|
||||
static inline bool main_stop() {return run_main(guid_main_stop);}
|
||||
static inline bool main_stop_after_current() {return run_main(guid_main_stop_after_current);}
|
||||
static inline bool main_volume_down() {return run_main(guid_main_volume_down);}
|
||||
static inline bool main_volume_up() {return run_main(guid_main_volume_up);}
|
||||
static inline bool main_volume_mute() {return run_main(guid_main_volume_mute);}
|
||||
static inline bool main_add_directory() {return run_main(guid_main_add_directory);}
|
||||
static inline bool main_add_files() {return run_main(guid_main_add_files);}
|
||||
static inline bool main_add_location() {return run_main(guid_main_add_location);}
|
||||
static inline bool main_add_playlist() {return run_main(guid_main_add_playlist);}
|
||||
static inline bool main_clear_playlist() {return run_main(guid_main_clear_playlist);}
|
||||
static inline bool main_create_playlist() {return run_main(guid_main_create_playlist);}
|
||||
static inline bool main_highlight_playing() {return run_main(guid_main_highlight_playing);}
|
||||
static inline bool main_load_playlist() {return run_main(guid_main_load_playlist);}
|
||||
static inline bool main_next_playlist() {return run_main(guid_main_next_playlist);}
|
||||
static inline bool main_previous_playlist() {return run_main(guid_main_previous_playlist);}
|
||||
static inline bool main_open() {return run_main(guid_main_open);}
|
||||
static inline bool main_remove_playlist() {return run_main(guid_main_remove_playlist);}
|
||||
static inline bool main_remove_dead_entries() {return run_main(guid_main_remove_dead_entries);}
|
||||
static inline bool main_remove_duplicates() {return run_main(guid_main_remove_duplicates);}
|
||||
static inline bool main_rename_playlist() {return run_main(guid_main_rename_playlist);}
|
||||
static inline bool main_save_all_playlists() {return run_main(guid_main_save_all_playlists);}
|
||||
static inline bool main_save_playlist() {return run_main(guid_main_save_playlist);}
|
||||
static inline bool main_playlist_search() {return run_main(guid_main_playlist_search);}
|
||||
static inline bool main_playlist_sel_crop() {return run_main(guid_main_playlist_sel_crop);}
|
||||
static inline bool main_playlist_sel_remove() {return run_main(guid_main_playlist_sel_remove);}
|
||||
static inline bool main_playlist_sel_invert() {return run_main(guid_main_playlist_sel_invert);}
|
||||
static inline bool main_playlist_undo() {return run_main(guid_main_playlist_undo);}
|
||||
static inline bool main_show_console() {return run_main(guid_main_show_console);}
|
||||
static inline bool main_play_cd() {return run_main(guid_main_play_cd);}
|
||||
static inline bool main_restart_resetconfig() {return run_main(guid_main_restart_resetconfig);}
|
||||
static inline bool main_playlist_moveback() {return run_main(guid_main_playlist_moveback);}
|
||||
static inline bool main_playlist_moveforward() {return run_main(guid_main_playlist_moveforward);}
|
||||
static inline bool main_saveconfig() {return run_main(guid_main_saveconfig);}
|
||||
};
|
|
@ -0,0 +1,40 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
|
||||
|
||||
bool contextmenu_item::item_get_display_data_root(pfc::string_base & p_out,unsigned & p_displayflags,unsigned p_index,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const GUID & p_caller)
|
||||
{
|
||||
bool status = false;
|
||||
pfc::ptrholder_t<contextmenu_item_node_root> root ( instantiate_item(p_index,p_data,p_caller) );
|
||||
if (root.is_valid()) status = root->get_display_data(p_out,p_displayflags,p_data,p_caller);
|
||||
return status;
|
||||
}
|
||||
|
||||
|
||||
static contextmenu_item_node * g_find_node(const GUID & p_guid,contextmenu_item_node * p_parent)
|
||||
{
|
||||
if (p_parent->get_guid() == p_guid) return p_parent;
|
||||
else if (p_parent->get_type() == contextmenu_item_node::TYPE_POPUP)
|
||||
{
|
||||
t_size n, m = p_parent->get_children_count();
|
||||
for(n=0;n<m;n++)
|
||||
{
|
||||
contextmenu_item_node * temp = g_find_node(p_guid,p_parent->get_child(n));
|
||||
if (temp) return temp;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
else return 0;
|
||||
}
|
||||
|
||||
bool contextmenu_item::item_get_display_data(pfc::string_base & p_out,unsigned & p_displayflags,unsigned p_index,const GUID & p_node,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const GUID & p_caller)
|
||||
{
|
||||
bool status = false;
|
||||
pfc::ptrholder_t<contextmenu_item_node_root> root ( instantiate_item(p_index,p_data,p_caller) );
|
||||
if (root.is_valid())
|
||||
{
|
||||
contextmenu_item_node * node = g_find_node(p_node,root.get_ptr());
|
||||
if (node) status = node->get_display_data(p_out,p_displayflags,p_data,p_caller);
|
||||
}
|
||||
return status;
|
||||
}
|
|
@ -0,0 +1,401 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
static void fix_ampersand(const char * src,pfc::string_base & out)
|
||||
{
|
||||
out.reset();
|
||||
unsigned ptr = 0;
|
||||
while(src[ptr])
|
||||
{
|
||||
if (src[ptr]=='&')
|
||||
{
|
||||
out.add_string("&&");
|
||||
ptr++;
|
||||
while(src[ptr]=='&')
|
||||
{
|
||||
out.add_string("&&");
|
||||
ptr++;
|
||||
}
|
||||
}
|
||||
else out.add_byte(src[ptr++]);
|
||||
}
|
||||
}
|
||||
|
||||
static unsigned flags_to_win32(unsigned flags)
|
||||
{
|
||||
unsigned ret = 0;
|
||||
if (flags & contextmenu_item_node::FLAG_RADIOCHECKED) {/* dealt with elsewhere */}
|
||||
else if (flags & contextmenu_item_node::FLAG_CHECKED) ret |= MF_CHECKED;
|
||||
if (flags & contextmenu_item_node::FLAG_DISABLED) ret |= MF_DISABLED;
|
||||
if (flags & contextmenu_item_node::FLAG_GRAYED) ret |= MF_GRAYED;
|
||||
return ret;
|
||||
}
|
||||
|
||||
void contextmenu_manager::win32_build_menu(HMENU menu,contextmenu_node * parent,int base_id,int max_id)//menu item identifiers are base_id<=N<base_id+max_id (if theres too many items, they will be clipped)
|
||||
{
|
||||
if (parent!=0 && parent->get_type()==contextmenu_item_node::TYPE_POPUP)
|
||||
{
|
||||
pfc::string8_fastalloc temp;
|
||||
t_size child_idx,child_num = parent->get_num_children();
|
||||
for(child_idx=0;child_idx<child_num;child_idx++)
|
||||
{
|
||||
contextmenu_node * child = parent->get_child(child_idx);
|
||||
if (child)
|
||||
{
|
||||
const char * name = child->get_name();
|
||||
if (strchr(name,'&')) {fix_ampersand(name,temp);name=temp;}
|
||||
contextmenu_item_node::t_type type = child->get_type();
|
||||
if (type==contextmenu_item_node::TYPE_POPUP)
|
||||
{
|
||||
HMENU new_menu = CreatePopupMenu();
|
||||
uAppendMenu(menu,MF_STRING|MF_POPUP | flags_to_win32(child->get_display_flags()),(UINT_PTR)new_menu,name);
|
||||
win32_build_menu(new_menu,child,base_id,max_id);
|
||||
}
|
||||
else if (type==contextmenu_item_node::TYPE_SEPARATOR)
|
||||
{
|
||||
uAppendMenu(menu,MF_SEPARATOR,0,0);
|
||||
}
|
||||
else if (type==contextmenu_item_node::TYPE_COMMAND)
|
||||
{
|
||||
int id = child->get_id();
|
||||
if (id>=0 && (max_id<0 || id<max_id))
|
||||
{
|
||||
const unsigned flags = child->get_display_flags();
|
||||
const UINT ID = base_id+id;
|
||||
uAppendMenu(menu,MF_STRING | flags_to_win32(flags),ID,name);
|
||||
if (flags & contextmenu_item_node::FLAG_RADIOCHECKED) CheckMenuRadioItem(menu,ID,ID,ID,MF_BYCOMMAND);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool contextmenu_manager::get_description_by_id(unsigned id,pfc::string_base & out) {
|
||||
contextmenu_node * ptr = find_by_id(id);
|
||||
if (ptr == NULL) return false;
|
||||
return ptr->get_description(out);
|
||||
}
|
||||
bool contextmenu_manager::execute_by_id(unsigned id) {
|
||||
contextmenu_node * ptr = find_by_id(id);
|
||||
if (ptr == NULL) return false;
|
||||
ptr->execute();
|
||||
return true;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
|
||||
void contextmenu_manager::win32_run_menu_popup(HWND parent,const POINT * pt)
|
||||
{
|
||||
enum {ID_CUSTOM_BASE = 1};
|
||||
|
||||
int cmd;
|
||||
|
||||
{
|
||||
POINT p;
|
||||
if (pt) p = *pt;
|
||||
else GetCursorPos(&p);
|
||||
|
||||
HMENU hmenu = CreatePopupMenu();
|
||||
try {
|
||||
|
||||
win32_build_menu(hmenu,ID_CUSTOM_BASE,-1);
|
||||
menu_helpers::win32_auto_mnemonics(hmenu);
|
||||
|
||||
cmd = TrackPopupMenu(hmenu,TPM_RIGHTBUTTON|TPM_NONOTIFY|TPM_RETURNCMD,p.x,p.y,0,parent,0);
|
||||
} catch(...) {DestroyMenu(hmenu); throw;}
|
||||
|
||||
DestroyMenu(hmenu);
|
||||
}
|
||||
|
||||
if (cmd>0)
|
||||
{
|
||||
if (cmd>=ID_CUSTOM_BASE)
|
||||
{
|
||||
execute_by_id(cmd - ID_CUSTOM_BASE);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void contextmenu_manager::win32_run_menu_context(HWND parent,const pfc::list_base_const_t<metadb_handle_ptr> & data,const POINT * pt,unsigned flags)
|
||||
{
|
||||
service_ptr_t<contextmenu_manager> manager;
|
||||
contextmenu_manager::g_create(manager);
|
||||
manager->init_context(data,flags);
|
||||
manager->win32_run_menu_popup(parent,pt);
|
||||
}
|
||||
|
||||
void contextmenu_manager::win32_run_menu_context_playlist(HWND parent,const POINT * pt,unsigned flags)
|
||||
{
|
||||
service_ptr_t<contextmenu_manager> manager;
|
||||
contextmenu_manager::g_create(manager);
|
||||
manager->init_context_playlist(flags);
|
||||
manager->win32_run_menu_popup(parent,pt);
|
||||
}
|
||||
|
||||
|
||||
namespace {
|
||||
class mnemonic_manager
|
||||
{
|
||||
pfc::string8_fastalloc used;
|
||||
bool is_used(unsigned c)
|
||||
{
|
||||
char temp[8];
|
||||
temp[pfc::utf8_encode_char(uCharLower(c),temp)]=0;
|
||||
return !!strstr(used,temp);
|
||||
}
|
||||
|
||||
static bool is_alphanumeric(char c)
|
||||
{
|
||||
return (c>='a' && c<='z') || (c>='A' && c<='Z') || (c>='0' && c<='9');
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
void insert(const char * src,unsigned idx,pfc::string_base & out)
|
||||
{
|
||||
out.reset();
|
||||
out.add_string(src,idx);
|
||||
out.add_string("&");
|
||||
out.add_string(src+idx);
|
||||
used.add_char(uCharLower(src[idx]));
|
||||
}
|
||||
public:
|
||||
bool check_string(const char * src)
|
||||
{//check for existing mnemonics
|
||||
const char * ptr = src;
|
||||
while(ptr = strchr(ptr,'&'))
|
||||
{
|
||||
if (ptr[1]=='&') ptr+=2;
|
||||
else
|
||||
{
|
||||
unsigned c = 0;
|
||||
if (pfc::utf8_decode_char(ptr+1,c)>0)
|
||||
{
|
||||
if (!is_used(c)) used.add_char(uCharLower(c));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
bool process_string(const char * src,pfc::string_base & out)//returns if changed
|
||||
{
|
||||
if (check_string(src)) {out=src;return false;}
|
||||
unsigned idx=0;
|
||||
while(src[idx]==' ') idx++;
|
||||
while(src[idx])
|
||||
{
|
||||
if (is_alphanumeric(src[idx]) && !is_used(src[idx]))
|
||||
{
|
||||
insert(src,idx,out);
|
||||
return true;
|
||||
}
|
||||
|
||||
while(src[idx] && src[idx]!=' ' && src[idx]!='\t') idx++;
|
||||
if (src[idx]=='\t') break;
|
||||
while(src[idx]==' ') idx++;
|
||||
}
|
||||
|
||||
//no success picking first letter of one of words
|
||||
idx=0;
|
||||
while(src[idx])
|
||||
{
|
||||
if (src[idx]=='\t') break;
|
||||
if (is_alphanumeric(src[idx]) && !is_used(src[idx]))
|
||||
{
|
||||
insert(src,idx,out);
|
||||
return true;
|
||||
}
|
||||
idx++;
|
||||
}
|
||||
|
||||
//giving up
|
||||
out = src;
|
||||
return false;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void menu_helpers::win32_auto_mnemonics(HMENU menu)
|
||||
{
|
||||
mnemonic_manager mgr;
|
||||
unsigned n, m = GetMenuItemCount(menu);
|
||||
pfc::string8_fastalloc temp,temp2;
|
||||
for(n=0;n<m;n++)//first pass, check existing mnemonics
|
||||
{
|
||||
unsigned type = uGetMenuItemType(menu,n);
|
||||
if (type==MFT_STRING)
|
||||
{
|
||||
uGetMenuString(menu,n,temp,MF_BYPOSITION);
|
||||
mgr.check_string(temp);
|
||||
}
|
||||
}
|
||||
|
||||
for(n=0;n<m;n++)
|
||||
{
|
||||
HMENU submenu = GetSubMenu(menu,n);
|
||||
if (submenu) win32_auto_mnemonics(submenu);
|
||||
|
||||
{
|
||||
unsigned type = uGetMenuItemType(menu,n);
|
||||
if (type==MFT_STRING)
|
||||
{
|
||||
unsigned state = submenu ? 0 : GetMenuState(menu,n,MF_BYPOSITION);
|
||||
unsigned id = GetMenuItemID(menu,n);
|
||||
uGetMenuString(menu,n,temp,MF_BYPOSITION);
|
||||
if (mgr.process_string(temp,temp2))
|
||||
{
|
||||
uModifyMenu(menu,n,MF_BYPOSITION|MF_STRING|state,id,temp2);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static bool test_key(unsigned k)
|
||||
{
|
||||
return (GetKeyState(k) & 0x8000) ? true : false;
|
||||
}
|
||||
|
||||
#define F_SHIFT (HOTKEYF_SHIFT<<8)
|
||||
#define F_CTRL (HOTKEYF_CONTROL<<8)
|
||||
#define F_ALT (HOTKEYF_ALT<<8)
|
||||
#define F_WIN (HOTKEYF_EXT<<8)
|
||||
|
||||
static t_uint32 get_key_code(WPARAM wp) {
|
||||
t_uint32 code = (t_uint32)(wp & 0xFF);
|
||||
if (test_key(VK_CONTROL)) code|=F_CTRL;
|
||||
if (test_key(VK_SHIFT)) code|=F_SHIFT;
|
||||
if (test_key(VK_MENU)) code|=F_ALT;
|
||||
if (test_key(VK_LWIN) || test_key(VK_RWIN)) code|=F_WIN;
|
||||
return code;
|
||||
}
|
||||
|
||||
static t_uint32 get_key_code(WPARAM wp, t_uint32 mods) {
|
||||
t_uint32 code = (t_uint32)(wp & 0xFF);
|
||||
if (mods & MOD_CONTROL) code|=F_CTRL;
|
||||
if (mods & MOD_SHIFT) code|=F_SHIFT;
|
||||
if (mods & MOD_ALT) code|=F_ALT;
|
||||
if (mods & MOD_WIN) code|=F_WIN;
|
||||
return code;
|
||||
}
|
||||
|
||||
bool keyboard_shortcut_manager::on_keydown(shortcut_type type,WPARAM wp)
|
||||
{
|
||||
if (type==TYPE_CONTEXT) return false;
|
||||
metadb_handle_list dummy;
|
||||
return process_keydown(type,dummy,get_key_code(wp));
|
||||
}
|
||||
|
||||
bool keyboard_shortcut_manager::on_keydown_context(const pfc::list_base_const_t<metadb_handle_ptr> & data,WPARAM wp,const GUID & caller)
|
||||
{
|
||||
if (data.get_count()==0) return false;
|
||||
return process_keydown_ex(TYPE_CONTEXT,data,get_key_code(wp),caller);
|
||||
}
|
||||
|
||||
bool keyboard_shortcut_manager::on_keydown_auto(WPARAM wp)
|
||||
{
|
||||
if (on_keydown(TYPE_MAIN,wp)) return true;
|
||||
if (on_keydown(TYPE_CONTEXT_PLAYLIST,wp)) return true;
|
||||
if (on_keydown(TYPE_CONTEXT_NOW_PLAYING,wp)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool keyboard_shortcut_manager::on_keydown_auto_playlist(WPARAM wp)
|
||||
{
|
||||
metadb_handle_list data;
|
||||
static_api_ptr_t<playlist_manager> api;
|
||||
api->activeplaylist_get_selected_items(data);
|
||||
return on_keydown_auto_context(data,wp,contextmenu_item::caller_playlist);
|
||||
}
|
||||
|
||||
bool keyboard_shortcut_manager::on_keydown_auto_context(const pfc::list_base_const_t<metadb_handle_ptr> & data,WPARAM wp,const GUID & caller)
|
||||
{
|
||||
if (on_keydown_context(data,wp,caller)) return true;
|
||||
else return on_keydown_auto(wp);
|
||||
}
|
||||
|
||||
static bool should_relay_key_restricted(UINT p_key) {
|
||||
switch(p_key) {
|
||||
case VK_LEFT:
|
||||
case VK_RIGHT:
|
||||
case VK_UP:
|
||||
case VK_DOWN:
|
||||
return false;
|
||||
default:
|
||||
return (p_key >= VK_F1 && p_key <= VK_F24) || IsKeyPressed(VK_CONTROL) || IsKeyPressed(VK_LWIN) || IsKeyPressed(VK_RWIN);
|
||||
}
|
||||
}
|
||||
|
||||
bool keyboard_shortcut_manager::on_keydown_restricted_auto(WPARAM wp) {
|
||||
if (!should_relay_key_restricted(wp)) return false;
|
||||
return on_keydown_auto(wp);
|
||||
}
|
||||
bool keyboard_shortcut_manager::on_keydown_restricted_auto_playlist(WPARAM wp) {
|
||||
if (!should_relay_key_restricted(wp)) return false;
|
||||
return on_keydown_auto_playlist(wp);
|
||||
}
|
||||
bool keyboard_shortcut_manager::on_keydown_restricted_auto_context(const pfc::list_base_const_t<metadb_handle_ptr> & data,WPARAM wp,const GUID & caller) {
|
||||
if (!should_relay_key_restricted(wp)) return false;
|
||||
return on_keydown_auto_context(data,wp,caller);
|
||||
}
|
||||
|
||||
bool keyboard_shortcut_manager_v2::pretranslate_message(const MSG * msg, HWND thisPopupWnd) {
|
||||
switch(msg->message) {
|
||||
case WM_KEYDOWN:
|
||||
case WM_SYSKEYDOWN:
|
||||
if (thisPopupWnd != NULL && FindOwningPopup(msg->hwnd) == thisPopupWnd) {
|
||||
const t_uint32 modifiers = GetHotkeyModifierFlags();
|
||||
/*if (filterTypableWindowMessage(msg, modifiers))*/ {
|
||||
if (process_keydown_simple(get_key_code(msg->wParam,modifiers))) return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool keyboard_shortcut_manager::is_text_key(t_uint32 vkCode) {
|
||||
return vkCode == VK_SPACE
|
||||
|| (vkCode >= '0' && vkCode < 0x40)
|
||||
|| (vkCode > 0x40 && vkCode < VK_LWIN)
|
||||
|| (vkCode >= VK_NUMPAD0 && vkCode <= VK_DIVIDE)
|
||||
|| (vkCode >= VK_OEM_1 && vkCode <= VK_OEM_3)
|
||||
|| (vkCode >= VK_OEM_4 && vkCode <= VK_OEM_8)
|
||||
;
|
||||
}
|
||||
|
||||
bool keyboard_shortcut_manager::is_typing_key(t_uint32 vkCode) {
|
||||
return is_text_key(vkCode)
|
||||
|| vkCode == VK_BACK
|
||||
|| vkCode == VK_RETURN
|
||||
|| vkCode == VK_INSERT
|
||||
|| (vkCode > VK_SPACE && vkCode < '0');
|
||||
}
|
||||
|
||||
bool keyboard_shortcut_manager::is_typing_key_combo(t_uint32 vkCode, t_uint32 modifiers) {
|
||||
if (!is_typing_modifier(modifiers)) return false;
|
||||
return is_typing_key(vkCode);
|
||||
}
|
||||
|
||||
bool keyboard_shortcut_manager::is_typing_modifier(t_uint32 flags) {
|
||||
flags &= ~MOD_SHIFT;
|
||||
return flags == 0 || flags == (MOD_ALT | MOD_CONTROL);
|
||||
}
|
||||
|
||||
bool keyboard_shortcut_manager::is_typing_message(HWND editbox, const MSG * msg) {
|
||||
if (msg->hwnd != editbox) return false;
|
||||
return is_typing_message(msg);
|
||||
}
|
||||
bool keyboard_shortcut_manager::is_typing_message(const MSG * msg) {
|
||||
if (msg->message != WM_KEYDOWN && msg->message != WM_SYSKEYDOWN) return false;
|
||||
return is_typing_key_combo(msg->wParam, GetHotkeyModifierFlags());
|
||||
}
|
|
@ -0,0 +1,80 @@
|
|||
class NOVTABLE message_filter
|
||||
{
|
||||
public:
|
||||
virtual bool pretranslate_message(MSG * p_msg) = 0;
|
||||
};
|
||||
|
||||
class NOVTABLE idle_handler {
|
||||
public:
|
||||
virtual bool on_idle() = 0;
|
||||
};
|
||||
|
||||
class NOVTABLE message_loop : public service_base
|
||||
{
|
||||
public:
|
||||
virtual void add_message_filter(message_filter * ptr) = 0;
|
||||
virtual void remove_message_filter(message_filter * ptr) = 0;
|
||||
|
||||
virtual void add_idle_handler(idle_handler * ptr) = 0;
|
||||
virtual void remove_idle_handler(idle_handler * ptr) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(message_loop);
|
||||
};
|
||||
|
||||
class NOVTABLE message_loop_v2 : public message_loop {
|
||||
public:
|
||||
virtual void add_message_filter_ex(message_filter * ptr, t_uint32 lowest, t_uint32 highest) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(message_loop_v2, message_loop);
|
||||
};
|
||||
|
||||
class message_filter_impl_base : public message_filter {
|
||||
public:
|
||||
message_filter_impl_base() {static_api_ptr_t<message_loop>()->add_message_filter(this);}
|
||||
message_filter_impl_base(t_uint32 lowest, t_uint32 highest) {
|
||||
static_api_ptr_t<message_loop> api;
|
||||
message_loop_v2::ptr apiV2;
|
||||
if (api->service_query_t(apiV2)) {
|
||||
apiV2->add_message_filter_ex(this, lowest, highest);
|
||||
} else {
|
||||
api->add_message_filter(this);
|
||||
}
|
||||
}
|
||||
~message_filter_impl_base() {static_api_ptr_t<message_loop>()->remove_message_filter(this);}
|
||||
bool pretranslate_message(MSG * p_msg) {return false;}
|
||||
|
||||
PFC_CLASS_NOT_COPYABLE(message_filter_impl_base,message_filter_impl_base);
|
||||
};
|
||||
|
||||
class message_filter_impl_accel : public message_filter_impl_base {
|
||||
protected:
|
||||
bool pretranslate_message(MSG * p_msg) {
|
||||
if (m_wnd != NULL) {
|
||||
if (GetActiveWindow() == m_wnd) {
|
||||
if (TranslateAccelerator(m_wnd,m_accel.get(),p_msg) != 0) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
public:
|
||||
message_filter_impl_accel(HINSTANCE p_instance,const TCHAR * p_accel) : m_wnd(NULL) {
|
||||
m_accel.load(p_instance,p_accel);
|
||||
}
|
||||
void set_wnd(HWND p_wnd) {m_wnd = p_wnd;}
|
||||
private:
|
||||
HWND m_wnd;
|
||||
win32_accelerator m_accel;
|
||||
|
||||
PFC_CLASS_NOT_COPYABLE(message_filter_impl_accel,message_filter_impl_accel);
|
||||
};
|
||||
|
||||
class idle_handler_impl_base : public idle_handler {
|
||||
public:
|
||||
idle_handler_impl_base() {static_api_ptr_t<message_loop>()->add_idle_handler(this);}
|
||||
~idle_handler_impl_base() {static_api_ptr_t<message_loop>()->remove_idle_handler(this);}
|
||||
bool on_idle() {return true;}
|
||||
|
||||
PFC_CLASS_NOT_COPYABLE_EX(idle_handler_impl_base)
|
||||
};
|
|
@ -0,0 +1,76 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
|
||||
void metadb::handle_create_replace_path_canonical(metadb_handle_ptr & p_out,const metadb_handle_ptr & p_source,const char * p_new_path) {
|
||||
handle_create(p_out,make_playable_location(p_new_path,p_source->get_subsong_index()));
|
||||
}
|
||||
|
||||
void metadb::handle_create_replace_path(metadb_handle_ptr & p_out,const metadb_handle_ptr & p_source,const char * p_new_path) {
|
||||
pfc::string8 path;
|
||||
filesystem::g_get_canonical_path(p_new_path,path);
|
||||
handle_create_replace_path_canonical(p_out,p_source,path);
|
||||
}
|
||||
|
||||
void metadb::handle_replace_path_canonical(metadb_handle_ptr & p_out,const char * p_new_path) {
|
||||
metadb_handle_ptr temp;
|
||||
handle_create_replace_path_canonical(temp,p_out,p_new_path);
|
||||
p_out = temp;
|
||||
}
|
||||
|
||||
|
||||
metadb_io::t_load_info_state metadb_io::load_info(metadb_handle_ptr p_item,t_load_info_type p_type,HWND p_parent_window,bool p_show_errors) {
|
||||
return load_info_multi(pfc::list_single_ref_t<metadb_handle_ptr>(p_item),p_type,p_parent_window,p_show_errors);
|
||||
}
|
||||
|
||||
metadb_io::t_update_info_state metadb_io::update_info(metadb_handle_ptr p_item,file_info & p_info,HWND p_parent_window,bool p_show_errors)
|
||||
{
|
||||
file_info * blah = &p_info;
|
||||
return update_info_multi(pfc::list_single_ref_t<metadb_handle_ptr>(p_item),pfc::list_single_ref_t<file_info*>(blah),p_parent_window,p_show_errors);
|
||||
}
|
||||
|
||||
|
||||
void metadb_io::hint_async(metadb_handle_ptr p_item,const file_info & p_info,const t_filestats & p_stats,bool p_fresh)
|
||||
{
|
||||
const file_info * blargh = &p_info;
|
||||
hint_multi_async(pfc::list_single_ref_t<metadb_handle_ptr>(p_item),pfc::list_single_ref_t<const file_info *>(blargh),pfc::list_single_ref_t<t_filestats>(p_stats),bit_array_val(p_fresh));
|
||||
}
|
||||
|
||||
|
||||
bool metadb::g_get_random_handle(metadb_handle_ptr & p_out) {
|
||||
if (static_api_ptr_t<playback_control>()->get_now_playing(p_out)) return true;
|
||||
|
||||
{
|
||||
static_api_ptr_t<playlist_manager> api;
|
||||
|
||||
t_size playlist_count = api->get_playlist_count();
|
||||
t_size active_playlist = api->get_active_playlist();
|
||||
if (active_playlist != infinite) {
|
||||
if (api->playlist_get_focus_item_handle(p_out,active_playlist)) return true;
|
||||
}
|
||||
|
||||
for(t_size n = 0; n < playlist_count; n++) {
|
||||
if (api->playlist_get_focus_item_handle(p_out,n)) return true;
|
||||
}
|
||||
|
||||
if (active_playlist != infinite) {
|
||||
t_size item_count = api->playlist_get_item_count(active_playlist);
|
||||
if (item_count > 0) {
|
||||
if (api->playlist_get_item_handle(p_out,active_playlist,0)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
for(t_size n = 0; n < playlist_count; n++) {
|
||||
t_size item_count = api->playlist_get_item_count(n);
|
||||
if (item_count > 0) {
|
||||
if (api->playlist_get_item_handle(p_out,n,0)) return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
void metadb_io_v2::update_info_async_simple(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,const pfc::list_base_const_t<const file_info*> & p_new_info, HWND p_parent_window,t_uint32 p_op_flags,completion_notify_ptr p_notify) {
|
||||
update_info_async(p_list,new service_impl_t<file_info_filter_impl>(p_list,p_new_info),p_parent_window,p_op_flags,p_notify);
|
||||
}
|
|
@ -0,0 +1,312 @@
|
|||
//! API for tag read/write operations. Legal to call from main thread only, except for hint_multi_async() / hint_async() / hint_reader().\n
|
||||
//! Implemented only by core, do not reimplement.\n
|
||||
//! Use static_api_ptr_t template to access metadb_io methods.\n
|
||||
//! WARNING: Methods that perform file access (tag reads/writes) run a modal message loop. They SHOULD NOT be called from global callbacks and such.
|
||||
class NOVTABLE metadb_io : public service_base
|
||||
{
|
||||
public:
|
||||
enum t_load_info_type {
|
||||
load_info_default,
|
||||
load_info_force,
|
||||
load_info_check_if_changed
|
||||
};
|
||||
|
||||
enum t_update_info_state {
|
||||
update_info_success,
|
||||
update_info_aborted,
|
||||
update_info_errors,
|
||||
};
|
||||
|
||||
enum t_load_info_state {
|
||||
load_info_success,
|
||||
load_info_aborted,
|
||||
load_info_errors,
|
||||
};
|
||||
|
||||
//! No longer use - returns false always.
|
||||
__declspec(deprecated) virtual bool is_busy() = 0;
|
||||
//! No longer used - returns false always.
|
||||
__declspec(deprecated) virtual bool is_updating_disabled() = 0;
|
||||
//! No longer used - returns false always.
|
||||
__declspec(deprecated) virtual bool is_file_updating_blocked() = 0;
|
||||
//! No longer used.
|
||||
__declspec(deprecated) virtual void highlight_running_process() = 0;
|
||||
//! Loads tags from multiple items. Use the async version in metadb_io_v2 instead if possible.
|
||||
__declspec(deprecated) virtual t_load_info_state load_info_multi(metadb_handle_list_cref p_list,t_load_info_type p_type,HWND p_parent_window,bool p_show_errors) = 0;
|
||||
//! Updates tags on multiple items. Use the async version in metadb_io_v2 instead if possible.
|
||||
__declspec(deprecated) virtual t_update_info_state update_info_multi(metadb_handle_list_cref p_list,const pfc::list_base_const_t<file_info*> & p_new_info,HWND p_parent_window,bool p_show_errors) = 0;
|
||||
//! Rewrites tags on multiple items. Use the async version in metadb_io_v2 instead if possible.
|
||||
__declspec(deprecated) virtual t_update_info_state rewrite_info_multi(metadb_handle_list_cref p_list,HWND p_parent_window,bool p_show_errors) = 0;
|
||||
//! Removes tags from multiple items. Use the async version in metadb_io_v2 instead if possible.
|
||||
__declspec(deprecated) virtual t_update_info_state remove_info_multi(metadb_handle_list_cref p_list,HWND p_parent_window,bool p_show_errors) = 0;
|
||||
|
||||
virtual void hint_multi(metadb_handle_list_cref p_list,const pfc::list_base_const_t<const file_info*> & p_infos,const pfc::list_base_const_t<t_filestats> & p_stats,const bit_array & p_fresh_mask) = 0;
|
||||
|
||||
virtual void hint_multi_async(metadb_handle_list_cref p_list,const pfc::list_base_const_t<const file_info*> & p_infos,const pfc::list_base_const_t<t_filestats> & p_stats,const bit_array & p_fresh_mask) = 0;
|
||||
|
||||
virtual void hint_reader(service_ptr_t<class input_info_reader> p_reader,const char * p_path,abort_callback & p_abort) = 0;
|
||||
|
||||
//! For internal use only.
|
||||
virtual void path_to_handles_simple(const char * p_path, metadb_handle_list_ref p_out) = 0;
|
||||
|
||||
//! Dispatches metadb_io_callback calls with specified items. To be used with metadb_display_field_provider when your component needs specified items refreshed.
|
||||
virtual void dispatch_refresh(metadb_handle_list_cref p_list) = 0;
|
||||
|
||||
void dispatch_refresh(metadb_handle_ptr const & handle) {dispatch_refresh(pfc::list_single_ref_t<metadb_handle_ptr>(handle));}
|
||||
|
||||
void hint_async(metadb_handle_ptr p_item,const file_info & p_info,const t_filestats & p_stats,bool p_fresh);
|
||||
|
||||
__declspec(deprecated) t_load_info_state load_info(metadb_handle_ptr p_item,t_load_info_type p_type,HWND p_parent_window,bool p_show_errors);
|
||||
__declspec(deprecated) t_update_info_state update_info(metadb_handle_ptr p_item,file_info & p_info,HWND p_parent_window,bool p_show_errors);
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(metadb_io);
|
||||
};
|
||||
|
||||
//! Implementing this class gives you direct control over which part of file_info gets altered during a tag update uperation. To be used with metadb_io_v2::update_info_async().
|
||||
class NOVTABLE file_info_filter : public service_base {
|
||||
public:
|
||||
//! Alters specified file_info entry; called as a part of tag update process. Specified file_info has been read from a file, and will be written back.\n
|
||||
//! WARNING: This will be typically called from another thread than main app thread (precisely, from thread created by tag updater). You should copy all relevant data to members of your file_info_filter instance in constructor and reference only member data in apply_filter() implementation.
|
||||
//! @returns True when you have altered file_info and changes need to be written back to the file; false if no changes have been made.
|
||||
virtual bool apply_filter(metadb_handle_ptr p_location,t_filestats p_stats,file_info & p_info) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(file_info_filter,service_base);
|
||||
};
|
||||
|
||||
//! Advanced interface for passing infos read from files to metadb backend. Use metadb_io_v2::create_hint_list() to instantiate.
|
||||
class NOVTABLE metadb_hint_list : public service_base {
|
||||
public:
|
||||
//! Adds a hint to the list.
|
||||
//! @param p_location Location of the item the hint applies to.
|
||||
//! @param p_info file_info object describing the item.
|
||||
//! @param p_stats Information about the file containing item the hint applies to.
|
||||
//! @param p_freshflag Set to true if the info has been directly read from the file, false if it comes from another source such as a playlist file.
|
||||
virtual void add_hint(metadb_handle_ptr const & p_location,const file_info & p_info,const t_filestats & p_stats,bool p_freshflag) = 0;
|
||||
//! Reads info from specified info reader instance and adds hints. May throw an exception in case info read has failed.
|
||||
virtual void add_hint_reader(const char * p_path,service_ptr_t<input_info_reader> const & p_reader,abort_callback & p_abort) = 0;
|
||||
//! Call this when you're done working with this metadb_hint_list instance, to apply hints and dispatch callbacks. If you don't call this, all added hints will be ignored.
|
||||
virtual void on_done() = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(metadb_hint_list,service_base);
|
||||
};
|
||||
|
||||
//! New in 0.9.3. Extends metadb_io functionality with nonblocking versions of tag read/write functions, and some other utility features.
|
||||
class NOVTABLE metadb_io_v2 : public metadb_io {
|
||||
public:
|
||||
enum {
|
||||
//! By default, when some part of requested operation could not be performed for reasons other than user abort, a popup dialog with description of the problem is spawned.\n
|
||||
//! Set this flag to disable error notification.
|
||||
op_flag_no_errors = 1 << 0,
|
||||
//! Set this flag to make the progress dialog not steal focus on creation.
|
||||
op_flag_background = 1 << 1,
|
||||
//! Set this flag to delay the progress dialog becoming visible, so it does not appear at all during short operations. Also implies op_flag_background effect.
|
||||
op_flag_delay_ui = 1 << 2,
|
||||
};
|
||||
|
||||
//! Preloads information from the specified tracks.
|
||||
//! @param p_list List of items to process.
|
||||
//! @param p_op_flags Can be null, or one or more of op_flag_* enum values combined, altering behaviors of the operation.
|
||||
//! @param p_notify Called when the task is completed. Status code is one of t_load_info_state values. Can be null if caller doesn't care.
|
||||
virtual void load_info_async(metadb_handle_list_cref p_list,t_load_info_type p_type,HWND p_parent_window,t_uint32 p_op_flags,completion_notify_ptr p_notify) = 0;
|
||||
//! Updates tags of the specified tracks.
|
||||
//! @param p_list List of items to process.
|
||||
//! @param p_op_flags Can be null, or one or more of op_flag_* enum values combined, altering behaviors of the operation.
|
||||
//! @param p_notify Called when the task is completed. Status code is one of t_update_info values. Can be null if caller doesn't care.
|
||||
//! @param p_filter Callback handling actual file_info alterations. Typically used to replace entire meta part of file_info, or to alter something else such as ReplayGain while leaving meta intact.
|
||||
virtual void update_info_async(metadb_handle_list_cref p_list,service_ptr_t<file_info_filter> p_filter,HWND p_parent_window,t_uint32 p_op_flags,completion_notify_ptr p_notify) = 0;
|
||||
//! Rewrites tags of the specified tracks; similar to update_info_async() but using last known/cached file_info values rather than values passed by caller.
|
||||
//! @param p_list List of items to process.
|
||||
//! @param p_op_flags Can be null, or one or more of op_flag_* enum values combined, altering behaviors of the operation.
|
||||
//! @param p_notify Called when the task is completed. Status code is one of t_update_info values. Can be null if caller doesn't care.
|
||||
virtual void rewrite_info_async(metadb_handle_list_cref p_list,HWND p_parent_window,t_uint32 p_op_flags,completion_notify_ptr p_notify) = 0;
|
||||
//! Strips all tags / metadata fields from the specified tracks.
|
||||
//! @param p_list List of items to process.
|
||||
//! @param p_op_flags Can be null, or one or more of op_flag_* enum values combined, altering behaviors of the operation.
|
||||
//! @param p_notify Called when the task is completed. Status code is one of t_update_info values. Can be null if caller doesn't care.
|
||||
virtual void remove_info_async(metadb_handle_list_cref p_list,HWND p_parent_window,t_uint32 p_op_flags,completion_notify_ptr p_notify) = 0;
|
||||
|
||||
//! Creates a metadb_hint_list object.
|
||||
virtual metadb_hint_list::ptr create_hint_list() = 0;
|
||||
|
||||
//! Updates tags of the specified tracks. Helper; uses update_info_async internally.
|
||||
//! @param p_list List of items to process.
|
||||
//! @param p_op_flags Can be null, or one or more of op_flag_* enum values combined, altering behaviors of the operation.
|
||||
//! @param p_notify Called when the task is completed. Status code is one of t_update_info values. Can be null if caller doesn't care.
|
||||
//! @param p_new_info New infos to write to specified items.
|
||||
void update_info_async_simple(metadb_handle_list_cref p_list,const pfc::list_base_const_t<const file_info*> & p_new_info, HWND p_parent_window,t_uint32 p_op_flags,completion_notify_ptr p_notify);
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(metadb_io_v2,metadb_io);
|
||||
};
|
||||
|
||||
|
||||
//! Dynamically-registered version of metadb_io_callback. See metadb_io_callback for documentation, register instances using metadb_io_v3::register_callback(). It's recommended that you use the metadb_io_callback_dynamic_impl_base helper class to manage registration/unregistration.
|
||||
class NOVTABLE metadb_io_callback_dynamic {
|
||||
public:
|
||||
virtual void on_changed_sorted(metadb_handle_list_cref p_items_sorted, bool p_fromhook) = 0;
|
||||
};
|
||||
|
||||
//! New (0.9.5)
|
||||
class NOVTABLE metadb_io_v3 : public metadb_io_v2 {
|
||||
public:
|
||||
virtual void register_callback(metadb_io_callback_dynamic * p_callback) = 0;
|
||||
virtual void unregister_callback(metadb_io_callback_dynamic * p_callback) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(metadb_io_v3,metadb_io_v2);
|
||||
};
|
||||
|
||||
//! metadb_io_callback_dynamic implementation helper.
|
||||
class metadb_io_callback_dynamic_impl_base : public metadb_io_callback_dynamic {
|
||||
public:
|
||||
void on_changed_sorted(metadb_handle_list_cref p_items_sorted, bool p_fromhook) {}
|
||||
|
||||
metadb_io_callback_dynamic_impl_base() {static_api_ptr_t<metadb_io_v3>()->register_callback(this);}
|
||||
~metadb_io_callback_dynamic_impl_base() {static_api_ptr_t<metadb_io_v3>()->unregister_callback(this);}
|
||||
|
||||
PFC_CLASS_NOT_COPYABLE_EX(metadb_io_callback_dynamic_impl_base)
|
||||
};
|
||||
//! Callback service receiving notifications about metadb contents changes.
|
||||
class NOVTABLE metadb_io_callback : public service_base {
|
||||
public:
|
||||
//! Called when metadb contents change. (Or, one of display hook component requests display update).
|
||||
//! @param p_items_sorted List of items that have been updated. The list is always sorted by pointer value, to allow fast bsearch to test whether specific item has changed.
|
||||
//! @param p_fromhook Set to true when actual file contents haven't changed but one of metadb_display_field_provider implementations requested an update so output of metadb_handle::format_title() etc has changed.
|
||||
virtual void on_changed_sorted(metadb_handle_list_cref p_items_sorted, bool p_fromhook) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(metadb_io_callback);
|
||||
};
|
||||
|
||||
//! Entrypoint service for metadb_handle related operations.\n
|
||||
//! Implemented only by core, do not reimplement.\n
|
||||
//! Use static_api_ptr_t template to access it, e.g. static_api_ptr_t<metadb>()->handle_create(myhandle,mylocation);
|
||||
class NOVTABLE metadb : public service_base
|
||||
{
|
||||
public:
|
||||
//! Locks metadb to prevent other threads from modifying it while you're working with some of its contents. Some functions (metadb_handle::get_info_locked(), metadb_handle::get_info_async_locked()) can be called only from inside metadb lock section.
|
||||
virtual void database_lock()=0;
|
||||
//! Unlocks metadb after database_lock(). Some functions (metadb_handle::get_info_locked(), metadb_handle::get_info_async_locked()) can be called only from inside metadb lock section.
|
||||
virtual void database_unlock()=0;
|
||||
|
||||
//! Returns a metadb_handle object referencing the specified location. If one doesn't exist yet a new one is created. There can be only one metadb_handle object referencing specific location. \n
|
||||
//! This function should never fail unless there's something critically wrong (can't allocate memory for the new object, etc). \n
|
||||
//! Speed: O(log(n)) to total number of metadb_handles present. It's recommended to pass metadb_handles around whenever possible rather than pass playable_locations then retrieve metadb_handles on demand when needed.
|
||||
//! @param p_out Receives the metadb_handle pointer.
|
||||
//! @param p_location Location to create a metadb_handle for.
|
||||
virtual void handle_create(metadb_handle_ptr & p_out,const playable_location & p_location)=0;
|
||||
|
||||
void handle_create_replace_path_canonical(metadb_handle_ptr & p_out,const metadb_handle_ptr & p_source,const char * p_new_path);
|
||||
void handle_replace_path_canonical(metadb_handle_ptr & p_out,const char * p_new_path);
|
||||
void handle_create_replace_path(metadb_handle_ptr & p_out,const metadb_handle_ptr & p_source,const char * p_new_path);
|
||||
|
||||
//! Helper function; attempts to retrieve a handle to any known playable location to be used for e.g. titleformatting script preview.\n
|
||||
//! @returns True on success; false on failure (no known playable locations).
|
||||
static bool g_get_random_handle(metadb_handle_ptr & p_out);
|
||||
|
||||
enum {case_sensitive = true};
|
||||
typedef pfc::comparator_strcmp path_comparator;
|
||||
|
||||
inline static int path_compare_ex(const char * p1,t_size len1,const char * p2,t_size len2) {return case_sensitive ? pfc::strcmp_ex(p1,len1,p2,len2) : stricmp_utf8_ex(p1,len1,p2,len2);}
|
||||
inline static int path_compare(const char * p1,const char * p2) {return case_sensitive ? strcmp(p1,p2) : stricmp_utf8(p1,p2);}
|
||||
inline static int path_compare_metadb_handle(const metadb_handle_ptr & p1,const metadb_handle_ptr & p2) {return path_compare(p1->get_path(),p2->get_path());}
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(metadb);
|
||||
};
|
||||
|
||||
//! Metadb lock sync helper. For use around metadb_handle "locked" methods.
|
||||
class in_metadb_sync {
|
||||
public:
|
||||
in_metadb_sync() {
|
||||
m_api->database_lock();
|
||||
}
|
||||
~in_metadb_sync() {
|
||||
m_api->database_unlock();
|
||||
}
|
||||
private:
|
||||
static_api_ptr_t<metadb> m_api;
|
||||
};
|
||||
|
||||
//! Metadb lock sync helper. For use around metadb_handle "locked" methods.
|
||||
class in_metadb_sync_fromptr {
|
||||
public:
|
||||
in_metadb_sync_fromptr(const service_ptr_t<metadb> & p_api) : m_api(p_api) {m_api->database_lock();}
|
||||
~in_metadb_sync_fromptr() {m_api->database_unlock();}
|
||||
private:
|
||||
service_ptr_t<metadb> m_api;
|
||||
};
|
||||
|
||||
//! Metadb lock sync helper. For use around metadb_handle "locked" methods.
|
||||
class in_metadb_sync_fromhandle {
|
||||
public:
|
||||
in_metadb_sync_fromhandle(const service_ptr_t<metadb_handle> & p_api) : m_api(p_api) {m_api->metadb_lock();}
|
||||
~in_metadb_sync_fromhandle() {m_api->metadb_unlock();}
|
||||
private:
|
||||
service_ptr_t<metadb_handle> m_api;
|
||||
};
|
||||
|
||||
class titleformat_text_out;
|
||||
class titleformat_hook_function_params;
|
||||
|
||||
|
||||
/*!
|
||||
Implementing this service lets you provide your own title-formatting fields that are parsed globally with each call to metadb_handle::format_title methods. \n
|
||||
This should be implemented only where absolutely necessary, for safety and performance reasons. Any expensive operations inside the process_field() method may severely damage performance of affected title-formatting calls. \n
|
||||
You must NEVER make any other foobar2000 API calls from inside process_field, other than possibly querying information from the passed metadb_handle pointer; you should read your own implementation-specific private data and return as soon as possible. You must not make any assumptions about calling context (threading etc). \n
|
||||
It is guaranteed that process_field() is called only inside a metadb lock scope so you can safely call "locked" metadb_handle methods on the metadb_handle pointer you get. You must not lock metadb by yourself inside process_field() - while it is always called from inside a metadb lock scope, it may be called from another thread than the one maintaining the lock because of multi-CPU optimizations active. \n
|
||||
If there are multiple metadb_display_field_provider services registered providing fields of the same name, which one gets called is undefined. \n
|
||||
IMPORTANT: Any components implementing metadb_display_field_provider MUST call metadb_io::dispatch_refresh() with affected metadb_handles whenever info that they present changes. Otherwise, anything rendering title-formatting strings that reference your data will not update properly, resulting in unreliable/broken output, repaint glitches, etc. \n
|
||||
Do not expect a process_field() call each time somebody uses title formatting, calling code might perform its own caching of strings that you return, getting new ones only after metadb_io::dispatch_refresh() with relevant items. \n
|
||||
If you can't reliably notify other components about changes of content of fields that you provide (such as when your fields provide some kind of global information and not information specific to item identified by passed metadb_handle), you should not be providing those fields in first place. You must not change returned values of your fields without dispatching appropriate notifications. \n
|
||||
Use static service_factory_single_t<myclass> to register your metadb_display_field_provider implementations. Do not call other people's metadb_display_field_provider services directly, they're meant to be called by backend only. \n
|
||||
List of fields that you provide is expected to be fixed at run-time. The backend will enumerate your fields only once and refer to them by indexes later. \n
|
||||
*/
|
||||
|
||||
class NOVTABLE metadb_display_field_provider : public service_base {
|
||||
public:
|
||||
//! Returns number of fields provided by this metadb_display_field_provider implementation.
|
||||
virtual t_uint32 get_field_count() = 0;
|
||||
//! Returns name of specified field provided by this metadb_display_field_provider implementation. Names are not case sensitive. It's strongly recommended that you keep your field names plain English / ASCII only.
|
||||
virtual void get_field_name(t_uint32 index, pfc::string_base & out) = 0;
|
||||
//! Evaluates the specified field.
|
||||
//! @param index Index of field being processed : 0 <= index < get_field_count().
|
||||
//! @param handle Handle to item being processed. You can safely call "locked" methods on this handle to retrieve track information and such.
|
||||
//! @param out Interface receiving your text output.
|
||||
//! @returns Return true to indicate that the field is present so if it's enclosed in square brackets, contents of those brackets should not be skipped, false otherwise.
|
||||
virtual bool process_field(t_uint32 index, metadb_handle * handle, titleformat_text_out * out) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(metadb_display_field_provider);
|
||||
};
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
//! Helper implementation of file_info_filter_impl.
|
||||
class file_info_filter_impl : public file_info_filter {
|
||||
public:
|
||||
file_info_filter_impl(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,const pfc::list_base_const_t<const file_info*> & p_new_info) {
|
||||
pfc::dynamic_assert(p_list.get_count() == p_new_info.get_count());
|
||||
pfc::array_t<t_size> order;
|
||||
order.set_size(p_list.get_count());
|
||||
order_helper::g_fill(order.get_ptr(),order.get_size());
|
||||
p_list.sort_get_permutation_t(pfc::compare_t<metadb_handle_ptr,metadb_handle_ptr>,order.get_ptr());
|
||||
m_handles.set_count(order.get_size());
|
||||
m_infos.set_size(order.get_size());
|
||||
for(t_size n = 0; n < order.get_size(); n++) {
|
||||
m_handles[n] = p_list[order[n]];
|
||||
m_infos[n] = *p_new_info[order[n]];
|
||||
}
|
||||
}
|
||||
|
||||
bool apply_filter(metadb_handle_ptr p_location,t_filestats p_stats,file_info & p_info) {
|
||||
t_size index;
|
||||
if (m_handles.bsearch_t(pfc::compare_t<metadb_handle_ptr,metadb_handle_ptr>,p_location,index)) {
|
||||
p_info = m_infos[index];
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
private:
|
||||
metadb_handle_list m_handles;
|
||||
pfc::array_t<file_info_impl> m_infos;
|
||||
};
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
|
||||
double metadb_handle::get_length()
|
||||
{
|
||||
double rv = 0;
|
||||
in_metadb_sync_fromhandle l_sync(this);
|
||||
const file_info * info;
|
||||
if (get_info_locked(info))
|
||||
rv = info->get_length();
|
||||
return rv;
|
||||
}
|
||||
|
||||
t_filetimestamp metadb_handle::get_filetimestamp()
|
||||
{
|
||||
return get_filestats().m_timestamp;
|
||||
}
|
||||
|
||||
t_filesize metadb_handle::get_filesize()
|
||||
{
|
||||
return get_filestats().m_size;
|
||||
}
|
||||
|
||||
bool metadb_handle::format_title_legacy(titleformat_hook * p_hook,pfc::string_base & p_out,const char * p_spec,titleformat_text_filter * p_filter)
|
||||
{
|
||||
service_ptr_t<titleformat_object> script;
|
||||
if (static_api_ptr_t<titleformat_compiler>()->compile(script,p_spec)) {
|
||||
return format_title(p_hook,p_out,script,p_filter);
|
||||
} else {
|
||||
p_out.reset();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool metadb_handle::g_should_reload(const t_filestats & p_old_stats,const t_filestats & p_new_stats,bool p_fresh)
|
||||
{
|
||||
if (p_new_stats.m_timestamp == filetimestamp_invalid) return p_fresh;
|
||||
else if (p_fresh) return p_old_stats!= p_new_stats;
|
||||
else return p_old_stats.m_timestamp < p_new_stats.m_timestamp;
|
||||
}
|
||||
|
||||
bool metadb_handle::should_reload(const t_filestats & p_new_stats, bool p_fresh) const
|
||||
{
|
||||
if (!is_info_loaded_async()) return true;
|
||||
else return g_should_reload(get_filestats(),p_new_stats,p_fresh);
|
||||
}
|
|
@ -0,0 +1,207 @@
|
|||
class titleformat_hook;
|
||||
class titleformat_text_filter;
|
||||
|
||||
//! A metadb_handle object represents interface to reference-counted file_info cache entry for the specified location.\n
|
||||
//! To obtain a metadb_handle to specific location, use metadb::handle_create(). To obtain a list of metadb_handle objects corresponding to specific path (directory, playlist, multitrack file, etc), use relevant playlist_incoming_item_filter methods (recommended), or call playlist_loader methods directly.\n
|
||||
//! A metadb_handle is also the most efficient way of passing playable object locations around because it provides fast access to both location and infos, and is reference counted so duplicating it is as fast as possible.\n
|
||||
//! To retrieve a path of a file from a metadb_handle, use metadb_handle::get_path() function. Note that metadb_handle is NOT just file path, some formats support multiple subsongs per physical file, which are signaled using subsong indexes.
|
||||
|
||||
class NOVTABLE metadb_handle : public service_base
|
||||
{
|
||||
public:
|
||||
//! Retrieves location represented by this metadb_handle object. Returned reference is valid until calling context releases metadb_handle that returned it (metadb_handle_ptr is deallocated etc).
|
||||
virtual const playable_location & get_location() const = 0;//never fails, returned pointer valid till the object is released
|
||||
|
||||
|
||||
//! Renders information about item referenced by this metadb_handle object.
|
||||
//! @param p_hook Optional callback object overriding fields and functions; set to NULL if not used.
|
||||
//! @param p_out String receiving the output on success.
|
||||
//! @param p_script Titleformat script to use. Use titleformat_compiler service to create one.
|
||||
//! @param p_filter Optional callback object allowing input to be filtered according to context (i.e. removal of linebreak characters present in tags when rendering playlist lines). Set to NULL when not used.
|
||||
//! @returns true on success, false when dummy file_info instance was used because actual info is was not (yet) known.
|
||||
virtual bool format_title(titleformat_hook * p_hook,pfc::string_base & p_out,const service_ptr_t<class titleformat_object> & p_script,titleformat_text_filter * p_filter) = 0;
|
||||
|
||||
//! Locks metadb to prevent other threads from modifying it while you're working with some of its contents. Some functions (metadb_handle::get_info_locked(), metadb_handle::get_info_async_locked()) can be called only from inside metadb lock section.
|
||||
//! Same as metadb::database_lock().
|
||||
virtual void metadb_lock() = 0;
|
||||
//! Unlocks metadb after metadb_lock(). Some functions (metadb_handle::get_info_locked(), metadb_handle::get_info_async_locked()) can be called only from inside metadb lock section.
|
||||
//! Same as metadb::database_unlock().
|
||||
virtual void metadb_unlock() = 0;
|
||||
|
||||
//! Returns last seen file stats, filestats_invalid if unknown.
|
||||
virtual t_filestats get_filestats() const = 0;
|
||||
|
||||
//! Queries whether cached info about item referenced by this metadb_handle object is already available.\n
|
||||
//! Note that state of cached info changes only inside main thread, so you can safely assume that it doesn't change while some block of your code inside main thread is being executed.
|
||||
virtual bool is_info_loaded() const = 0;
|
||||
//! Queries cached info about item referenced by this metadb_handle object. Returns true on success, false when info is not yet known.\n
|
||||
//! Note that state of cached info changes only inside main thread, so you can safely assume that it doesn't change while some block of your code inside main thread is being executed.
|
||||
virtual bool get_info(file_info & p_info) const = 0;
|
||||
//! Queries cached info about item referenced by this metadb_handle object. Returns true on success, false when info is not yet known. This is more efficient than get_info() since no data is copied.\n
|
||||
//! You must lock the metadb before calling this function, and unlock it after you are done working with the returned pointer, to ensure multithread safety.\n
|
||||
//! Note that state of cached info changes only inside main thread, so you can safely assume that it doesn't change while some block of your code inside main thread is being executed.
|
||||
//! @param p_info On success, receives a pointer to metadb's file_info object. The pointer is for temporary use only, and becomes invalid when metadb is unlocked.
|
||||
virtual bool get_info_locked(const file_info * & p_info) const = 0;
|
||||
|
||||
//! Queries whether cached info about item referenced by this metadb_handle object is already available.\n
|
||||
//! This is intended for use in special cases when you need to immediately retrieve info sent by metadb_io hint from another thread; state of returned data can be altered by any thread, as opposed to non-async methods.
|
||||
virtual bool is_info_loaded_async() const = 0;
|
||||
//! Queries cached info about item referenced by this metadb_handle object. Returns true on success, false when info is not yet known.\n
|
||||
//! This is intended for use in special cases when you need to immediately retrieve info sent by metadb_io hint from another thread; state of returned data can be altered by any thread, as opposed to non-async methods.
|
||||
virtual bool get_info_async(file_info & p_info) const = 0;
|
||||
//! Queries cached info about item referenced by this metadb_handle object. Returns true on success, false when info is not yet known. This is more efficient than get_info() since no data is copied.\n
|
||||
//! You must lock the metadb before calling this function, and unlock it after you are done working with the returned pointer, to ensure multithread safety.\n
|
||||
//! This is intended for use in special cases when you need to immediately retrieve info sent by metadb_io hint from another thread; state of returned data can be altered by any thread, as opposed to non-async methods.
|
||||
//! @param p_info On success, receives a pointer to metadb's file_info object. The pointer is for temporary use only, and becomes invalid when metadb is unlocked.
|
||||
virtual bool get_info_async_locked(const file_info * & p_info) const = 0;
|
||||
|
||||
|
||||
//! Renders information about item referenced by this metadb_handle object, using external file_info data.
|
||||
virtual void format_title_from_external_info(const file_info & p_info,titleformat_hook * p_hook,pfc::string_base & p_out,const service_ptr_t<class titleformat_object> & p_script,titleformat_text_filter * p_filter) = 0;
|
||||
|
||||
|
||||
//! New in 0.9.5.
|
||||
virtual bool format_title_nonlocking(titleformat_hook * p_hook,pfc::string_base & p_out,const service_ptr_t<class titleformat_object> & p_script,titleformat_text_filter * p_filter) = 0;
|
||||
|
||||
//! New in 0.9.5.
|
||||
virtual void format_title_from_external_info_nonlocking(const file_info & p_info,titleformat_hook * p_hook,pfc::string_base & p_out,const service_ptr_t<class titleformat_object> & p_script,titleformat_text_filter * p_filter) = 0;
|
||||
|
||||
static bool g_should_reload(const t_filestats & p_old_stats,const t_filestats & p_new_stats,bool p_fresh);
|
||||
bool should_reload(const t_filestats & p_new_stats,bool p_fresh) const;
|
||||
|
||||
|
||||
//! Helper provided for backwards compatibility; takes formatting script as text string and calls relevant titleformat_compiler methods; returns false when the script could not be compiled.\n
|
||||
//! See format_title() for descriptions of parameters.\n
|
||||
//! Bottleneck warning: you should consider using precompiled titleformat script object and calling regular format_title() instead when processing large numbers of items.
|
||||
bool format_title_legacy(titleformat_hook * p_hook,pfc::string_base & out,const char * p_spec,titleformat_text_filter * p_filter);
|
||||
|
||||
//! Retrieves path of item described by this metadb_handle instance. Returned string is valid until calling context releases metadb_handle that returned it (metadb_handle_ptr is deallocated etc).
|
||||
inline const char * get_path() const {return get_location().get_path();}
|
||||
//! Retrieves subsong index of item described by this metadb_handle instance (used for multiple playable tracks within single physical file).
|
||||
inline t_uint32 get_subsong_index() const {return get_location().get_subsong_index();}
|
||||
|
||||
double get_length();//helper
|
||||
|
||||
t_filetimestamp get_filetimestamp();
|
||||
t_filesize get_filesize();
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(metadb_handle,service_base);
|
||||
};
|
||||
|
||||
typedef service_ptr_t<metadb_handle> metadb_handle_ptr;
|
||||
|
||||
typedef pfc::list_base_t<metadb_handle_ptr> & metadb_handle_list_ref;
|
||||
typedef pfc::list_base_const_t<metadb_handle_ptr> const & metadb_handle_list_cref;
|
||||
|
||||
namespace metadb_handle_list_helper {
|
||||
void sort_by_format(metadb_handle_list_ref p_list,const char * spec,titleformat_hook * p_hook);
|
||||
void sort_by_format_get_order(metadb_handle_list_cref p_list,t_size* order,const char * spec,titleformat_hook * p_hook);
|
||||
void sort_by_format(metadb_handle_list_ref p_list,const service_ptr_t<titleformat_object> & p_script,titleformat_hook * p_hook, int direction = 1);
|
||||
void sort_by_format_get_order(metadb_handle_list_cref p_list,t_size* order,const service_ptr_t<titleformat_object> & p_script,titleformat_hook * p_hook,int p_direction = 1);
|
||||
|
||||
void sort_by_relative_path(metadb_handle_list_ref p_list);
|
||||
void sort_by_relative_path_get_order(metadb_handle_list_cref p_list,t_size* order);
|
||||
|
||||
void remove_duplicates(pfc::list_base_t<metadb_handle_ptr> & p_list);
|
||||
void sort_by_pointer_remove_duplicates(pfc::list_base_t<metadb_handle_ptr> & p_list);
|
||||
void sort_by_path_quick(pfc::list_base_t<metadb_handle_ptr> & p_list);
|
||||
|
||||
void sort_by_pointer(pfc::list_base_t<metadb_handle_ptr> & p_list);
|
||||
t_size bsearch_by_pointer(const pfc::list_base_const_t<metadb_handle_ptr> & p_list,const metadb_handle_ptr & val);
|
||||
|
||||
double calc_total_duration(const pfc::list_base_const_t<metadb_handle_ptr> & p_list);
|
||||
|
||||
void sort_by_path(pfc::list_base_t<metadb_handle_ptr> & p_list);
|
||||
|
||||
t_filesize calc_total_size(metadb_handle_list_cref list, bool skipUnknown = false);
|
||||
t_filesize calc_total_size_ex(metadb_handle_list_cref list, bool & foundUnknown);
|
||||
};
|
||||
|
||||
template<template<typename> class t_alloc = pfc::alloc_fast >
|
||||
class metadb_handle_list_t : public service_list_t<metadb_handle,t_alloc> {
|
||||
private:
|
||||
typedef metadb_handle_list_t<t_alloc> t_self;
|
||||
typedef list_base_const_t<metadb_handle_ptr> t_interface;
|
||||
public:
|
||||
inline void sort_by_format(const char * spec,titleformat_hook * p_hook) {
|
||||
return metadb_handle_list_helper::sort_by_format(*this, spec, p_hook);
|
||||
}
|
||||
inline void sort_by_format_get_order(t_size* order,const char * spec,titleformat_hook * p_hook) const {
|
||||
metadb_handle_list_helper::sort_by_format_get_order(*this, order, spec, p_hook);
|
||||
}
|
||||
|
||||
inline void sort_by_format(const service_ptr_t<titleformat_object> & p_script,titleformat_hook * p_hook, int direction = 1) {
|
||||
metadb_handle_list_helper::sort_by_format(*this, p_script, p_hook, direction);
|
||||
}
|
||||
inline void sort_by_format_get_order(t_size* order,const service_ptr_t<titleformat_object> & p_script,titleformat_hook * p_hook) const {
|
||||
metadb_handle_list_helper::sort_by_format_get_order(*this, order, p_script, p_hook);
|
||||
}
|
||||
|
||||
inline void sort_by_relative_path() {
|
||||
metadb_handle_list_helper::sort_by_relative_path(*this);
|
||||
}
|
||||
inline void sort_by_relative_path_get_order(t_size* order) const {
|
||||
metadb_handle_list_helper::sort_by_relative_path_get_order(*this,order);
|
||||
}
|
||||
|
||||
inline void remove_duplicates() {metadb_handle_list_helper::remove_duplicates(*this);}
|
||||
inline void sort_by_pointer_remove_duplicates() {metadb_handle_list_helper::sort_by_pointer_remove_duplicates(*this);}
|
||||
inline void sort_by_path_quick() {metadb_handle_list_helper::sort_by_path_quick(*this);}
|
||||
|
||||
inline void sort_by_pointer() {metadb_handle_list_helper::sort_by_pointer(*this);}
|
||||
inline t_size bsearch_by_pointer(const metadb_handle_ptr & val) const {return metadb_handle_list_helper::bsearch_by_pointer(*this,val);}
|
||||
|
||||
inline double calc_total_duration() const {return metadb_handle_list_helper::calc_total_duration(*this);}
|
||||
|
||||
inline void sort_by_path() {metadb_handle_list_helper::sort_by_path(*this);}
|
||||
|
||||
const t_self & operator=(const t_self & p_source) {remove_all(); add_items(p_source);return *this;}
|
||||
const t_self & operator=(const t_interface & p_source) {remove_all(); add_items(p_source);return *this;}
|
||||
metadb_handle_list_t(const t_self & p_source) {add_items(p_source);}
|
||||
metadb_handle_list_t(const t_interface & p_source) {add_items(p_source);}
|
||||
metadb_handle_list_t() {}
|
||||
|
||||
t_self & operator+=(const t_interface & source) {add_items(source); return *this;}
|
||||
t_self & operator+=(const metadb_handle_ptr & source) {add_item(source); return *this;}
|
||||
};
|
||||
|
||||
typedef metadb_handle_list_t<> metadb_handle_list;
|
||||
|
||||
namespace metadb_handle_list_helper {
|
||||
void sorted_by_pointer_extract_difference(metadb_handle_list const & p_list_1,metadb_handle_list const & p_list_2,metadb_handle_list & p_list_1_specific,metadb_handle_list & p_list_2_specific);
|
||||
};
|
||||
|
||||
class metadb_handle_lock
|
||||
{
|
||||
metadb_handle_ptr m_ptr;
|
||||
public:
|
||||
inline metadb_handle_lock(const metadb_handle_ptr & param)
|
||||
{
|
||||
m_ptr = param;
|
||||
m_ptr->metadb_lock();
|
||||
}
|
||||
inline ~metadb_handle_lock() {m_ptr->metadb_unlock();}
|
||||
};
|
||||
|
||||
inline pfc::string_base & operator<<(pfc::string_base & p_fmt,const metadb_handle_ptr & p_location) {
|
||||
if (p_location.is_valid())
|
||||
return p_fmt << p_location->get_location();
|
||||
else
|
||||
return p_fmt << "[invalid location]";
|
||||
}
|
||||
|
||||
|
||||
class string_format_title {
|
||||
public:
|
||||
string_format_title(metadb_handle_ptr p_item,const char * p_script) {
|
||||
p_item->format_title_legacy(NULL,m_data,p_script,NULL);
|
||||
}
|
||||
string_format_title(metadb_handle_ptr p_item,service_ptr_t<class titleformat_object> p_script) {
|
||||
p_item->format_title(NULL,m_data,p_script,NULL);
|
||||
}
|
||||
|
||||
const char * get_ptr() const {return m_data.get_ptr();}
|
||||
operator const char * () const {return m_data.get_ptr();}
|
||||
private:
|
||||
pfc::string8_fastalloc m_data;
|
||||
};
|
|
@ -0,0 +1,386 @@
|
|||
#include "foobar2000.h"
|
||||
#include <shlwapi.h>
|
||||
|
||||
namespace {
|
||||
|
||||
wchar_t * makeSortString(const char * in) {
|
||||
wchar_t * out = new wchar_t[pfc::stringcvt::estimate_utf8_to_wide(in) + 1];
|
||||
out[0] = ' ';//StrCmpLogicalW bug workaround.
|
||||
pfc::stringcvt::convert_utf8_to_wide_unchecked(out + 1, in);
|
||||
return out;
|
||||
}
|
||||
|
||||
struct custom_sort_data {
|
||||
wchar_t * text;
|
||||
t_size index;
|
||||
};
|
||||
}
|
||||
|
||||
template<int direction>
|
||||
static int custom_sort_compare(const custom_sort_data & elem1, const custom_sort_data & elem2 ) {
|
||||
int ret = direction * StrCmpLogicalW(elem1.text,elem2.text);
|
||||
if (ret == 0) ret = pfc::sgn_t((t_ssize)elem1.index - (t_ssize)elem2.index);
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
template<int direction>
|
||||
static int _cdecl _custom_sort_compare(const void * v1, const void * v2) {
|
||||
return custom_sort_compare<direction>(*reinterpret_cast<const custom_sort_data*>(v1),*reinterpret_cast<const custom_sort_data*>(v2));
|
||||
}
|
||||
void metadb_handle_list_helper::sort_by_format(metadb_handle_list_ref p_list,const char * spec,titleformat_hook * p_hook)
|
||||
{
|
||||
service_ptr_t<titleformat_object> script;
|
||||
if (static_api_ptr_t<titleformat_compiler>()->compile(script,spec))
|
||||
sort_by_format(p_list,script,p_hook);
|
||||
}
|
||||
|
||||
void metadb_handle_list_helper::sort_by_format_get_order(metadb_handle_list_cref p_list,t_size* order,const char * spec,titleformat_hook * p_hook)
|
||||
{
|
||||
service_ptr_t<titleformat_object> script;
|
||||
if (static_api_ptr_t<titleformat_compiler>()->compile(script,spec))
|
||||
sort_by_format_get_order(p_list,order,script,p_hook);
|
||||
}
|
||||
|
||||
void metadb_handle_list_helper::sort_by_format(metadb_handle_list_ref p_list,const service_ptr_t<titleformat_object> & p_script,titleformat_hook * p_hook, int direction)
|
||||
{
|
||||
const t_size count = p_list.get_count();
|
||||
pfc::array_t<t_size> order; order.set_size(count);
|
||||
sort_by_format_get_order(p_list,order.get_ptr(),p_script,p_hook,direction);
|
||||
p_list.reorder(order.get_ptr());
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
class tfhook_sort : public titleformat_hook {
|
||||
public:
|
||||
tfhook_sort() {
|
||||
m_API->seed((unsigned)__rdtsc());
|
||||
}
|
||||
bool process_field(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,bool & p_found_flag) {
|
||||
return false;
|
||||
}
|
||||
bool process_function(titleformat_text_out * p_out,const char * p_name,t_size p_name_length,titleformat_hook_function_params * p_params,bool & p_found_flag) {
|
||||
if (stricmp_utf8_ex(p_name, p_name_length, "rand", ~0) == 0) {
|
||||
t_size param_count = p_params->get_param_count();
|
||||
t_uint32 val;
|
||||
if (param_count == 1) {
|
||||
t_uint32 mod = (t_uint32)p_params->get_param_uint(0);
|
||||
if (mod > 0) {
|
||||
val = m_API->genrand(mod);
|
||||
} else {
|
||||
val = 0;
|
||||
}
|
||||
} else {
|
||||
val = m_API->genrand(0xFFFFFFFF);
|
||||
}
|
||||
p_out->write_int(titleformat_inputtypes::unknown, val);
|
||||
p_found_flag = true;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
private:
|
||||
static_api_ptr_t<genrand_service> m_API;
|
||||
};
|
||||
|
||||
class tfthread : public pfc::thread {
|
||||
public:
|
||||
tfthread(pfc::counter * walk, metadb_handle_list_cref items,custom_sort_data * out,titleformat_object::ptr script,titleformat_hook * hook) : m_walk(walk), m_items(items), m_out(out), m_script(script), m_hook(hook) {}
|
||||
~tfthread() {waitTillDone();}
|
||||
|
||||
|
||||
|
||||
void threadProc() {
|
||||
TRACK_CALL_TEXT("metadb_handle sort helper thread");
|
||||
|
||||
tfhook_sort myHook;
|
||||
titleformat_hook_impl_splitter hookSplitter(&myHook, m_hook);
|
||||
titleformat_hook * const hookPtr = m_hook ? pfc::safe_cast<titleformat_hook*>(&hookSplitter) : &myHook;
|
||||
|
||||
pfc::string8_fastalloc temp; temp.prealloc(512);
|
||||
const t_size total = m_items.get_size();
|
||||
for(;;) {
|
||||
const t_size index = (*m_walk)++;
|
||||
if (index >= total) break;
|
||||
m_out[index].index = index;
|
||||
m_items[index]->format_title_nonlocking(hookPtr,temp,m_script,0);
|
||||
m_out[index].text = makeSortString(temp);
|
||||
}
|
||||
}
|
||||
private:
|
||||
pfc::counter * const m_walk;
|
||||
metadb_handle_list_cref m_items;
|
||||
custom_sort_data * const m_out;
|
||||
titleformat_object::ptr const m_script;
|
||||
titleformat_hook * const m_hook;
|
||||
};
|
||||
}
|
||||
|
||||
void metadb_handle_list_helper::sort_by_format_get_order(metadb_handle_list_cref p_list,t_size* order,const service_ptr_t<titleformat_object> & p_script,titleformat_hook * p_hook,int p_direction)
|
||||
{
|
||||
// pfc::hires_timer timer; timer.start();
|
||||
|
||||
const t_size count = p_list.get_count();
|
||||
pfc::array_t<custom_sort_data> data; data.set_size(count);
|
||||
|
||||
{
|
||||
in_metadb_sync sync;
|
||||
pfc::counter counter(0);
|
||||
pfc::array_t<pfc::rcptr_t<tfthread> > threads; threads.set_size(pfc::getOptimalWorkerThreadCountEx(p_list.get_count() / 128));
|
||||
PFC_ASSERT( threads.get_size() > 0 );
|
||||
for(t_size walk = 0; walk < threads.get_size(); ++walk) {
|
||||
threads[walk].new_t(&counter,p_list,data.get_ptr(),p_script,p_hook);
|
||||
}
|
||||
for(t_size walk = 1; walk < threads.get_size(); ++walk) threads[walk]->start();
|
||||
threads[0]->threadProc();
|
||||
for(t_size walk = 1; walk < threads.get_size(); ++walk) threads[walk]->waitTillDone();
|
||||
}
|
||||
// console::formatter() << "metadb_handle sort: prepared in " << pfc::format_time_ex(timer.query(),6);
|
||||
|
||||
pfc::sort_t(data, p_direction > 0 ? custom_sort_compare<1> : custom_sort_compare<-1>,count);
|
||||
//qsort(data.get_ptr(),count,sizeof(custom_sort_data),p_direction > 0 ? _custom_sort_compare<1> : _custom_sort_compare<-1>);
|
||||
|
||||
|
||||
// console::formatter() << "metadb_handle sort: sorted in " << pfc::format_time_ex(timer.query(),6);
|
||||
|
||||
for(t_size n=0;n<count;n++)
|
||||
{
|
||||
order[n]=data[n].index;
|
||||
delete[] data[n].text;
|
||||
}
|
||||
|
||||
// console::formatter() << "metadb_handle sort: finished in " << pfc::format_time_ex(timer.query(),6);
|
||||
}
|
||||
|
||||
void metadb_handle_list_helper::sort_by_relative_path(metadb_handle_list_ref p_list)
|
||||
{
|
||||
const t_size count = p_list.get_count();
|
||||
pfc::array_t<t_size> order; order.set_size(count);
|
||||
sort_by_relative_path_get_order(p_list,order.get_ptr());
|
||||
p_list.reorder(order.get_ptr());
|
||||
}
|
||||
|
||||
void metadb_handle_list_helper::sort_by_relative_path_get_order(metadb_handle_list_cref p_list,t_size* order)
|
||||
{
|
||||
const t_size count = p_list.get_count();
|
||||
t_size n;
|
||||
pfc::array_t<custom_sort_data> data;
|
||||
data.set_size(count);
|
||||
static_api_ptr_t<library_manager> api;
|
||||
|
||||
pfc::string8_fastalloc temp;
|
||||
temp.prealloc(512);
|
||||
for(n=0;n<count;n++)
|
||||
{
|
||||
metadb_handle_ptr item;
|
||||
p_list.get_item_ex(item,n);
|
||||
if (!api->get_relative_path(item,temp)) temp = "";
|
||||
data[n].index = n;
|
||||
data[n].text = makeSortString(temp);
|
||||
//data[n].subsong = item->get_subsong_index();
|
||||
}
|
||||
|
||||
pfc::sort_t(data,custom_sort_compare<1>,count);
|
||||
//qsort(data.get_ptr(),count,sizeof(custom_sort_data),(int (__cdecl *)(const void *elem1, const void *elem2 ))custom_sort_compare);
|
||||
|
||||
for(n=0;n<count;n++)
|
||||
{
|
||||
order[n]=data[n].index;
|
||||
delete[] data[n].text;
|
||||
}
|
||||
}
|
||||
|
||||
void metadb_handle_list_helper::remove_duplicates(metadb_handle_list_ref p_list)
|
||||
{
|
||||
t_size count = p_list.get_count();
|
||||
if (count>0)
|
||||
{
|
||||
bit_array_bittable mask(count);
|
||||
pfc::array_t<t_size> order; order.set_size(count);
|
||||
order_helper::g_fill(order);
|
||||
|
||||
p_list.sort_get_permutation_t(pfc::compare_t<metadb_handle_ptr,metadb_handle_ptr>,order.get_ptr());
|
||||
|
||||
t_size n;
|
||||
bool found = false;
|
||||
for(n=0;n<count-1;n++)
|
||||
{
|
||||
if (p_list.get_item(order[n])==p_list.get_item(order[n+1]))
|
||||
{
|
||||
found = true;
|
||||
mask.set(order[n+1],true);
|
||||
}
|
||||
}
|
||||
|
||||
if (found) p_list.remove_mask(mask);
|
||||
}
|
||||
}
|
||||
|
||||
void metadb_handle_list_helper::sort_by_pointer_remove_duplicates(metadb_handle_list_ref p_list)
|
||||
{
|
||||
t_size count = p_list.get_count();
|
||||
if (count>0)
|
||||
{
|
||||
sort_by_pointer(p_list);
|
||||
bool b_found = false;
|
||||
t_size n;
|
||||
for(n=0;n<count-1;n++)
|
||||
{
|
||||
if (p_list.get_item(n)==p_list.get_item(n+1))
|
||||
{
|
||||
b_found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (b_found)
|
||||
{
|
||||
bit_array_bittable mask(count);
|
||||
t_size n;
|
||||
for(n=0;n<count-1;n++)
|
||||
{
|
||||
if (p_list.get_item(n)==p_list.get_item(n+1))
|
||||
mask.set(n+1,true);
|
||||
}
|
||||
p_list.remove_mask(mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void metadb_handle_list_helper::sort_by_path_quick(metadb_handle_list_ref p_list)
|
||||
{
|
||||
p_list.sort_t(metadb::path_compare_metadb_handle);
|
||||
}
|
||||
|
||||
|
||||
void metadb_handle_list_helper::sort_by_pointer(metadb_handle_list_ref p_list)
|
||||
{
|
||||
//it seems MSVC71 /GL does something highly retarded here
|
||||
//p_list.sort_t(pfc::compare_t<metadb_handle_ptr,metadb_handle_ptr>);
|
||||
p_list.sort();
|
||||
}
|
||||
|
||||
t_size metadb_handle_list_helper::bsearch_by_pointer(metadb_handle_list_cref p_list,const metadb_handle_ptr & val)
|
||||
{
|
||||
t_size blah;
|
||||
if (p_list.bsearch_t(pfc::compare_t<metadb_handle_ptr,metadb_handle_ptr>,val,blah)) return blah;
|
||||
else return ~0;
|
||||
}
|
||||
|
||||
|
||||
void metadb_handle_list_helper::sorted_by_pointer_extract_difference(metadb_handle_list const & p_list_1,metadb_handle_list const & p_list_2,metadb_handle_list & p_list_1_specific,metadb_handle_list & p_list_2_specific)
|
||||
{
|
||||
t_size found_1, found_2;
|
||||
const t_size count_1 = p_list_1.get_count(), count_2 = p_list_2.get_count();
|
||||
t_size ptr_1, ptr_2;
|
||||
|
||||
found_1 = found_2 = 0;
|
||||
ptr_1 = ptr_2 = 0;
|
||||
while(ptr_1 < count_1 || ptr_2 < count_2)
|
||||
{
|
||||
while(ptr_1 < count_1 && (ptr_2 == count_2 || p_list_1[ptr_1] < p_list_2[ptr_2]))
|
||||
{
|
||||
found_1++;
|
||||
t_size ptr_1_new = ptr_1 + 1;
|
||||
while(ptr_1_new < count_1 && p_list_1[ptr_1_new] == p_list_1[ptr_1]) ptr_1_new++;
|
||||
ptr_1 = ptr_1_new;
|
||||
}
|
||||
while(ptr_2 < count_2 && (ptr_1 == count_1 || p_list_2[ptr_2] < p_list_1[ptr_1]))
|
||||
{
|
||||
found_2++;
|
||||
t_size ptr_2_new = ptr_2 + 1;
|
||||
while(ptr_2_new < count_2 && p_list_2[ptr_2_new] == p_list_2[ptr_2]) ptr_2_new++;
|
||||
ptr_2 = ptr_2_new;
|
||||
}
|
||||
while(ptr_1 < count_1 && ptr_2 < count_2 && p_list_1[ptr_1] == p_list_2[ptr_2]) {ptr_1++; ptr_2++;}
|
||||
}
|
||||
|
||||
|
||||
|
||||
p_list_1_specific.set_count(found_1);
|
||||
p_list_2_specific.set_count(found_2);
|
||||
if (found_1 > 0 || found_2 > 0)
|
||||
{
|
||||
found_1 = found_2 = 0;
|
||||
ptr_1 = ptr_2 = 0;
|
||||
|
||||
while(ptr_1 < count_1 || ptr_2 < count_2)
|
||||
{
|
||||
while(ptr_1 < count_1 && (ptr_2 == count_2 || p_list_1[ptr_1] < p_list_2[ptr_2]))
|
||||
{
|
||||
p_list_1_specific[found_1++] = p_list_1[ptr_1];
|
||||
t_size ptr_1_new = ptr_1 + 1;
|
||||
while(ptr_1_new < count_1 && p_list_1[ptr_1_new] == p_list_1[ptr_1]) ptr_1_new++;
|
||||
ptr_1 = ptr_1_new;
|
||||
}
|
||||
while(ptr_2 < count_2 && (ptr_1 == count_1 || p_list_2[ptr_2] < p_list_1[ptr_1]))
|
||||
{
|
||||
p_list_2_specific[found_2++] = p_list_2[ptr_2];
|
||||
t_size ptr_2_new = ptr_2 + 1;
|
||||
while(ptr_2_new < count_2 && p_list_2[ptr_2_new] == p_list_2[ptr_2]) ptr_2_new++;
|
||||
ptr_2 = ptr_2_new;
|
||||
}
|
||||
while(ptr_1 < count_1 && ptr_2 < count_2 && p_list_1[ptr_1] == p_list_2[ptr_2]) {ptr_1++; ptr_2++;}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
double metadb_handle_list_helper::calc_total_duration(metadb_handle_list_cref p_list)
|
||||
{
|
||||
double ret = 0;
|
||||
t_size n, m = p_list.get_count();
|
||||
for(n=0;n<m;n++)
|
||||
{
|
||||
double temp = p_list.get_item(n)->get_length();
|
||||
if (temp > 0) ret += temp;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void metadb_handle_list_helper::sort_by_path(metadb_handle_list_ref p_list)
|
||||
{
|
||||
sort_by_format(p_list,"%path_sort%",NULL);
|
||||
}
|
||||
|
||||
|
||||
t_filesize metadb_handle_list_helper::calc_total_size(metadb_handle_list_cref p_list, bool skipUnknown) {
|
||||
metadb_handle_list list(p_list);
|
||||
list.sort_t(metadb::path_compare_metadb_handle);
|
||||
|
||||
t_filesize ret = 0;
|
||||
t_size n, m = list.get_count();
|
||||
for(n=0;n<m;n++) {
|
||||
if (n==0 || metadb::path_compare(list[n-1]->get_path(),list[n]->get_path())) {
|
||||
t_filesize t = list[n]->get_filesize();
|
||||
if (t == filesize_invalid) {
|
||||
if (!skipUnknown) return filesize_invalid;
|
||||
} else {
|
||||
ret += t;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
t_filesize metadb_handle_list_helper::calc_total_size_ex(metadb_handle_list_cref p_list, bool & foundUnknown) {
|
||||
foundUnknown = false;
|
||||
metadb_handle_list list(p_list);
|
||||
list.sort_t(metadb::path_compare_metadb_handle);
|
||||
|
||||
t_filesize ret = 0;
|
||||
t_size n, m = list.get_count();
|
||||
for(n=0;n<m;n++) {
|
||||
if (n==0 || metadb::path_compare(list[n-1]->get_path(),list[n]->get_path())) {
|
||||
t_filesize t = list[n]->get_filesize();
|
||||
if (t == filesize_invalid) {
|
||||
foundUnknown = true;
|
||||
} else {
|
||||
ret += t;
|
||||
}
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
//! Service for plugging your nonmodal dialog windows into main app loop to receive IsDialogMessage()-translated messages.\n
|
||||
//! Note that all methods are valid from main app thread only.\n
|
||||
//! Usage: static_api_ptr_t<modeless_dialog_manager> or modeless_dialog_manager::g_add / modeless_dialog_manager::g_remove.
|
||||
class NOVTABLE modeless_dialog_manager : public service_base {
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(modeless_dialog_manager);
|
||||
public:
|
||||
//! Adds specified window to global list of windows to receive IsDialogMessage().
|
||||
virtual void add(HWND p_wnd) = 0;
|
||||
//! Removes specified window from global list of windows to receive IsDialogMessage().
|
||||
virtual void remove(HWND p_wnd) = 0;
|
||||
|
||||
//! Static helper; see add().
|
||||
static void g_add(HWND p_wnd) {static_api_ptr_t<modeless_dialog_manager>()->add(p_wnd);}
|
||||
//! Static helper; see remove().
|
||||
static void g_remove(HWND p_wnd) {static_api_ptr_t<modeless_dialog_manager>()->remove(p_wnd);}
|
||||
|
||||
};
|
|
@ -0,0 +1,149 @@
|
|||
class NOVTABLE playlist_dataobject_desc {
|
||||
public:
|
||||
virtual t_size get_entry_count() const = 0;
|
||||
virtual void get_entry_name(t_size which, pfc::string_base & out) const = 0;
|
||||
virtual void get_entry_content(t_size which, metadb_handle_list_ref out) const = 0;
|
||||
|
||||
virtual void set_entry_count(t_size count) = 0;
|
||||
virtual void set_entry_name(t_size which, const char * name) = 0;
|
||||
virtual void set_entry_content(t_size which, metadb_handle_list_cref content) = 0;
|
||||
|
||||
void copy(playlist_dataobject_desc const & source) {
|
||||
const t_size count = source.get_entry_count(); set_entry_count(count);
|
||||
metadb_handle_list content; pfc::string8 name;
|
||||
for(t_size walk = 0; walk < count; ++walk) {
|
||||
source.get_entry_name(walk,name); source.get_entry_content(walk,content);
|
||||
set_entry_name(walk,name); set_entry_content(walk,content);
|
||||
}
|
||||
}
|
||||
protected:
|
||||
~playlist_dataobject_desc() {}
|
||||
private:
|
||||
const playlist_dataobject_desc & operator=(const playlist_dataobject_desc &) {return *this;}
|
||||
};
|
||||
|
||||
class NOVTABLE playlist_dataobject_desc_v2 : public playlist_dataobject_desc {
|
||||
public:
|
||||
virtual void get_side_data(t_size which, mem_block_container & out) const = 0;
|
||||
virtual void set_side_data(t_size which, const void * data, t_size size) = 0;
|
||||
|
||||
void copy(playlist_dataobject_desc_v2 const & source) {
|
||||
const t_size count = source.get_entry_count(); set_entry_count(count);
|
||||
metadb_handle_list content; pfc::string8 name;
|
||||
mem_block_container_impl_t<pfc::alloc_fast_aggressive> sideData;
|
||||
for(t_size walk = 0; walk < count; ++walk) {
|
||||
source.get_entry_name(walk,name); source.get_entry_content(walk,content); source.get_side_data(walk, sideData);
|
||||
set_entry_name(walk,name); set_entry_content(walk,content); set_side_data(walk, sideData.get_ptr(), sideData.get_size());
|
||||
}
|
||||
}
|
||||
|
||||
void set_from_playlist_manager(bit_array const & mask) {
|
||||
static_api_ptr_t<playlist_manager_v4> api;
|
||||
const t_size pltotal = api->get_playlist_count();
|
||||
const t_size total = mask.calc_count(true,0,pltotal);
|
||||
set_entry_count(total);
|
||||
t_size done = 0;
|
||||
pfc::string8 name; metadb_handle_list content;
|
||||
abort_callback_dummy abort;
|
||||
for(t_size walk = 0; walk < pltotal; ++walk) if (mask[walk]) {
|
||||
pfc::dynamic_assert( done < total );
|
||||
api->playlist_get_name(walk,name); api->playlist_get_all_items(walk,content);
|
||||
set_entry_name(done,name); set_entry_content(done,content);
|
||||
stream_writer_buffer_simple sideData; api->playlist_get_sideinfo(walk, &sideData, abort);
|
||||
set_side_data(done,sideData.m_buffer.get_ptr(), sideData.m_buffer.get_size());
|
||||
++done;
|
||||
}
|
||||
pfc::dynamic_assert( done == total );
|
||||
}
|
||||
|
||||
const playlist_dataobject_desc_v2 & operator=(const playlist_dataobject_desc_v2& source) {copy(source); return *this;}
|
||||
protected:
|
||||
~playlist_dataobject_desc_v2() {}
|
||||
};
|
||||
|
||||
class playlist_dataobject_desc_impl : public playlist_dataobject_desc_v2 {
|
||||
public:
|
||||
playlist_dataobject_desc_impl() {}
|
||||
playlist_dataobject_desc_impl(const playlist_dataobject_desc_v2 & source) {copy(source);}
|
||||
|
||||
t_size get_entry_count() const {return m_entries.get_size();}
|
||||
void get_entry_name(t_size which, pfc::string_base & out) const {
|
||||
if (which < m_entries.get_size()) out = m_entries[which].m_name;
|
||||
else throw pfc::exception_invalid_params();
|
||||
}
|
||||
void get_entry_content(t_size which, metadb_handle_list_ref out) const {
|
||||
if (which < m_entries.get_size()) out = m_entries[which].m_content;
|
||||
else throw pfc::exception_invalid_params();
|
||||
}
|
||||
void set_entry_count(t_size count) {
|
||||
m_entries.set_size(count);
|
||||
}
|
||||
void set_entry_name(t_size which, const char * name) {
|
||||
if (which < m_entries.get_size()) m_entries[which].m_name = name;
|
||||
else throw pfc::exception_invalid_params();
|
||||
}
|
||||
void set_entry_content(t_size which, metadb_handle_list_cref content) {
|
||||
if (which < m_entries.get_size()) m_entries[which].m_content = content;
|
||||
else throw pfc::exception_invalid_params();
|
||||
}
|
||||
void get_side_data(t_size which, mem_block_container & out) const {
|
||||
if (which < m_entries.get_size()) out.set(m_entries[which].m_sideData);
|
||||
else throw pfc::exception_invalid_params();
|
||||
}
|
||||
void set_side_data(t_size which, const void * data, t_size size) {
|
||||
if (which < m_entries.get_size()) m_entries[which].m_sideData.set_data_fromptr(reinterpret_cast<const t_uint8*>(data), size);
|
||||
else throw pfc::exception_invalid_params();
|
||||
}
|
||||
private:
|
||||
struct entry { metadb_handle_list m_content; pfc::string8 m_name; pfc::array_t<t_uint8> m_sideData; };
|
||||
pfc::array_t<entry> m_entries;
|
||||
};
|
||||
|
||||
//! \since 0.9.5
|
||||
//! Provides various methods for interaction between foobar2000 and OLE IDataObjects, Windows Clipboard, drag&drop and such.
|
||||
//! To instantiate, use static_api_ptr_t<ole_interaction>.
|
||||
class NOVTABLE ole_interaction : public service_base {
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(ole_interaction)
|
||||
public:
|
||||
enum {
|
||||
KClipboardFormatSimpleLocations,
|
||||
KClipboardFormatFPL,
|
||||
KClipboardFormatMultiFPL,
|
||||
KClipboardFormatTotal
|
||||
};
|
||||
//! Retrieves clipboard format ID for one of foobar2000's internal data formats.
|
||||
//! @param which One of KClipboardFormat* constants.
|
||||
virtual t_uint32 get_clipboard_format(t_uint32 which) = 0;
|
||||
|
||||
//! Creates an IDataObject from a group of tracks.
|
||||
virtual pfc::com_ptr_t<IDataObject> create_dataobject(metadb_handle_list_cref source) = 0;
|
||||
|
||||
//! Creates an IDataObject from one or more playlists, including playlist name info for re-creating those playlists later.
|
||||
virtual pfc::com_ptr_t<IDataObject> create_dataobject(const playlist_dataobject_desc & source) = 0;
|
||||
|
||||
//! Attempts to parse an IDataObject as playlists.
|
||||
virtual HRESULT parse_dataobject_playlists(pfc::com_ptr_t<IDataObject> obj, playlist_dataobject_desc & out) = 0;
|
||||
|
||||
//! For internal use only. Will succeed only if the metadb_handle list can be generated immediately, without performing potentially timeconsuming tasks such as parsing media files (for an example when the specified IDataObject contains data in one of our internal formats).
|
||||
virtual HRESULT parse_dataobject_immediate(pfc::com_ptr_t<IDataObject> obj, metadb_handle_list_ref out) = 0;
|
||||
|
||||
//! Attempts to parse an IDataObject into a dropped_files_data object (list of metadb_handles if immediately available, list of file paths otherwise).
|
||||
virtual HRESULT parse_dataobject(pfc::com_ptr_t<IDataObject> obj, dropped_files_data & out) = 0;
|
||||
|
||||
//! Checks whether the specified IDataObject appears to be parsable by our parse_dataobject methods.
|
||||
virtual HRESULT check_dataobject(pfc::com_ptr_t<IDataObject> obj, DWORD & dropEffect, bool & isNative) = 0;
|
||||
|
||||
//! Checks whether the specified IDataObject appears to be parsable as playlists (parse_dataobject_playlists method).
|
||||
virtual HRESULT check_dataobject_playlists(pfc::com_ptr_t<IDataObject> obj) = 0;
|
||||
};
|
||||
|
||||
//! \since 0.9.5.4
|
||||
class NOVTABLE ole_interaction_v2 : public ole_interaction {
|
||||
FB2K_MAKE_SERVICE_INTERFACE(ole_interaction_v2, ole_interaction)
|
||||
public:
|
||||
//! Creates an IDataObject from one or more playlists, including playlist name info for re-creating those playlists later.
|
||||
virtual pfc::com_ptr_t<IDataObject> create_dataobject(const playlist_dataobject_desc_v2 & source) = 0;
|
||||
|
||||
//! Attempts to parse an IDataObject as playlists.
|
||||
virtual HRESULT parse_dataobject_playlists(pfc::com_ptr_t<IDataObject> obj, playlist_dataobject_desc_v2 & out) = 0;
|
||||
};
|
|
@ -0,0 +1,14 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
void packet_decoder::g_open(service_ptr_t<packet_decoder> & p_out,bool p_decode,const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size,abort_callback & p_abort)
|
||||
{
|
||||
service_enum_t<packet_decoder_entry> e;
|
||||
service_ptr_t<packet_decoder_entry> ptr;
|
||||
while(e.next(ptr)) {
|
||||
if (ptr->is_our_setup(p_owner,p_param1,p_param2,p_param2size)) {
|
||||
ptr->open(p_out,p_decode,p_owner,p_param1,p_param2,p_param2size,p_abort);
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw exception_io_data();
|
||||
}
|
|
@ -0,0 +1,113 @@
|
|||
//! Provides interface to decode various audio data types to PCM. Use packet_decoder_factory_t template to register.
|
||||
|
||||
class NOVTABLE packet_decoder : public service_base {
|
||||
protected:
|
||||
//! Prototype of function that must be implemented by packet_decoder implementation but is not accessible through packet_decoder interface itself.
|
||||
//! Determines whether specific packet_decoder implementation supports specified decoder setup data.
|
||||
static bool g_is_our_setup(const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size) {return false;}
|
||||
|
||||
//! Prototype of function that must be implemented by packet_decoder implementation but is not accessible through packet_decoder interface itself.
|
||||
//! Initializes packet decoder instance with specified decoder setup data. This is called only once, before any other methods.
|
||||
//! @param p_decode If set to true, decode() and reset_after_seek() calls can be expected later. If set to false, those methods will not be called on this packet_decoder instance - for an example when caller is only retrieving information about the file rather than preparing to decode it.
|
||||
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) {throw exception_io_data();}
|
||||
public:
|
||||
|
||||
|
||||
//! Forwards additional information about stream being decoded. \n
|
||||
//! Calling: this must be called immediately after packet_decoder object is created, before any other methods are called.\n
|
||||
//! Implementation: this is called after open() (which is called by implementation framework immediately after creation), and before any other methods are called.
|
||||
virtual t_size set_stream_property(const GUID & p_type,t_size p_param1,const void * p_param2,t_size p_param2size) = 0;
|
||||
|
||||
|
||||
//! Retrieves additional user-readable tech infos that decoder can provide.
|
||||
//! @param p_info Interface receiving information about the stream being decoded. Note that it already contains partial info about the file; existing info should not be erased, decoder-provided info should be merged with it.
|
||||
virtual void get_info(file_info & p_info) = 0;
|
||||
|
||||
//! Returns many frames back to start decoding when seeking.
|
||||
virtual unsigned get_max_frame_dependency()=0;
|
||||
//! Returns much time back to start decoding when seeking (for containers where going back by specified number of frames is not trivial).
|
||||
virtual double get_max_frame_dependency_time()=0;
|
||||
|
||||
//! Flushes decoder after seeking.
|
||||
virtual void reset_after_seek()=0;
|
||||
|
||||
//! Decodes a block of audio data.\n
|
||||
//! It may return empty chunk even when successful (caused by encoder+decoder delay for an example), caller must check for it and handle it appropriately.
|
||||
virtual void decode(const void * p_buffer,t_size p_bytes,audio_chunk & p_chunk,abort_callback & p_abort)=0;
|
||||
|
||||
//! Returns whether this packet decoder supports analyze_first_frame() function.
|
||||
virtual bool analyze_first_frame_supported() = 0;
|
||||
//! Optional. Some codecs need to analyze first frame of the stream to return additional info about the stream, such as encoding setup. This can be only called immediately after instantiation (and set_stream_property() if present), before any actual decoding or get_info(). Caller can determine whether this method is supported or not by calling analyze_first_frame_supported(), to avoid reading first frame when decoder won't utiilize the extra info for an example. If particular decoder can't utilize first frame info in any way (and analyze_first_frame_supported() returns false), this function should do nothing and succeed.
|
||||
virtual void analyze_first_frame(const void * p_buffer,t_size p_bytes,abort_callback & p_abort) = 0;
|
||||
|
||||
//! Static helper, creates a packet_decoder instance and initializes it with specific decoder setup data.
|
||||
static void g_open(service_ptr_t<packet_decoder> & p_out,bool p_decode,const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size,abort_callback & p_abort);
|
||||
|
||||
static const GUID owner_MP4,owner_matroska,owner_MP3,owner_MP2,owner_MP1,owner_MP4_ALAC,owner_ADTS,owner_ADIF, owner_Ogg, owner_MP4_AMR, owner_MP4_AMR_WB;
|
||||
|
||||
struct matroska_setup
|
||||
{
|
||||
const char * codec_id;
|
||||
unsigned sample_rate,sample_rate_output;
|
||||
unsigned channels;
|
||||
unsigned codec_private_size;
|
||||
const void * codec_private;
|
||||
};
|
||||
//owner_MP4: param1 - codec ID (MP4 audio type), param2 - MP4 codec initialization data
|
||||
//owner_MP3: raw MP3/MP2 file, parameters ignored
|
||||
//owner_matroska: param2 = matroska_setup struct, param2size size must be equal to sizeof(matroska_setup)
|
||||
|
||||
|
||||
//these are used to initialize PCM decoder
|
||||
static const GUID property_samplerate,property_bitspersample,property_channels,property_byteorder,property_signed,property_channelmask;
|
||||
//property_samplerate : param1 == sample rate in hz
|
||||
//property_bitspersample : param1 == bits per sample
|
||||
//property_channels : param1 == channel count
|
||||
//property_byteorder : if (param1) little_endian; else big_endian;
|
||||
//property_signed : if (param1) signed; else unsigned;
|
||||
|
||||
|
||||
//property_ogg_header : p_param1 = unused, p_param2 = ogg_packet structure, retval: 0 when more headers are wanted, 1 when done parsing headers
|
||||
//property_ogg_query_sample_rate : returns sample rate, no parameters
|
||||
//property_ogg_packet : p_param1 = unused, p_param2 = ogg_packet strucute
|
||||
static const GUID property_ogg_header, property_ogg_query_sample_rate, property_ogg_packet;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(packet_decoder,service_base);
|
||||
};
|
||||
|
||||
class NOVTABLE packet_decoder_streamparse : public packet_decoder
|
||||
{
|
||||
public:
|
||||
virtual void decode_ex(const void * p_buffer,t_size p_bytes,t_size & p_bytes_processed,audio_chunk & p_chunk,abort_callback & p_abort) = 0;
|
||||
virtual void analyze_first_frame_ex(const void * p_buffer,t_size p_bytes,t_size & p_bytes_processed,abort_callback & p_abort) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(packet_decoder_streamparse,packet_decoder);
|
||||
};
|
||||
|
||||
class NOVTABLE packet_decoder_entry : public service_base
|
||||
{
|
||||
public:
|
||||
virtual bool is_our_setup(const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size) = 0;
|
||||
virtual void open(service_ptr_t<packet_decoder> & p_out,bool p_decode,const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size,abort_callback & p_abort) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(packet_decoder_entry);
|
||||
};
|
||||
|
||||
|
||||
template<class T>
|
||||
class packet_decoder_entry_impl_t : public packet_decoder_entry
|
||||
{
|
||||
public:
|
||||
bool is_our_setup(const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size) {
|
||||
return T::g_is_our_setup(p_owner,p_param1,p_param2,p_param2size);
|
||||
}
|
||||
void open(service_ptr_t<packet_decoder> & p_out,bool p_decode,const GUID & p_owner,t_size p_param1,const void * p_param2,t_size p_param2size,abort_callback & p_abort) {
|
||||
assert(is_our_setup(p_owner,p_param1,p_param2,p_param2size));
|
||||
service_ptr_t<T> instance = new service_impl_t<T>();
|
||||
instance->open(p_owner,p_decode,p_param1,p_param2,p_param2size,p_abort);
|
||||
p_out = instance.get_ptr();
|
||||
}
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class packet_decoder_factory_t : public service_factory_single_t<packet_decoder_entry_impl_t<T> > {};
|
|
@ -0,0 +1,149 @@
|
|||
/*!
|
||||
Class receiving notifications about playback events. Note that all methods are called only from app's main thread.
|
||||
Use play_callback_manager to register your dynamically created instances. Statically registered version is available too - see play_callback_static.
|
||||
*/
|
||||
class NOVTABLE play_callback {
|
||||
public:
|
||||
//! Playback process is being initialized. on_playback_new_track() should be called soon after this when first file is successfully opened for decoding.
|
||||
virtual void FB2KAPI on_playback_starting(play_control::t_track_command p_command,bool p_paused) = 0;
|
||||
//! Playback advanced to new track.
|
||||
virtual void FB2KAPI on_playback_new_track(metadb_handle_ptr p_track) = 0;
|
||||
//! Playback stopped.
|
||||
virtual void FB2KAPI on_playback_stop(play_control::t_stop_reason p_reason) = 0;
|
||||
//! User has seeked to specific time.
|
||||
virtual void FB2KAPI on_playback_seek(double p_time) = 0;
|
||||
//! Called on pause/unpause.
|
||||
virtual void FB2KAPI on_playback_pause(bool p_state) = 0;
|
||||
//! Called when currently played file gets edited.
|
||||
virtual void FB2KAPI on_playback_edited(metadb_handle_ptr p_track) = 0;
|
||||
//! Dynamic info (VBR bitrate etc) change.
|
||||
virtual void FB2KAPI on_playback_dynamic_info(const file_info & p_info) = 0;
|
||||
//! Per-track dynamic info (stream track titles etc) change. Happens less often than on_playback_dynamic_info().
|
||||
virtual void FB2KAPI on_playback_dynamic_info_track(const file_info & p_info) = 0;
|
||||
//! Called every second, for time display
|
||||
virtual void FB2KAPI on_playback_time(double p_time) = 0;
|
||||
//! User changed volume settings. Possibly called when not playing.
|
||||
//! @param p_new_val new volume level in dB; 0 for full volume.
|
||||
virtual void FB2KAPI on_volume_change(float p_new_val) = 0;
|
||||
|
||||
enum {
|
||||
flag_on_playback_starting = 1 << 0,
|
||||
flag_on_playback_new_track = 1 << 1,
|
||||
flag_on_playback_stop = 1 << 2,
|
||||
flag_on_playback_seek = 1 << 3,
|
||||
flag_on_playback_pause = 1 << 4,
|
||||
flag_on_playback_edited = 1 << 5,
|
||||
flag_on_playback_dynamic_info = 1 << 6,
|
||||
flag_on_playback_dynamic_info_track = 1 << 7,
|
||||
flag_on_playback_time = 1 << 8,
|
||||
flag_on_volume_change = 1 << 9,
|
||||
|
||||
flag_on_playback_all = flag_on_playback_starting | flag_on_playback_new_track |
|
||||
flag_on_playback_stop | flag_on_playback_seek |
|
||||
flag_on_playback_pause | flag_on_playback_edited |
|
||||
flag_on_playback_dynamic_info | flag_on_playback_dynamic_info_track | flag_on_playback_time,
|
||||
};
|
||||
protected:
|
||||
play_callback() {}
|
||||
~play_callback() {}
|
||||
};
|
||||
|
||||
//! Standard API (always present); manages registrations of dynamic play_callbacks.
|
||||
//! Usage: use static_api_ptr_t<play_callback_manager>.
|
||||
//! Do not reimplement.
|
||||
class NOVTABLE play_callback_manager : public service_base
|
||||
{
|
||||
public:
|
||||
//! Registers a play_callback object.
|
||||
//! @param p_callback Interface to register.
|
||||
//! @param p_flags Indicates which notifications are requested.
|
||||
//! @param p_forward_status_on_register Set to true to have the callback immediately receive current playback status as notifications if playback is active (eg. to receive info about playback process that started before our callback was registered).
|
||||
virtual void FB2KAPI register_callback(play_callback * p_callback,unsigned p_flags,bool p_forward_status_on_register) = 0;
|
||||
//! Unregisters a play_callback object.
|
||||
//! @p_callback Previously registered interface to unregister.
|
||||
virtual void FB2KAPI unregister_callback(play_callback * p_callback) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(play_callback_manager);
|
||||
};
|
||||
|
||||
//! Implementation helper.
|
||||
class play_callback_impl_base : public play_callback {
|
||||
public:
|
||||
play_callback_impl_base(unsigned p_flags = ~0) {
|
||||
static_api_ptr_t<play_callback_manager>()->register_callback(this,p_flags,false);
|
||||
}
|
||||
~play_callback_impl_base() {
|
||||
static_api_ptr_t<play_callback_manager>()->unregister_callback(this);
|
||||
}
|
||||
void play_callback_reregister(unsigned flags, bool refresh = false) {
|
||||
static_api_ptr_t<play_callback_manager> api;
|
||||
api->unregister_callback(this);
|
||||
api->register_callback(this,flags,refresh);
|
||||
}
|
||||
void on_playback_starting(play_control::t_track_command p_command,bool p_paused) {}
|
||||
void on_playback_new_track(metadb_handle_ptr p_track) {}
|
||||
void on_playback_stop(play_control::t_stop_reason p_reason) {}
|
||||
void on_playback_seek(double p_time) {}
|
||||
void on_playback_pause(bool p_state) {}
|
||||
void on_playback_edited(metadb_handle_ptr p_track) {}
|
||||
void on_playback_dynamic_info(const file_info & p_info) {}
|
||||
void on_playback_dynamic_info_track(const file_info & p_info) {}
|
||||
void on_playback_time(double p_time) {}
|
||||
void on_volume_change(float p_new_val) {}
|
||||
|
||||
PFC_CLASS_NOT_COPYABLE_EX(play_callback_impl_base)
|
||||
};
|
||||
|
||||
//! Static (autoregistered) version of play_callback. Use play_callback_static_factory_t to register.
|
||||
class play_callback_static : public service_base, public play_callback {
|
||||
public:
|
||||
//! Controls which methods your callback wants called; returned value should not change in run time, you should expect it to be queried only once (on startup). See play_callback::flag_* constants.
|
||||
virtual unsigned get_flags() = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(play_callback_static);
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class play_callback_static_factory_t : public service_factory_single_t<T> {};
|
||||
|
||||
|
||||
//! Gets notified about tracks being played. Notification occurs when at least 60s of the track has been played, or the track has reached its end after at least 1/3 of it has been played through.
|
||||
//! Use playback_statistics_collector_factory_t to register.
|
||||
class NOVTABLE playback_statistics_collector : public service_base {
|
||||
public:
|
||||
virtual void on_item_played(metadb_handle_ptr p_item) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(playback_statistics_collector);
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
class playback_statistics_collector_factory_t : public service_factory_single_t<T> {};
|
||||
|
||||
|
||||
|
||||
|
||||
//! Helper providing a simplified interface for receiving playback events, in case your code does not care about the kind of playback event that has occured; useful typically for GUI/rendering code that just refreshes some control whenever a playback state change occurs.
|
||||
class playback_event_notify : private play_callback_impl_base {
|
||||
public:
|
||||
playback_event_notify(playback_control::t_display_level level = playback_control::display_level_all) : play_callback_impl_base(GrabCBFlags(level)) {}
|
||||
|
||||
static t_uint32 GrabCBFlags(playback_control::t_display_level level) {
|
||||
t_uint32 flags = flag_on_playback_starting | flag_on_playback_new_track | flag_on_playback_stop | flag_on_playback_pause | flag_on_playback_edited | flag_on_volume_change;
|
||||
if (level >= playback_control::display_level_titles) flags |= flag_on_playback_dynamic_info_track;
|
||||
if (level >= playback_control::display_level_all) flags |= flag_on_playback_seek | flag_on_playback_dynamic_info | flag_on_playback_time;
|
||||
return flags;
|
||||
}
|
||||
protected:
|
||||
virtual void on_playback_event() {}
|
||||
private:
|
||||
void on_playback_starting(play_control::t_track_command p_command,bool p_paused) {on_playback_event();}
|
||||
void on_playback_new_track(metadb_handle_ptr p_track) {on_playback_event();}
|
||||
void on_playback_stop(play_control::t_stop_reason p_reason) {on_playback_event();}
|
||||
void on_playback_seek(double p_time) {on_playback_event();}
|
||||
void on_playback_pause(bool p_state) {on_playback_event();}
|
||||
void on_playback_edited(metadb_handle_ptr p_track) {on_playback_event();}
|
||||
void on_playback_dynamic_info(const file_info & p_info) {on_playback_event();}
|
||||
void on_playback_dynamic_info_track(const file_info & p_info) {on_playback_event();}
|
||||
void on_playback_time(double p_time) {on_playback_event();}
|
||||
void on_volume_change(float p_new_val) {on_playback_event();}
|
||||
};
|
|
@ -0,0 +1,23 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
int playable_location::g_compare(const playable_location & p_item1,const playable_location & p_item2) {
|
||||
int ret = metadb::path_compare(p_item1.get_path(),p_item2.get_path());
|
||||
if (ret != 0) return ret;
|
||||
return pfc::compare_t(p_item1.get_subsong(),p_item2.get_subsong());
|
||||
}
|
||||
|
||||
pfc::string_base & operator<<(pfc::string_base & p_fmt,const playable_location & p_location)
|
||||
{
|
||||
p_fmt << "\"" << file_path_display(p_location.get_path()) << "\"";
|
||||
t_uint32 index = p_location.get_subsong_index();
|
||||
if (index != 0) p_fmt << " / index: " << p_location.get_subsong_index();
|
||||
return p_fmt;
|
||||
}
|
||||
|
||||
|
||||
bool playable_location::operator==(const playable_location & p_other) const {
|
||||
return metadb::path_compare(get_path(),p_other.get_path()) == 0 && get_subsong() == p_other.get_subsong();
|
||||
}
|
||||
bool playable_location::operator!=(const playable_location & p_other) const {
|
||||
return !(*this == p_other);
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
#ifndef _FOOBAR2000_PLAYABLE_LOCATION_H_
|
||||
#define _FOOBAR2000_PLAYABLE_LOCATION_H_
|
||||
|
||||
//playable_location stores location of a playable resource, currently implemented as file path and integer for indicating multiple playable "subsongs" per file
|
||||
//also see: file_info.h
|
||||
//for getting more info about resource referenced by a playable_location, see metadb.h
|
||||
|
||||
//char* strings are all UTF-8
|
||||
|
||||
class NOVTABLE playable_location//interface (for passing around between DLLs)
|
||||
{
|
||||
public:
|
||||
virtual const char * get_path() const =0;
|
||||
virtual void set_path(const char*)=0;
|
||||
virtual t_uint32 get_subsong() const =0;
|
||||
virtual void set_subsong(t_uint32)=0;
|
||||
|
||||
void copy(const playable_location & p_other) {
|
||||
set_path(p_other.get_path());
|
||||
set_subsong(p_other.get_subsong());
|
||||
}
|
||||
|
||||
static int g_compare(const playable_location & p_item1,const playable_location & p_item2);
|
||||
|
||||
const playable_location & operator=(const playable_location & src) {copy(src);return *this;}
|
||||
|
||||
bool operator==(const playable_location & p_other) const;
|
||||
bool operator!=(const playable_location & p_other) const;
|
||||
|
||||
inline bool is_empty() {return get_path()[0]==0 && get_subsong()==0;}
|
||||
inline void reset() {set_path("");set_subsong(0);}
|
||||
inline t_uint32 get_subsong_index() const {return get_subsong();}
|
||||
inline void set_subsong_index(t_uint32 v) {set_subsong(v);}
|
||||
|
||||
class comparator {
|
||||
public:
|
||||
static int compare(const playable_location & v1, const playable_location & v2) {return g_compare(v1,v2);}
|
||||
};
|
||||
|
||||
protected:
|
||||
playable_location() {}
|
||||
~playable_location() {}
|
||||
};
|
||||
|
||||
typedef playable_location * pplayable_location;
|
||||
typedef playable_location const * pcplayable_location;
|
||||
typedef playable_location & rplayable_location;
|
||||
typedef playable_location const & rcplayable_location;
|
||||
|
||||
class playable_location_impl : public playable_location//implementation
|
||||
{
|
||||
public:
|
||||
const char * get_path() const {return m_path;}
|
||||
void set_path(const char* p_path) {m_path=p_path;}
|
||||
t_uint32 get_subsong() const {return m_subsong;}
|
||||
void set_subsong(t_uint32 p_subsong) {m_subsong=p_subsong;}
|
||||
|
||||
const playable_location_impl & operator=(const playable_location & src) {copy(src);return *this;}
|
||||
const playable_location_impl & operator=(const playable_location_impl & src) {copy(src);return *this;}
|
||||
|
||||
playable_location_impl() : m_subsong(0) {}
|
||||
playable_location_impl(const char * p_path,t_uint32 p_subsong) : m_path(p_path), m_subsong(p_subsong) {}
|
||||
playable_location_impl(const playable_location & src) {copy(src);}
|
||||
playable_location_impl(const playable_location_impl & src) {copy(src);}
|
||||
|
||||
private:
|
||||
pfc::string_simple m_path;
|
||||
t_uint32 m_subsong;
|
||||
};
|
||||
|
||||
// usage: something( make_playable_location("file://c:\blah.ogg",0) );
|
||||
// only for use as a parameter to a function taking const playable_location &
|
||||
class make_playable_location : public playable_location
|
||||
{
|
||||
const char * path;
|
||||
t_uint32 num;
|
||||
|
||||
void set_path(const char*) {throw pfc::exception_not_implemented();}
|
||||
void set_subsong(t_uint32) {throw pfc::exception_not_implemented();}
|
||||
|
||||
public:
|
||||
const char * get_path() const {return path;}
|
||||
t_uint32 get_subsong() const {return num;}
|
||||
|
||||
make_playable_location(const char * p_path,t_uint32 p_num) : path(p_path), num(p_num) {}
|
||||
};
|
||||
|
||||
pfc::string_base & operator<<(pfc::string_base & p_fmt,const playable_location & p_location);
|
||||
|
||||
#endif //_FOOBAR2000_PLAYABLE_LOCATION_H_
|
|
@ -0,0 +1,13 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
|
||||
double playback_control::playback_get_length()
|
||||
{
|
||||
double rv = 0;
|
||||
metadb_handle_ptr ptr;
|
||||
if (get_now_playing(ptr))
|
||||
{
|
||||
rv = ptr->get_length();
|
||||
}
|
||||
return rv;
|
||||
}
|
|
@ -0,0 +1,135 @@
|
|||
//! Provides control for various playback-related operations.
|
||||
//! All methods provided by this interface work from main app thread only. Calling from another thread will do nothing or trigger an exception. If you need to trigger one of playback_control methods from another thread, see main_thread_callback.
|
||||
//! Do not call playback_control methods from inside any kind of global callback (e.g. playlist callback), otherwise race conditions may occur.
|
||||
//! Use static_api_ptr_t to instantiate. See static_api_ptr_t documentation for more info.
|
||||
class NOVTABLE playback_control : public service_base
|
||||
{
|
||||
public:
|
||||
|
||||
enum t_stop_reason {
|
||||
stop_reason_user = 0,
|
||||
stop_reason_eof,
|
||||
stop_reason_starting_another,
|
||||
stop_reason_shutting_down,
|
||||
};
|
||||
|
||||
|
||||
enum t_track_command {
|
||||
track_command_default = 0,
|
||||
track_command_play,
|
||||
track_command_next,
|
||||
track_command_prev,
|
||||
track_command_settrack,
|
||||
track_command_rand,
|
||||
track_command_resume,
|
||||
};
|
||||
|
||||
//! Retrieves now playing item handle.
|
||||
//! @returns true on success, false on failure (not playing).
|
||||
virtual bool get_now_playing(metadb_handle_ptr & p_out) = 0;
|
||||
//! Starts playback. If playback is already active, existing process is stopped first.
|
||||
//! @param p_command Specifies what track to start playback from. See t_track_Command enum for more info.
|
||||
//! @param p_paused Specifies whether playback should be started as paused.
|
||||
virtual void start(t_track_command p_command = track_command_play,bool p_paused = false) = 0;
|
||||
//! Stops playback.
|
||||
virtual void stop() = 0;
|
||||
//! Returns whether playback is active.
|
||||
virtual bool is_playing() = 0;
|
||||
//! Returns whether playback is active and in paused state.
|
||||
virtual bool is_paused() = 0;
|
||||
//! Toggles pause state if playback is active.
|
||||
//! @param p_state set to true when pausing or to false when unpausing.
|
||||
virtual void pause(bool p_state) = 0;
|
||||
|
||||
//! Retrieves stop-after-current-track option state.
|
||||
virtual bool get_stop_after_current() = 0;
|
||||
//! Alters stop-after-current-track option state.
|
||||
virtual void set_stop_after_current(bool p_state) = 0;
|
||||
|
||||
//! Alters playback volume level.
|
||||
//! @param p_value volume in dB; 0 for full volume.
|
||||
virtual void set_volume(float p_value) = 0;
|
||||
//! Retrieves playback volume level.
|
||||
//! @returns current playback volume level, in dB; 0 for full volume.
|
||||
virtual float get_volume() = 0;
|
||||
//! Alters playback volume level one step up.
|
||||
virtual void volume_up() = 0;
|
||||
//! Alters playback volume level one step down.
|
||||
virtual void volume_down() = 0;
|
||||
//! Toggles playback mute state.
|
||||
virtual void volume_mute_toggle() = 0;
|
||||
//! Seeks in currenly played track to specified time.
|
||||
//! @param p_time target time in seconds.
|
||||
virtual void playback_seek(double p_time) = 0;
|
||||
//! Seeks in currently played track by specified time forward or back.
|
||||
//! @param p_delta time in seconds to seek by; can be positive to seek forward or negative to seek back.
|
||||
virtual void playback_seek_delta(double p_delta) = 0;
|
||||
//! Returns whether currently played track is seekable. If it's not, playback_seek/playback_seek_delta calls will be ignored.
|
||||
virtual bool playback_can_seek() = 0;
|
||||
//! Returns current playback position within currently played track, in seconds.
|
||||
virtual double playback_get_position() = 0;
|
||||
|
||||
//! Type used to indicate level of dynamic playback-related info displayed. Safe to use with <> opereators, e.g. level above N always includes information rendered by level N.
|
||||
enum t_display_level {
|
||||
//! No playback-related info
|
||||
display_level_none,
|
||||
//! Static info and is_playing/is_paused stats
|
||||
display_level_basic,
|
||||
//! display_level_basic + dynamic track titles on e.g. live streams
|
||||
display_level_titles,
|
||||
//! display_level_titles + timing + VBR bitrate display etc
|
||||
display_level_all,
|
||||
};
|
||||
|
||||
//! Renders information about currently playing item.
|
||||
//! @param p_hook Optional callback object overriding fields and functions; set to NULL if not used.
|
||||
//! @param p_out String receiving the output on success.
|
||||
//! @param p_script Titleformat script to use. Use titleformat_compiler service to create one.
|
||||
//! @param p_filter Optional callback object allowing input to be filtered according to context (i.e. removal of linebreak characters present in tags when rendering playlist lines). Set to NULL when not used.
|
||||
//! @param p_level Indicates level of dynamic playback-related info displayed. See t_display_level enum for more details.
|
||||
//! @returns true on success, false when no item is currently being played.
|
||||
virtual bool playback_format_title(titleformat_hook * p_hook,pfc::string_base & p_out,const service_ptr_t<class titleformat_object> & p_script,titleformat_text_filter * p_filter,t_display_level p_level) = 0;
|
||||
|
||||
|
||||
|
||||
//! Helper; renders info about any item, including currently playing item info if the item is currently played.
|
||||
bool playback_format_title_ex(metadb_handle_ptr p_item,titleformat_hook * p_hook,pfc::string_base & p_out,const service_ptr_t<class titleformat_object> & p_script,titleformat_text_filter * p_filter,t_display_level p_level) {
|
||||
if (p_item.is_empty()) return playback_format_title(p_hook,p_out,p_script,p_filter,p_level);
|
||||
metadb_handle_ptr temp;
|
||||
if (get_now_playing(temp)) {
|
||||
if (temp == p_item) {
|
||||
return playback_format_title(p_hook,p_out,p_script,p_filter,p_level);
|
||||
}
|
||||
}
|
||||
p_item->format_title(p_hook,p_out,p_script,p_filter);
|
||||
return true;
|
||||
}
|
||||
|
||||
//! Helper; retrieves length of currently playing item.
|
||||
double playback_get_length();
|
||||
|
||||
//! Toggles stop-after-current state.
|
||||
void toggle_stop_after_current() {set_stop_after_current(!get_stop_after_current());}
|
||||
//! Toggles pause state.
|
||||
void toggle_pause() {pause(!is_paused());}
|
||||
|
||||
//! Starts playback if playback is inactive, otherwise toggles pause.
|
||||
void play_or_pause() {if (is_playing()) toggle_pause(); else start();}
|
||||
|
||||
//deprecated
|
||||
inline void play_start(t_track_command p_command = track_command_play,bool p_paused = false) {start(p_command,p_paused);}
|
||||
//deprecated
|
||||
inline void play_stop() {stop();}
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(playback_control);
|
||||
};
|
||||
|
||||
class playback_control_v2 : public playback_control {
|
||||
public:
|
||||
virtual float get_volume_step() = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(playback_control_v2,playback_control);
|
||||
};
|
||||
|
||||
//for compatibility with old code
|
||||
typedef playback_control play_control;
|
|
@ -0,0 +1,861 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
|
||||
namespace {
|
||||
class enum_items_callback_retrieve_item : public playlist_manager::enum_items_callback
|
||||
{
|
||||
metadb_handle_ptr m_item;
|
||||
public:
|
||||
enum_items_callback_retrieve_item() : m_item(0) {}
|
||||
bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected)
|
||||
{
|
||||
assert(m_item.is_empty());
|
||||
m_item = p_location;
|
||||
return false;
|
||||
}
|
||||
inline const metadb_handle_ptr & get_item() {return m_item;}
|
||||
};
|
||||
|
||||
class enum_items_callback_retrieve_selection : public playlist_manager::enum_items_callback
|
||||
{
|
||||
bool m_state;
|
||||
public:
|
||||
enum_items_callback_retrieve_selection() : m_state(false) {}
|
||||
bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected)
|
||||
{
|
||||
m_state = b_selected;
|
||||
return false;
|
||||
}
|
||||
inline bool get_state() {return m_state;}
|
||||
};
|
||||
|
||||
class enum_items_callback_retrieve_selection_mask : public playlist_manager::enum_items_callback
|
||||
{
|
||||
bit_array_var & m_out;
|
||||
public:
|
||||
enum_items_callback_retrieve_selection_mask(bit_array_var & p_out) : m_out(p_out) {}
|
||||
bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected)
|
||||
{
|
||||
m_out.set(p_index,b_selected);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class enum_items_callback_retrieve_all_items : public playlist_manager::enum_items_callback
|
||||
{
|
||||
pfc::list_base_t<metadb_handle_ptr> & m_out;
|
||||
public:
|
||||
enum_items_callback_retrieve_all_items(pfc::list_base_t<metadb_handle_ptr> & p_out) : m_out(p_out) {m_out.remove_all();}
|
||||
bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected)
|
||||
{
|
||||
m_out.add_item(p_location);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class enum_items_callback_retrieve_selected_items : public playlist_manager::enum_items_callback
|
||||
{
|
||||
pfc::list_base_t<metadb_handle_ptr> & m_out;
|
||||
public:
|
||||
enum_items_callback_retrieve_selected_items(pfc::list_base_t<metadb_handle_ptr> & p_out) : m_out(p_out) {m_out.remove_all();}
|
||||
bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected)
|
||||
{
|
||||
if (b_selected) m_out.add_item(p_location);
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class enum_items_callback_count_selection : public playlist_manager::enum_items_callback
|
||||
{
|
||||
t_size m_counter,m_max;
|
||||
public:
|
||||
enum_items_callback_count_selection(t_size p_max) : m_max(p_max), m_counter(0) {}
|
||||
bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected)
|
||||
{
|
||||
if (b_selected)
|
||||
{
|
||||
if (++m_counter >= m_max) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
inline t_size get_count() {return m_counter;}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
void playlist_manager::playlist_get_all_items(t_size p_playlist,pfc::list_base_t<metadb_handle_ptr> & out)
|
||||
{
|
||||
playlist_get_items(p_playlist,out,bit_array_true());
|
||||
}
|
||||
|
||||
void playlist_manager::playlist_get_selected_items(t_size p_playlist,pfc::list_base_t<metadb_handle_ptr> & out)
|
||||
{
|
||||
playlist_enum_items(p_playlist,enum_items_callback_retrieve_selected_items(out),bit_array_true());
|
||||
}
|
||||
|
||||
void playlist_manager::playlist_get_selection_mask(t_size p_playlist,bit_array_var & out)
|
||||
{
|
||||
playlist_enum_items(p_playlist,enum_items_callback_retrieve_selection_mask(out),bit_array_true());
|
||||
}
|
||||
|
||||
bool playlist_manager::playlist_is_item_selected(t_size p_playlist,t_size p_item)
|
||||
{
|
||||
enum_items_callback_retrieve_selection callback;
|
||||
playlist_enum_items(p_playlist,callback,bit_array_one(p_item));
|
||||
return callback.get_state();
|
||||
}
|
||||
|
||||
metadb_handle_ptr playlist_manager::playlist_get_item_handle(t_size playlist, t_size item) {
|
||||
metadb_handle_ptr temp;
|
||||
if (!playlist_get_item_handle(temp, playlist, item)) throw pfc::exception_invalid_params();
|
||||
PFC_ASSERT( temp.is_valid() );
|
||||
return temp;
|
||||
|
||||
}
|
||||
bool playlist_manager::playlist_get_item_handle(metadb_handle_ptr & p_out,t_size p_playlist,t_size p_item)
|
||||
{
|
||||
enum_items_callback_retrieve_item callback;
|
||||
playlist_enum_items(p_playlist,callback,bit_array_one(p_item));
|
||||
p_out = callback.get_item();
|
||||
return p_out.is_valid();
|
||||
}
|
||||
|
||||
void playlist_manager::g_make_selection_move_permutation(t_size * p_output,t_size p_count,const bit_array & p_selection,int p_delta) {
|
||||
pfc::create_move_items_permutation(p_output,p_count,p_selection,p_delta);
|
||||
}
|
||||
|
||||
bool playlist_manager::playlist_move_selection(t_size p_playlist,int p_delta) {
|
||||
if (p_delta==0) return true;
|
||||
|
||||
t_size count = playlist_get_item_count(p_playlist);
|
||||
|
||||
pfc::array_t<t_size> order; order.set_size(count);
|
||||
pfc::array_t<bool> selection; selection.set_size(count);
|
||||
|
||||
playlist_get_selection_mask(p_playlist,bit_array_var_table(selection.get_ptr(),selection.get_size()));
|
||||
g_make_selection_move_permutation(order.get_ptr(),count,bit_array_table(selection.get_ptr(),selection.get_size()),p_delta);
|
||||
return playlist_reorder_items(p_playlist,order.get_ptr(),count);
|
||||
}
|
||||
|
||||
//retrieving status
|
||||
t_size playlist_manager::activeplaylist_get_item_count()
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist == infinite) return 0;
|
||||
else return playlist_get_item_count(playlist);
|
||||
}
|
||||
|
||||
void playlist_manager::activeplaylist_enum_items(enum_items_callback & p_callback,const bit_array & p_mask)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) playlist_enum_items(playlist,p_callback,p_mask);
|
||||
}
|
||||
|
||||
t_size playlist_manager::activeplaylist_get_focus_item()
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist == infinite) return infinite;
|
||||
else return playlist_get_focus_item(playlist);
|
||||
}
|
||||
|
||||
bool playlist_manager::activeplaylist_get_name(pfc::string_base & p_out)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist == infinite) return false;
|
||||
else return playlist_get_name(playlist,p_out);
|
||||
}
|
||||
|
||||
//modifying playlist
|
||||
bool playlist_manager::activeplaylist_reorder_items(const t_size * order,t_size count)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) return playlist_reorder_items(playlist,order,count);
|
||||
else return false;
|
||||
}
|
||||
|
||||
void playlist_manager::activeplaylist_set_selection(const bit_array & affected,const bit_array & status)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) playlist_set_selection(playlist,affected,status);
|
||||
}
|
||||
|
||||
bool playlist_manager::activeplaylist_remove_items(const bit_array & mask)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) return playlist_remove_items(playlist,mask);
|
||||
else return false;
|
||||
}
|
||||
|
||||
bool playlist_manager::activeplaylist_replace_item(t_size p_item,const metadb_handle_ptr & p_new_item)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) return playlist_replace_item(playlist,p_item,p_new_item);
|
||||
else return false;
|
||||
}
|
||||
|
||||
void playlist_manager::activeplaylist_set_focus_item(t_size p_item)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) playlist_set_focus_item(playlist,p_item);
|
||||
}
|
||||
|
||||
t_size playlist_manager::activeplaylist_insert_items(t_size p_base,const pfc::list_base_const_t<metadb_handle_ptr> & data,const bit_array & p_selection)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) return playlist_insert_items(playlist,p_base,data,p_selection);
|
||||
else return infinite;
|
||||
}
|
||||
|
||||
void playlist_manager::activeplaylist_ensure_visible(t_size p_item)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) playlist_ensure_visible(playlist,p_item);
|
||||
}
|
||||
|
||||
bool playlist_manager::activeplaylist_rename(const char * p_name,t_size p_name_len)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) return playlist_rename(playlist,p_name,p_name_len);
|
||||
else return false;
|
||||
}
|
||||
|
||||
bool playlist_manager::activeplaylist_is_item_selected(t_size p_item)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) return playlist_is_item_selected(playlist,p_item);
|
||||
else return false;
|
||||
}
|
||||
|
||||
metadb_handle_ptr playlist_manager::activeplaylist_get_item_handle(t_size p_item) {
|
||||
metadb_handle_ptr temp;
|
||||
if (!activeplaylist_get_item_handle(temp, p_item)) throw pfc::exception_invalid_params();
|
||||
PFC_ASSERT( temp.is_valid() );
|
||||
return temp;
|
||||
}
|
||||
bool playlist_manager::activeplaylist_get_item_handle(metadb_handle_ptr & p_out,t_size p_item)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) return playlist_get_item_handle(p_out,playlist,p_item);
|
||||
else return false;
|
||||
}
|
||||
|
||||
void playlist_manager::activeplaylist_move_selection(int p_delta)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) playlist_move_selection(playlist,p_delta);
|
||||
}
|
||||
|
||||
void playlist_manager::activeplaylist_get_selection_mask(bit_array_var & out)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) playlist_get_selection_mask(playlist,out);
|
||||
}
|
||||
|
||||
void playlist_manager::activeplaylist_get_all_items(pfc::list_base_t<metadb_handle_ptr> & out)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) playlist_get_all_items(playlist,out);
|
||||
}
|
||||
|
||||
void playlist_manager::activeplaylist_get_selected_items(pfc::list_base_t<metadb_handle_ptr> & out)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) playlist_get_selected_items(playlist,out);
|
||||
}
|
||||
|
||||
bool playlist_manager::remove_playlist(t_size idx)
|
||||
{
|
||||
return remove_playlists(bit_array_one(idx));
|
||||
}
|
||||
|
||||
bool playlist_incoming_item_filter::process_location(const char * url,pfc::list_base_t<metadb_handle_ptr> & out,bool filter,const char * p_mask,const char * p_exclude,HWND p_parentwnd)
|
||||
{
|
||||
return process_locations(pfc::list_single_ref_t<const char*>(url),out,filter,p_mask,p_exclude,p_parentwnd);
|
||||
}
|
||||
|
||||
void playlist_manager::playlist_clear(t_size p_playlist)
|
||||
{
|
||||
playlist_remove_items(p_playlist,bit_array_true());
|
||||
}
|
||||
|
||||
void playlist_manager::activeplaylist_clear()
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) playlist_clear(playlist);
|
||||
}
|
||||
|
||||
bool playlist_manager::playlist_update_content(t_size playlist, metadb_handle_list_cref content, bool bUndoBackup) {
|
||||
metadb_handle_list old;
|
||||
playlist_get_all_items(playlist, old);
|
||||
if (old.get_size() == 0) {
|
||||
if (content.get_size() == 0) return false;
|
||||
if (bUndoBackup) playlist_undo_backup(playlist);
|
||||
playlist_add_items(playlist, content, bit_array_false());
|
||||
return true;
|
||||
}
|
||||
pfc::avltree_t<metadb_handle_ptr> itemsOld, itemsNew;
|
||||
|
||||
for(t_size walk = 0; walk < old.get_size(); ++walk) itemsOld += old[walk];
|
||||
for(t_size walk = 0; walk < content.get_size(); ++walk) itemsNew += content[walk];
|
||||
bit_array_bittable removeMask(old.get_size());
|
||||
bit_array_bittable filterMask(content.get_size());
|
||||
bool gotNew = false, filterNew = false, gotRemove = false;
|
||||
for(t_size walk = 0; walk < content.get_size(); ++walk) {
|
||||
const bool state = !itemsOld.have_item(content[walk]);
|
||||
if (state) gotNew = true;
|
||||
else filterNew = true;
|
||||
filterMask.set(walk, state);
|
||||
}
|
||||
for(t_size walk = 0; walk < old.get_size(); ++walk) {
|
||||
const bool state = !itemsNew.have_item(old[walk]);
|
||||
if (state) gotRemove = true;
|
||||
removeMask.set(walk, state);
|
||||
}
|
||||
if (!gotNew && !gotRemove) return false;
|
||||
if (bUndoBackup) playlist_undo_backup(playlist);
|
||||
if (gotRemove) {
|
||||
playlist_remove_items(playlist, removeMask);
|
||||
}
|
||||
if (gotNew) {
|
||||
if (filterNew) {
|
||||
metadb_handle_list temp(content);
|
||||
temp.filter_mask(filterMask);
|
||||
playlist_add_items(playlist, temp, bit_array_false());
|
||||
} else {
|
||||
playlist_add_items(playlist, content, bit_array_false());
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool playlist_manager::playlist_add_items(t_size playlist,const pfc::list_base_const_t<metadb_handle_ptr> & data,const bit_array & p_selection)
|
||||
{
|
||||
return playlist_insert_items(playlist,infinite,data,p_selection) != infinite;
|
||||
}
|
||||
|
||||
bool playlist_manager::activeplaylist_add_items(const pfc::list_base_const_t<metadb_handle_ptr> & data,const bit_array & p_selection)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) return playlist_add_items(playlist,data,p_selection);
|
||||
else return false;
|
||||
}
|
||||
|
||||
bool playlist_manager::playlist_insert_items_filter(t_size p_playlist,t_size p_base,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,bool p_select)
|
||||
{
|
||||
metadb_handle_list temp;
|
||||
static_api_ptr_t<playlist_incoming_item_filter> api;
|
||||
if (!api->filter_items(p_data,temp))
|
||||
return false;
|
||||
return playlist_insert_items(p_playlist,p_base,temp,bit_array_val(p_select)) != infinite;
|
||||
}
|
||||
|
||||
bool playlist_manager::activeplaylist_insert_items_filter(t_size p_base,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,bool p_select)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) return playlist_insert_items_filter(playlist,p_base,p_data,p_select);
|
||||
else return false;
|
||||
}
|
||||
|
||||
bool playlist_manager::playlist_insert_locations(t_size p_playlist,t_size p_base,const pfc::list_base_const_t<const char*> & p_urls,bool p_select,HWND p_parentwnd)
|
||||
{
|
||||
metadb_handle_list temp;
|
||||
static_api_ptr_t<playlist_incoming_item_filter> api;
|
||||
if (!api->process_locations(p_urls,temp,true,0,0,p_parentwnd)) return false;
|
||||
return playlist_insert_items(p_playlist,p_base,temp,bit_array_val(p_select)) != infinite;
|
||||
}
|
||||
|
||||
bool playlist_manager::activeplaylist_insert_locations(t_size p_base,const pfc::list_base_const_t<const char*> & p_urls,bool p_select,HWND p_parentwnd)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) return playlist_insert_locations(playlist,p_base,p_urls,p_select,p_parentwnd);
|
||||
else return false;
|
||||
}
|
||||
|
||||
bool playlist_manager::playlist_add_items_filter(t_size p_playlist,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,bool p_select)
|
||||
{
|
||||
return playlist_insert_items_filter(p_playlist,infinite,p_data,p_select);
|
||||
}
|
||||
|
||||
bool playlist_manager::activeplaylist_add_items_filter(const pfc::list_base_const_t<metadb_handle_ptr> & p_data,bool p_select)
|
||||
{
|
||||
return activeplaylist_insert_items_filter(infinite,p_data,p_select);
|
||||
}
|
||||
|
||||
bool playlist_manager::playlist_add_locations(t_size p_playlist,const pfc::list_base_const_t<const char*> & p_urls,bool p_select,HWND p_parentwnd)
|
||||
{
|
||||
return playlist_insert_locations(p_playlist,infinite,p_urls,p_select,p_parentwnd);
|
||||
}
|
||||
bool playlist_manager::activeplaylist_add_locations(const pfc::list_base_const_t<const char*> & p_urls,bool p_select,HWND p_parentwnd)
|
||||
{
|
||||
return activeplaylist_insert_locations(infinite,p_urls,p_select,p_parentwnd);
|
||||
}
|
||||
|
||||
void playlist_manager::reset_playing_playlist()
|
||||
{
|
||||
set_playing_playlist(get_active_playlist());
|
||||
}
|
||||
|
||||
void playlist_manager::playlist_clear_selection(t_size p_playlist)
|
||||
{
|
||||
playlist_set_selection(p_playlist,bit_array_true(),bit_array_false());
|
||||
}
|
||||
|
||||
void playlist_manager::activeplaylist_clear_selection()
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) playlist_clear_selection(playlist);
|
||||
}
|
||||
|
||||
void playlist_manager::activeplaylist_undo_backup()
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) playlist_undo_backup(playlist);
|
||||
}
|
||||
|
||||
bool playlist_manager::activeplaylist_undo_restore()
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) return playlist_undo_restore(playlist);
|
||||
else return false;
|
||||
}
|
||||
|
||||
bool playlist_manager::activeplaylist_redo_restore()
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) return playlist_redo_restore(playlist);
|
||||
else return false;
|
||||
}
|
||||
|
||||
void playlist_manager::playlist_remove_selection(t_size p_playlist,bool p_crop)
|
||||
{
|
||||
bit_array_bittable table(playlist_get_item_count(p_playlist));
|
||||
playlist_get_selection_mask(p_playlist,table);
|
||||
if (p_crop) playlist_remove_items(p_playlist,bit_array_not(table));
|
||||
else playlist_remove_items(p_playlist,table);
|
||||
}
|
||||
|
||||
void playlist_manager::activeplaylist_remove_selection(bool p_crop)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) playlist_remove_selection(playlist,p_crop);
|
||||
}
|
||||
|
||||
void playlist_manager::activeplaylist_item_format_title(t_size p_item,titleformat_hook * p_hook,pfc::string_base & out,const service_ptr_t<titleformat_object> & p_script,titleformat_text_filter * p_filter,play_control::t_display_level p_playback_info_level)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist == infinite) out = "NJET";
|
||||
else playlist_item_format_title(playlist,p_item,p_hook,out,p_script,p_filter,p_playback_info_level);
|
||||
}
|
||||
|
||||
void playlist_manager::playlist_set_selection_single(t_size p_playlist,t_size p_item,bool p_state)
|
||||
{
|
||||
playlist_set_selection(p_playlist,bit_array_one(p_item),bit_array_val(p_state));
|
||||
}
|
||||
|
||||
void playlist_manager::activeplaylist_set_selection_single(t_size p_item,bool p_state)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) playlist_set_selection_single(playlist,p_item,p_state);
|
||||
}
|
||||
|
||||
t_size playlist_manager::playlist_get_selection_count(t_size p_playlist,t_size p_max)
|
||||
{
|
||||
enum_items_callback_count_selection callback(p_max);
|
||||
playlist_enum_items(p_playlist,callback,bit_array_true());
|
||||
return callback.get_count();
|
||||
}
|
||||
|
||||
t_size playlist_manager::activeplaylist_get_selection_count(t_size p_max)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) return playlist_get_selection_count(playlist,p_max);
|
||||
else return 0;
|
||||
}
|
||||
|
||||
bool playlist_manager::playlist_get_focus_item_handle(metadb_handle_ptr & p_out,t_size p_playlist)
|
||||
{
|
||||
t_size index = playlist_get_focus_item(p_playlist);
|
||||
if (index == infinite) return false;
|
||||
return playlist_get_item_handle(p_out,p_playlist,index);
|
||||
}
|
||||
|
||||
bool playlist_manager::activeplaylist_get_focus_item_handle(metadb_handle_ptr & p_out)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) return playlist_get_focus_item_handle(p_out,playlist);
|
||||
else return false;
|
||||
}
|
||||
|
||||
t_size playlist_manager::find_playlist(const char * p_name,t_size p_name_length)
|
||||
{
|
||||
t_size n, m = get_playlist_count();
|
||||
pfc::string_formatter temp;
|
||||
for(n=0;n<m;n++) {
|
||||
if (!playlist_get_name(n,temp)) break;
|
||||
if (stricmp_utf8_ex(temp,temp.length(),p_name,p_name_length) == 0) return n;
|
||||
}
|
||||
return infinite;
|
||||
}
|
||||
|
||||
t_size playlist_manager::find_or_create_playlist_unlocked(const char * p_name, t_size p_name_length) {
|
||||
t_size n, m = get_playlist_count();
|
||||
pfc::string_formatter temp;
|
||||
for(n=0;n<m;n++) {
|
||||
if (!playlist_lock_is_present(n) && playlist_get_name(n,temp)) {
|
||||
if (stricmp_utf8_ex(temp,~0,p_name,p_name_length) == 0) return n;
|
||||
}
|
||||
}
|
||||
return create_playlist(p_name,p_name_length,infinite);
|
||||
}
|
||||
t_size playlist_manager::find_or_create_playlist(const char * p_name,t_size p_name_length)
|
||||
{
|
||||
t_size index = find_playlist(p_name,p_name_length);
|
||||
if (index != infinite) return index;
|
||||
return create_playlist(p_name,p_name_length,infinite);
|
||||
}
|
||||
|
||||
t_size playlist_manager::create_playlist_autoname(t_size p_index) {
|
||||
static const char new_playlist_text[] = "New Playlist";
|
||||
if (find_playlist(new_playlist_text,infinite) == infinite) return create_playlist(new_playlist_text,infinite,p_index);
|
||||
for(t_size walk = 2; ; walk++) {
|
||||
pfc::string_fixed_t<64> namebuffer;
|
||||
namebuffer << new_playlist_text << " (" << walk << ")";
|
||||
if (find_playlist(namebuffer,infinite) == infinite) return create_playlist(namebuffer,infinite,p_index);
|
||||
}
|
||||
}
|
||||
|
||||
bool playlist_manager::activeplaylist_sort_by_format(const char * spec,bool p_sel_only)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) return playlist_sort_by_format(playlist,spec,p_sel_only);
|
||||
else return false;
|
||||
}
|
||||
|
||||
bool playlist_manager::highlight_playing_item()
|
||||
{
|
||||
t_size playlist,item;
|
||||
if (!get_playing_item_location(&playlist,&item)) return false;
|
||||
set_active_playlist(playlist);
|
||||
playlist_set_focus_item(playlist,item);
|
||||
playlist_set_selection(playlist,bit_array_true(),bit_array_one(item));
|
||||
playlist_ensure_visible(playlist,item);
|
||||
return true;
|
||||
}
|
||||
|
||||
void playlist_manager::playlist_get_items(t_size p_playlist,pfc::list_base_t<metadb_handle_ptr> & out,const bit_array & p_mask)
|
||||
{
|
||||
playlist_enum_items(p_playlist,enum_items_callback_retrieve_all_items(out),p_mask);
|
||||
}
|
||||
|
||||
void playlist_manager::activeplaylist_get_items(pfc::list_base_t<metadb_handle_ptr> & out,const bit_array & p_mask)
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist != infinite) playlist_get_items(playlist,out,p_mask);
|
||||
}
|
||||
|
||||
void playlist_manager::active_playlist_fix()
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist == infinite)
|
||||
{
|
||||
t_size max = get_playlist_count();
|
||||
if (max == 0)
|
||||
{
|
||||
create_playlist_autoname();
|
||||
}
|
||||
set_active_playlist(0);
|
||||
}
|
||||
}
|
||||
|
||||
namespace {
|
||||
class enum_items_callback_remove_list : public playlist_manager::enum_items_callback
|
||||
{
|
||||
const metadb_handle_list & m_data;
|
||||
bit_array_var & m_table;
|
||||
t_size m_found;
|
||||
public:
|
||||
enum_items_callback_remove_list(const metadb_handle_list & p_data,bit_array_var & p_table) : m_data(p_data), m_table(p_table), m_found(0) {}
|
||||
bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected)
|
||||
{
|
||||
bool found = m_data.bsearch_by_pointer(p_location) != infinite;
|
||||
m_table.set(p_index,found);
|
||||
if (found) m_found++;
|
||||
return true;
|
||||
}
|
||||
|
||||
inline t_size get_found() const {return m_found;}
|
||||
};
|
||||
}
|
||||
|
||||
void playlist_manager::remove_items_from_all_playlists(const pfc::list_base_const_t<metadb_handle_ptr> & p_data)
|
||||
{
|
||||
t_size playlist_num, playlist_max = get_playlist_count();
|
||||
if (playlist_max != infinite)
|
||||
{
|
||||
metadb_handle_list temp;
|
||||
temp.add_items(p_data);
|
||||
temp.sort_by_pointer();
|
||||
for(playlist_num = 0; playlist_num < playlist_max; playlist_num++ )
|
||||
{
|
||||
t_size playlist_item_count = playlist_get_item_count(playlist_num);
|
||||
if (playlist_item_count == infinite) break;
|
||||
bit_array_bittable table(playlist_item_count);
|
||||
enum_items_callback_remove_list callback(temp,table);
|
||||
playlist_enum_items(playlist_num,callback,bit_array_true());
|
||||
if (callback.get_found()>0)
|
||||
playlist_remove_items(playlist_num,table);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool playlist_manager::get_all_items(pfc::list_base_t<metadb_handle_ptr> & out)
|
||||
{
|
||||
t_size n, m = get_playlist_count();
|
||||
if (m == infinite) return false;
|
||||
enum_items_callback_retrieve_all_items callback(out);
|
||||
for(n=0;n<m;n++)
|
||||
{
|
||||
playlist_enum_items(n,callback,bit_array_true());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
t_uint32 playlist_manager::activeplaylist_lock_get_filter_mask()
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist == infinite) return ~0;
|
||||
else return playlist_lock_get_filter_mask(playlist);
|
||||
}
|
||||
|
||||
bool playlist_manager::activeplaylist_is_undo_available()
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist == infinite) return false;
|
||||
else return playlist_is_undo_available(playlist);
|
||||
}
|
||||
|
||||
bool playlist_manager::activeplaylist_is_redo_available()
|
||||
{
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist == infinite) return false;
|
||||
else return playlist_is_redo_available(playlist);
|
||||
}
|
||||
|
||||
bool playlist_manager::remove_playlist_switch(t_size idx)
|
||||
{
|
||||
bool need_switch = get_active_playlist() == idx;
|
||||
if (remove_playlist(idx))
|
||||
{
|
||||
if (need_switch)
|
||||
{
|
||||
t_size total = get_playlist_count();
|
||||
if (total > 0)
|
||||
{
|
||||
if (idx >= total) idx = total-1;
|
||||
set_active_playlist(idx);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
else return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool t_playback_queue_item::operator==(const t_playback_queue_item & p_item) const
|
||||
{
|
||||
return m_handle == p_item.m_handle && m_playlist == p_item.m_playlist && m_item == p_item.m_item;
|
||||
}
|
||||
|
||||
bool t_playback_queue_item::operator!=(const t_playback_queue_item & p_item) const
|
||||
{
|
||||
return m_handle != p_item.m_handle || m_playlist != p_item.m_playlist || m_item != p_item.m_item;
|
||||
}
|
||||
|
||||
|
||||
|
||||
bool playlist_manager::activeplaylist_execute_default_action(t_size p_item) {
|
||||
t_size idx = get_active_playlist();
|
||||
if (idx == infinite) return false;
|
||||
else return playlist_execute_default_action(idx,p_item);
|
||||
}
|
||||
|
||||
namespace {
|
||||
class completion_notify_dfd : public completion_notify {
|
||||
public:
|
||||
completion_notify_dfd(const pfc::list_base_const_t<metadb_handle_ptr> & p_data,service_ptr_t<process_locations_notify> p_notify) : m_data(p_data), m_notify(p_notify) {}
|
||||
void on_completion(unsigned p_code) {
|
||||
switch(p_code) {
|
||||
case metadb_io::load_info_aborted:
|
||||
m_notify->on_aborted();
|
||||
break;
|
||||
default:
|
||||
m_notify->on_completion(m_data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
private:
|
||||
metadb_handle_list m_data;
|
||||
service_ptr_t<process_locations_notify> m_notify;
|
||||
};
|
||||
};
|
||||
|
||||
void dropped_files_data_impl::to_handles_async_ex(t_uint32 p_op_flags,HWND p_parentwnd,service_ptr_t<process_locations_notify> p_notify) {
|
||||
if (m_is_paths) {
|
||||
static_api_ptr_t<playlist_incoming_item_filter_v2>()->process_locations_async(
|
||||
m_paths,
|
||||
p_op_flags,
|
||||
NULL,
|
||||
NULL,
|
||||
p_parentwnd,
|
||||
p_notify);
|
||||
} else {
|
||||
t_uint32 flags = 0;
|
||||
if (p_op_flags & playlist_incoming_item_filter_v2::op_flag_background) flags |= metadb_io_v2::op_flag_background;
|
||||
if (p_op_flags & playlist_incoming_item_filter_v2::op_flag_delay_ui) flags |= metadb_io_v2::op_flag_delay_ui;
|
||||
static_api_ptr_t<metadb_io_v2>()->load_info_async(m_handles,metadb_io::load_info_default,p_parentwnd,flags,new service_impl_t<completion_notify_dfd>(m_handles,p_notify));
|
||||
}
|
||||
}
|
||||
void dropped_files_data_impl::to_handles_async(bool p_filter,HWND p_parentwnd,service_ptr_t<process_locations_notify> p_notify) {
|
||||
to_handles_async_ex(p_filter ? 0 : playlist_incoming_item_filter_v2::op_flag_no_filter,p_parentwnd,p_notify);
|
||||
}
|
||||
|
||||
bool dropped_files_data_impl::to_handles(pfc::list_base_t<metadb_handle_ptr> & p_out,bool p_filter,HWND p_parentwnd) {
|
||||
if (m_is_paths) {
|
||||
return static_api_ptr_t<playlist_incoming_item_filter>()->process_locations(m_paths,p_out,p_filter,NULL,NULL,p_parentwnd);
|
||||
} else {
|
||||
if (static_api_ptr_t<metadb_io>()->load_info_multi(m_handles,metadb_io::load_info_default,p_parentwnd,true) == metadb_io::load_info_aborted) return false;
|
||||
p_out = m_handles;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
void playlist_manager::playlist_activate_delta(int p_delta) {
|
||||
const t_size total = get_playlist_count();
|
||||
if (total > 0) {
|
||||
t_size active = get_active_playlist();
|
||||
|
||||
//clip p_delta to -(total-1)...(total-1) range
|
||||
if (p_delta < 0) {
|
||||
p_delta = - ( (-p_delta) % (t_ssize)total );
|
||||
} else {
|
||||
p_delta = p_delta % total;
|
||||
}
|
||||
if (p_delta != 0) {
|
||||
if (active == infinite) {
|
||||
//special case when no playlist is active
|
||||
if (p_delta > 0) {
|
||||
active = (t_size)(p_delta - 1);
|
||||
} else {
|
||||
active = (total + p_delta);//p_delta is negative
|
||||
}
|
||||
} else {
|
||||
active = (t_size) (active + total + p_delta) % total;
|
||||
}
|
||||
set_active_playlist(active % total);
|
||||
}
|
||||
}
|
||||
}
|
||||
namespace {
|
||||
class enum_items_callback_get_selected_count : public playlist_manager::enum_items_callback {
|
||||
public:
|
||||
enum_items_callback_get_selected_count() : m_found() {}
|
||||
t_size get_count() const {return m_found;}
|
||||
bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) {
|
||||
if (b_selected) m_found++;
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
t_size m_found;
|
||||
};
|
||||
}
|
||||
t_size playlist_manager::playlist_get_selected_count(t_size p_playlist,bit_array const & p_mask) {
|
||||
enum_items_callback_get_selected_count callback;
|
||||
playlist_enum_items(p_playlist,callback,p_mask);
|
||||
return callback.get_count();
|
||||
}
|
||||
|
||||
namespace {
|
||||
class enum_items_callback_find_item : public playlist_manager::enum_items_callback {
|
||||
public:
|
||||
enum_items_callback_find_item(metadb_handle_ptr p_lookingFor) : m_result(infinite), m_lookingFor(p_lookingFor) {}
|
||||
t_size result() const {return m_result;}
|
||||
bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) {
|
||||
if (p_location == m_lookingFor) {
|
||||
m_result = p_index;
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
private:
|
||||
metadb_handle_ptr m_lookingFor;
|
||||
t_size m_result;
|
||||
};
|
||||
class enum_items_callback_find_item_selected : public playlist_manager::enum_items_callback {
|
||||
public:
|
||||
enum_items_callback_find_item_selected(metadb_handle_ptr p_lookingFor) : m_result(infinite), m_lookingFor(p_lookingFor) {}
|
||||
t_size result() const {return m_result;}
|
||||
bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) {
|
||||
if (b_selected && p_location == m_lookingFor) {
|
||||
m_result = p_index;
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
private:
|
||||
metadb_handle_ptr m_lookingFor;
|
||||
t_size m_result;
|
||||
};
|
||||
}
|
||||
|
||||
bool playlist_manager::playlist_find_item(t_size p_playlist,metadb_handle_ptr p_item,t_size & p_result) {
|
||||
enum_items_callback_find_item callback(p_item);
|
||||
playlist_enum_items(p_playlist,callback,bit_array_true());
|
||||
t_size result = callback.result();
|
||||
if (result == infinite) return false;
|
||||
p_result = result;
|
||||
return true;
|
||||
}
|
||||
bool playlist_manager::playlist_find_item_selected(t_size p_playlist,metadb_handle_ptr p_item,t_size & p_result) {
|
||||
enum_items_callback_find_item_selected callback(p_item);
|
||||
playlist_enum_items(p_playlist,callback,bit_array_true());
|
||||
t_size result = callback.result();
|
||||
if (result == infinite) return false;
|
||||
p_result = result;
|
||||
return true;
|
||||
}
|
||||
t_size playlist_manager::playlist_set_focus_by_handle(t_size p_playlist,metadb_handle_ptr p_item) {
|
||||
t_size index;
|
||||
if (!playlist_find_item(p_playlist,p_item,index)) index = infinite;
|
||||
playlist_set_focus_item(p_playlist,index);
|
||||
return index;
|
||||
}
|
||||
bool playlist_manager::activeplaylist_find_item(metadb_handle_ptr p_item,t_size & p_result) {
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist == infinite) return false;
|
||||
return playlist_find_item(playlist,p_item,p_result);
|
||||
}
|
||||
t_size playlist_manager::activeplaylist_set_focus_by_handle(metadb_handle_ptr p_item) {
|
||||
t_size playlist = get_active_playlist();
|
||||
if (playlist == infinite) return infinite;
|
||||
return playlist_set_focus_by_handle(playlist,p_item);
|
||||
}
|
||||
|
||||
pfc::com_ptr_t<interface IDataObject> playlist_incoming_item_filter::create_dataobject_ex(metadb_handle_list_cref data) {
|
||||
pfc::com_ptr_t<interface IDataObject> temp; temp.attach( create_dataobject(data) ); PFC_ASSERT( temp.is_valid() ); return temp;
|
||||
}
|
||||
|
||||
void playlist_manager_v3::recycler_restore_by_id(t_uint32 id) {
|
||||
t_size which = recycler_find_by_id(id);
|
||||
if (which != ~0) recycler_restore(which);
|
||||
}
|
||||
|
||||
t_size playlist_manager_v3::recycler_find_by_id(t_uint32 id) {
|
||||
const t_size total = recycler_get_count();
|
||||
for(t_size walk = 0; walk < total; ++walk) {
|
||||
if (id == recycler_get_id(walk)) return walk;
|
||||
}
|
||||
return ~0;
|
||||
}
|
|
@ -0,0 +1,873 @@
|
|||
//! This interface allows filtering of playlist modification operations.\n
|
||||
//! Implemented by components "locking" playlists; use playlist_manager::playlist_lock_install() etc to takeover specific playlist with your instance of playlist_lock.
|
||||
class NOVTABLE playlist_lock : public service_base {
|
||||
public:
|
||||
enum {
|
||||
filter_add = 1 << 0,
|
||||
filter_remove = 1 << 1,
|
||||
filter_reorder = 1 << 2,
|
||||
filter_replace = 1 << 3,
|
||||
filter_rename = 1 << 4,
|
||||
filter_remove_playlist = 1 << 5,
|
||||
filter_default_action = 1 << 6,
|
||||
};
|
||||
|
||||
//! Queries whether specified item insertiion operation is allowed in the locked playlist.
|
||||
//! @param p_base Index from which the items are being inserted.
|
||||
//! @param p_data Items being inserted.
|
||||
//! @param p_selection Caller-requested selection state of items being inserted.
|
||||
//! @returns True to allow the operation, false to block it.
|
||||
virtual bool query_items_add(t_size p_base, const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const bit_array & p_selection) = 0;
|
||||
//! Queries whether specified item reorder operation is allowed in the locked playlist.
|
||||
//! @param p_order Pointer to array containing permutation defining requested reorder operation.
|
||||
//! @param p_count Number of items in array pointed to by p_order. This should always be equal to number of items on the locked playlist.
|
||||
//! @returns True to allow the operation, false to block it.
|
||||
virtual bool query_items_reorder(const t_size * p_order,t_size p_count) = 0;
|
||||
//! Queries whether specified item removal operation is allowed in the locked playlist.
|
||||
//! @param p_mask Specifies which items from locked playlist are being removed.
|
||||
//! @param p_force If set to true, the call is made only for notification purpose and items are getting removed regardless (after e.g. they have been physically removed).
|
||||
//! @returns True to allow the operation, false to block it. Note that return value is ignored if p_force is set to true.
|
||||
virtual bool query_items_remove(const bit_array & p_mask,bool p_force) = 0;
|
||||
//! Queries whether specified item replacement operation is allowed in the locked playlist.
|
||||
//! @param p_index Index of the item being replaced.
|
||||
//! @param p_old Old value of the item being replaced.
|
||||
//! @param p_new New value of the item being replaced.
|
||||
//! @returns True to allow the operation, false to block it.
|
||||
virtual bool query_item_replace(t_size p_index,const metadb_handle_ptr & p_old,const metadb_handle_ptr & p_new)=0;
|
||||
//! Queries whether renaming the locked playlist is allowed.
|
||||
//! @param p_new_name Requested new name of the playlist; a UTF-8 encoded string.
|
||||
//! @param p_new_name_len Length limit of the name string, in bytes (actual string may be shorter if null terminator is encountered before). Set this to infinite to use plain null-terminated strings.
|
||||
//! @returns True to allow the operation, false to block it.
|
||||
virtual bool query_playlist_rename(const char * p_new_name,t_size p_new_name_len) = 0;
|
||||
//! Queries whether removal of the locked playlist is allowed. Note that the lock will be released when the playlist is removed.
|
||||
//! @returns True to allow the operation, false to block it.
|
||||
virtual bool query_playlist_remove() = 0;
|
||||
//! Executes "default action" (doubleclick etc) for specified playlist item. When the playlist is not locked, default action starts playback of the item.
|
||||
//! @returns True if custom default action was executed, false to fall-through to default one for non-locked playlists (start playback).
|
||||
virtual bool execute_default_action(t_size p_item) = 0;
|
||||
//! Notifies lock about changed index of the playlist, in result of user reordering playlists or removing other playlists.
|
||||
virtual void on_playlist_index_change(t_size p_new_index) = 0;
|
||||
//! Notifies lock about the locked playlist getting removed.
|
||||
virtual void on_playlist_remove() = 0;
|
||||
//! Retrieves human-readable name of playlist lock to display.
|
||||
virtual void get_lock_name(pfc::string_base & p_out) = 0;
|
||||
//! Requests user interface of component controlling the playlist lock to be shown.
|
||||
virtual void show_ui() = 0;
|
||||
//! Queries which actions the lock filters. The return value must not change while the lock is registered with playlist_manager. The return value is a combination of one or more filter_* constants.
|
||||
virtual t_uint32 get_filter_mask() = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(playlist_lock,service_base);
|
||||
};
|
||||
|
||||
struct t_playback_queue_item {
|
||||
metadb_handle_ptr m_handle;
|
||||
t_size m_playlist,m_item;
|
||||
|
||||
bool operator==(const t_playback_queue_item & p_item) const;
|
||||
bool operator!=(const t_playback_queue_item & p_item) const;
|
||||
};
|
||||
|
||||
|
||||
//! This service provides methods for all sorts of playlist interaction.\n
|
||||
//! All playlist_manager methods are valid only from main app thread.\n
|
||||
//! Usage: static_api_ptr_t<playlist_manager>.
|
||||
class NOVTABLE playlist_manager : public service_base
|
||||
{
|
||||
public:
|
||||
|
||||
//! Callback interface for playlist enumeration methods.
|
||||
class NOVTABLE enum_items_callback {
|
||||
public:
|
||||
//! @returns True to continue enumeration, false to abort.
|
||||
virtual bool on_item(t_size p_index,const metadb_handle_ptr & p_location,bool b_selected) = 0;//return false to stop
|
||||
};
|
||||
|
||||
//! Retrieves number of playlists.
|
||||
virtual t_size get_playlist_count() = 0;
|
||||
//! Retrieves index of active playlist; infinite if no playlist is active.
|
||||
virtual t_size get_active_playlist() = 0;
|
||||
//! Sets active playlist (infinite to set no active playlist).
|
||||
virtual void set_active_playlist(t_size p_index) = 0;
|
||||
//! Retrieves playlist from which items to be played are taken from.
|
||||
virtual t_size get_playing_playlist() = 0;
|
||||
//! Sets playlist from which items to be played are taken from.
|
||||
virtual void set_playing_playlist(t_size p_index) = 0;
|
||||
//! Removes playlists according to specified mask. See also: bit_array.
|
||||
virtual bool remove_playlists(const bit_array & p_mask) = 0;
|
||||
//! Creates a new playlist.
|
||||
//! @param p_name Name of playlist to create; a UTF-8 encoded string.
|
||||
//! @param p_name_length Length limit of playlist name string, in bytes (actual string may be shorter if null terminator is encountered before). Set this to infinite to use plain null-terminated strings.
|
||||
//! @param p_index Index at which to insert new playlist; set to infinite to put it at the end of playlist list.
|
||||
//! @returns Actual index of newly inserted playlist, infinite on failure (call from invalid context).
|
||||
virtual t_size create_playlist(const char * p_name,t_size p_name_length,t_size p_index) = 0;
|
||||
//! Reorders the playlist list according to specified permutation.
|
||||
//! @returns True on success, false on failure (call from invalid context).
|
||||
virtual bool reorder(const t_size * p_order,t_size p_count) = 0;
|
||||
|
||||
|
||||
//! Retrieves number of items on specified playlist.
|
||||
virtual t_size playlist_get_item_count(t_size p_playlist) = 0;
|
||||
//! Enumerates contents of specified playlist.
|
||||
virtual void playlist_enum_items(t_size p_playlist,enum_items_callback & p_callback,const bit_array & p_mask) = 0;
|
||||
//! Retrieves index of focus item on specified playlist; returns infinite when no item has focus.
|
||||
virtual t_size playlist_get_focus_item(t_size p_playlist) = 0;
|
||||
//! Retrieves name of specified playlist. Should never fail unless the parameters are invalid.
|
||||
virtual bool playlist_get_name(t_size p_playlist,pfc::string_base & p_out) = 0;
|
||||
|
||||
//! Reorders items in specified playlist according to specified permutation.
|
||||
virtual bool playlist_reorder_items(t_size p_playlist,const t_size * p_order,t_size p_count) = 0;
|
||||
//! Selects/deselects items on specified playlist.
|
||||
//! @param p_playlist Index of playlist to alter.
|
||||
//! @param p_affected Mask of items to alter.
|
||||
//! @param p_status Mask of selected/deselected state to apply to items specified by p_affected.
|
||||
virtual void playlist_set_selection(t_size p_playlist,const bit_array & p_affected,const bit_array & p_status) = 0;
|
||||
//! Removes specified items from specified playlist. Returns true on success or false on failure (playlist locked).
|
||||
virtual bool playlist_remove_items(t_size p_playlist,const bit_array & mask)=0;
|
||||
//! Replaces specified item on specified playlist. Returns true on success or false on failure (playlist locked).
|
||||
virtual bool playlist_replace_item(t_size p_playlist,t_size p_item,const metadb_handle_ptr & p_new_item) = 0;
|
||||
//! Sets index of focus item on specified playlist; use infinite to set no focus item.
|
||||
virtual void playlist_set_focus_item(t_size p_playlist,t_size p_item) = 0;
|
||||
//! Inserts new items into specified playlist, at specified position.
|
||||
virtual t_size playlist_insert_items(t_size p_playlist,t_size p_base,const pfc::list_base_const_t<metadb_handle_ptr> & data,const bit_array & p_selection) = 0;
|
||||
//! Tells playlist renderers to make sure that specified item is visible.
|
||||
virtual void playlist_ensure_visible(t_size p_playlist,t_size p_item) = 0;
|
||||
//! Renames specified playlist.
|
||||
//! @param p_name New name of playlist; a UTF-8 encoded string.
|
||||
//! @param p_name_length Length limit of playlist name string, in bytes (actual string may be shorter if null terminator is encountered before). Set this to infinite to use plain null-terminated strings.
|
||||
//! @returns True on success, false on failure (playlist locked).
|
||||
virtual bool playlist_rename(t_size p_index,const char * p_name,t_size p_name_length) = 0;
|
||||
|
||||
|
||||
//! Creates an undo restore point for specified playlist.
|
||||
virtual void playlist_undo_backup(t_size p_playlist) = 0;
|
||||
//! Reverts specified playlist to last undo restore point and generates a redo restore point.
|
||||
//! @returns True on success, false on failure (playlist locked or no restore point available).
|
||||
virtual bool playlist_undo_restore(t_size p_playlist) = 0;
|
||||
//! Reverts specified playlist to next redo restore point and generates an undo restore point.
|
||||
//! @returns True on success, false on failure (playlist locked or no restore point available).
|
||||
virtual bool playlist_redo_restore(t_size p_playlist) = 0;
|
||||
//! Returns whether an undo restore point is available for specified playlist.
|
||||
virtual bool playlist_is_undo_available(t_size p_playlist) = 0;
|
||||
//! Returns whether a redo restore point is available for specified playlist.
|
||||
virtual bool playlist_is_redo_available(t_size p_playlist) = 0;
|
||||
|
||||
//! Renders information about specified playlist item, using specified titleformatting script parameters.
|
||||
//! @param p_playlist Index of playlist containing item being processed.
|
||||
//! @param p_item Index of item being processed in the playlist containing it.
|
||||
//! @param p_hook Titleformatting script hook to use; see titleformat_hook documentation for more info. Set to NULL when hook functionality is not needed.
|
||||
//! @param p_out String object receiving results.
|
||||
//! @param p_script Compiled titleformatting script to use; see titleformat_object cocumentation for more info.
|
||||
//! @param p_filter Text filter to use; see titleformat_text_filter documentation for more info. Set to NULL when text filter functionality is not needed.
|
||||
//! @param p_playback_info_level Level of playback related information requested. See playback_control::t_display_level documentation for more info.
|
||||
virtual void playlist_item_format_title(t_size p_playlist,t_size p_item,titleformat_hook * p_hook,pfc::string_base & p_out,const service_ptr_t<titleformat_object> & p_script,titleformat_text_filter * p_filter,playback_control::t_display_level p_playback_info_level)=0;
|
||||
|
||||
|
||||
//! Retrieves playlist position of currently playing item.
|
||||
//! @param p_playlist Receives index of playlist containing currently playing item on success.
|
||||
//! @param p_index Receives index of currently playing item in the playlist that contains it on success.
|
||||
//! @returns True on success, false on failure (not playing or currently played item has been removed from the playlist it was on when starting).
|
||||
virtual bool get_playing_item_location(t_size * p_playlist,t_size * p_index) = 0;
|
||||
|
||||
//! Sorts specified playlist - entire playlist or selection only - by specified title formatting pattern, or randomizes the order.
|
||||
//! @param p_playlist Index of playlist to alter.
|
||||
//! @param p_pattern Title formatting pattern to sort by (an UTF-8 encoded null-termindated string). Set to NULL to randomize the order of items.
|
||||
//! @param p_sel_only Set to false to sort/randomize whole playlist, or to true to sort/randomize only selection on the playlist.
|
||||
//! @returns True on success, false on failure (playlist locked etc).
|
||||
virtual bool playlist_sort_by_format(t_size p_playlist,const char * p_pattern,bool p_sel_only) = 0;
|
||||
|
||||
//! For internal use only; p_items must be sorted by metadb::path_compare; use file_operation_callback static methods instead of calling this directly.
|
||||
virtual void on_files_deleted_sorted(const pfc::list_base_const_t<const char *> & p_items) = 0;
|
||||
//! For internal use only; p_from must be sorted by metadb::path_compare; use file_operation_callback static methods instead of calling this directly.
|
||||
virtual void on_files_moved_sorted(const pfc::list_base_const_t<const char *> & p_from,const pfc::list_base_const_t<const char *> & p_to) = 0;
|
||||
|
||||
virtual bool playlist_lock_install(t_size p_playlist,const service_ptr_t<playlist_lock> & p_lock) = 0;//returns false when invalid playlist or already locked
|
||||
virtual bool playlist_lock_uninstall(t_size p_playlist,const service_ptr_t<playlist_lock> & p_lock) = 0;
|
||||
virtual bool playlist_lock_is_present(t_size p_playlist) = 0;
|
||||
virtual bool playlist_lock_query_name(t_size p_playlist,pfc::string_base & p_out) = 0;
|
||||
virtual bool playlist_lock_show_ui(t_size p_playlist) = 0;
|
||||
virtual t_uint32 playlist_lock_get_filter_mask(t_size p_playlist) = 0;
|
||||
|
||||
|
||||
//! Retrieves number of available playback order modes.
|
||||
virtual t_size playback_order_get_count() = 0;
|
||||
//! Retrieves name of specified playback order move.
|
||||
//! @param p_index Index of playback order mode to query, from 0 to playback_order_get_count() return value - 1.
|
||||
//! @returns Null-terminated UTF-8 encoded string containing name of the playback order mode. Returned pointer points to statically allocated string and can be safely stored without having to free it later.
|
||||
virtual const char * playback_order_get_name(t_size p_index) = 0;
|
||||
//! Retrieves GUID of specified playback order mode. Used for managing playback modes without relying on names.
|
||||
//! @param p_index Index of playback order mode to query, from 0 to playback_order_get_count() return value - 1.
|
||||
virtual GUID playback_order_get_guid(t_size p_index) = 0;
|
||||
//! Retrieves index of active playback order mode.
|
||||
virtual t_size playback_order_get_active() = 0;
|
||||
//! Sets index of active playback order mode.
|
||||
virtual void playback_order_set_active(t_size p_index) = 0;
|
||||
|
||||
virtual void queue_remove_mask(bit_array const & p_mask) = 0;
|
||||
virtual void queue_add_item_playlist(t_size p_playlist,t_size p_item) = 0;
|
||||
virtual void queue_add_item(metadb_handle_ptr p_item) = 0;
|
||||
virtual t_size queue_get_count() = 0;
|
||||
virtual void queue_get_contents(pfc::list_base_t<t_playback_queue_item> & p_out) = 0;
|
||||
//! Returns index (0-based) on success, infinite on failure (item not in queue).
|
||||
virtual t_size queue_find_index(t_playback_queue_item const & p_item) = 0;
|
||||
|
||||
//! Registers a playlist callback; registered object receives notifications about any modifications of any of loaded playlists.
|
||||
//! @param p_callback Callback interface to register.
|
||||
//! @param p_flags Flags indicating which callback methods are requested. See playlist_callback::flag_* constants for more info. The main purpose of flags parameter is working set optimization by not calling methods that do nothing.
|
||||
virtual void register_callback(class playlist_callback * p_callback,unsigned p_flags) = 0;
|
||||
//! Registers a playlist callback; registered object receives notifications about any modifications of active playlist.
|
||||
//! @param p_callback Callback interface to register.
|
||||
//! @param p_flags Flags indicating which callback methods are requested. See playlist_callback_single::flag_* constants for more info. The main purpose of flags parameter is working set optimization by not calling methods that do nothing.
|
||||
virtual void register_callback(class playlist_callback_single * p_callback,unsigned p_flags) = 0;
|
||||
//! Unregisters a playlist callback (playlist_callback version).
|
||||
virtual void unregister_callback(class playlist_callback * p_callback) = 0;
|
||||
//! Unregisters a playlist callback (playlist_callback_single version).
|
||||
virtual void unregister_callback(class playlist_callback_single * p_callback) = 0;
|
||||
//! Modifies flags indicating which calback methods are requested (playlist_callback version).
|
||||
virtual void modify_callback(class playlist_callback * p_callback,unsigned p_flags) = 0;
|
||||
//! Modifies flags indicating which calback methods are requested (playlist_callback_single version).
|
||||
virtual void modify_callback(class playlist_callback_single * p_callback,unsigned p_flags) = 0;
|
||||
|
||||
//! Executes default doubleclick/enter action for specified item on specified playlist (starts playing the item unless overridden by a lock to do something else).
|
||||
virtual bool playlist_execute_default_action(t_size p_playlist,t_size p_item) = 0;
|
||||
|
||||
|
||||
//! Helper; removes all items from the playback queue.
|
||||
void queue_flush() {queue_remove_mask(bit_array_true());}
|
||||
//! Helper; returns whether there are items in the playback queue.
|
||||
bool queue_is_active() {return queue_get_count() > 0;}
|
||||
|
||||
//! Helper; highlights currently playing item; returns true on success or false on failure (not playing or currently played item has been removed from playlist since playback started).
|
||||
bool highlight_playing_item();
|
||||
//! Helper; removes single playlist of specified index.
|
||||
bool remove_playlist(t_size p_playlist);
|
||||
//! Helper; removes single playlist of specified index, and switches to another playlist when possible.
|
||||
bool remove_playlist_switch(t_size p_playlist);
|
||||
|
||||
//! Helper; returns whether specified item on specified playlist is selected or not.
|
||||
bool playlist_is_item_selected(t_size p_playlist,t_size p_item);
|
||||
//! Helper; retrieves metadb_handle of the specified playlist item. Returns true on success, false on failure (invalid parameters).
|
||||
bool playlist_get_item_handle(metadb_handle_ptr & p_out,t_size p_playlist,t_size p_item);
|
||||
//! Helper; retrieves metadb_handle of the specified playlist item; throws pfc::exception_invalid_params() on failure.
|
||||
metadb_handle_ptr playlist_get_item_handle(t_size playlist, t_size item);
|
||||
|
||||
//! Moves selected items up/down in the playlist by specified offset.
|
||||
//! @param p_playlist Index of playlist to alter.
|
||||
//! @param p_delta Offset to move items by. Set it to a negative valuye to move up, or to a positive value to move down.
|
||||
//! @returns True on success, false on failure (e.g. playlist locked).
|
||||
bool playlist_move_selection(t_size p_playlist,int p_delta);
|
||||
//! Retrieves selection map of specific playlist, using bit_array_var interface.
|
||||
void playlist_get_selection_mask(t_size p_playlist,bit_array_var & out);
|
||||
void playlist_get_items(t_size p_playlist,pfc::list_base_t<metadb_handle_ptr> & out,const bit_array & p_mask);
|
||||
void playlist_get_all_items(t_size p_playlist,pfc::list_base_t<metadb_handle_ptr> & out);
|
||||
void playlist_get_selected_items(t_size p_playlist,pfc::list_base_t<metadb_handle_ptr> & out);
|
||||
|
||||
//! Clears contents of specified playlist (removes all items from it).
|
||||
void playlist_clear(t_size p_playlist);
|
||||
bool playlist_add_items(t_size playlist,const pfc::list_base_const_t<metadb_handle_ptr> & data,const bit_array & p_selection);
|
||||
void playlist_clear_selection(t_size p_playlist);
|
||||
void playlist_remove_selection(t_size p_playlist,bool p_crop = false);
|
||||
|
||||
|
||||
//! Changes contents of the specified playlist to the specified items, trying to reuse existing playlist content as much as possible (preserving selection/focus/etc). Order of items in playlist not guaranteed to be the same as in the specified item list.
|
||||
//! @returns true if the playlist has been altered, false if there was nothing to update.
|
||||
bool playlist_update_content(t_size playlist, metadb_handle_list_cref content, bool bUndoBackup);
|
||||
|
||||
//retrieving status
|
||||
t_size activeplaylist_get_item_count();
|
||||
void activeplaylist_enum_items(enum_items_callback & p_callback,const bit_array & p_mask);
|
||||
t_size activeplaylist_get_focus_item();//focus may be infinite if no item is focused
|
||||
bool activeplaylist_get_name(pfc::string_base & p_out);
|
||||
|
||||
//modifying playlist
|
||||
bool activeplaylist_reorder_items(const t_size * order,t_size count);
|
||||
void activeplaylist_set_selection(const bit_array & affected,const bit_array & status);
|
||||
bool activeplaylist_remove_items(const bit_array & mask);
|
||||
bool activeplaylist_replace_item(t_size p_item,const metadb_handle_ptr & p_new_item);
|
||||
void activeplaylist_set_focus_item(t_size p_item);
|
||||
t_size activeplaylist_insert_items(t_size p_base,const pfc::list_base_const_t<metadb_handle_ptr> & data,const bit_array & p_selection);
|
||||
void activeplaylist_ensure_visible(t_size p_item);
|
||||
bool activeplaylist_rename(const char * p_name,t_size p_name_len);
|
||||
|
||||
void activeplaylist_undo_backup();
|
||||
bool activeplaylist_undo_restore();
|
||||
bool activeplaylist_redo_restore();
|
||||
|
||||
bool activeplaylist_is_item_selected(t_size p_item);
|
||||
bool activeplaylist_get_item_handle(metadb_handle_ptr & item,t_size p_item);
|
||||
metadb_handle_ptr activeplaylist_get_item_handle(t_size p_item);
|
||||
void activeplaylist_move_selection(int p_delta);
|
||||
void activeplaylist_get_selection_mask(bit_array_var & out);
|
||||
void activeplaylist_get_items(pfc::list_base_t<metadb_handle_ptr> & out,const bit_array & p_mask);
|
||||
void activeplaylist_get_all_items(pfc::list_base_t<metadb_handle_ptr> & out);
|
||||
void activeplaylist_get_selected_items(pfc::list_base_t<metadb_handle_ptr> & out);
|
||||
void activeplaylist_clear();
|
||||
|
||||
bool activeplaylist_add_items(const pfc::list_base_const_t<metadb_handle_ptr> & data,const bit_array & p_selection);
|
||||
|
||||
bool playlist_insert_items_filter(t_size p_playlist,t_size p_base,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,bool p_select);
|
||||
bool activeplaylist_insert_items_filter(t_size p_base,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,bool p_select);
|
||||
|
||||
//! \deprecated (since 0.9.3) Use playlist_incoming_item_filter_v2::process_locations_async whenever possible
|
||||
bool playlist_insert_locations(t_size p_playlist,t_size p_base,const pfc::list_base_const_t<const char*> & p_urls,bool p_select,HWND p_parentwnd);
|
||||
//! \deprecated (since 0.9.3) Use playlist_incoming_item_filter_v2::process_locations_async whenever possible
|
||||
bool activeplaylist_insert_locations(t_size p_base,const pfc::list_base_const_t<const char*> & p_urls,bool p_select,HWND p_parentwnd);
|
||||
|
||||
bool playlist_add_items_filter(t_size p_playlist,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,bool p_select);
|
||||
bool activeplaylist_add_items_filter(const pfc::list_base_const_t<metadb_handle_ptr> & p_data,bool p_select);
|
||||
|
||||
bool playlist_add_locations(t_size p_playlist,const pfc::list_base_const_t<const char*> & p_urls,bool p_select,HWND p_parentwnd);
|
||||
bool activeplaylist_add_locations(const pfc::list_base_const_t<const char*> & p_urls,bool p_select,HWND p_parentwnd);
|
||||
|
||||
void reset_playing_playlist();
|
||||
|
||||
void activeplaylist_clear_selection();
|
||||
void activeplaylist_remove_selection(bool p_crop = false);
|
||||
|
||||
void activeplaylist_item_format_title(t_size p_item,titleformat_hook * p_hook,pfc::string_base & out,const service_ptr_t<titleformat_object> & p_script,titleformat_text_filter * p_filter,play_control::t_display_level p_playback_info_level);
|
||||
|
||||
void playlist_set_selection_single(t_size p_playlist,t_size p_item,bool p_state);
|
||||
void activeplaylist_set_selection_single(t_size p_item,bool p_state);
|
||||
|
||||
t_size playlist_get_selection_count(t_size p_playlist,t_size p_max);
|
||||
t_size activeplaylist_get_selection_count(t_size p_max);
|
||||
|
||||
bool playlist_get_focus_item_handle(metadb_handle_ptr & p_item,t_size p_playlist);
|
||||
bool activeplaylist_get_focus_item_handle(metadb_handle_ptr & item);
|
||||
|
||||
t_size find_playlist(const char * p_name,t_size p_name_length = ~0);
|
||||
t_size find_or_create_playlist(const char * p_name,t_size p_name_length = ~0);
|
||||
t_size find_or_create_playlist_unlocked(const char * p_name,t_size p_name_length = ~0);
|
||||
|
||||
t_size create_playlist_autoname(t_size p_index = infinite);
|
||||
|
||||
bool activeplaylist_sort_by_format(const char * spec,bool p_sel_only);
|
||||
|
||||
t_uint32 activeplaylist_lock_get_filter_mask();
|
||||
bool activeplaylist_is_undo_available();
|
||||
bool activeplaylist_is_redo_available();
|
||||
|
||||
bool activeplaylist_execute_default_action(t_size p_item);
|
||||
|
||||
void remove_items_from_all_playlists(const pfc::list_base_const_t<metadb_handle_ptr> & p_data);
|
||||
|
||||
void active_playlist_fix();
|
||||
|
||||
bool get_all_items(pfc::list_base_t<metadb_handle_ptr> & out);
|
||||
|
||||
void playlist_activate_delta(int p_delta);
|
||||
void playlist_activate_next() {playlist_activate_delta(1);}
|
||||
void playlist_activate_previous() {playlist_activate_delta(-1);}
|
||||
|
||||
|
||||
t_size playlist_get_selected_count(t_size p_playlist,bit_array const & p_mask);
|
||||
t_size activeplaylist_get_selected_count(bit_array const & p_mask) {return playlist_get_selected_count(get_active_playlist(),p_mask);}
|
||||
|
||||
bool playlist_find_item(t_size p_playlist,metadb_handle_ptr p_item,t_size & p_result);//inefficient, walks entire playlist
|
||||
bool playlist_find_item_selected(t_size p_playlist,metadb_handle_ptr p_item,t_size & p_result);//inefficient, walks entire playlist
|
||||
t_size playlist_set_focus_by_handle(t_size p_playlist,metadb_handle_ptr p_item);
|
||||
bool activeplaylist_find_item(metadb_handle_ptr p_item,t_size & p_result);//inefficient, walks entire playlist
|
||||
t_size activeplaylist_set_focus_by_handle(metadb_handle_ptr p_item);
|
||||
|
||||
static void g_make_selection_move_permutation(t_size * p_output,t_size p_count,const bit_array & p_selection,int p_delta);
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(playlist_manager);
|
||||
};
|
||||
|
||||
//! Extension of the playlist_manager service that manages playlist properties.
|
||||
//! Playlist properties come in two flavors: persistent and runtime.
|
||||
//! Persistent properties are blocks of binary that that will be preserved when the application is exited and restarted.
|
||||
//! Runtime properties are service pointers that will be lost when the application exits.
|
||||
//! \since 0.9.5
|
||||
class NOVTABLE playlist_manager_v2 : public playlist_manager {
|
||||
public:
|
||||
//! Write a persistent playlist property.
|
||||
//! \param p_playlist Index of the playlist
|
||||
//! \param p_property GUID that identifies the property
|
||||
//! \param p_stream stream that contains the data that will be associated with the property
|
||||
//! \param p_abort abort_callback that will be used when reading from p_stream
|
||||
virtual void playlist_set_property(t_size p_playlist,const GUID & p_property,stream_reader * p_stream,t_size p_size_hint,abort_callback & p_abort) = 0;
|
||||
//! Read a persistent playlist property.
|
||||
//! \param p_playlist Index of the playlist
|
||||
//! \param p_property GUID that identifies the property
|
||||
//! \param p_stream stream that will receive the stored data
|
||||
//! \param p_abort abort_callback that will be used when writing to p_stream
|
||||
//! \return true if the property exists, false otherwise
|
||||
virtual bool playlist_get_property(t_size p_playlist,const GUID & p_property,stream_writer * p_stream,abort_callback & p_abort) = 0;
|
||||
//! Test existence of a persistent playlist property.
|
||||
//! \param p_playlist Index of the playlist
|
||||
//! \param p_property GUID that identifies the property
|
||||
//! \return true if the property exists, false otherwise
|
||||
virtual bool playlist_have_property(t_size p_playlist,const GUID & p_property) = 0;
|
||||
//! Remove a persistent playlist property.
|
||||
//! \param p_playlist Index of the playlist
|
||||
//! \param p_property GUID that identifies the property
|
||||
//! \return true if the property existed, false otherwise
|
||||
virtual bool playlist_remove_property(t_size p_playlist,const GUID & p_property) = 0;
|
||||
|
||||
//! Write a runtime playlist property.
|
||||
//! \param p_playlist Index of the playlist
|
||||
//! \param p_property GUID that identifies the property
|
||||
//! \param p_data service pointer that will be associated with the property
|
||||
virtual void playlist_set_runtime_property(t_size p_playlist,const GUID & p_property,service_ptr_t<service_base> p_data) = 0;
|
||||
//! Read a runtime playlist property.
|
||||
//! \param p_playlist Index of the playlist
|
||||
//! \param p_property GUID that identifies the property
|
||||
//! \param p_data base service pointer reference that will receive the stored servive pointer
|
||||
//! \return true if the property exists, false otherwise
|
||||
virtual bool playlist_get_runtime_property(t_size p_playlist,const GUID & p_property,service_ptr_t<service_base> & p_data) = 0;
|
||||
//! Test existence of a runtime playlist property.
|
||||
//! \param p_playlist Index of the playlist
|
||||
//! \param p_property GUID that identifies the property
|
||||
//! \return true if the property exists, false otherwise
|
||||
virtual bool playlist_have_runtime_property(t_size p_playlist,const GUID & p_property) = 0;
|
||||
//! Remove a runtime playlist property.
|
||||
//! \param p_playlist Index of the playlist
|
||||
//! \param p_property GUID that identifies the property
|
||||
//! \return true if the property existed, false otherwise
|
||||
virtual bool playlist_remove_runtime_property(t_size p_playlist,const GUID & p_property) = 0;
|
||||
|
||||
//! Write a persistent playlist property.
|
||||
//! \param p_playlist Index of the playlist
|
||||
//! \param p_property GUID that identifies the property
|
||||
//! \param p_data array that contains the data that will be associated with the property
|
||||
template<typename t_array> void playlist_set_property(t_size p_playlist,const GUID & p_property,const t_array & p_data) {
|
||||
pfc::static_assert<sizeof(p_data[0]) == 1>();
|
||||
playlist_set_property(p_playlist,p_property,&stream_reader_memblock_ref(p_data),p_data.get_size(),abort_callback_impl());
|
||||
}
|
||||
//! Read a persistent playlist property.
|
||||
//! \param p_playlist Index of the playlist
|
||||
//! \param p_property GUID that identifies the property
|
||||
//! \param p_data array that will receive the stored data
|
||||
//! \return true if the property exists, false otherwise
|
||||
template<typename t_array> bool playlist_get_property(t_size p_playlist,const GUID & p_property,t_array & p_data) {
|
||||
pfc::static_assert<sizeof(p_data[0]) == 1>();
|
||||
typedef pfc::array_t<t_uint8,pfc::alloc_fast_aggressive> t_temp;
|
||||
t_temp temp;
|
||||
if (!playlist_get_property(p_playlist,p_property,&stream_writer_buffer_append_ref_t<t_temp>(temp),abort_callback_impl())) return false;
|
||||
p_data = temp;
|
||||
return true;
|
||||
}
|
||||
//! Read a runtime playlist property.
|
||||
//! \param p_playlist Index of the playlist
|
||||
//! \param p_property GUID that identifies the property
|
||||
//! \param p_data specific service pointer reference that will receive the stored servive pointer
|
||||
//! \return true if the property exists and can be converted to the type of p_data, false otherwise
|
||||
template<typename _t_interface> bool playlist_get_runtime_property(t_size p_playlist,const GUID & p_property,service_ptr_t<_t_interface> & p_data) {
|
||||
service_ptr_t<service_base> ptr;
|
||||
if (!playlist_get_runtime_property(p_playlist,p_property,ptr)) return false;
|
||||
return ptr->service_query_t(p_data);
|
||||
}
|
||||
|
||||
//! Write a persistent playlist property.
|
||||
//! \param p_playlist Index of the playlist
|
||||
//! \param p_property GUID that identifies the property
|
||||
//! \param p_value integer that will be associated with the property
|
||||
template<typename _t_int>
|
||||
void playlist_set_property_int(t_size p_playlist,const GUID & p_property,_t_int p_value) {
|
||||
pfc::array_t<t_uint8> temp; temp.set_size(sizeof(_t_int));
|
||||
pfc::encode_little_endian(temp.get_ptr(),p_value);
|
||||
playlist_set_property(p_playlist,p_property,temp);
|
||||
}
|
||||
//! Read a persistent playlist property.
|
||||
//! \param p_playlist Index of the playlist
|
||||
//! \param p_property GUID that identifies the property
|
||||
//! \param p_value integer reference that will receive the stored data
|
||||
//! \return true if the property exists and if the data is compatible with p_value, false otherwise
|
||||
template<typename _t_int>
|
||||
bool playlist_get_property_int(t_size p_playlist,const GUID & p_property,_t_int & p_value) {
|
||||
pfc::array_t<t_uint8> temp;
|
||||
if (!playlist_get_property(p_playlist,p_property,temp)) return false;
|
||||
if (temp.get_size() != sizeof(_t_int)) return false;
|
||||
pfc::decode_little_endian(p_value,temp.get_ptr());
|
||||
return true;
|
||||
}
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(playlist_manager_v2,playlist_manager)
|
||||
};
|
||||
|
||||
//! \since 0.9.5
|
||||
class NOVTABLE playlist_manager_v3 : public playlist_manager_v2 {
|
||||
FB2K_MAKE_SERVICE_INTERFACE(playlist_manager_v3,playlist_manager_v2)
|
||||
public:
|
||||
virtual t_size recycler_get_count() = 0;
|
||||
virtual void recycler_get_content(t_size which, metadb_handle_list_ref out) = 0;
|
||||
virtual void recycler_get_name(t_size which, pfc::string_base & out) = 0;
|
||||
virtual t_uint32 recycler_get_id(t_size which) = 0;
|
||||
virtual void recycler_purge(const bit_array & mask) = 0;
|
||||
virtual void recycler_restore(t_size which) = 0;
|
||||
|
||||
void recycler_restore_by_id(t_uint32 id);
|
||||
t_size recycler_find_by_id(t_uint32 id);
|
||||
};
|
||||
|
||||
//! \since 0.9.5.4
|
||||
class NOVTABLE playlist_manager_v4 : public playlist_manager_v3 {
|
||||
FB2K_MAKE_SERVICE_INTERFACE(playlist_manager_v4, playlist_manager_v3)
|
||||
public:
|
||||
virtual void playlist_get_sideinfo(t_size which, stream_writer * stream, abort_callback & abort) = 0;
|
||||
virtual t_size create_playlist_ex(const char * p_name,t_size p_name_length,t_size p_index, metadb_handle_list_cref content, stream_reader * sideInfo, abort_callback & abort) = 0;
|
||||
};
|
||||
|
||||
class NOVTABLE playlist_callback
|
||||
{
|
||||
public:
|
||||
virtual 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)=0;//inside any of these methods, you can call playlist APIs to get exact info about what happened (but only methods that read playlist state, not those that modify it)
|
||||
virtual void on_items_reordered(t_size p_playlist,const t_size * p_order,t_size p_count)=0;//changes selection too; doesnt actually change set of items that are selected or item having focus, just changes their order
|
||||
virtual void on_items_removing(t_size p_playlist,const bit_array & p_mask,t_size p_old_count,t_size p_new_count)=0;//called before actually removing them
|
||||
virtual void on_items_removed(t_size p_playlist,const bit_array & p_mask,t_size p_old_count,t_size p_new_count)=0;
|
||||
virtual void on_items_selection_change(t_size p_playlist,const bit_array & p_affected,const bit_array & p_state) = 0;
|
||||
virtual void on_item_focus_change(t_size p_playlist,t_size p_from,t_size p_to)=0;//focus may be -1 when no item has focus; reminder: focus may also change on other callbacks
|
||||
|
||||
virtual void on_items_modified(t_size p_playlist,const bit_array & p_mask)=0;
|
||||
virtual void on_items_modified_fromplayback(t_size p_playlist,const bit_array & p_mask,play_control::t_display_level p_level)=0;
|
||||
|
||||
struct t_on_items_replaced_entry
|
||||
{
|
||||
t_size m_index;
|
||||
metadb_handle_ptr m_old,m_new;
|
||||
};
|
||||
|
||||
virtual 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)=0;
|
||||
|
||||
virtual void on_item_ensure_visible(t_size p_playlist,t_size p_idx)=0;
|
||||
|
||||
virtual void on_playlist_activate(t_size p_old,t_size p_new) = 0;
|
||||
virtual void on_playlist_created(t_size p_index,const char * p_name,t_size p_name_len) = 0;
|
||||
virtual void on_playlists_reorder(const t_size * p_order,t_size p_count) = 0;
|
||||
virtual void on_playlists_removing(const bit_array & p_mask,t_size p_old_count,t_size p_new_count) = 0;
|
||||
virtual void on_playlists_removed(const bit_array & p_mask,t_size p_old_count,t_size p_new_count) = 0;
|
||||
virtual void on_playlist_renamed(t_size p_index,const char * p_new_name,t_size p_new_name_len) = 0;
|
||||
|
||||
virtual void on_default_format_changed() = 0;
|
||||
virtual void on_playback_order_changed(t_size p_new_index) = 0;
|
||||
virtual void on_playlist_locked(t_size p_playlist,bool p_locked) = 0;
|
||||
|
||||
enum {
|
||||
flag_on_items_added = 1 << 0,
|
||||
flag_on_items_reordered = 1 << 1,
|
||||
flag_on_items_removing = 1 << 2,
|
||||
flag_on_items_removed = 1 << 3,
|
||||
flag_on_items_selection_change = 1 << 4,
|
||||
flag_on_item_focus_change = 1 << 5,
|
||||
flag_on_items_modified = 1 << 6,
|
||||
flag_on_items_modified_fromplayback = 1 << 7,
|
||||
flag_on_items_replaced = 1 << 8,
|
||||
flag_on_item_ensure_visible = 1 << 9,
|
||||
flag_on_playlist_activate = 1 << 10,
|
||||
flag_on_playlist_created = 1 << 11,
|
||||
flag_on_playlists_reorder = 1 << 12,
|
||||
flag_on_playlists_removing = 1 << 13,
|
||||
flag_on_playlists_removed = 1 << 14,
|
||||
flag_on_playlist_renamed = 1 << 15,
|
||||
flag_on_default_format_changed = 1 << 16,
|
||||
flag_on_playback_order_changed = 1 << 17,
|
||||
flag_on_playlist_locked = 1 << 18,
|
||||
|
||||
flag_all = ~0,
|
||||
flag_item_ops = flag_on_items_added | flag_on_items_reordered | flag_on_items_removing | flag_on_items_removed | flag_on_items_selection_change | flag_on_item_focus_change | flag_on_items_modified | flag_on_items_modified_fromplayback | flag_on_items_replaced | flag_on_item_ensure_visible,
|
||||
flag_playlist_ops = flag_on_playlist_activate | flag_on_playlist_created | flag_on_playlists_reorder | flag_on_playlists_removing | flag_on_playlists_removed | flag_on_playlist_renamed | flag_on_playlist_locked,
|
||||
};
|
||||
protected:
|
||||
playlist_callback() {}
|
||||
~playlist_callback() {}
|
||||
};
|
||||
|
||||
class NOVTABLE playlist_callback_static : public service_base, public playlist_callback
|
||||
{
|
||||
public:
|
||||
virtual unsigned get_flags() = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(playlist_callback_static);
|
||||
};
|
||||
|
||||
class NOVTABLE playlist_callback_single
|
||||
{
|
||||
public:
|
||||
virtual void on_items_added(t_size p_base, const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const bit_array & p_selection)=0;//inside any of these methods, you can call playlist APIs to get exact info about what happened (but only methods that read playlist state, not those that modify it)
|
||||
virtual void on_items_reordered(const t_size * p_order,t_size p_count)=0;//changes selection too; doesnt actually change set of items that are selected or item having focus, just changes their order
|
||||
virtual void on_items_removing(const bit_array & p_mask,t_size p_old_count,t_size p_new_count)=0;//called before actually removing them
|
||||
virtual void on_items_removed(const bit_array & p_mask,t_size p_old_count,t_size p_new_count)=0;
|
||||
virtual void on_items_selection_change(const bit_array & p_affected,const bit_array & p_state) = 0;
|
||||
virtual void on_item_focus_change(t_size p_from,t_size p_to)=0;//focus may be -1 when no item has focus; reminder: focus may also change on other callbacks
|
||||
virtual void on_items_modified(const bit_array & p_mask)=0;
|
||||
virtual void on_items_modified_fromplayback(const bit_array & p_mask,play_control::t_display_level p_level)=0;
|
||||
virtual void on_items_replaced(const bit_array & p_mask,const pfc::list_base_const_t<playlist_callback::t_on_items_replaced_entry> & p_data)=0;
|
||||
virtual void on_item_ensure_visible(t_size p_idx)=0;
|
||||
|
||||
virtual void on_playlist_switch() = 0;
|
||||
virtual void on_playlist_renamed(const char * p_new_name,t_size p_new_name_len) = 0;
|
||||
virtual void on_playlist_locked(bool p_locked) = 0;
|
||||
|
||||
virtual void on_default_format_changed() = 0;
|
||||
virtual void on_playback_order_changed(t_size p_new_index) = 0;
|
||||
|
||||
enum {
|
||||
flag_on_items_added = 1 << 0,
|
||||
flag_on_items_reordered = 1 << 1,
|
||||
flag_on_items_removing = 1 << 2,
|
||||
flag_on_items_removed = 1 << 3,
|
||||
flag_on_items_selection_change = 1 << 4,
|
||||
flag_on_item_focus_change = 1 << 5,
|
||||
flag_on_items_modified = 1 << 6,
|
||||
flag_on_items_modified_fromplayback = 1 << 7,
|
||||
flag_on_items_replaced = 1 << 8,
|
||||
flag_on_item_ensure_visible = 1 << 9,
|
||||
flag_on_playlist_switch = 1 << 10,
|
||||
flag_on_playlist_renamed = 1 << 11,
|
||||
flag_on_playlist_locked = 1 << 12,
|
||||
flag_on_default_format_changed = 1 << 13,
|
||||
flag_on_playback_order_changed = 1 << 14,
|
||||
flag_all = ~0,
|
||||
};
|
||||
protected:
|
||||
playlist_callback_single() {}
|
||||
~playlist_callback_single() {}
|
||||
};
|
||||
|
||||
//! playlist_callback implementation helper - registers itself on creation / unregisters on destruction. Must not be instantiated statically!
|
||||
class playlist_callback_impl_base : public playlist_callback {
|
||||
public:
|
||||
playlist_callback_impl_base(t_uint32 p_flags = 0) {
|
||||
static_api_ptr_t<playlist_manager>()->register_callback(this,p_flags);
|
||||
}
|
||||
~playlist_callback_impl_base() {
|
||||
static_api_ptr_t<playlist_manager>()->unregister_callback(this);
|
||||
}
|
||||
void set_callback_flags(t_uint32 p_flags) {
|
||||
static_api_ptr_t<playlist_manager>()->modify_callback(this,p_flags);
|
||||
}
|
||||
//dummy implementations - avoid possible pure virtual function calls!
|
||||
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) {}
|
||||
void on_items_reordered(t_size p_playlist,const t_size * p_order,t_size p_count) {}
|
||||
void on_items_removing(t_size p_playlist,const bit_array & p_mask,t_size p_old_count,t_size p_new_count) {}
|
||||
void on_items_removed(t_size p_playlist,const bit_array & p_mask,t_size p_old_count,t_size p_new_count) {}
|
||||
void on_items_selection_change(t_size p_playlist,const bit_array & p_affected,const bit_array & p_state) {}
|
||||
void on_item_focus_change(t_size p_playlist,t_size p_from,t_size p_to) {}
|
||||
|
||||
void on_items_modified(t_size p_playlist,const bit_array & p_mask) {}
|
||||
void on_items_modified_fromplayback(t_size p_playlist,const bit_array & p_mask,play_control::t_display_level p_level) {}
|
||||
|
||||
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_item_ensure_visible(t_size p_playlist,t_size p_idx) {}
|
||||
|
||||
void on_playlist_activate(t_size p_old,t_size p_new) {}
|
||||
void on_playlist_created(t_size p_index,const char * p_name,t_size p_name_len) {}
|
||||
void on_playlists_reorder(const t_size * p_order,t_size p_count) {}
|
||||
void on_playlists_removing(const bit_array & p_mask,t_size p_old_count,t_size p_new_count) {}
|
||||
void on_playlists_removed(const bit_array & p_mask,t_size p_old_count,t_size p_new_count) {}
|
||||
void on_playlist_renamed(t_size p_index,const char * p_new_name,t_size p_new_name_len) {}
|
||||
|
||||
void on_default_format_changed() {}
|
||||
void on_playback_order_changed(t_size p_new_index) {}
|
||||
void on_playlist_locked(t_size p_playlist,bool p_locked) {}
|
||||
};
|
||||
|
||||
//! playlist_callback_single implementation helper - registers itself on creation / unregisters on destruction. Must not be instantiated statically!
|
||||
class playlist_callback_single_impl_base : public playlist_callback_single {
|
||||
protected:
|
||||
playlist_callback_single_impl_base(t_uint32 p_flags = 0) {
|
||||
static_api_ptr_t<playlist_manager>()->register_callback(this,p_flags);
|
||||
}
|
||||
void set_callback_flags(t_uint32 p_flags) {
|
||||
static_api_ptr_t<playlist_manager>()->modify_callback(this,p_flags);
|
||||
}
|
||||
~playlist_callback_single_impl_base() {
|
||||
static_api_ptr_t<playlist_manager>()->unregister_callback(this);
|
||||
}
|
||||
|
||||
//dummy implementations - avoid possible pure virtual function calls!
|
||||
void on_items_added(t_size p_base, const pfc::list_base_const_t<metadb_handle_ptr> & p_data,const bit_array & p_selection) {}
|
||||
void on_items_reordered(const t_size * p_order,t_size p_count) {}
|
||||
void on_items_removing(const bit_array & p_mask,t_size p_old_count,t_size p_new_count) {}
|
||||
void on_items_removed(const bit_array & p_mask,t_size p_old_count,t_size p_new_count) {}
|
||||
void on_items_selection_change(const bit_array & p_affected,const bit_array & p_state) {}
|
||||
void on_item_focus_change(t_size p_from,t_size p_to) {}
|
||||
void on_items_modified(const bit_array & p_mask) {}
|
||||
void on_items_modified_fromplayback(const bit_array & p_mask,play_control::t_display_level p_level) {}
|
||||
void on_items_replaced(const bit_array & p_mask,const pfc::list_base_const_t<playlist_callback::t_on_items_replaced_entry> & p_data) {}
|
||||
void on_item_ensure_visible(t_size p_idx) {}
|
||||
|
||||
void on_playlist_switch() {}
|
||||
void on_playlist_renamed(const char * p_new_name,t_size p_new_name_len) {}
|
||||
void on_playlist_locked(bool p_locked) {}
|
||||
|
||||
void on_default_format_changed() {}
|
||||
void on_playback_order_changed(t_size p_new_index) {}
|
||||
|
||||
PFC_CLASS_NOT_COPYABLE(playlist_callback_single_impl_base,playlist_callback_single_impl_base);
|
||||
};
|
||||
|
||||
class playlist_callback_single_static : public service_base, public playlist_callback_single
|
||||
{
|
||||
public:
|
||||
virtual unsigned get_flags() = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(playlist_callback_single_static);
|
||||
};
|
||||
|
||||
|
||||
//! Class used for async processing of IDataObject. Content of IDataObject can be dumped into dropped_files_data without any time-consuming operations - won't block calling app when used inside drag&drop handler - and actual time-consuming processing (listing directories and reading infos) can be done later.\n
|
||||
//! \deprecated In 0.9.3 and up, instead of going thru dropped_files_data, you can use playlist_incoming_item_filter_v2::process_dropped_files_async().
|
||||
class NOVTABLE dropped_files_data {
|
||||
public:
|
||||
virtual void set_paths(pfc::string_list_const const & p_paths) = 0;
|
||||
virtual void set_handles(const pfc::list_base_const_t<metadb_handle_ptr> & p_handles) = 0;
|
||||
protected:
|
||||
dropped_files_data() {}
|
||||
~dropped_files_data() {}
|
||||
};
|
||||
|
||||
|
||||
class NOVTABLE playlist_incoming_item_filter : public service_base {
|
||||
public:
|
||||
//! Pre-sorts incoming items according to user-configured settings, removes duplicates. \n
|
||||
//! @param in Items to process.
|
||||
//! @param out Receives processed item list. \n
|
||||
//! NOTE: because of an implementation bug in pre-0.9.5, the output list should be emptied before calling filter_items(), otherwise the results will be inconsistent/unpredictable.
|
||||
//! @returns True when there's one or more item in the output list, false when the output list is empty.
|
||||
virtual bool filter_items(metadb_handle_list_cref in,metadb_handle_list_ref out) = 0;
|
||||
|
||||
//! Converts one or more paths to a list of metadb_handles; displays a progress dialog.\n
|
||||
//! Note that this function creates modal dialog and does not return until the operation has completed.
|
||||
//! @returns True on success, false on user abort.
|
||||
//! \deprecated Use playlist_incoming_item_filter_v2::process_locations_async() when possible.
|
||||
virtual bool process_locations(const pfc::list_base_const_t<const char*> & p_urls,pfc::list_base_t<metadb_handle_ptr> & p_out,bool p_filter,const char * p_restrict_mask_override, const char * p_exclude_mask_override,HWND p_parentwnd) = 0;
|
||||
|
||||
//! Converts an IDataObject to a list of metadb_handles.
|
||||
//! Using this function is strongly disrecommended as it implies blocking the drag&drop source app (as well as our app).\n
|
||||
//! @returns True on success, false on user abort or unknown data format.
|
||||
//! \deprecated Use playlist_incoming_item_filter_v2::process_dropped_files_async() when possible.
|
||||
virtual bool process_dropped_files(interface IDataObject * pDataObject,pfc::list_base_t<metadb_handle_ptr> & p_out,bool p_filter,HWND p_parentwnd) = 0;
|
||||
|
||||
//! Checks whether IDataObject contains one of known data formats that can be translated to a list of metadb_handles.
|
||||
virtual bool process_dropped_files_check(interface IDataObject * pDataObject) = 0;
|
||||
|
||||
//! Checks whether IDataObject contains our own private data format (drag&drop within the app etc).
|
||||
virtual bool process_dropped_files_check_if_native(interface IDataObject * pDataObject) = 0;
|
||||
|
||||
//! Creates an IDataObject from specified metadb_handle list. The caller is responsible for releasing the returned object. It is recommended that you use create_dataobject_ex() to get an autopointer that ensures proper deletion.
|
||||
virtual interface IDataObject * create_dataobject(const pfc::list_base_const_t<metadb_handle_ptr> & p_data) = 0;
|
||||
|
||||
//! Checks whether IDataObject contains one of known data formats that can be translated to a list of metadb_handles.\n
|
||||
//! This function also returns drop effects to use (see: IDropTarget::DragEnter(), IDropTarget::DragOver() ). In certain cases, drag effects are necessary for drag&drop to work at all (such as dragging links from IE).\n
|
||||
virtual bool process_dropped_files_check_ex(interface IDataObject * pDataObject, DWORD * p_effect) = 0;
|
||||
|
||||
//! Dumps IDataObject content to specified dropped_files_data object, without any time-consuming processing.\n
|
||||
//! Using this function instead of process_dropped_files() and processing dropped_files_data outside drop handler allows you to avoid blocking drop source app when processing large directories etc.\n
|
||||
//! Note: since 0.9.3, it is recommended to use playlist_incoming_item_filter_v2::process_dropped_files_async() instead.
|
||||
//! @returns True on success, false when IDataObject does not contain any of known data formats.
|
||||
virtual bool process_dropped_files_delayed(dropped_files_data & p_out,interface IDataObject * pDataObject) = 0;
|
||||
|
||||
//! Helper - calls process_locations() with a single URL. See process_locations() for more info.
|
||||
bool process_location(const char * url,pfc::list_base_t<metadb_handle_ptr> & out,bool filter,const char * p_mask,const char * p_exclude,HWND p_parentwnd);
|
||||
//! Helper - returns a pfc::com_ptr_t<> rather than a raw pointer.
|
||||
pfc::com_ptr_t<interface IDataObject> create_dataobject_ex(metadb_handle_list_cref data);
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(playlist_incoming_item_filter);
|
||||
};
|
||||
|
||||
//! For use with playlist_incoming_item_filter_v2::process_locations_async().
|
||||
//! \since 0.9.3
|
||||
class NOVTABLE process_locations_notify : public service_base {
|
||||
public:
|
||||
virtual void on_completion(const pfc::list_base_const_t<metadb_handle_ptr> & p_items) = 0;
|
||||
virtual void on_aborted() = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(process_locations_notify,service_base);
|
||||
};
|
||||
|
||||
typedef service_ptr_t<process_locations_notify> process_locations_notify_ptr;
|
||||
|
||||
//! \since 0.9.3
|
||||
class NOVTABLE playlist_incoming_item_filter_v2 : public playlist_incoming_item_filter {
|
||||
public:
|
||||
enum {
|
||||
//! Set this to disable presorting (according to user settings) and duplicate removal in output list. Should be unset in most cases.
|
||||
op_flag_no_filter = 1 << 0,
|
||||
//! Set this flag to make the progress dialog not steal focus on creation.
|
||||
op_flag_background = 1 << 1,
|
||||
//! Set this flag to delay the progress dialog becoming visible, so it does not appear at all during short operations. Also implies op_flag_background effect.
|
||||
op_flag_delay_ui = 1 << 2,
|
||||
};
|
||||
|
||||
//! Converts one or more paths to a list of metadb_handles. The function returns immediately; specified callback object receives results when the operation has completed.
|
||||
//! @param p_urls List of paths to process.
|
||||
//! @param p_op_flags Can be null, or one or more of op_flag_* enum values combined, altering behaviors of the operation.
|
||||
//! @param p_restrict_mask_override Override of "restrict incoming items to" setting. Pass NULL to use the value from preferences.
|
||||
//! @param p_exclude_mask_override Override of "exclude file types" setting. Pass NULL to use value from preferences.
|
||||
//! @param p_parentwnd Parent window for spawned progress dialogs.
|
||||
//! @param p_notify Callback receiving notifications about success/abort of the operation as well as output item list.
|
||||
virtual void process_locations_async(const pfc::list_base_const_t<const char*> & p_urls,t_uint32 p_op_flags,const char * p_restrict_mask_override, const char * p_exclude_mask_override,HWND p_parentwnd,process_locations_notify_ptr p_notify) = 0;
|
||||
|
||||
//! Converts an IDataObject to a list of metadb_handles. The function returns immediately; specified callback object receives results when the operation has completed.
|
||||
//! @param p_dataobject IDataObject to process.
|
||||
//! @param p_op_flags Can be null, or one or more of op_flag_* enum values combined, altering behaviors of the operation.
|
||||
//! @param p_parentwnd Parent window for spawned progress dialogs.
|
||||
//! @param p_notify Callback receiving notifications about success/abort of the operation as well as output item list.
|
||||
virtual void process_dropped_files_async(interface IDataObject * p_dataobject,t_uint32 p_op_flags,HWND p_parentwnd,process_locations_notify_ptr p_notify) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(playlist_incoming_item_filter_v2,playlist_incoming_item_filter);
|
||||
};
|
||||
|
||||
//! \since 0.9.5
|
||||
class playlist_incoming_item_filter_v3 : public playlist_incoming_item_filter_v2 {
|
||||
public:
|
||||
virtual bool auto_playlist_name(metadb_handle_list_cref data,pfc::string_base & out) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE(playlist_incoming_item_filter_v3,playlist_incoming_item_filter_v2)
|
||||
};
|
||||
|
||||
//! Implementation of dropped_files_data.
|
||||
class dropped_files_data_impl : public dropped_files_data {
|
||||
public:
|
||||
dropped_files_data_impl() : m_is_paths(false) {}
|
||||
void set_paths(pfc::string_list_const const & p_paths) {
|
||||
m_is_paths = true;
|
||||
m_paths = p_paths;
|
||||
}
|
||||
void set_handles(const pfc::list_base_const_t<metadb_handle_ptr> & p_handles) {
|
||||
m_is_paths = false;
|
||||
m_handles = p_handles;
|
||||
}
|
||||
|
||||
void to_handles_async(bool p_filter,HWND p_parentwnd,service_ptr_t<process_locations_notify> p_notify);
|
||||
//! @param p_op_flags Can be null, or one or more of playlist_incoming_item_filter_v2::op_flag_* enum values combined, altering behaviors of the operation.
|
||||
void to_handles_async_ex(t_uint32 p_op_flags,HWND p_parentwnd,service_ptr_t<process_locations_notify> p_notify);
|
||||
bool to_handles(pfc::list_base_t<metadb_handle_ptr> & p_out,bool p_filter,HWND p_parentwnd);
|
||||
private:
|
||||
pfc::string_list_impl m_paths;
|
||||
metadb_handle_list m_handles;
|
||||
bool m_is_paths;
|
||||
};
|
||||
|
||||
|
||||
class NOVTABLE playback_queue_callback : public service_base
|
||||
{
|
||||
public:
|
||||
enum t_change_origin {
|
||||
changed_user_added,
|
||||
changed_user_removed,
|
||||
changed_playback_advance,
|
||||
};
|
||||
virtual void on_changed(t_change_origin p_origin) = 0;
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(playback_queue_callback);
|
||||
};
|
||||
|
||||
|
||||
class playlist_lock_change_notify : private playlist_callback_single_impl_base {
|
||||
public:
|
||||
playlist_lock_change_notify() : playlist_callback_single_impl_base(flag_on_playlist_switch|flag_on_playlist_locked) {}
|
||||
protected:
|
||||
virtual void on_lock_state_change() {}
|
||||
bool is_playlist_command_available(t_uint32 what) const {
|
||||
static_api_ptr_t<playlist_manager> api;
|
||||
const t_size active = api->get_active_playlist();
|
||||
if (active == ~0) return false;
|
||||
return (api->playlist_lock_get_filter_mask(active) & what) == 0;
|
||||
}
|
||||
private:
|
||||
void on_playlist_switch() {on_lock_state_change();}
|
||||
void on_playlist_locked(bool p_locked) {on_lock_state_change();}
|
||||
};
|
|
@ -0,0 +1,270 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
static void process_path_internal(const char * p_path,const service_ptr_t<file> & p_reader,playlist_loader_callback_v2 & callback,playlist_loader_callback::t_entry_type type,const t_filestats & p_stats);
|
||||
|
||||
namespace {
|
||||
class archive_callback_impl : public archive_callback
|
||||
{
|
||||
public:
|
||||
archive_callback_impl(playlist_loader_callback_v2 & p_callback) : m_callback(p_callback) {}
|
||||
bool on_entry(archive * owner,const char * p_path,const t_filestats & p_stats,const service_ptr_t<file> & p_reader)
|
||||
{
|
||||
process_path_internal(p_path,p_reader,m_callback,playlist_loader_callback::entry_directory_enumerated,p_stats);
|
||||
return !m_callback.is_aborting();
|
||||
}
|
||||
bool is_aborting() const {return m_callback.is_aborting();}
|
||||
abort_callback_event get_abort_event() const {return m_callback.get_abort_event();}
|
||||
private:
|
||||
playlist_loader_callback_v2 & m_callback;
|
||||
};
|
||||
}
|
||||
|
||||
void playlist_loader::g_load_playlist_filehint(file::ptr fileHint,const char * p_path,playlist_loader_callback & p_callback) {
|
||||
TRACK_CALL_TEXT("playlist_loader::g_load_playlist_filehint");
|
||||
pfc::string8 filepath;
|
||||
|
||||
filesystem::g_get_canonical_path(p_path,filepath);
|
||||
|
||||
pfc::string_extension extension(filepath);
|
||||
|
||||
service_ptr_t<file> l_file = fileHint;
|
||||
|
||||
if (l_file.is_empty()) {
|
||||
filesystem::ptr fs;
|
||||
if (filesystem::g_get_interface(fs,filepath)) {
|
||||
if (fs->supports_content_types()) {
|
||||
fs->open(l_file,filepath,filesystem::open_mode_read,p_callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
service_enum_t<playlist_loader> e;
|
||||
|
||||
if (l_file.is_valid()) {
|
||||
pfc::string8 content_type;
|
||||
if (l_file->get_content_type(content_type)) {
|
||||
service_ptr_t<playlist_loader> l;
|
||||
e.reset(); while(e.next(l)) {
|
||||
if (l->is_our_content_type(content_type)) {
|
||||
try {
|
||||
TRACK_CODE("playlist_loader::open",l->open(filepath,l_file,p_callback));
|
||||
return;
|
||||
} catch(exception_io_unsupported_format) {
|
||||
l_file->reopen(p_callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (extension.length()>0) {
|
||||
service_ptr_t<playlist_loader> l;
|
||||
e.reset(); while(e.next(l)) {
|
||||
if (stricmp_utf8(l->get_extension(),extension) == 0) {
|
||||
if (l_file.is_empty()) filesystem::g_open_read(l_file,filepath,p_callback);
|
||||
try {
|
||||
TRACK_CODE("playlist_loader::open",l->open(filepath,l_file,p_callback));
|
||||
return;
|
||||
} catch(exception_io_unsupported_format) {
|
||||
l_file->reopen(p_callback);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
throw exception_io_unsupported_format();
|
||||
}
|
||||
void playlist_loader::g_load_playlist(const char * p_path,playlist_loader_callback & callback) {
|
||||
g_load_playlist_filehint(NULL,p_path,callback);
|
||||
}
|
||||
|
||||
static void index_tracks_helper(const char * p_path,const service_ptr_t<file> & p_reader,const t_filestats & p_stats,playlist_loader_callback::t_entry_type p_type,playlist_loader_callback & p_callback,bool & p_got_input)
|
||||
{
|
||||
TRACK_CALL_TEXT("index_tracks_helper");
|
||||
if (p_reader.is_empty() && filesystem::g_is_remote_safe(p_path))
|
||||
{
|
||||
TRACK_CALL_TEXT("remote");
|
||||
metadb_handle_ptr handle;
|
||||
p_callback.handle_create(handle,make_playable_location(p_path,0));
|
||||
p_got_input = true;
|
||||
p_callback.on_entry(handle,p_type,p_stats,true);
|
||||
} else {
|
||||
TRACK_CALL_TEXT("hintable");
|
||||
service_ptr_t<input_info_reader> instance;
|
||||
input_entry::g_open_for_info_read(instance,p_reader,p_path,p_callback);
|
||||
|
||||
t_filestats stats = instance->get_file_stats(p_callback);
|
||||
|
||||
t_uint32 subsong,subsong_count = instance->get_subsong_count();
|
||||
for(subsong=0;subsong<subsong_count;subsong++)
|
||||
{
|
||||
TRACK_CALL_TEXT("subsong-loop");
|
||||
p_callback.check();
|
||||
metadb_handle_ptr handle;
|
||||
t_uint32 index = instance->get_subsong(subsong);
|
||||
p_callback.handle_create(handle,make_playable_location(p_path,index));
|
||||
|
||||
p_got_input = true;
|
||||
if (p_callback.want_info(handle,p_type,stats,true))
|
||||
{
|
||||
file_info_impl info;
|
||||
TRACK_CODE("get_info",instance->get_info(index,info,p_callback));
|
||||
p_callback.on_entry_info(handle,p_type,stats,info,true);
|
||||
}
|
||||
else
|
||||
{
|
||||
p_callback.on_entry(handle,p_type,stats,true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void track_indexer__g_get_tracks_wrap(const char * p_path,const service_ptr_t<file> & p_reader,const t_filestats & p_stats,playlist_loader_callback::t_entry_type p_type,playlist_loader_callback & p_callback) {
|
||||
bool got_input = false;
|
||||
bool fail = false;
|
||||
try {
|
||||
index_tracks_helper(p_path,p_reader,p_stats,p_type,p_callback,got_input);
|
||||
} catch(exception_aborted) {
|
||||
throw;
|
||||
} catch(exception_io_unsupported_format) {
|
||||
fail = true;
|
||||
} catch(std::exception const & e) {
|
||||
fail = true;
|
||||
console::formatter() << "could not enumerate tracks (" << e << ") on:\n" << file_path_display(p_path);
|
||||
}
|
||||
if (fail) {
|
||||
if (!got_input && !p_callback.is_aborting()) {
|
||||
if (p_type == playlist_loader_callback::entry_user_requested)
|
||||
{
|
||||
metadb_handle_ptr handle;
|
||||
p_callback.handle_create(handle,make_playable_location(p_path,0));
|
||||
p_callback.on_entry(handle,p_type,p_stats,true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
static void process_path_internal(const char * p_path,const service_ptr_t<file> & p_reader,playlist_loader_callback_v2 & p_callback,playlist_loader_callback::t_entry_type p_type,const t_filestats & p_stats)
|
||||
{
|
||||
//p_path must be canonical
|
||||
|
||||
p_callback.check();
|
||||
|
||||
p_callback.on_progress(p_path);
|
||||
|
||||
|
||||
{
|
||||
if (p_reader.is_empty()) {
|
||||
directory_callback_impl directory_results(true);
|
||||
try {
|
||||
filesystem::g_list_directory(p_path,directory_results,p_callback);
|
||||
for(t_size n=0;n<directory_results.get_count();n++) {
|
||||
process_path_internal(directory_results.get_item(n),0,p_callback,playlist_loader_callback::entry_directory_enumerated,directory_results.get_item_stats(n));
|
||||
}
|
||||
return;
|
||||
} catch(exception_aborted) {throw;}
|
||||
catch(...) {
|
||||
//do nothing, fall thru
|
||||
//fixme - catch only filesystem exceptions?
|
||||
}
|
||||
}
|
||||
|
||||
bool found = false;
|
||||
|
||||
|
||||
{
|
||||
archive_callback_impl archive_results(p_callback);
|
||||
service_enum_t<filesystem> e;
|
||||
service_ptr_t<filesystem> f;
|
||||
while(e.next(f)) {
|
||||
p_callback.check();
|
||||
service_ptr_t<archive> arch;
|
||||
if (f->service_query_t(arch)) {
|
||||
if (p_reader.is_valid()) p_reader->reopen(p_callback);
|
||||
|
||||
try {
|
||||
TRACK_CODE("archive::archive_list",arch->archive_list(p_path,p_reader,archive_results,true));
|
||||
return;
|
||||
} catch(exception_aborted) {throw;}
|
||||
catch(...) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
{
|
||||
service_ptr_t<link_resolver> ptr;
|
||||
if (link_resolver::g_find(ptr,p_path))
|
||||
{
|
||||
if (p_reader.is_valid()) p_reader->reopen(p_callback);
|
||||
|
||||
pfc::string8 temp;
|
||||
try {
|
||||
TRACK_CODE("link_resolver::resolve",ptr->resolve(p_reader,p_path,temp,p_callback));
|
||||
|
||||
track_indexer__g_get_tracks_wrap(temp,0,filestats_invalid,playlist_loader_callback::entry_from_playlist,p_callback);
|
||||
return;//success
|
||||
} catch(exception_aborted) {throw;}
|
||||
catch(...) {}
|
||||
}
|
||||
}
|
||||
|
||||
if (p_callback.is_path_wanted(p_path,p_type)) {
|
||||
track_indexer__g_get_tracks_wrap(p_path,p_reader,p_stats,p_type,p_callback);
|
||||
}
|
||||
}
|
||||
|
||||
void playlist_loader::g_process_path(const char * p_filename,playlist_loader_callback_v2 & callback,playlist_loader_callback::t_entry_type type)
|
||||
{
|
||||
TRACK_CALL_TEXT("playlist_loader::g_process_path");
|
||||
|
||||
file_path_canonical filename(p_filename);
|
||||
|
||||
process_path_internal(filename,0,callback,type,filestats_invalid);
|
||||
}
|
||||
|
||||
void playlist_loader::g_save_playlist(const char * p_filename,const pfc::list_base_const_t<metadb_handle_ptr> & data,abort_callback & p_abort)
|
||||
{
|
||||
TRACK_CALL_TEXT("playlist_loader::g_save_playlist");
|
||||
pfc::string8 filename;
|
||||
filesystem::g_get_canonical_path(p_filename,filename);
|
||||
try {
|
||||
service_ptr_t<file> r;
|
||||
filesystem::g_open(r,filename,filesystem::open_mode_write_new,p_abort);
|
||||
|
||||
pfc::string_extension ext(filename);
|
||||
|
||||
service_enum_t<playlist_loader> e;
|
||||
service_ptr_t<playlist_loader> l;
|
||||
if (e.first(l)) do {
|
||||
if (l->can_write() && !stricmp_utf8(ext,l->get_extension())) {
|
||||
try {
|
||||
TRACK_CODE("playlist_loader::write",l->write(filename,r,data,p_abort));
|
||||
return;
|
||||
} catch(exception_io_data) {}
|
||||
}
|
||||
} while(e.next(l));
|
||||
throw exception_io_data();
|
||||
} catch(...) {
|
||||
try {filesystem::g_remove(filename,p_abort);} catch(...) {}
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
bool playlist_loader::g_process_path_ex(const char * filename,playlist_loader_callback_v2 & callback,playlist_loader_callback::t_entry_type type)
|
||||
{
|
||||
try {
|
||||
g_load_playlist(filename,callback);
|
||||
return true;
|
||||
} catch(exception_io_unsupported_format) {//not a playlist format
|
||||
g_process_path(filename,callback,type);
|
||||
return false;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,151 @@
|
|||
//! Callback interface receiving item locations from playlist loader. Also inherits abort_callback methods and can be passed to functions that require an abort_callback. \n
|
||||
//! See playlist_loader_callback_impl for a basic implementation of playlist_loader_callback. Typically, you call one of standard services such as playlist_incoming_item_filter instead of implementing this interface and calling playlist_loader methods directly.
|
||||
class NOVTABLE playlist_loader_callback : public abort_callback {
|
||||
public:
|
||||
//! Enumeration type representing origin of item passed to playlist_loader_callback.
|
||||
enum t_entry_type {
|
||||
//! User-requested (such as directly dropped to window or picked in openfiledialog).
|
||||
entry_user_requested,
|
||||
//! From directory content enumeration.
|
||||
entry_directory_enumerated,
|
||||
//! Referenced by playlist file.
|
||||
entry_from_playlist,
|
||||
};
|
||||
//! Indicates specified path being processed; provided for updating GUI. Note that optimally GUI should not be updated every time this is called because that could introduce a bottleneck.
|
||||
virtual void on_progress(const char * p_path) = 0;
|
||||
|
||||
//! Receives an item from one of playlist_loader functions.
|
||||
//! @param p_item Item location, in form of metadb_handle pointer.
|
||||
//! @param p_type Origin of this item - see t_entry_type for more info.
|
||||
//! @param p_stats File stats of this item; set to filestats_invalid if not available.
|
||||
//! @param p_fresh Fresh flag; indicates whether stats are directly from filesystem (true) or as stored earlier in a playlist file (false).
|
||||
virtual void on_entry(const metadb_handle_ptr & p_item,t_entry_type p_type,const t_filestats & p_stats,bool p_fresh) = 0;
|
||||
//! Queries whether file_info for specified item is requested. In typical scenario, if want_info() returns false, on_entry() will be called with same parameters; otherwise caller will attempt to read info from the item and call on_entry_info() with same parameters and file_info read from the item.
|
||||
//! @param p_item Item location, in form of metadb_handle pointer.
|
||||
//! @param p_type Origin of this item - see t_entry_type for more info.
|
||||
//! @param p_stats File stats of this item; set to filestats_invalid if not available.
|
||||
//! @param p_fresh Fresh flag; indicates whether stats are directly from filesystem (true) or as stored earlier in a playlist file (false).
|
||||
virtual bool want_info(const metadb_handle_ptr & p_item,t_entry_type p_type,const t_filestats & p_stats,bool p_fresh) = 0;
|
||||
//! Receives an item from one of playlist_loader functions; including file_info data. Except for file_info to be typically used as hint for metadb backend, behavior of this method is same as on_entry().
|
||||
//! @param p_item Item location, in form of metadb_handle pointer.
|
||||
//! @param p_type Origin of this item - see t_entry_type for more info.
|
||||
//! @param p_stats File stats of this item; set to filestats_invalid if not available.
|
||||
//! @param p_info Information about the item, read from the file directly (if p_fresh is set to true) or from e.g. playlist file (if p_fresh is set to false).
|
||||
//! @param p_fresh Fresh flag; indicates whether stats are directly from filesystem (true) or as stored earlier in a playlist file (false).
|
||||
virtual void on_entry_info(const metadb_handle_ptr & p_item,t_entry_type p_type,const t_filestats & p_stats,const file_info & p_info,bool p_fresh) = 0;
|
||||
|
||||
//! Same as metadb::handle_create(); provided here to avoid repeated metadb instantiation bottleneck since calling code will need this function often.
|
||||
virtual void handle_create(metadb_handle_ptr & p_out,const playable_location & p_location) = 0;
|
||||
|
||||
protected:
|
||||
playlist_loader_callback() {}
|
||||
~playlist_loader_callback() {}
|
||||
};
|
||||
|
||||
class NOVTABLE playlist_loader_callback_v2 : public playlist_loader_callback {
|
||||
public:
|
||||
//! Returns whether further on_entry() calls for this file are wanted. Typically always returns true, can be used to optimize cases when directories are searched for files matching specific pattern only so unwanted files aren't parsed unnecessarily.
|
||||
//! @param path Canonical path to the media file being processed.
|
||||
virtual bool is_path_wanted(const char * path, t_entry_type type) = 0;
|
||||
|
||||
protected:
|
||||
playlist_loader_callback_v2() {}
|
||||
~playlist_loader_callback_v2() {}
|
||||
};
|
||||
|
||||
//! Basic implementation of playlist_loader_callback.
|
||||
class playlist_loader_callback_impl : public playlist_loader_callback_v2 {
|
||||
public:
|
||||
|
||||
playlist_loader_callback_impl(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();}
|
||||
|
||||
const metadb_handle_ptr & get_item(t_size idx) const {return m_data[idx];}
|
||||
const metadb_handle_ptr & operator[](t_size idx) const {return m_data[idx];}
|
||||
t_size get_count() const {return m_data.get_count();}
|
||||
|
||||
const metadb_handle_list & get_data() const {return m_data;}
|
||||
|
||||
void on_progress(const char * path) {}
|
||||
|
||||
void on_entry(const metadb_handle_ptr & ptr,t_entry_type type,const t_filestats & p_stats,bool p_fresh) {m_data.add_item(ptr);}
|
||||
bool want_info(const metadb_handle_ptr & ptr,t_entry_type type,const t_filestats & p_stats,bool p_fresh) {return false;}
|
||||
void on_entry_info(const metadb_handle_ptr & ptr,t_entry_type type,const t_filestats & p_stats,const file_info & p_info,bool p_fresh) {m_data.add_item(ptr);}
|
||||
|
||||
void handle_create(metadb_handle_ptr & p_out,const playable_location & p_location) {m_api->handle_create(p_out,p_location);}
|
||||
|
||||
bool is_path_wanted(const char * path,t_entry_type type) {return true;}
|
||||
|
||||
private:
|
||||
metadb_handle_list m_data;
|
||||
abort_callback & m_abort;
|
||||
static_api_ptr_t<metadb> m_api;
|
||||
};
|
||||
|
||||
//! Service handling playlist file operations. There are multiple implementations handling different playlist formats; you can add new implementations to allow new custom playlist file formats to be read or written.\n
|
||||
//! Also provides static helper functions for turning a filesystem path into a list of playable item locations. \n
|
||||
//! Note that you should typically call playlist_incoming_item_filter methods instead of calling playlist_loader methods directly to get a list of playable items from a specified path; this way you get a core-implemented threading and abortable dialog displaying progress.\n
|
||||
//! To register your own implementation, use playlist_loader_factory_t template.\n
|
||||
//! To call existing implementations, use static helper methods of playlist_loader class.
|
||||
class NOVTABLE playlist_loader : public service_base {
|
||||
public:
|
||||
//! Parses specified playlist file into list of playable locations. Throws exception_io or derivatives on failure, exception_aborted on abort. If specified file is not a recognized playlist file, exception_io_unsupported_format is thrown.
|
||||
//! @param p_path Path of playlist file to parse. Used for relative path handling purposes (p_file parameter is used for actual file access).
|
||||
//! @param p_file File interface to use for reading. Read/write pointer must be set to beginning by caller before calling.
|
||||
//! @param p_callback Callback object receiving enumerated playable item locations as well as signaling user aborting the operation.
|
||||
virtual void open(const char * p_path, const service_ptr_t<file> & p_file,playlist_loader_callback & p_callback) = 0;
|
||||
//! Writes a playlist file containing specific item list to specified file. Will fail (pfc::exception_not_implemented) if specified playlist_loader is read-only (can_write() returns false).
|
||||
//! @param p_path Path of playlist file to write. Used for relative path handling purposes (p_file parameter is used for actual file access).
|
||||
//! @param p_file File interface to use for writing. Caller should ensure that the file is empty (0 bytes long) before calling.
|
||||
//! @param p_data List of items to save to playlist file.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation. Note that aborting a save playlist operation will most likely leave user with corrupted/incomplete file.
|
||||
virtual void write(const char * p_path, const service_ptr_t<file> & p_file,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,abort_callback & p_abort) = 0;
|
||||
//! Returns extension of file format handled by this playlist_loader implementation (a UTF-8 encoded null-terminated string).
|
||||
virtual const char * get_extension() = 0;
|
||||
//! Returns whether this playlist_loader implementation supports writing. If can_write() returns false, all write() calls will fail.
|
||||
virtual bool can_write() = 0;
|
||||
//! Returns whether specified content type is one of playlist types supported by this playlist_loader implementation or not.
|
||||
//! @param p_content_type Content type to query, a UTF-8 encoded null-terminated string.
|
||||
virtual bool is_our_content_type(const char* p_content_type) = 0;
|
||||
//! Returns whether playlist format extension supported by this implementation should be listed on file types associations page.
|
||||
virtual bool is_associatable() = 0;
|
||||
|
||||
//! Attempts to load a playlist file from specified filesystem path. Throws exception_io or derivatives on failure, exception_aborted on abort. If specified file is not a recognized playlist file, exception_io_unsupported_format is thrown. \n
|
||||
//! Equivalent to g_load_playlist_filehint(NULL,p_path,p_callback).
|
||||
//! @param p_path Filesystem path to load playlist from, a UTF-8 encoded null-terminated string.
|
||||
//! @param p_callback Callback object receiving enumerated playable item locations as well as signaling user aborting the operation.
|
||||
static void g_load_playlist(const char * p_path,playlist_loader_callback & p_callback);
|
||||
|
||||
//! Attempts to load a playlist file from specified filesystem path. Throws exception_io or derivatives on failure, exception_aborted on abort. If specified file is not a recognized playlist file, exception_io_unsupported_format is thrown.
|
||||
//! @param p_path Filesystem path to load playlist from, a UTF-8 encoded null-terminated string.
|
||||
//! @param p_callback Callback object receiving enumerated playable item locations as well as signaling user aborting the operation.
|
||||
//! @param fileHint File object to read from, can be NULL if not available.
|
||||
static void g_load_playlist_filehint(file::ptr fileHint,const char * p_path,playlist_loader_callback & p_callback);
|
||||
|
||||
//! Saves specified list of locations into a playlist file. Throws exception_io or derivatives on failure, exception_aborted on abort.
|
||||
//! @param p_path Filesystem path to save playlist to, a UTF-8 encoded null-terminated string.
|
||||
//! @param p_data List of items to save to playlist file.
|
||||
//! @param p_abort abort_callback object signaling user aborting the operation. Note that aborting a save playlist operation will most likely leave user with corrupted/incomplete file.
|
||||
static void g_save_playlist(const char * p_path,const pfc::list_base_const_t<metadb_handle_ptr> & p_data,abort_callback & p_abort);
|
||||
|
||||
//! Processes specified path to generate list of playable items. Includes recursive directory/archive enumeration. \n
|
||||
//! Does not touch playlist files encountered - use g_process_path_ex() if specified path is possibly a playlist file; playlist files found inside directories or archives are ignored regardless.\n
|
||||
//! Warning: caller must handle exceptions which will occur in case of I/O failure.
|
||||
//! @param p_path Filesystem path to process; a UTF-8 encoded null-terminated string.
|
||||
//! @param p_callback Callback object receiving enumerated playable item locations as well as signaling user aborting the operation.
|
||||
//! @param p_type Origin of p_path string. Reserved for internal use in recursive calls, should be left at default value; it controls various internal behaviors.
|
||||
static void g_process_path(const char * p_path,playlist_loader_callback_v2 & p_callback,playlist_loader_callback::t_entry_type p_type = playlist_loader_callback::entry_user_requested);
|
||||
|
||||
//! Calls attempts to process specified path as a playlist; if that fails (i.e. not a playlist), calls g_process_path with same parameters. See g_process_path for parameter descriptions. \n
|
||||
//! Warning: caller must handle exceptions which will occur in case of I/O failure or playlist parsing failure.
|
||||
//! @returns True if specified path was processed as a playlist file, false otherwise (relevant in some scenarios where output is sorted after loading, playlist file contents should not be sorted).
|
||||
static bool g_process_path_ex(const char * p_path,playlist_loader_callback_v2 & p_callback,playlist_loader_callback::t_entry_type p_type = playlist_loader_callback::entry_user_requested);
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(playlist_loader);
|
||||
};
|
||||
|
||||
template<typename t_myclass>
|
||||
class playlist_loader_factory_t : public service_factory_single_t<t_myclass> {};
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
#include "foobar2000.h"
|
||||
|
||||
void popup_message::g_show_ex(const char * p_msg,unsigned p_msg_length,const char * p_title,unsigned p_title_length,t_icon p_icon)
|
||||
{
|
||||
static_api_ptr_t<popup_message>()->show_ex(p_msg,p_msg_length,p_title,p_title_length,p_icon);
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
|
||||
//! This interface allows you to show generic nonmodal noninteractive dialog with a text message. This should be used instead of MessageBox where possible.\n
|
||||
//! Usage: use popup_message::g_show / popup_message::g_show_ex static helpers, or static_api_ptr_t<popup_message>.\n
|
||||
//! Note that all strings are UTF-8.
|
||||
|
||||
class NOVTABLE popup_message : public service_base {
|
||||
public:
|
||||
enum t_icon {icon_information, icon_error, icon_query};
|
||||
//! Activates the popup dialog; returns immediately (the dialog remains visible).
|
||||
//! @param p_msg Message to show (UTF-8 encoded string).
|
||||
//! @param p_msg_length Length limit of message string to show, in bytes (actual string may be shorter if null terminator is encountered before). Set this to infinite to use plain null-terminated strings.
|
||||
//! @param p_title Title of dialog to show (UTF-8 encoded string).
|
||||
//! @param p_title_length Length limit of the title string, in bytes (actual string may be shorter if null terminator is encountered before). Set this to infinite to use plain null-terminated strings.
|
||||
//! @param p_icon Icon of the dialog - can be set to icon_information, icon_error or icon_query.
|
||||
virtual void show_ex(const char * p_msg,unsigned p_msg_length,const char * p_title,unsigned p_title_length,t_icon p_icon = icon_information) = 0;
|
||||
|
||||
//! Activates the popup dialog; returns immediately (the dialog remains visible); helper function built around show_ex(), takes null terminated strings with no length limit parameters.
|
||||
//! @param p_msg Message to show (UTF-8 encoded string).
|
||||
//! @param p_title Title of dialog to show (UTF-8 encoded string).
|
||||
//! @param p_icon Icon of the dialog - can be set to icon_information, icon_error or icon_query.
|
||||
inline void show(const char * p_msg,const char * p_title,t_icon p_icon = icon_information) {show_ex(p_msg,infinite,p_title,infinite,p_icon);}
|
||||
|
||||
//! Static helper function instantiating the service and activating the message dialog. See show_ex() for description of parameters.
|
||||
static void g_show_ex(const char * p_msg,unsigned p_msg_length,const char * p_title,unsigned p_title_length,t_icon p_icon = icon_information);
|
||||
//! Static helper function instantiating the service and activating the message dialog. See show() for description of parameters.
|
||||
static inline void g_show(const char * p_msg,const char * p_title,t_icon p_icon = icon_information) {g_show_ex(p_msg,infinite,p_title,infinite,p_icon);}
|
||||
|
||||
static void g_complain(const char * what) {
|
||||
g_show(what, "Information", icon_error);
|
||||
}
|
||||
|
||||
static void g_complain(const char * p_whatFailed, const std::exception & p_exception) {
|
||||
g_complain(p_whatFailed,p_exception.what());
|
||||
}
|
||||
static void g_complain(const char * p_whatFailed, const char * msg) {
|
||||
g_complain(pfc::string_formatter() << p_whatFailed << ": " << msg);
|
||||
}
|
||||
|
||||
FB2K_MAKE_SERVICE_INTERFACE_ENTRYPOINT(popup_message);
|
||||
};
|
||||
|
||||
#define EXCEPTION_TO_POPUP_MESSAGE(CODE,LABEL) try { CODE; } catch(std::exception const & e) {popup_message::g_complain(LABEL,e);}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue