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