#pragma once
/*
* Copyright (C) 2015 Team Kodi
*
* This Program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This Program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Kodi; see the file COPYING. If not, see
* .
*
*/
#include "../AddonBase.h"
#include "../Filesystem.h"
#ifdef BUILD_KODI_ADDON
#include "../IFileTypes.h"
#else
#include "filesystem/IFileTypes.h"
#include "PlatformDefs.h"
#endif
namespace kodi { namespace addon { class CInstanceVFS; }}
extern "C"
{
struct VFSURL
{
const char* url;
const char* domain;
const char* hostname;
const char* filename;
unsigned int port;
const char* options;
const char* username;
const char* password;
const char* redacted;
const char* sharename;
};
typedef struct VFSGetDirectoryCallbacks /* internal */
{
bool (__cdecl* get_keyboard_input)(void* ctx, const char* heading, char** input, bool hidden_input);
void (__cdecl* set_error_dialog)(void* ctx, const char* heading, const char* line1, const char* line2, const char* line3);
void (__cdecl* require_authentication)(void* ctx, const char* url);
void* ctx;
} VFSGetDirectoryCallbacks;
typedef struct AddonProps_VFSEntry /* internal */
{
int dummy;
} AddonProps_VFSEntry;
typedef struct AddonToKodiFuncTable_VFSEntry /* internal */
{
KODI_HANDLE kodiInstance;
} AddonToKodiFuncTable_VFSEntry;
struct AddonInstance_VFSEntry;
typedef struct KodiToAddonFuncTable_VFSEntry /* internal */
{
kodi::addon::CInstanceVFS* addonInstance;
void* (__cdecl* open) (const AddonInstance_VFSEntry* instance, const VFSURL* url);
void* (__cdecl* open_for_write) (const AddonInstance_VFSEntry* instance, const VFSURL* url, bool overwrite);
ssize_t (__cdecl* read) (const AddonInstance_VFSEntry* instance, void* context, void* buffer, size_t buf_size);
ssize_t (__cdecl* write) (const AddonInstance_VFSEntry* instance, void* context, const void* buffer, size_t buf_size);
int64_t (__cdecl* seek) (const AddonInstance_VFSEntry* instance, void* context, int64_t position, int whence);
int (__cdecl* truncate) (const AddonInstance_VFSEntry* instance, void* context, int64_t size);
int64_t (__cdecl* get_length) (const AddonInstance_VFSEntry* instance, void* context);
int64_t (__cdecl* get_position) (const AddonInstance_VFSEntry* instance, void* context);
int (__cdecl* get_chunk_size) (const AddonInstance_VFSEntry* instance, void* context);
int (__cdecl* io_control) (const AddonInstance_VFSEntry* instance, void* context, XFILE::EIoControl request, void* param);
int (__cdecl* stat) (const AddonInstance_VFSEntry* instance, const VFSURL* url, struct __stat64* buffer);
bool (__cdecl* close) (const AddonInstance_VFSEntry* instance, void* context);
bool (__cdecl* exists) (const AddonInstance_VFSEntry* instance, const VFSURL* url);
void (__cdecl* clear_out_idle) (const AddonInstance_VFSEntry* instance);
void (__cdecl* disconnect_all) (const AddonInstance_VFSEntry* instance);
bool (__cdecl* delete_it) (const AddonInstance_VFSEntry* instance, const VFSURL* url);
bool (__cdecl* rename) (const AddonInstance_VFSEntry* instance, const VFSURL* url, const VFSURL* url2);
bool (__cdecl* directory_exists) (const AddonInstance_VFSEntry* instance, const VFSURL* url);
bool (__cdecl* remove_directory) (const AddonInstance_VFSEntry* instance, const VFSURL* url);
bool (__cdecl* create_directory) (const AddonInstance_VFSEntry* instance, const VFSURL* url);
bool (__cdecl* get_directory) (const AddonInstance_VFSEntry* instance,
const VFSURL* url,
VFSDirEntry** entries,
int* num_entries,
VFSGetDirectoryCallbacks* callbacks);
bool (__cdecl* contains_files) (const AddonInstance_VFSEntry* instance,
const VFSURL* url,
VFSDirEntry** entries,
int* num_entries,
char* rootpath);
void (__cdecl* free_directory) (const AddonInstance_VFSEntry* instance, VFSDirEntry* entries, int num_entries);
} KodiToAddonFuncTable_VFSEntry;
typedef struct AddonInstance_VFSEntry /* internal */
{
AddonProps_VFSEntry props;
AddonToKodiFuncTable_VFSEntry toKodi;
KodiToAddonFuncTable_VFSEntry toAddon;
} AddonInstance_VFSEntry;
} /* extern "C" */
namespace kodi
{
namespace addon
{
class CInstanceVFS : public IAddonInstance
{
public:
explicit CInstanceVFS(KODI_HANDLE instance)
: IAddonInstance(ADDON_INSTANCE_VFS)
{
if (CAddonBase::m_interface->globalSingleInstance != nullptr)
throw std::logic_error("kodi::addon::CInstanceVFS: Creation of multiple together with single instance way is not allowed!");
SetAddonStruct(instance);
}
~CInstanceVFS() override = default;
/// @brief Open a file for input
///
/// @param[in] url The URL of the file
/// @return Context for the opened file
virtual void* Open(const VFSURL& url) { return nullptr; }
/// @brief Open a file for output
///
/// @param[in] url The URL of the file
/// @param[in] overWrite Whether or not to overwrite an existing file
/// @return Context for the opened file
///
virtual void* OpenForWrite(const VFSURL& url, bool overWrite) { return nullptr; }
/// @brief Read from a file
///
/// @param[in] context The context of the file
/// @param[out] buffer The buffer to read data into
/// @param[in] uiBufSize Number of bytes to read
/// @return Number of bytes read
///
virtual ssize_t Read(void* context, void* buffer, size_t uiBufSize) { return -1; }
/// @brief Write to a file
///
/// @param[in] context The context of the file
/// @param[in] buffer The buffer to read data from
/// @param[in] uiBufSize Number of bytes to write
/// @return Number of bytes written
///
virtual ssize_t Write(void* context, const void* buffer, size_t uiBufSize) { return -1; }
/// @brief Seek in a file
///
/// @param[in] context The context of the file
/// @param[in] position The position to seek to
/// @param[in] whence Position in file 'position' is relative to (SEEK_CUR, SEEK_SET, SEEK_END)
/// @return Offset in file after seek
///
virtual int64_t Seek(void* context, int64_t position, int whence) { return -1; }
/// @brief Truncate a file
///
/// @param[in] context The context of the file
/// @param[in] size The size to truncate the file to
/// @return 0 on success, -1 on error
///
virtual int Truncate(void* context, int64_t size) { return -1; }
/// @brief Get total size of a file
///
/// @param[in] context The context of the file
/// @return Total file size
///
virtual int64_t GetLength(void* context) { return 0; }
/// @brief Get current position in a file
///
/// @param[in] context The context of the file
/// @return Current position
///
virtual int64_t GetPosition(void* context) { return 0; }
/// @brief Get chunk size of a file
///
/// @param[in] context The context of the file
/// @return Chunk size
///
virtual int GetChunkSize(void* context) { return 1; }
/// @brief Perform an IO-control on the file
///
/// @param[in] context The context of the file
/// @param[in] request The requested IO-control
/// @param[in] param Parameter attached to the IO-control
/// @return -1 on error, >= 0 on success
///
virtual int IoControl(void* context, XFILE::EIoControl request, void* param) { return -1; }
/// @brief Close a file
///
/// @param[in] context The context of the file
/// @return True on success, false on failure
///
virtual bool Close(void* context) { return false; }
/// @brief Stat a file
///
/// @param[in] url The URL of the file
/// @param[in] buffer The buffer to store results in
/// @return -1 on error, 0 otherwise
///
virtual int Stat(const VFSURL& url, struct __stat64* buffer) { return 0; }
/// @brief Check for file existence
///
/// @param[in] url The URL of the file
/// @return True if file exists, false otherwise
///
virtual bool Exists(const VFSURL& url) { return false; }
/// @brief Clear out any idle connections
///
virtual void ClearOutIdle() { }
/// @brief Disconnect all connections
///
virtual void DisconnectAll() { }
/// @brief Delete a file
///
/// @param[in] url The URL of the file
/// @return True if deletion was successful, false otherwise
///
virtual bool Delete(const VFSURL& url) { return false; }
/// @brief Rename a file
///
/// @param[in] url The URL of the source file
/// @param[in] url2 The URL of the destination file
/// @return True if deletion was successful, false otherwise
///
virtual bool Rename(const VFSURL& url, const VFSURL& url2) { return false; }
/// @brief Check for directory existence
///
/// @param[in] url The URL of the file
/// @return True if directory exists, false otherwise
///
virtual bool DirectoryExists(const VFSURL& url) { return false; }
/// @brief Remove a directory
///
/// @param[in] url The URL of the directory
/// @return True if removal was successful, false otherwise
///
virtual bool RemoveDirectory(const VFSURL& url) { return false; }
/// @brief Create a directory
///
/// @param[in] url The URL of the file
/// @return True if creation was successful, false otherwise
///
virtual bool CreateDirectory(const VFSURL& url) { return false; }
/// @brief Callback functions on GetDirectory()
///
/// This functions becomes available during call of GetDirectory() from
/// Kodi.
///
/// If GetDirectory() returns false becomes the parts from here used on
/// next call of the function.
///
/// **Example:**
/// ~~~~~~~~~~~~~{.cpp}
///
/// #include
///
/// ...
///
/// bool CMyFile::GetDirectory(const VFSURL& url, std::vector& items, CVFSCallbacks callbacks)
/// {
/// std::string neededString;
/// callbacks.GetKeyboardInput("Test", neededString, true);
/// if (neededString.empty())
/// return false;
///
/// /* Do the work */
/// ...
/// return true;
/// }
/// ~~~~~~~~~~~~~
///
//@{
class CVFSCallbacks
{
public:
/// @brief Require keyboard input
///
/// Becomes called if GetDirectory() returns false and GetDirectory()
/// becomes after entry called again.
///
/// @param[in] heading The heading of the keyboard dialog
/// @param[out] input The resulting string. Returns string after
/// second call!
/// @param[in] hiddenInput To show input only as "*" on dialog
/// @return True if input was received, false otherwise
///
bool GetKeyboardInput(const std::string& heading, std::string& input, bool hiddenInput = false)
{
char* cInput = nullptr;
bool ret = m_cb->get_keyboard_input(m_cb->ctx, heading.c_str(), &cInput, hiddenInput);
if (cInput)
{
input = cInput;
::kodi::addon::CAddonBase::m_interface->toKodi->free_string(::kodi::addon::CAddonBase::m_interface->toKodi->kodiBase, cInput);
}
return ret;
}
/// @brief Display an error dialog
///
/// @param[in] heading The heading of the error dialog
/// @param[in] line1 The first line of the error dialog
/// @param[in] line2 [opt] The second line of the error dialog
/// @param[in] line3 [opt] The third line of the error dialog
///
void SetErrorDialog(const std::string& heading, const std::string& line1, const std::string& line2 = "", const std::string& line3 = "")
{
m_cb->set_error_dialog(m_cb->ctx, heading.c_str(), line1.c_str(), line2.c_str(), line3.c_str());
}
/// @brief Prompt the user for authentication of a URL
///
/// @param[in] url The URL
void RequireAuthentication(const std::string& url)
{
m_cb->require_authentication(m_cb->ctx, url.c_str());
}
explicit CVFSCallbacks(const VFSGetDirectoryCallbacks* cb) : m_cb(cb) { }
private:
const VFSGetDirectoryCallbacks* m_cb;
};
//@}
/// @brief List a directory
///
/// @param[in] url The URL of the directory
/// @param[out] entries The entries in the directory
/// @param[in] callbacks A callback structure
/// @return Context for the directory listing
///
virtual bool GetDirectory(const VFSURL& url,
std::vector& entries,
CVFSCallbacks callbacks) { return false; }
/// @brief Check if file should be presented as a directory (multiple streams)
///
/// @param[in] url The URL of the file
/// @param[out] entries The entries in the directory
/// @param[out] rootPath Path to root directory if multiple entries
/// @return Context for the directory listing
///
virtual bool ContainsFiles(const VFSURL& url,
std::vector& entries,
std::string& rootPath) { return false; }
private:
void SetAddonStruct(KODI_HANDLE instance)
{
if (instance == nullptr)
throw std::logic_error("kodi::addon::CInstanceVFS: Creation with empty addon structure not allowed, table must be given from Kodi!");
m_instanceData = static_cast(instance);
m_instanceData->toAddon.addonInstance = this;
m_instanceData->toAddon.open = ADDON_Open;
m_instanceData->toAddon.open_for_write = ADDON_OpenForWrite;
m_instanceData->toAddon.read = ADDON_Read;
m_instanceData->toAddon.write = ADDON_Write;
m_instanceData->toAddon.seek = ADDON_Seek;
m_instanceData->toAddon.truncate = ADDON_Truncate;
m_instanceData->toAddon.get_length = ADDON_GetLength;
m_instanceData->toAddon.get_position = ADDON_GetPosition;
m_instanceData->toAddon.get_chunk_size = ADDON_GetChunkSize;
m_instanceData->toAddon.io_control = ADDON_IoControl;
m_instanceData->toAddon.stat = ADDON_Stat;
m_instanceData->toAddon.close = ADDON_Close;
m_instanceData->toAddon.exists = ADDON_Exists;
m_instanceData->toAddon.clear_out_idle = ADDON_ClearOutIdle;
m_instanceData->toAddon.disconnect_all = ADDON_DisconnectAll;
m_instanceData->toAddon.delete_it = ADDON_Delete;
m_instanceData->toAddon.rename = ADDON_Rename;
m_instanceData->toAddon.directory_exists = ADDON_DirectoryExists;
m_instanceData->toAddon.remove_directory = ADDON_RemoveDirectory;
m_instanceData->toAddon.create_directory = ADDON_CreateDirectory;
m_instanceData->toAddon.get_directory = ADDON_GetDirectory;
m_instanceData->toAddon.free_directory = ADDON_FreeDirectory;
m_instanceData->toAddon.contains_files = ADDON_ContainsFiles;
}
inline static void* ADDON_Open(const AddonInstance_VFSEntry* instance, const VFSURL* url)
{
return instance->toAddon.addonInstance->Open(*url);
}
inline static void* ADDON_OpenForWrite(const AddonInstance_VFSEntry* instance, const VFSURL* url, bool overWrite)
{
return instance->toAddon.addonInstance->OpenForWrite(*url, overWrite);
}
inline static ssize_t ADDON_Read(const AddonInstance_VFSEntry* instance, void* context, void* buffer, size_t uiBufSize)
{
return instance->toAddon.addonInstance->Read(context, buffer, uiBufSize);
}
inline static ssize_t ADDON_Write(const AddonInstance_VFSEntry* instance, void* context, const void* buffer, size_t uiBufSize)
{
return instance->toAddon.addonInstance->Write(context, buffer, uiBufSize);
}
inline static int64_t ADDON_Seek(const AddonInstance_VFSEntry* instance, void* context, int64_t position, int whence)
{
return instance->toAddon.addonInstance->Seek(context, position, whence);
}
inline static int ADDON_Truncate(const AddonInstance_VFSEntry* instance, void* context, int64_t size)
{
return instance->toAddon.addonInstance->Truncate(context, size);
}
inline static int64_t ADDON_GetLength(const AddonInstance_VFSEntry* instance, void* context)
{
return instance->toAddon.addonInstance->GetLength(context);
}
inline static int64_t ADDON_GetPosition(const AddonInstance_VFSEntry* instance, void* context)
{
return instance->toAddon.addonInstance->GetPosition(context);
}
inline static int ADDON_GetChunkSize(const AddonInstance_VFSEntry* instance, void* context)
{
return instance->toAddon.addonInstance->GetChunkSize(context);
}
inline static int ADDON_IoControl(const AddonInstance_VFSEntry* instance, void* context, XFILE::EIoControl request, void* param)
{
return instance->toAddon.addonInstance->IoControl(context, request, param);
}
inline static int ADDON_Stat(const AddonInstance_VFSEntry* instance, const VFSURL* url, struct __stat64* buffer)
{
return instance->toAddon.addonInstance->Stat(*url, buffer);
}
inline static bool ADDON_Close(const AddonInstance_VFSEntry* instance, void* context)
{
return instance->toAddon.addonInstance->Close(context);
}
inline static bool ADDON_Exists(const AddonInstance_VFSEntry* instance, const VFSURL* url)
{
return instance->toAddon.addonInstance->Exists(*url);
}
inline static void ADDON_ClearOutIdle(const AddonInstance_VFSEntry* instance)
{
return instance->toAddon.addonInstance->ClearOutIdle();
}
inline static void ADDON_DisconnectAll(const AddonInstance_VFSEntry* instance)
{
return instance->toAddon.addonInstance->DisconnectAll();
}
inline static bool ADDON_Delete(const AddonInstance_VFSEntry* instance, const VFSURL* url)
{
return instance->toAddon.addonInstance->Delete(*url);
}
inline static bool ADDON_Rename(const AddonInstance_VFSEntry* instance, const VFSURL* url, const VFSURL* url2)
{
return instance->toAddon.addonInstance->Rename(*url, *url2);
}
inline static bool ADDON_DirectoryExists(const AddonInstance_VFSEntry* instance, const VFSURL* url)
{
return instance->toAddon.addonInstance->DirectoryExists(*url);
}
inline static bool ADDON_RemoveDirectory(const AddonInstance_VFSEntry* instance, const VFSURL* url)
{
return instance->toAddon.addonInstance->RemoveDirectory(*url);
}
inline static bool ADDON_CreateDirectory(const AddonInstance_VFSEntry* instance, const VFSURL* url)
{
return instance->toAddon.addonInstance->CreateDirectory(*url);
}
inline static bool ADDON_GetDirectory(const AddonInstance_VFSEntry* instance,
const VFSURL* url,
VFSDirEntry** retEntries,
int* num_entries,
VFSGetDirectoryCallbacks* callbacks)
{
std::vector addonEntries;
bool ret = instance->toAddon.addonInstance->GetDirectory(*url, addonEntries, CVFSCallbacks(callbacks));
if (ret)
{
VFSDirEntry* entries = static_cast(malloc(sizeof(VFSDirEntry) * addonEntries.size()));
for (unsigned int i = 0; i < addonEntries.size(); ++i)
{
entries[i].label = strdup(addonEntries[i].Label().c_str());
entries[i].title = strdup(addonEntries[i].Title().c_str());
entries[i].path = strdup(addonEntries[i].Path().c_str());
entries[i].folder = addonEntries[i].IsFolder();
entries[i].size = addonEntries[i].Size();
entries[i].num_props = 0;
const std::map& props = addonEntries[i].GetProperties();
if (!props.empty())
{
entries[i].properties = static_cast(malloc(sizeof(VFSProperty)*props.size()));
for (const auto& prop : props)
{
entries[i].properties[entries[i].num_props].name = strdup(prop.first.c_str());
entries[i].properties[entries[i].num_props].val = strdup(prop.second.c_str());
++entries[i].num_props;
}
}
else
entries[i].properties = nullptr;
}
*retEntries = entries;
*num_entries = addonEntries.size();
}
return ret;
}
inline static void ADDON_FreeDirectory(const AddonInstance_VFSEntry* instance, VFSDirEntry* entries, int num_entries)
{
for (int i = 0; i < num_entries; ++i)
{
if (entries[i].properties)
{
for (unsigned int j = 0; j < entries[i].num_props; ++j)
{
free(entries[i].properties[j].name);
free(entries[i].properties[j].val);
}
free(entries[i].properties);
}
free(entries[i].label);
free(entries[i].title);
free(entries[i].path);
}
free(entries);
}
inline static bool ADDON_ContainsFiles(const AddonInstance_VFSEntry* instance,
const VFSURL* url,
VFSDirEntry** retEntries,
int* num_entries,
char* rootpath)
{
std::string cppRootPath;
std::vector addonEntries;
bool ret = instance->toAddon.addonInstance->ContainsFiles(*url, addonEntries, cppRootPath);
if (ret)
{
strncpy(rootpath, cppRootPath.c_str(), ADDON_STANDARD_STRING_LENGTH);
VFSDirEntry* entries = static_cast(malloc(sizeof(VFSDirEntry) * addonEntries.size()));
for (unsigned int i = 0; i < addonEntries.size(); ++i)
{
entries[i].label = strdup(addonEntries[i].Label().c_str());
entries[i].title = strdup(addonEntries[i].Title().c_str());
entries[i].path = strdup(addonEntries[i].Path().c_str());
entries[i].folder = addonEntries[i].IsFolder();
entries[i].size = addonEntries[i].Size();
entries[i].num_props = 0;
const std::map& props = addonEntries[i].GetProperties();
if (!props.empty())
{
entries[i].properties = static_cast(malloc(sizeof(VFSProperty)*props.size()));
for (const auto& prop : props)
{
entries[i].properties[entries[i].num_props].name = strdup(prop.first.c_str());
entries[i].properties[entries[i].num_props].val = strdup(prop.second.c_str());
++entries[i].num_props;
}
}
else
entries[i].properties = nullptr;
}
*retEntries = entries;
*num_entries = addonEntries.size();
}
return ret;
}
AddonInstance_VFSEntry* m_instanceData;
};
} /* namespace addon */
} /* namespace kodi */